I tried things as a tic tac toe ending but it didn't work and I tried making a restart button that also didn't show up
I looked at mostly all reddit and stack blogs that didn't answer my question
does anyone know how it can be done?
This is the codepen in javascript
// draw settings
const context = c.getContext `2d`; // canvas context
const drawDistance = 800; // how far ahead to draw
const cameraDepth = 2; // FOV of camera
const segmentLength = 100; // length of each road segment
const roadWidth = 500; // how wide is road
const curbWidth = 150; // with of warning track
const dashLineWidth = 9; // width of the dashed line
const maxPlayerX = 2e3; // limit player offset
const mountainCount = 30; // how many mountains are there
const timeDelta = 1 / 60; // inverse frame rate
const PI = Math.PI; // shorthand for Math.PI
// player settings
const height = 150; // high of player above ground
const maxSpeed = 500; // limit max player speed
const playerAccel = 1; // player forward acceleration
const playerBrake = -3; // player breaking acceleration
const turnControl = .3; // player turning rate
const jumpAccel = 25; // z speed added for jump
const springConstant = .01; // spring players pitch
const collisionSlow = .1; // slow down from collisions
const pitchLerp = .1; // rate camera pitch changes
const pitchSpringDamp = .9; // dampen the pitch spring
const elasticity = 1.2; // bounce elasticity
const centrifugal = .002; // how much turns pull player
const forwardDamp = .999; // dampen player z speed
const lateralDamp = .7; // dampen player x speed
const offRoadDamp = .98; // more damping when off road
const gravity = -1; // gravity to apply in y axis
const cameraTurnScale = 2; // how much to rotate camera
const worldRotateScale = .00005; // how much to rotate world
// level settings
const maxTime = 20; // time to start
const checkPointTime = 15; // add time at checkpoints
const checkPointDistance = 1e5; // how far between checkpoints
const maxDifficultySegment = 9e3; // how far until max difficulty
const roadEnd = 1e4; // how far until end of road
//////////////////////////////////////////////////////////////////
// mouse input
//////////////////////////////////////////////////////////////////
mouseDown =
mousePressed =
mouseUpFrames =
mouseX = 0;
onmouseup = e => mouseDown = 0;
onmousedown = e => mousePressed ? mouseDown = 1 : mousePressed = 1;
onmousemove = e => mouseX = e.x / window.innerWidth * 2 - 1;
//////////////////////////////////////////////////////////////////
// math and helper functions
//////////////////////////////////////////////////////////////////
Clamp = (v, a, b) => Math.min(Math.max(v, a), b);
ClampAngle = (a) => (a + PI) % (2 * PI) + (a + PI < 0 ? PI : -PI);
Lerp = (p, a, b) => a + Clamp(p, 0, 1) * (b - a);
R = (a = 1, b = 0) => Lerp((Math.sin(++randSeed) + 1) * 1e5 % 1, a, b);
LSHA = (l, s = 0, h = 0, a = 1) => `hsl(${h+hueShift},${s}%,${l}%,${a})`;
// simple 3d vector class
class Vec3 {
constructor(x = 0, y = 0, z = 0) {
this.x = x;
this.y = y;
this.z = z;
}
Add = (v) => (
v = v < 1e5 ? new Vec3(v, v, v) : v,
new Vec3(this.x + v.x, this.y + v.y, this.z + v.z));
Multiply = (v) => (
v = v < 1e5 ? new Vec3(v, v, v) : v,
new Vec3(this.x * v.x, this.y * v.y, this.z * v.z));
}
// draw a trapazoid shaped poly
DrawPoly = (x1, y1, w1, x2, y2, w2, fillStyle) => {
context.beginPath(context.fillStyle = fillStyle);
context.lineTo(x1 - w1, y1 | 0);
context.lineTo(x1 + w1, y1 | 0);
context.lineTo(x2 + w2, y2 | 0);
context.lineTo(x2 - w2, y2 | 0);
context.fill();
}
// draw outlined hud text
DrawText = (text, posX) => {
// scale text so it works in tiny CodePen iframe
const size = c.height / 79;
context.font = size + 'em impact'; // set font size
context.fillStyle = LSHA(99, 0, 0, .5); // set font color
context.fillText(text, posX, size * 14); // fill text
context.lineWidth = size / 2.5; // line width
context.strokeText(text, posX, size * 14); // outline text
/*
context.font = '9em impact'; // set font size
context.fillStyle = LSHA(99,0,0,.5); // set font color
context.fillText(text, posX, 129); // fill text
context.lineWidth = 3; // line width
context.strokeText(text, posX, 129); // outline text
*/
}
//////////////////////////////////////////////////////////////////
// build the road with procedural generation
//////////////////////////////////////////////////////////////////
roadGenLengthMax = // end of section
roadGenLength = // distance left
roadGenTaper = // length of taper
roadGenFreqX = // X wave frequency
roadGenFreqY = // Y wave frequency
roadGenScaleX = // X wave amplitude
roadGenScaleY = 0; // Y wave amplitude
roadGenWidth = roadWidth; // starting road width
startRandSeed = randSeed = Date.now(); // set random seed
road = []; // clear road
// generate the road
for (i = 0; i < roadEnd * 2; ++i) // build road past end
{
if (roadGenLength++ > roadGenLengthMax) // is end of section?
{
// calculate difficulty percent
d = Math.min(1, i / maxDifficultySegment);
// randomize road settings
roadGenWidth = roadWidth * R(1 - d * .7, 3 - 2 * d); // road width
roadGenFreqX = R(Lerp(d, .01, .02)); // X curves
roadGenFreqY = R(Lerp(d, .01, .03)); // Y bumps
roadGenScaleX = i > roadEnd ? 0 : R(Lerp(d, .2, .6)); // X scale
roadGenScaleY = R(Lerp(d, 1e3, 2e3)); // Y scale
// apply taper and move back
roadGenTaper = R(99, 1e3) | 0; // random taper
roadGenLengthMax = roadGenTaper + R(99, 1e3); // random length
roadGenLength = 0; // reset length
i -= roadGenTaper; // subtract taper
}
// make a wavy road
x = Math.sin(i * roadGenFreqX) * roadGenScaleX;
y = Math.sin(i * roadGenFreqY) * roadGenScaleY;
road[i] = road[i] ? road[i] : {
x: x,
y: y,
w: roadGenWidth
};
// apply taper from last section and lerp values
p = Clamp(roadGenLength / roadGenTaper, 0, 1);
road[i].x = Lerp(p, road[i].x, x);
road[i].y = Lerp(p, road[i].y, y);
road[i].w = i > roadEnd ? 0 : Lerp(p, road[i].w, roadGenWidth);
// calculate road pitch angle
road[i].a = road[i - 1] ?
Math.atan2(road[i - 1].y - road[i].y, segmentLength) : 0;
}
//////////////////////////////////////////////////////////////////
// init game
//////////////////////////////////////////////////////////////////
// reset everything
velocity = new Vec3(pitchSpring = pitchSpringSpeed = pitchRoad = hueShift = 0);
position = new Vec3(0, height); // set player start pos
nextCheckPoint = checkPointDistance; // init next checkpoint
time = maxTime; // set the start time
heading = randSeed; // random world heading
//////////////////////////////////////////////////////////////////
// update and render frame
//////////////////////////////////////////////////////////////////
Update = () => {
// get player road segment
s = position.z / segmentLength | 0; // current road segment
p = position.z / segmentLength % 1; // percent along segment
// get lerped values between last and current road segment
roadX = Lerp(p, road[s].x, road[s + 1].x);
roadY = Lerp(p, road[s].y, road[s + 1].y) + height;
roadA = Lerp(p, road[s].a, road[s + 1].a);
// update player velocity
lastVelocity = velocity.Add(0);
velocity.y += gravity;
velocity.x *= lateralDamp;
velocity.z = Math.max(0, time ? forwardDamp * velocity.z : 0);
// add velocity to position
position = position.Add(velocity);
// limit player x position (how far off road)
position.x = Clamp(position.x, -maxPlayerX, maxPlayerX);
// check if on ground
if (position.y < roadY) {
position.y = roadY; // match y to ground plane
airFrame = 0; // reset air frames
// get the dot product of the ground normal and the velocity
dp = Math.cos(roadA) * velocity.y + Math.sin(roadA) * velocity.z;
// bounce velocity against ground normal
velocity = new Vec3(0, Math.cos(roadA), Math.sin(roadA))
.Multiply(-elasticity * dp).Add(velocity);
// apply player brake and accel
velocity.z +=
mouseDown ? playerBrake :
Lerp(velocity.z / maxSpeed, mousePressed * playerAccel, 0);
// check if off road
if (Math.abs(position.x) > road[s].w) {
velocity.z *= offRoadDamp; // slow down
pitchSpring += Math.sin(position.z / 99) ** 4 / 99; // rumble
}
}
// update player turning and apply centrifugal force
turn = Lerp(velocity.z / maxSpeed, mouseX * turnControl, 0);
velocity.x +=
velocity.z * turn -
velocity.z ** 2 * centrifugal * roadX;
// update jump
if (airFrame++ < 6 && time &&
mouseDown && mouseUpFrames && mouseUpFrames < 9) {
velocity.y += jumpAccel; // apply jump velocity
airFrame = 9; // prevent jumping again
}
mouseUpFrames = mouseDown ? 0 : mouseUpFrames + 1;
// pitch down with vertical velocity when in air
airPercent = (position.y - roadY) / 99;
pitchSpringSpeed += Lerp(airPercent, 0, velocity.y / 4e4);
// update player pitch spring
pitchSpringSpeed += (velocity.z - lastVelocity.z) / 2e3;
pitchSpringSpeed -= pitchSpring * springConstant;
pitchSpringSpeed *= pitchSpringDamp;
pitchSpring += pitchSpringSpeed;
pitchRoad = Lerp(pitchLerp, pitchRoad, Lerp(airPercent, -roadA, 0));
playerPitch = pitchSpring + pitchRoad;
// update heading
heading = ClampAngle(heading + velocity.z * roadX * worldRotateScale);
cameraHeading = turn * cameraTurnScale;
// was checkpoint crossed?
if (position.z > nextCheckPoint) {
time += checkPointTime; // add more time
nextCheckPoint += checkPointDistance; // set next checkpoint
hueShift += 36; // shift hue
}
//////////////////////////////////////////////////////////////////
// draw background - sky, sun/moon, mountains, and horizon
//////////////////////////////////////////////////////////////////
// clear the screen and set size
c.width = window.innerWidth, c.height = window.innerHeight;
// pre calculate projection scale, flip y
projectScale = (new Vec3(1, -1, 1)).Multiply(c.width / 2 / cameraDepth);
// get horizon, offset, and light amount
horizon = c.height / 2 - Math.tan(playerPitch) * projectScale.y;
backgroundOffset = Math.sin(cameraHeading) / 2;
light = Math.cos(heading);
// create linear gradient for sky
g = context.createLinearGradient(0, horizon - c.height / 2, 0, horizon);
g.addColorStop(0, LSHA(39 + light * 25, 49 + light * 19, 230 - light * 19));
g.addColorStop(1, LSHA(5, 79, 250 - light * 9));
// draw sky as full screen poly
DrawPoly(c.width / 2, 0, c.width / 2, c.width / 2, c.height, c.width / 2, g);
// draw sun and moon (0=sun, 1=moon)
for (i = 2; i--;) {
// create radial gradient
g = context.createRadialGradient(
x = c.width * (.5 + Lerp(
(heading / PI / 2 + .5 + i / 2) % 1,
4, -4) - backgroundOffset),
y = horizon - c.width / 5,
c.width / 25,
x, y, i ? c.width / 23 : c.width);
g.addColorStop(0, LSHA(i ? 70 : 99));
g.addColorStop(1, LSHA(0, 0, 0, 0));
// draw full screen poly
DrawPoly(c.width / 2, 0, c.width / 2, c.width / 2, c.height, c.width / 2, g);
}
// set random seed for mountains
randSeed = startRandSeed;
// draw mountains
for (i = mountainCount; i--;) {
angle = ClampAngle(heading + R(19));
light = Math.cos(angle - heading);
DrawPoly(
x = c.width * (.5 + Lerp(angle / PI / 2 + .5, 4, -4) - backgroundOffset),
y = horizon,
w = R(.2, .8) ** 2 * c.width / 2,
x + w * R(-.5, .5),
y - R(.5, .8) * w, 0,
LSHA(R(15, 25) + i / 3 - light * 9, i / 2 + R(19), R(220, 230)));
}
// draw horizon
DrawPoly(
c.width / 2, horizon, c.width / 2, c.width / 2, c.height, c.width / 2,
LSHA(25, 30, 95));
//////////////////////////////////////////////////////////////////
// draw road and objects
//////////////////////////////////////////////////////////////////
// calculate road x offsets and projections
for (x = w = i = 0; i < drawDistance + 1;) {
p = new Vec3(x += w += road[s + i].x, // sum local road offsets
road[s + i].y, (s + i) * segmentLength) // road y and z pos
.Add(position.Multiply(-1)); // get local camera space
// apply camera heading
p.x = p.x * Math.cos(cameraHeading) - p.z * Math.sin(cameraHeading);
// tilt camera pitch and invert z
z = 1 / (p.z * Math.cos(playerPitch) - p.y * Math.sin(playerPitch));
p.y = p.y * Math.cos(playerPitch) - p.z * Math.sin(playerPitch);
p.z = z;
// project road segment to canvas space
road[s + i++].p = // projected road point
p.Multiply(new Vec3(z, z, 1)) // projection
.Multiply(projectScale) // scale
.Add(new Vec3(c.width / 2, c.height / 2)); // center on canvas
}
// draw the road segments
let segment2 = road[s + drawDistance]; // store the last segment
for (i = drawDistance; i--;) // iterate in reverse
{
// get projected road points
segment1 = road[s + i];
p1 = segment1.p;
p2 = segment2.p;
// random seed and lighting
randSeed = startRandSeed + s + i;
light = Math.sin(segment1.a) * Math.cos(heading) * 99;
// check near and far clip
if (p1.z < 1e5 && p1.z > 0) {
// fade in road resolution over distance
if (i % (Lerp(i / drawDistance, 1, 9) | 0) == 0) {
// ground
DrawPoly(c.width / 2, p1.y, c.width / 2,
c.width / 2, p2.y, c.width / 2,
LSHA(25 + light, 30, 95));
// curb if wide enough
if (segment1.w > 400)
DrawPoly(p1.x, p1.y, p1.z * (segment1.w + curbWidth),
p2.x, p2.y, p2.z * (segment2.w + curbWidth),
LSHA(((s + i) % 19 < 9 ? 50 : 20) + light));
// road and checkpoint marker
DrawPoly(p1.x, p1.y, p1.z * segment1.w,
p2.x, p2.y, p2.z * segment2.w,
LSHA(((s + i) * segmentLength % checkPointDistance < 300 ? 70 : 7) + light));
// dashed lines if wide and close enough
if ((segment1.w > 300) && (s + i) % 9 == 0 && i < drawDistance / 3)
DrawPoly(p1.x, p1.y, p1.z * dashLineWidth,
p2.x, p2.y, p2.z * dashLineWidth,
LSHA(70 + light));
// save this segment
segment2 = segment1;
}
// random object (tree or rock)
if (R() < .2 && s + i > 29) {
// player object collision check
x = 2 * roadWidth * R(10, -10) * R(9); // choose object pos
const objectHeight = (R(2) | 0) * 400; // choose tree or rock
if (!segment1.h // dont hit same object
&&
Math.abs(position.x - x) < 200 // X
&&
Math.abs(position.z - (s + i) * segmentLength) < 200 // Z
&&
position.y - height < segment1.y + objectHeight + 200) // Y
{
// slow player and mark object as hit
velocity = velocity.Multiply(segment1.h = collisionSlow);
}
// draw road object
const alpha = Lerp(i / drawDistance, 4, 0); // fade in object
if (objectHeight) {
// tree trunk
DrawPoly(x = p1.x + p1.z * x, p1.y, p1.z * 29,
x, p1.y - 99 * p1.z, p1.z * 29,
LSHA(5 + R(9), 50 + R(9), 29 + R(9), alpha));
// tree leaves
DrawPoly(x, p1.y - R(50, 99) * p1.z, p1.z * R(199, 250),
x, p1.y - R(600, 800) * p1.z, 0,
LSHA(25 + R(9), 80 + R(9), 9 + R(29), alpha));
} else {
// rock
DrawPoly(x = p1.x + p1.z * x, p1.y, p1.z * R(200, 250),
x + p1.z * (R(99, -99)), p1.y - R(200, 250) * p1.z, p1.z * R(99),
LSHA(50 + R(19), 25 + R(19), 209 + R(9), alpha));
}
}
}
}
//////////////////////////////////////////////////////////////////
// draw and update time
//////////////////////////////////////////////////////////////////
if (mousePressed) {
time = Clamp(time - timeDelta, 0, maxTime); // update time
DrawText(Math.ceil(time), 9); // show time
context.textAlign = 'right'; // right alignment
DrawText(0 | position.z / 1e3, c.width - 9); // show distance
} else {
context.textAlign = 'center'; // center alignment
DrawText('HUE JUMPER', c.width / 2); // draw title text
}
requestAnimationFrame(Update); // kick off next frame
}
Update(); // kick off update loop
<canvas id="c"></canvas>
I had a typo in my comment because I tried to format as a comment.
if (mousePressed) {
time = Clamp(time - timeDelta, 0, maxTime); // update time
if (time <= 0) {
DrawText("Done", 9); /* or clear the canvas and write text */
return;
}
...
// draw settings
const context = c.getContext `2d`; // canvas context
const drawDistance = 800; // how far ahead to draw
const cameraDepth = 2; // FOV of camera
const segmentLength = 100; // length of each road segment
const roadWidth = 500; // how wide is road
const curbWidth = 150; // with of warning track
const dashLineWidth = 9; // width of the dashed line
const maxPlayerX = 2e3; // limit player offset
const mountainCount = 30; // how many mountains are there
const timeDelta = 1 / 60; // inverse frame rate
const PI = Math.PI; // shorthand for Math.PI
// player settings
const height = 150; // high of player above ground
const maxSpeed = 500; // limit max player speed
const playerAccel = 1; // player forward acceleration
const playerBrake = -3; // player breaking acceleration
const turnControl = .3; // player turning rate
const jumpAccel = 25; // z speed added for jump
const springConstant = .01; // spring players pitch
const collisionSlow = .1; // slow down from collisions
const pitchLerp = .1; // rate camera pitch changes
const pitchSpringDamp = .9; // dampen the pitch spring
const elasticity = 1.2; // bounce elasticity
const centrifugal = .002; // how much turns pull player
const forwardDamp = .999; // dampen player z speed
const lateralDamp = .7; // dampen player x speed
const offRoadDamp = .98; // more damping when off road
const gravity = -1; // gravity to apply in y axis
const cameraTurnScale = 2; // how much to rotate camera
const worldRotateScale = .00005; // how much to rotate world
// level settings
const maxTime = 20; // time to start
const checkPointTime = 15; // add time at checkpoints
const checkPointDistance = 1e5; // how far between checkpoints
const maxDifficultySegment = 9e3; // how far until max difficulty
const roadEnd = 1e4; // how far until end of road
//////////////////////////////////////////////////////////////////
// mouse input
//////////////////////////////////////////////////////////////////
mouseDown =
mousePressed =
mouseUpFrames =
mouseX = 0;
onmouseup = e => mouseDown = 0;
onmousedown = e => mousePressed ? mouseDown = 1 : mousePressed = 1;
onmousemove = e => mouseX = e.x / window.innerWidth * 2 - 1;
//////////////////////////////////////////////////////////////////
// math and helper functions
//////////////////////////////////////////////////////////////////
Clamp = (v, a, b) => Math.min(Math.max(v, a), b);
ClampAngle = (a) => (a + PI) % (2 * PI) + (a + PI < 0 ? PI : -PI);
Lerp = (p, a, b) => a + Clamp(p, 0, 1) * (b - a);
R = (a = 1, b = 0) => Lerp((Math.sin(++randSeed) + 1) * 1e5 % 1, a, b);
LSHA = (l, s = 0, h = 0, a = 1) => `hsl(${h+hueShift},${s}%,${l}%,${a})`;
// simple 3d vector class
class Vec3 {
constructor(x = 0, y = 0, z = 0) {
this.x = x;
this.y = y;
this.z = z;
}
Add = (v) => (
v = v < 1e5 ? new Vec3(v, v, v) : v,
new Vec3(this.x + v.x, this.y + v.y, this.z + v.z));
Multiply = (v) => (
v = v < 1e5 ? new Vec3(v, v, v) : v,
new Vec3(this.x * v.x, this.y * v.y, this.z * v.z));
}
// draw a trapazoid shaped poly
DrawPoly = (x1, y1, w1, x2, y2, w2, fillStyle) => {
context.beginPath(context.fillStyle = fillStyle);
context.lineTo(x1 - w1, y1 | 0);
context.lineTo(x1 + w1, y1 | 0);
context.lineTo(x2 + w2, y2 | 0);
context.lineTo(x2 - w2, y2 | 0);
context.fill();
}
// draw outlined hud text
DrawText = (text, posX) => {
// scale text so it works in tiny CodePen iframe
const size = c.height / 79;
context.font = size + 'em impact'; // set font size
context.fillStyle = LSHA(99, 0, 0, .5); // set font color
context.fillText(text, posX, size * 14); // fill text
context.lineWidth = size / 2.5; // line width
context.strokeText(text, posX, size * 14); // outline text
/*
context.font = '9em impact'; // set font size
context.fillStyle = LSHA(99,0,0,.5); // set font color
context.fillText(text, posX, 129); // fill text
context.lineWidth = 3; // line width
context.strokeText(text, posX, 129); // outline text
*/
}
//////////////////////////////////////////////////////////////////
// build the road with procedural generation
//////////////////////////////////////////////////////////////////
roadGenLengthMax = // end of section
roadGenLength = // distance left
roadGenTaper = // length of taper
roadGenFreqX = // X wave frequency
roadGenFreqY = // Y wave frequency
roadGenScaleX = // X wave amplitude
roadGenScaleY = 0; // Y wave amplitude
roadGenWidth = roadWidth; // starting road width
startRandSeed = randSeed = Date.now(); // set random seed
road = []; // clear road
// generate the road
for (i = 0; i < roadEnd * 2; ++i) // build road past end
{
if (roadGenLength++ > roadGenLengthMax) // is end of section?
{
// calculate difficulty percent
d = Math.min(1, i / maxDifficultySegment);
// randomize road settings
roadGenWidth = roadWidth * R(1 - d * .7, 3 - 2 * d); // road width
roadGenFreqX = R(Lerp(d, .01, .02)); // X curves
roadGenFreqY = R(Lerp(d, .01, .03)); // Y bumps
roadGenScaleX = i > roadEnd ? 0 : R(Lerp(d, .2, .6)); // X scale
roadGenScaleY = R(Lerp(d, 1e3, 2e3)); // Y scale
// apply taper and move back
roadGenTaper = R(99, 1e3) | 0; // random taper
roadGenLengthMax = roadGenTaper + R(99, 1e3); // random length
roadGenLength = 0; // reset length
i -= roadGenTaper; // subtract taper
}
// make a wavy road
x = Math.sin(i * roadGenFreqX) * roadGenScaleX;
y = Math.sin(i * roadGenFreqY) * roadGenScaleY;
road[i] = road[i] ? road[i] : {
x: x,
y: y,
w: roadGenWidth
};
// apply taper from last section and lerp values
p = Clamp(roadGenLength / roadGenTaper, 0, 1);
road[i].x = Lerp(p, road[i].x, x);
road[i].y = Lerp(p, road[i].y, y);
road[i].w = i > roadEnd ? 0 : Lerp(p, road[i].w, roadGenWidth);
// calculate road pitch angle
road[i].a = road[i - 1] ?
Math.atan2(road[i - 1].y - road[i].y, segmentLength) : 0;
}
//////////////////////////////////////////////////////////////////
// init game
//////////////////////////////////////////////////////////////////
// reset everything
velocity = new Vec3(pitchSpring = pitchSpringSpeed = pitchRoad = hueShift = 0);
position = new Vec3(0, height); // set player start pos
nextCheckPoint = checkPointDistance; // init next checkpoint
time = maxTime; // set the start time
heading = randSeed; // random world heading
//////////////////////////////////////////////////////////////////
// update and render frame
//////////////////////////////////////////////////////////////////
Update = () => {
// get player road segment
s = position.z / segmentLength | 0; // current road segment
p = position.z / segmentLength % 1; // percent along segment
// get lerped values between last and current road segment
roadX = Lerp(p, road[s].x, road[s + 1].x);
roadY = Lerp(p, road[s].y, road[s + 1].y) + height;
roadA = Lerp(p, road[s].a, road[s + 1].a);
// update player velocity
lastVelocity = velocity.Add(0);
velocity.y += gravity;
velocity.x *= lateralDamp;
velocity.z = Math.max(0, time ? forwardDamp * velocity.z : 0);
// add velocity to position
position = position.Add(velocity);
// limit player x position (how far off road)
position.x = Clamp(position.x, -maxPlayerX, maxPlayerX);
// check if on ground
if (position.y < roadY) {
position.y = roadY; // match y to ground plane
airFrame = 0; // reset air frames
// get the dot product of the ground normal and the velocity
dp = Math.cos(roadA) * velocity.y + Math.sin(roadA) * velocity.z;
// bounce velocity against ground normal
velocity = new Vec3(0, Math.cos(roadA), Math.sin(roadA))
.Multiply(-elasticity * dp).Add(velocity);
// apply player brake and accel
velocity.z +=
mouseDown ? playerBrake :
Lerp(velocity.z / maxSpeed, mousePressed * playerAccel, 0);
// check if off road
if (Math.abs(position.x) > road[s].w) {
velocity.z *= offRoadDamp; // slow down
pitchSpring += Math.sin(position.z / 99) ** 4 / 99; // rumble
}
}
// update player turning and apply centrifugal force
turn = Lerp(velocity.z / maxSpeed, mouseX * turnControl, 0);
velocity.x +=
velocity.z * turn -
velocity.z ** 2 * centrifugal * roadX;
// update jump
if (airFrame++ < 6 && time &&
mouseDown && mouseUpFrames && mouseUpFrames < 9) {
velocity.y += jumpAccel; // apply jump velocity
airFrame = 9; // prevent jumping again
}
mouseUpFrames = mouseDown ? 0 : mouseUpFrames + 1;
// pitch down with vertical velocity when in air
airPercent = (position.y - roadY) / 99;
pitchSpringSpeed += Lerp(airPercent, 0, velocity.y / 4e4);
// update player pitch spring
pitchSpringSpeed += (velocity.z - lastVelocity.z) / 2e3;
pitchSpringSpeed -= pitchSpring * springConstant;
pitchSpringSpeed *= pitchSpringDamp;
pitchSpring += pitchSpringSpeed;
pitchRoad = Lerp(pitchLerp, pitchRoad, Lerp(airPercent, -roadA, 0));
playerPitch = pitchSpring + pitchRoad;
// update heading
heading = ClampAngle(heading + velocity.z * roadX * worldRotateScale);
cameraHeading = turn * cameraTurnScale;
// was checkpoint crossed?
if (position.z > nextCheckPoint) {
time += checkPointTime; // add more time
nextCheckPoint += checkPointDistance; // set next checkpoint
hueShift += 36; // shift hue
}
//////////////////////////////////////////////////////////////////
// draw background - sky, sun/moon, mountains, and horizon
//////////////////////////////////////////////////////////////////
// clear the screen and set size
c.width = window.innerWidth, c.height = window.innerHeight;
// pre calculate projection scale, flip y
projectScale = (new Vec3(1, -1, 1)).Multiply(c.width / 2 / cameraDepth);
// get horizon, offset, and light amount
horizon = c.height / 2 - Math.tan(playerPitch) * projectScale.y;
backgroundOffset = Math.sin(cameraHeading) / 2;
light = Math.cos(heading);
// create linear gradient for sky
g = context.createLinearGradient(0, horizon - c.height / 2, 0, horizon);
g.addColorStop(0, LSHA(39 + light * 25, 49 + light * 19, 230 - light * 19));
g.addColorStop(1, LSHA(5, 79, 250 - light * 9));
// draw sky as full screen poly
DrawPoly(c.width / 2, 0, c.width / 2, c.width / 2, c.height, c.width / 2, g);
// draw sun and moon (0=sun, 1=moon)
for (i = 2; i--;) {
// create radial gradient
g = context.createRadialGradient(
x = c.width * (.5 + Lerp(
(heading / PI / 2 + .5 + i / 2) % 1,
4, -4) - backgroundOffset),
y = horizon - c.width / 5,
c.width / 25,
x, y, i ? c.width / 23 : c.width);
g.addColorStop(0, LSHA(i ? 70 : 99));
g.addColorStop(1, LSHA(0, 0, 0, 0));
// draw full screen poly
DrawPoly(c.width / 2, 0, c.width / 2, c.width / 2, c.height, c.width / 2, g);
}
// set random seed for mountains
randSeed = startRandSeed;
// draw mountains
for (i = mountainCount; i--;) {
angle = ClampAngle(heading + R(19));
light = Math.cos(angle - heading);
DrawPoly(
x = c.width * (.5 + Lerp(angle / PI / 2 + .5, 4, -4) - backgroundOffset),
y = horizon,
w = R(.2, .8) ** 2 * c.width / 2,
x + w * R(-.5, .5),
y - R(.5, .8) * w, 0,
LSHA(R(15, 25) + i / 3 - light * 9, i / 2 + R(19), R(220, 230)));
}
// draw horizon
DrawPoly(
c.width / 2, horizon, c.width / 2, c.width / 2, c.height, c.width / 2,
LSHA(25, 30, 95));
//////////////////////////////////////////////////////////////////
// draw road and objects
//////////////////////////////////////////////////////////////////
// calculate road x offsets and projections
for (x = w = i = 0; i < drawDistance + 1;) {
p = new Vec3(x += w += road[s + i].x, // sum local road offsets
road[s + i].y, (s + i) * segmentLength) // road y and z pos
.Add(position.Multiply(-1)); // get local camera space
// apply camera heading
p.x = p.x * Math.cos(cameraHeading) - p.z * Math.sin(cameraHeading);
// tilt camera pitch and invert z
z = 1 / (p.z * Math.cos(playerPitch) - p.y * Math.sin(playerPitch));
p.y = p.y * Math.cos(playerPitch) - p.z * Math.sin(playerPitch);
p.z = z;
// project road segment to canvas space
road[s + i++].p = // projected road point
p.Multiply(new Vec3(z, z, 1)) // projection
.Multiply(projectScale) // scale
.Add(new Vec3(c.width / 2, c.height / 2)); // center on canvas
}
// draw the road segments
let segment2 = road[s + drawDistance]; // store the last segment
for (i = drawDistance; i--;) // iterate in reverse
{
// get projected road points
segment1 = road[s + i];
p1 = segment1.p;
p2 = segment2.p;
// random seed and lighting
randSeed = startRandSeed + s + i;
light = Math.sin(segment1.a) * Math.cos(heading) * 99;
// check near and far clip
if (p1.z < 1e5 && p1.z > 0) {
// fade in road resolution over distance
if (i % (Lerp(i / drawDistance, 1, 9) | 0) == 0) {
// ground
DrawPoly(c.width / 2, p1.y, c.width / 2,
c.width / 2, p2.y, c.width / 2,
LSHA(25 + light, 30, 95));
// curb if wide enough
if (segment1.w > 400)
DrawPoly(p1.x, p1.y, p1.z * (segment1.w + curbWidth),
p2.x, p2.y, p2.z * (segment2.w + curbWidth),
LSHA(((s + i) % 19 < 9 ? 50 : 20) + light));
// road and checkpoint marker
DrawPoly(p1.x, p1.y, p1.z * segment1.w,
p2.x, p2.y, p2.z * segment2.w,
LSHA(((s + i) * segmentLength % checkPointDistance < 300 ? 70 : 7) + light));
// dashed lines if wide and close enough
if ((segment1.w > 300) && (s + i) % 9 == 0 && i < drawDistance / 3)
DrawPoly(p1.x, p1.y, p1.z * dashLineWidth,
p2.x, p2.y, p2.z * dashLineWidth,
LSHA(70 + light));
// save this segment
segment2 = segment1;
}
// random object (tree or rock)
if (R() < .2 && s + i > 29) {
// player object collision check
x = 2 * roadWidth * R(10, -10) * R(9); // choose object pos
const objectHeight = (R(2) | 0) * 400; // choose tree or rock
if (!segment1.h // dont hit same object
&&
Math.abs(position.x - x) < 200 // X
&&
Math.abs(position.z - (s + i) * segmentLength) < 200 // Z
&&
position.y - height < segment1.y + objectHeight + 200) // Y
{
// slow player and mark object as hit
velocity = velocity.Multiply(segment1.h = collisionSlow);
}
// draw road object
const alpha = Lerp(i / drawDistance, 4, 0); // fade in object
if (objectHeight) {
// tree trunk
DrawPoly(x = p1.x + p1.z * x, p1.y, p1.z * 29,
x, p1.y - 99 * p1.z, p1.z * 29,
LSHA(5 + R(9), 50 + R(9), 29 + R(9), alpha));
// tree leaves
DrawPoly(x, p1.y - R(50, 99) * p1.z, p1.z * R(199, 250),
x, p1.y - R(600, 800) * p1.z, 0,
LSHA(25 + R(9), 80 + R(9), 9 + R(29), alpha));
} else {
// rock
DrawPoly(x = p1.x + p1.z * x, p1.y, p1.z * R(200, 250),
x + p1.z * (R(99, -99)), p1.y - R(200, 250) * p1.z, p1.z * R(99),
LSHA(50 + R(19), 25 + R(19), 209 + R(9), alpha));
}
}
}
}
//////////////////////////////////////////////////////////////////
// draw and update time
//////////////////////////////////////////////////////////////////
if (mousePressed) {
time = Clamp(time - timeDelta, 0, maxTime); // update time
if (time <= 0) {
DrawText("Done", 9); /* or clear the canvas and write text */
return;
}
DrawText(Math.ceil(time), 9); // show time
context.textAlign = 'right'; // right alignment
DrawText(0 | position.z / 1e3, c.width - 9); // show distance
} else {
context.textAlign = 'center'; // center alignment
DrawText('HUE JUMPER', c.width / 2); // draw title text
}
requestAnimationFrame(Update); // kick off next frame
}
Update(); // kick off update loop
<canvas id="c"></canvas>
I am trying to make a rotating zooming recursive golden triangle. It draws a golden triangle, then it draws another one inside it and so on. This was easy, but the challenge is making it zoom in and rotate around the point that the triangles are approaching.
To make it zoom in on that point infinitely I need to come up with the formula to calculate which point the triangles are approaching.
Running demo at this point: https://waltari10.github.io/recursive-golden-triangle/index.html
Repository: https://github.com/Waltari10/recursive-golden-triangle
/**
*
* #param {float[]} pivot
* #param {float} angle
* #param {float[]} point
* #returns {float[]} point
*/
function rotatePoint(pivot, angle, point)
{
const s = Math.sin(angle);
const c = Math.cos(angle);
const pointOriginX = point[0] - pivot[0];
const pointOriginY = point[1] - pivot[1];
// rotate point
const xNew = (pointOriginX * c) - (pointOriginY * s);
const yNew = (pointOriginX * s) + (pointOriginY * c);
const newPoint = [
pivot[0] + xNew,
pivot[1] + yNew,
]
return newPoint;
}
// https://www.onlinemath4all.com/90-degree-clockwise-rotation.html
// https://stackoverflow.com/questions/2259476/rotating-a-point-about-another-point-2d
// Position is half way between points B and C 72 and 72, because AB/BC is golden ratio
function drawGoldenTriangle(pos, height, rotation, color = [0,255,0,255], pivot) {
// golden triangle degrees 72, 72, 36
// golden gnomon 36, 36, 108
// AB/BC is the golden ratio number
// https://www.mathsisfun.com/algebra/sohcahtoa.html
const baseLength = (Math.tan(degToRad(18)) * height) * 2;
const pointA = rotatePoint(pos, rotation, [pos[0], pos[1] - height]); // sharpest angle
const pointB = rotatePoint(pos, rotation, [pos[0] - (baseLength / 2), pos[1]]);
const pointC = rotatePoint(pos, rotation, [pos[0] + (baseLength / 2), pos[1]]);
drawTriangle(pointA, pointB, pointC, [0,255,0,255]);
}
let i = 0;
function drawRecursiveGoldenTriangle(pos, height, rotation, pivot) {
drawGoldenTriangle(pos, height, rotation, [0,255,0,255], pivot);
i++;
if (i > 10) {
return;
}
const hypotenuseLength = height / Math.cos(degToRad(18));
const baseLength = (Math.tan(degToRad(18)) * height) * 2;
const goldenRatio = hypotenuseLength / baseLength;
const newHeight = height / goldenRatio;
const newRotation = rotation - 108 * Math.PI/180
const newPointC = rotatePoint(pos, rotation, [pos[0] + (baseLength / 2), pos[1]]);
// Go half baselength up CA direction from pointC to get new position
const newHypotenuseLength = baseLength;
const newBaseLength = newHypotenuseLength / goldenRatio;
let newPosXRelative = Math.cos(newRotation) * (newBaseLength / 2)
let newPosYRelative = Math.sin(newRotation) * (newBaseLength / 2)
const newPos = [newPointC[0] + newPosXRelative, newPointC[1] + newPosYRelative];
drawRecursiveGoldenTriangle(newPos, newHeight, newRotation, [0,255,0,255], pivot);
}
let triangleHeight = height - 50;
let pivotPoint = [(width/2),(height/2) -50];
let triangleLocation = [width/2, height/2 + 300];
let triangleRotation = 0;
function loop() {
i = 0;
const startTime = Date.now()
wipeCanvasData();
// triangleHeight++;
// triangleRotation = triangleRotation + 0.005;
// drawX(pivotPoint)
// drawX(triangleLocation)
// Pivot point determines the point which the recursive golden
// triangle rotates around. Should be the point that triangles
// approach.
drawRecursiveGoldenTriangle(triangleLocation, triangleHeight, triangleRotation, pivotPoint);
updateCanvas()
const renderTime = Date.now() - startTime
timeDelta = renderTime < targetFrameDuration ? targetFrameDuration : renderTime
this.setTimeout(() => {
loop()
}, targetFrameDuration - renderTime)
}
loop()
What would be the formula to calculate the point that recursive golden triangle is approaching? Or is there some clever hack I could do in this situation?
The starting point of the logarithmic spiral is calculated by startingPoint(a,b,c) where a,b,c are the points of your triangle:
The triangle in the snippet is not a proper 'golden triangle' but the calculations should be correct...
const distance = (p1, p2) => Math.hypot(p2.x - p1.x, p2.y - p1.y);
const intersection = (p1, p2, p3, p4) => {
const l1A = (p2.y - p1.y) / (p2.x - p1.x);
const l1B = p1.y - l1A * p1.x;
const l2A = (p4.y - p3.y) / (p4.x - p3.x);
const l2B = p3.y - l2A * p3.x;
const x = (l2B - l1B) / (l1A - l2A);
const y = x * l1A + l1B;
return {x, y};
}
const startingPoint = (a, b, c) => {
const ac = distance(a, c);
const ab = distance(a, b);
const bc = distance(b, c);
// Law of cosines
const alpha = Math.acos((ab * ab + ac * ac - bc * bc) / (2 * ab * ac));
const gamma = Math.acos((ac * ac + bc * bc - ab * ab) / (2 * ac * bc));
const delta = Math.PI - alpha / 2 - gamma;
// Law of sines
const cd = ac * Math.sin(alpha / 2) / Math.sin(delta);
const d = {
x: cd * (b.x - c.x) / bc + c.x,
y: cd * (b.y - c.y) / bc + c.y
};
const e = {
x: (a.x + c.x) / 2,
y: (a.y + c.y) / 2
};
const f = {
x: (a.x + b.x) / 2,
y: (a.y + b.y) / 2,
};
return intersection(c, f, d, e);
};
d3.select('svg').append('path')
.attr('d', 'M 100,50 L150,200 H 50 Z')
.style('fill', 'none')
.style('stroke', 'blue')
const point = startingPoint({x: 50, y: 200},{x: 100, y: 50},{x: 150, y: 200});
console.log(point);
d3.select('svg').append('circle')
.attr('cx', point.x)
.attr('cy', point.y)
.attr('r', 5)
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/5.7.0/d3.min.js"></script>
<svg width="200" height="400"></svg>
Edit
Here's a new version which correctly applies the length and model but doesn't position the model correctly. I figured it might help.
http://codepen.io/pixelass/pen/78f9e97579f99dc4ae0473e33cae27d5?editors=001
I have 2 canvas instances
model
result
On the model view the user can drag the handles to modify the model
The result view should then apply the model to every segment (relatively)
This is just a basic l-system logic for fractal curves though I am having problems applying the model to the segments.
Se the picture below: The red lines should replicate the model, but I can't figure out how to correctly apply the logic
I have a demo version here: http://codepen.io/pixelass/pen/c4d7650af7ce4901425b326ad7a4b259
ES6
// simplify Math
'use strict';
Object.getOwnPropertyNames(Math).map(function(prop) {
window[prop] = Math[prop];
});
// add missing math functions
var rad = (degree)=> {
return degree * PI / 180;
};
var deg = (radians)=> {
return radians * 180 / PI;
};
// get our drawing areas
var model = document.getElementById('model');
var modelContext = model.getContext('2d');
var result = document.getElementById('result');
var resultContext = result.getContext('2d');
var setSize = function setSize() {
model.height = 200;
model.width = 200;
result.height = 400;
result.width = 400;
};
// size of the grabbing dots
var dotSize = 5;
// flag to determine if we are grabbing a point
var grab = -1;
// set size to init instances
setSize();
//
var iterations = 1;
// define points
// this only defines the initial model
var returnPoints = function returnPoints(width) {
return [{
x: 0,
y: width
}, {
x: width / 3,
y: width
}, {
x: width / 2,
y: width / 3*2
}, {
x: width / 3 * 2,
y: width
}, {
x: width,
y: width
}];
};
// set initial state for model
var points = returnPoints(model.width);
// handle interaction
// grab points only if hovering
var grabPoint = function grabPoint(e) {
var X = e.layerX;
var Y = e.layerY;
for (var i = 1; i < points.length - 1; i++) {
if (abs(X - points[i].x) < dotSize && abs(Y - points[i].y) < dotSize) {
model.classList.add('grabbing');
grab = i;
}
}
};
// release point
var releasePoint = function releasePoint(e) {
if (grab > -1) {
model.classList.add('grab');
model.classList.remove('grabbing');
}
grab = -1;
};
// set initial state for result
// handle mouse movement on the model canvas
var handleMove = function handleMove(e) {
// determine current mouse position
var X = e.layerX;
var Y = e.layerY;
// clear classes
model.classList.remove('grabbing');
model.classList.remove('grab');
// check if hovering a dot
for (var i = 1; i < points.length - 1; i++) {
if (abs(X - points[i].x) < dotSize && abs(Y - points[i].y) < dotSize) {
// indicate grabbable
model.classList.add('grab');
}
}
// if grabbing
if (grab > -1) {
// indicate grabbing
model.classList.add('grabbing');
// modify dot on the model canvas
points[grab] = {
x: X,
y: Y
};
// modify dots on the result canvas
drawSegment({
x: points[grab - 1].x,
y: points[grab - 1].y
}, {
x: X,
y: Y
});
}
};
let m2 = points[1].x / points[4].x
let m3 = points[2].x / points[4].x
let m4 = points[3].x / points[4].x
let n2 = points[1].y / points[4].y
let n3 = points[2].y / points[4].y
let n4 = points[3].y / points[4].y
var drawSegment = function drawSegment(start, end) {
var dx = end.x - start.x
var dy = end.y - start.y
var dist = sqrt(dx * dx + dy * dy)
var angle = atan2(dy, dx)
let x1 = end.x
let y1 = end.y
let x2 = round(cos(angle) * dist)
let y2 = round(sin(angle) * dist)
resultContext.srtokeStyle = 'red'
resultContext.beginPath()
resultContext.moveTo(x1, y1)
resultContext.lineTo(x2, y2)
resultContext.stroke()
m2 = points[1].x / points[4].x
m3 = points[2].x / points[4].x
m4 = points[3].x / points[4].x
n2 = points[1].y / points[4].y
n3 = points[2].y / points[4].y
n4 = points[3].y / points[4].y
};
var drawDots = function drawDots(points) {
// draw dots
for (var i = 1; i < points.length - 1; i++) {
modelContext.lineWidth = 4; //
modelContext.beginPath();
modelContext.strokeStyle = 'hsla(' + 360 / 5 * i + ',100%,40%,1)';
modelContext.fillStyle = 'hsla(0,100%,100%,1)';
modelContext.arc(points[i].x, points[i].y, dotSize, 0, 2 * PI);
modelContext.stroke();
modelContext.fill();
}
};
var drawModel = function drawModel(ctx, points, n) {
var dx = points[1].x - points[0].x
var dy = points[1].y - points[0].y
var dist = sqrt(dx * dx + dy * dy)
var angle = atan2(dy, dx)
let x1 = points[1].x
let y1 = points[1].y
let x2 = round(cos(angle) * dist)
let y2 = round(sin(angle) * dist)
ctx.strokeStyle = 'hsla(0,0%,80%,1)';
ctx.lineWidth = 1;
ctx.beginPath();
ctx.moveTo(points[0].x,
points[0].y)
ctx.lineTo(points[1].x * m2,
points[1].y * n2)
ctx.lineTo(points[1].x * m3,
points[1].y * n3)
ctx.lineTo(points[1].x * m4,
points[1].y * n4)
ctx.lineTo(points[1].x,
points[1].y)
ctx.stroke();
ctx.strokeStyle = 'hsla(100,100%,80%,1)';
ctx.beginPath();
ctx.moveTo(points[0].x,
points[0].y)
ctx.lineTo(points[1].x,
points[1].y)
ctx.stroke()
if (n > 0 ) {
drawModel(resultContext, [{
x: points[0].x,
y: points[0].y
}, {
x: points[1].x * m2,
y: points[1].y * n2
}], n - 1);
drawModel(resultContext, [{
x: points[1].x * m2,
y: points[1].y * n2
}, {
x: points[1].x * m3,
y: points[1].y * n3
}], n - 1);
/*
drawModel(resultContext, [{
x: points[1].x * m3,
y: points[1].y * m3
}, {
x: points[1].x * m4,
y: points[1].y * n4
}], n - 1);
drawModel(resultContext, [{
x: points[1].x * m4,
y: points[1].y * m4
}, {
x: points[1].x,
y: points[1].y
}], n - 1);*/
} else {
ctx.strokeStyle = 'hsla(0,100%,50%,1)';
ctx.beginPath();
ctx.moveTo(points[0].x,
points[0].y)
ctx.lineTo(points[1].x * m2,
points[1].y * n2)
ctx.lineTo(points[1].x * m3,
points[1].y * n3)
ctx.lineTo(points[1].x * m4,
points[1].y * n4)
ctx.lineTo(points[1].x,
points[1].y)
ctx.stroke();
}
};
var draw = function draw() {
// clear both screens
modelContext.fillStyle = 'hsla(0,0%,100%,.5)';
modelContext.fillRect(0, 0, model.width, model.height);
resultContext.fillStyle = 'hsla(0,0%,100%,1)';
resultContext.fillRect(0, 0, result.width, result.height);
// draw model
drawModel(modelContext, [{
x: 0,
y: 200
}, {
x: 200,
y: 200
}]);
drawModel(resultContext, [{
x: 0,
y: 400
}, {
x: 400,
y: 400
}],iterations);
// draw the dots to indicate grabbing points
drawDots(points);
// redraw
requestAnimationFrame(draw);
};
window.addEventListener('resize', setSize);
model.addEventListener('mousemove', handleMove);
model.addEventListener('mousedown', grabPoint);
window.addEventListener('mouseup', releasePoint);
setSize();
draw();
Write a function to transform a point given the point, an old origin (the start of the model line segment), a new origin (the start of the child line segment), an angle and a scale (you have already calculated these):
var transformPoint = function transformPoint(point, oldOrigin, newOrigin, angle, dist) {
// subtract old origin to rotate and scale relative to it:
var x = point.x - oldOrigin.x;
var y = point.y - oldOrigin.y;
// rotate by angle
var sine = sin(angle)
var cosine = cos(angle)
var rotatedX = (x * cosine) - (y * sine);
var rotatedY = (x * sine) + (y * cosine);
// scale
rotatedX *= dist;
rotatedY *= dist;
// offset by new origin and return:
return {x: rotatedX + newOrigin.x - oldOrigin.x, y: rotatedY + newOrigin.y - oldOrigin.y }
}
You need to translate it by the old origin (so that you can rotate around it), then rotate, then scale, then translate by the new origin. Then return the point.
modelLogic[0] is the old origin because it defines the start of the segment in the model and points[0] is the new origin because that is what it is mapped to by the transformation.
You can call the function from your drawModel function like this:
let p1 = transformPoint(modelLogic[0], modelLogic[0], points[0], angle, dist);
let p2 = transformPoint(modelLogic[1], modelLogic[0], points[0], angle, dist);
let p3 = transformPoint(modelLogic[2], modelLogic[0], points[0], angle, dist);
let p4 = transformPoint(modelLogic[3], modelLogic[0], points[0], angle, dist);
let p5 = transformPoint(modelLogic[4], modelLogic[0], points[0], angle, dist);
and change your drawing code to use the returned points p1, p2 etc instead of x1, y1, x2, y2 etc.
Alternatively, you can create a single matrix to represent all of these translation, rotation and scaling transforms and transform each point by it in turn.