207 lines
5.4 KiB
JavaScript
207 lines
5.4 KiB
JavaScript
|
|
const thing = document.getElementById('cursor-thing');
|
|
let updateTimerId;
|
|
|
|
let lastUpdate = 0;
|
|
|
|
// targeting
|
|
let mouseX = 0;
|
|
let mouseY = 0;
|
|
let scrollX = document.scrollingElement.scrollLeft;
|
|
let scrollY = document.scrollingElement.scrollTop;
|
|
const getTargetX = () => mouseX + scrollX;
|
|
const getTargetY = () => mouseY + scrollY;
|
|
|
|
// physics
|
|
let xPos = 0;
|
|
let yPos = 0;
|
|
let angle = 0;
|
|
let velocity = 0;
|
|
const maxVel = 600;
|
|
let accel = 0;
|
|
const maxAccel = 1500;
|
|
const accelCurve = (x) => maxAccel*(x/200)**3; // maxAccel when >150px distance
|
|
|
|
// more physics
|
|
const slideFric = .9; // coefficient of friction
|
|
const turnSpeedCoeff = .08; // coefficient of turning
|
|
|
|
// other stuff
|
|
let brake = false;
|
|
const brakeDist = 70;
|
|
const duckSize = 50;
|
|
|
|
// animation
|
|
let alt = false;
|
|
let lastAlt = 0;
|
|
|
|
|
|
function update() {
|
|
// timing
|
|
thisUpdate = Date.now();
|
|
deltaT = (thisUpdate - lastUpdate)/1000.0;
|
|
lastUpdate = thisUpdate;
|
|
|
|
// update trajectory
|
|
targetX = getTargetX();
|
|
targetY = getTargetY();
|
|
|
|
xDelta = targetX - xPos;
|
|
yDelta = targetY - yPos;
|
|
|
|
// find target angle
|
|
let targetAngle = Math.atan(yDelta/xDelta);
|
|
if (isNaN(targetAngle)) targetAngle = 0;
|
|
if (xDelta < 0) targetAngle = Math.PI*.5 - (targetAngle*-1);
|
|
else targetAngle = -((targetAngle*-1) - Math.PI*1.5);
|
|
|
|
// find shortest rotation delta
|
|
let aAngle = angle;
|
|
let aTargetAngle = targetAngle;
|
|
if (aTargetAngle > aAngle && aTargetAngle - aAngle > Math.PI) {
|
|
aAngle += Math.PI;
|
|
aTargetAngle -= Math.PI;
|
|
} else if (aAngle > aTargetAngle && aAngle - aTargetAngle > Math.PI) {
|
|
aAngle -= Math.PI;
|
|
aTargetAngle += Math.PI;
|
|
}
|
|
|
|
rDelta = aTargetAngle - aAngle;
|
|
|
|
// console.log(`myAngle: ${angle * (180 / Math.PI)} target angle: ${targetAngle * (180 / Math.PI)}`)
|
|
// console.log(`deelta: ${(rDelta) * (180 / Math.PI)} `);
|
|
|
|
// apply delta
|
|
angle += rDelta * turnSpeedCoeff;
|
|
|
|
// counter overflow
|
|
if (angle > 2*Math.PI) angle -= 2*Math.PI;
|
|
else if (angle < 0) angle += 2*Math.PI;
|
|
|
|
// stop when close
|
|
let dist = Math.sqrt((xPos-targetX)**2 + (yPos-targetY)**2);
|
|
brake = dist < brakeDist;
|
|
if (!brake) {
|
|
accel = accelCurve(dist);
|
|
if (accel > maxAccel) accel = maxAccel;
|
|
else if (accel < -maxAccel) accel = -maxAccel;
|
|
} else accel = 0;
|
|
|
|
// physics
|
|
velocity += accel * deltaT;
|
|
|
|
if (brake) {
|
|
// basic friction
|
|
velocity *= slideFric;
|
|
}
|
|
|
|
// vel limit
|
|
if (velocity > maxVel) velocity = maxVel;
|
|
else if (velocity < -maxVel) velocity = -maxVel
|
|
|
|
// translate vector to 2D
|
|
let hAngle = angle % Math.PI;
|
|
let right = angle > Math.PI;
|
|
let xVel = Math.sin(hAngle) * velocity;
|
|
let yVel = Math.cos(hAngle) * velocity;
|
|
if (right) {
|
|
yVel *= -1;
|
|
} else {
|
|
xVel *= -1;
|
|
}
|
|
|
|
// apply velocity
|
|
xPos += xVel * deltaT;
|
|
yPos += yVel * deltaT;
|
|
|
|
// bounce duck at edge of screen
|
|
if (xPos > document.scrollingElement.scrollWidth - duckSize) {
|
|
xPos = document.scrollingElement.scrollWidth - duckSize;
|
|
angle = 2*Math.PI - angle;
|
|
} else if (xPos < 0) {
|
|
xPos = 0;
|
|
angle = 2*Math.PI - angle;
|
|
}
|
|
if (yPos > document.scrollingElement.scrollHeight - duckSize) {
|
|
yPos = document.scrollingElement.scrollHeight - duckSize;
|
|
angle = Math.PI - angle;
|
|
} else if (yPos < 0) {
|
|
yPos = 0;
|
|
angle = Math.PI - angle;
|
|
}
|
|
|
|
// limit angle again
|
|
if (angle > 2*Math.PI) angle -= 2*Math.PI;
|
|
else if (angle < 0) angle += 2*Math.PI;
|
|
|
|
// console.log(`accel: ${accel}`);
|
|
// console.log(`velocity: ${velocity} (${xVel}, ${yVel})`)
|
|
|
|
let visualAngle = angle + (rDelta/2);
|
|
visualAngle = targetAngle;
|
|
|
|
thing.style.left = xPos + 'px';
|
|
thing.style.top = yPos + 'px';
|
|
// thing.style.rotate = visualAngle + 'rad';
|
|
// if (brake) {
|
|
// thing.style['background-color'] = '#00ff00';
|
|
// } else {
|
|
// thing.style['background-color'] = '#ff0000';
|
|
// }
|
|
|
|
let tAngle = visualAngle + Math.PI/4;
|
|
if (tAngle < Math.PI/2 || tAngle > Math.PI * 2) {
|
|
// front-facing
|
|
thing.style['background-position-x'] = (2*duckSize) + 'px';
|
|
} else if (tAngle < Math.PI) {
|
|
// left-facing
|
|
thing.style['background-position-x'] = '0px';
|
|
} else if (tAngle < Math.PI*3/2) {
|
|
// right-facing
|
|
thing.style['background-position-x'] = (3*duckSize) + 'px';
|
|
} else {
|
|
// back-facing
|
|
thing.style['background-position-x'] = duckSize + 'px';
|
|
}
|
|
|
|
if (thisUpdate - lastAlt > 70) {
|
|
lastAlt = thisUpdate;
|
|
alt = !alt;
|
|
}
|
|
|
|
if (velocity > 50) {
|
|
thing.style['background-position-y'] = (duckSize * (alt + 1)) + 'px';
|
|
} else {
|
|
thing.style['background-position-y'] = '0px';
|
|
}
|
|
|
|
}
|
|
|
|
// scroll and mouse are separate so the cursor is followed when scrolled
|
|
document.addEventListener('mousemove', e => {
|
|
mouseX = e.clientX;
|
|
mouseY = e.clientY;
|
|
});
|
|
|
|
document.addEventListener('scroll', e => {
|
|
el = e.target.scrollingElement;
|
|
scrollX = el.scrollLeft;
|
|
scrollY = el.scrollTop;
|
|
});
|
|
|
|
// kick-start duck
|
|
let duck_start = false;
|
|
function click_duck() {
|
|
if (duck_start) return;
|
|
duck_start = true;
|
|
|
|
xPos = thing.offsetLeft;
|
|
yPos = thing.offsetTop;
|
|
thing.style.position = 'absolute';
|
|
thing.style.cursor = 'auto';
|
|
lastUpdate = Date.now();
|
|
|
|
updateTimerId = setInterval(update, 10);
|
|
}
|
|
|