CARS and also better node editing
This commit is contained in:
parent
657f228d47
commit
08d63395e3
583
bundle.js
583
bundle.js
@ -5,10 +5,6 @@
|
||||
const Constants = {
|
||||
TWO_PI: Math.PI * 2
|
||||
};
|
||||
const map = (value, x1, y1, x2, y2)=>(value - x1) * (y2 - x2) / (y1 - x1) + x2;
|
||||
const Constants1 = {
|
||||
TWO_PI: Math.PI * 2
|
||||
};
|
||||
class Vector {
|
||||
x;
|
||||
y;
|
||||
@ -63,6 +59,7 @@ class Vector {
|
||||
this.y += y ?? 0;
|
||||
this.z += z ?? 0;
|
||||
}
|
||||
return this;
|
||||
}
|
||||
sub(v, y, z) {
|
||||
if (arguments.length === 1 && typeof v !== 'number') {
|
||||
@ -77,6 +74,7 @@ class Vector {
|
||||
this.y -= y ?? 0;
|
||||
this.z -= z ?? 0;
|
||||
}
|
||||
return this;
|
||||
}
|
||||
mult(v) {
|
||||
if (typeof v === 'number') {
|
||||
@ -100,6 +98,7 @@ class Vector {
|
||||
this.y /= v.y;
|
||||
this.z /= v.z;
|
||||
}
|
||||
return this;
|
||||
}
|
||||
rotate(angle) {
|
||||
const prev_x = this.x;
|
||||
@ -107,6 +106,7 @@ class Vector {
|
||||
const s = Math.sin(angle);
|
||||
this.x = c * this.x - s * this.y;
|
||||
this.y = s * prev_x + c * this.y;
|
||||
return this;
|
||||
}
|
||||
dist(v) {
|
||||
const dx = this.x - v.x, dy = this.y - v.y, dz = this.z - v.z;
|
||||
@ -139,6 +139,7 @@ class Vector {
|
||||
this.x = lerp_val(this.x, x, amt);
|
||||
this.y = lerp_val(this.y, y, amt);
|
||||
this.z = lerp_val(this.z, z, amt);
|
||||
return this;
|
||||
}
|
||||
normalize() {
|
||||
const m = this.mag();
|
||||
@ -152,6 +153,7 @@ class Vector {
|
||||
this.normalize();
|
||||
this.mult(high);
|
||||
}
|
||||
return this;
|
||||
}
|
||||
heading() {
|
||||
return -Math.atan2(-this.y, this.x);
|
||||
@ -191,7 +193,7 @@ class Vector {
|
||||
return Vector.fromAngle(Math.random() * (Math.PI * 2), v);
|
||||
}
|
||||
static random3D(v) {
|
||||
const angle = Math.random() * Constants1.TWO_PI;
|
||||
const angle = Math.random() * Constants.TWO_PI;
|
||||
const vz = Math.random() * 2 - 1;
|
||||
const mult = Math.sqrt(1 - vz * vz);
|
||||
const vx = mult * Math.cos(angle);
|
||||
@ -255,6 +257,7 @@ class Doodler {
|
||||
return this.ctx.canvas.height;
|
||||
}
|
||||
draggables = [];
|
||||
clickables = [];
|
||||
constructor({ width , height , canvas , bg , framerate }){
|
||||
if (!canvas) {
|
||||
canvas = document.createElement('canvas');
|
||||
@ -279,6 +282,10 @@ class Doodler {
|
||||
this.mouseY = e.clientY - rect.top;
|
||||
for (const d of this.draggables.filter((d)=>d.beingDragged)){
|
||||
d.point.add(e.movementX, e.movementY);
|
||||
d.onDrag && d.onDrag({
|
||||
x: e.movementX,
|
||||
y: e.movementY
|
||||
});
|
||||
}
|
||||
});
|
||||
this.startDrawLoop();
|
||||
@ -323,19 +330,19 @@ class Doodler {
|
||||
weight: 1
|
||||
});
|
||||
this.ctx.beginPath();
|
||||
this.ctx.arc(at.x, at.y, style?.weight || 1, 0, Constants1.TWO_PI);
|
||||
this.ctx.arc(at.x, at.y, style?.weight || 1, 0, Constants.TWO_PI);
|
||||
this.ctx.fill();
|
||||
}
|
||||
drawCircle(at, radius, style) {
|
||||
this.setStyle(style);
|
||||
this.ctx.beginPath();
|
||||
this.ctx.arc(at.x, at.y, radius, 0, Constants1.TWO_PI);
|
||||
this.ctx.arc(at.x, at.y, radius, 0, Constants.TWO_PI);
|
||||
this.ctx.stroke();
|
||||
}
|
||||
fillCircle(at, radius, style) {
|
||||
this.setStyle(style);
|
||||
this.ctx.beginPath();
|
||||
this.ctx.arc(at.x, at.y, radius, 0, Constants1.TWO_PI);
|
||||
this.ctx.arc(at.x, at.y, radius, 0, Constants.TWO_PI);
|
||||
this.ctx.fill();
|
||||
}
|
||||
drawRect(at, width, height, style) {
|
||||
@ -385,6 +392,12 @@ class Doodler {
|
||||
cb();
|
||||
this.ctx.restore();
|
||||
}
|
||||
drawImage(img, at, w, h) {
|
||||
w && h ? this.ctx.drawImage(img, at.x, at.y, w, h) : this.ctx.drawImage(img, at.x, at.y);
|
||||
}
|
||||
drawSprite(img, spritePos, sWidth, sHeight, at, width, height) {
|
||||
this.ctx.drawImage(img, spritePos.x, spritePos.y, sWidth, sHeight, at.x, at.y, width, height);
|
||||
}
|
||||
setStyle(style) {
|
||||
const ctx = this.ctx;
|
||||
ctx.fillStyle = style?.color || style?.fillColor || 'black';
|
||||
@ -414,20 +427,40 @@ class Doodler {
|
||||
}
|
||||
this.draggables = this.draggables.filter((d)=>d.point !== point);
|
||||
}
|
||||
addDragEvents({ onDragEnd , onDragStart , point }) {
|
||||
registerClickable(p1, p2, cb) {
|
||||
const top = Math.min(p1.y, p2.y);
|
||||
const left = Math.min(p1.x, p2.x);
|
||||
const bottom = Math.max(p1.y, p2.y);
|
||||
const right = Math.max(p1.x, p2.x);
|
||||
this.clickables.push({
|
||||
onClick: cb,
|
||||
checkBound: (p)=>p.y >= top && p.x >= left && p.y <= bottom && p.x <= right
|
||||
});
|
||||
}
|
||||
unregisterClickable(cb) {
|
||||
this.clickables = this.clickables.filter((c)=>c.onClick !== cb);
|
||||
}
|
||||
addDragEvents({ onDragEnd , onDragStart , onDrag , point }) {
|
||||
const d = this.draggables.find((d)=>d.point === point);
|
||||
if (d) {
|
||||
d.onDragEnd = onDragEnd;
|
||||
d.onDragStart = onDragStart;
|
||||
d.onDrag = onDrag;
|
||||
}
|
||||
}
|
||||
onClick(e) {
|
||||
const mouse = new Vector(this.mouseX, this.mouseY);
|
||||
for (const d of this.draggables){
|
||||
if (d.point.dist(new Vector(this.mouseX, this.mouseY)) <= d.radius) {
|
||||
if (d.point.dist(mouse) <= d.radius) {
|
||||
d.beingDragged = true;
|
||||
d.onDragStart?.call(null);
|
||||
} else d.beingDragged = false;
|
||||
}
|
||||
for (const c of this.clickables){
|
||||
if (c.checkBound(mouse)) {
|
||||
c.onClick();
|
||||
}
|
||||
}
|
||||
}
|
||||
offClick(e) {
|
||||
for (const d of this.draggables){
|
||||
@ -470,35 +503,79 @@ class Doodler {
|
||||
this.uiElements.delete(id);
|
||||
}
|
||||
}
|
||||
class ComplexPath {
|
||||
points = [];
|
||||
radius = 50;
|
||||
ctx;
|
||||
constructor(points){
|
||||
points && (this.points = points);
|
||||
class Train {
|
||||
nodes = [];
|
||||
cars = [];
|
||||
path;
|
||||
t;
|
||||
engineLength = 40;
|
||||
spacing = 30;
|
||||
constructor(track, cars = []){
|
||||
this.path = track;
|
||||
this.t = 0;
|
||||
this.nodes.push(this.path.followEvenPoints(this.t));
|
||||
this.nodes.push(this.path.followEvenPoints(this.t - this.real2Track(40)));
|
||||
this.cars.push(new TrainCar(55, document.getElementById('engine-sprites'), 80, 20, {
|
||||
at: new Vector(0, 60),
|
||||
width: 80,
|
||||
height: 20
|
||||
}));
|
||||
this.cars[0].points = this.nodes.map((n)=>n);
|
||||
let currentOffset = 40;
|
||||
for (const car of cars){
|
||||
currentOffset += this.spacing;
|
||||
const a = this.path.followEvenPoints(this.t - currentOffset);
|
||||
currentOffset += car.length;
|
||||
const b = this.path.followEvenPoints(this.t - currentOffset);
|
||||
car.points = [
|
||||
a,
|
||||
b
|
||||
];
|
||||
this.cars.push(car);
|
||||
}
|
||||
}
|
||||
setContext(ctx) {
|
||||
this.ctx = ctx;
|
||||
move() {
|
||||
this.t = (this.t + 1) % this.path.evenPoints.length;
|
||||
let currentOffset = 0;
|
||||
for (const car of this.cars){
|
||||
if (!car.points) return;
|
||||
const [a, b] = car.points;
|
||||
a.set(this.path.followEvenPoints(this.t - currentOffset));
|
||||
currentOffset += car.length;
|
||||
b.set(this.path.followEvenPoints(this.t - currentOffset));
|
||||
currentOffset += this.spacing;
|
||||
car.draw();
|
||||
}
|
||||
}
|
||||
real2Track(length) {
|
||||
return length / this.path.pointSpacing;
|
||||
}
|
||||
}
|
||||
class TrainCar {
|
||||
img;
|
||||
imgWidth;
|
||||
imgHeight;
|
||||
sprite;
|
||||
points;
|
||||
length;
|
||||
constructor(length, img, w, h, sprite){
|
||||
this.img = img;
|
||||
this.sprite = sprite;
|
||||
this.imgWidth = w;
|
||||
this.imgHeight = h;
|
||||
this.length = length;
|
||||
}
|
||||
draw() {
|
||||
if (!this.ctx || !this.points.length) return;
|
||||
const ctx = this.ctx;
|
||||
ctx.save();
|
||||
ctx.lineWidth = 2;
|
||||
ctx.strokeStyle = 'white';
|
||||
ctx.setLineDash([
|
||||
21,
|
||||
6
|
||||
]);
|
||||
let last = this.points[this.points.length - 1];
|
||||
for (const point of this.points){
|
||||
ctx.beginPath();
|
||||
ctx.moveTo(last.x, last.y);
|
||||
ctx.lineTo(point.x, point.y);
|
||||
ctx.stroke();
|
||||
last = point;
|
||||
}
|
||||
ctx.restore();
|
||||
if (!this.points) return;
|
||||
const [a, b] = this.points;
|
||||
const origin = Vector.add(Vector.sub(a, b).div(2), b);
|
||||
const angle = Vector.sub(b, a).heading();
|
||||
doodler.drawCircle(origin, 4, {
|
||||
color: 'blue'
|
||||
});
|
||||
doodler.drawRotated(origin, angle, ()=>{
|
||||
this.sprite ? doodler.drawSprite(this.img, this.sprite.at, this.sprite.width, this.sprite.height, origin.copy().sub(this.imgWidth / 2, this.imgHeight / 2), this.imgWidth, this.imgHeight) : doodler.drawImage(this.img, origin.copy().sub(this.imgWidth / 2, this.imgHeight / 2));
|
||||
});
|
||||
}
|
||||
}
|
||||
class PathSegment {
|
||||
@ -604,7 +681,7 @@ class PathSegment {
|
||||
const current = stepSize * i;
|
||||
points.push(this.getPointAtT(current));
|
||||
}
|
||||
return points.reduce((acc, cur)=>{
|
||||
this.length = points.reduce((acc, cur)=>{
|
||||
const prev = acc.prev;
|
||||
acc.prev = cur;
|
||||
if (!prev) return acc;
|
||||
@ -614,6 +691,7 @@ class PathSegment {
|
||||
prev: undefined,
|
||||
length: 0
|
||||
}).length;
|
||||
return this.length;
|
||||
}
|
||||
calculateEvenlySpacedPoints(spacing, resolution = 1) {
|
||||
const points = [];
|
||||
@ -638,254 +716,6 @@ class PathSegment {
|
||||
return points;
|
||||
}
|
||||
}
|
||||
class Mover {
|
||||
position;
|
||||
velocity;
|
||||
acceleration;
|
||||
maxSpeed;
|
||||
maxForce;
|
||||
_trailingPoint;
|
||||
_leadingPoint;
|
||||
get trailingPoint() {
|
||||
const desired = this.velocity.copy();
|
||||
desired.normalize();
|
||||
desired.mult(-this._trailingPoint);
|
||||
return Vector.add(this.position, desired);
|
||||
}
|
||||
get leadingPoint() {
|
||||
const desired = this.velocity.copy();
|
||||
desired.normalize();
|
||||
desired.mult(this._leadingPoint);
|
||||
return Vector.add(this.position, desired);
|
||||
}
|
||||
ctx;
|
||||
boundingBox;
|
||||
constructor(posOrRandom, vel, acc){
|
||||
if (typeof posOrRandom === 'boolean' && posOrRandom) {
|
||||
this.position = Vector.random2D(new Vector());
|
||||
this.velocity = Vector.random2D(new Vector());
|
||||
this.acceleration = new Vector();
|
||||
} else {
|
||||
this.position = posOrRandom || new Vector();
|
||||
this.velocity = vel || new Vector();
|
||||
this.acceleration = acc || new Vector();
|
||||
}
|
||||
this.boundingBox = {
|
||||
size: new Vector(20, 10),
|
||||
pos: new Vector(this.position.x - 10, this.position.y - 5)
|
||||
};
|
||||
this.maxSpeed = 3;
|
||||
this.maxForce = .3;
|
||||
this._trailingPoint = 0;
|
||||
this._leadingPoint = 0;
|
||||
this.init();
|
||||
}
|
||||
init() {}
|
||||
move() {
|
||||
this.velocity.limit(this.maxSpeed);
|
||||
this.acceleration.limit(this.maxForce);
|
||||
this.velocity.add(this.acceleration);
|
||||
this.position.add(this.velocity);
|
||||
this.edges();
|
||||
this.draw();
|
||||
}
|
||||
edges() {
|
||||
if (!this.ctx) return;
|
||||
if (this.position.x > this.ctx.canvas.width) this.position.x = 0;
|
||||
if (this.position.y > this.ctx.canvas.height) this.position.y = 0;
|
||||
if (this.position.x < 0) this.position.x = this.ctx.canvas.width;
|
||||
if (this.position.y < 0) this.position.y = this.ctx.canvas.height;
|
||||
}
|
||||
draw() {
|
||||
doodler.drawRotated(this.position, this.velocity.heading() || 0, ()=>{
|
||||
doodler.fillCenteredRect(this.position, this.boundingBox.size.x, this.boundingBox.size.y, {
|
||||
fillColor: 'white'
|
||||
});
|
||||
});
|
||||
if (!this.ctx) return;
|
||||
this.ctx.fillStyle = 'white';
|
||||
this.ctx.save();
|
||||
this.ctx.translate(this.position.x, this.position.y);
|
||||
this.ctx.rotate(this.velocity.heading() || 0);
|
||||
this.ctx.translate(-this.position.x, -this.position.y);
|
||||
this.ctx.translate(-(this.boundingBox.size.x / 2), -(this.boundingBox.size.y / 2));
|
||||
this.ctx.fillRect(this.position.x, this.position.y, this.boundingBox.size.x, this.boundingBox.size.y);
|
||||
this.ctx.restore();
|
||||
}
|
||||
setContext(ctx) {
|
||||
this.ctx = ctx;
|
||||
}
|
||||
applyForce(force) {
|
||||
this.acceleration.add(force);
|
||||
}
|
||||
static edges(point, width, height) {
|
||||
if (point.x > width) point.x = 0;
|
||||
if (point.y > height) point.y = 0;
|
||||
if (point.x < 0) point.x = width;
|
||||
if (point.y < 0) point.y = height;
|
||||
}
|
||||
}
|
||||
class Follower extends Mover {
|
||||
debug = true;
|
||||
follow(toFollow) {
|
||||
if (toFollow instanceof ComplexPath) {
|
||||
const predict = this.velocity.copy();
|
||||
predict.normalize();
|
||||
predict.mult(25);
|
||||
const predictpos = Vector.add(this.position, predict);
|
||||
if (this.ctx) Mover.edges(predict, this.ctx.canvas.width, this.ctx.canvas.height);
|
||||
let normal = null;
|
||||
let target = null;
|
||||
let worldRecord = 1000000;
|
||||
for(let i = 0; i < toFollow.points.length; i++){
|
||||
let a = toFollow.points[i];
|
||||
let b = toFollow.points[(i + 1) % toFollow.points.length];
|
||||
let normalPoint = getNormalPoint(predictpos, a, b);
|
||||
let dir = Vector.sub(b, a);
|
||||
if (normalPoint.x < Math.min(a.x, b.x) || normalPoint.x > Math.max(a.x, b.x) || normalPoint.y < Math.min(a.y, b.y) || normalPoint.y > Math.max(a.y, b.y)) {
|
||||
normalPoint = b.copy();
|
||||
a = toFollow.points[(i + 1) % toFollow.points.length];
|
||||
b = toFollow.points[(i + 2) % toFollow.points.length];
|
||||
dir = Vector.sub(b, a);
|
||||
}
|
||||
const d = Vector.dist(predictpos, normalPoint);
|
||||
if (d < worldRecord) {
|
||||
worldRecord = d;
|
||||
normal = normalPoint;
|
||||
dir.normalize();
|
||||
dir.mult(25);
|
||||
target = normal.copy();
|
||||
target.add(dir);
|
||||
}
|
||||
if (worldRecord > toFollow.radius) {
|
||||
return this.seek(target);
|
||||
}
|
||||
}
|
||||
if (this.debug && this.ctx) {
|
||||
this.ctx.strokeStyle = 'red';
|
||||
this.ctx.fillStyle = 'pink';
|
||||
this.ctx.beginPath();
|
||||
this.ctx.moveTo(this.position.x, this.position.y);
|
||||
this.ctx.lineTo(predictpos.x, predictpos.y);
|
||||
this.ctx.stroke();
|
||||
this.ctx.beginPath();
|
||||
this.ctx.arc(predictpos.x, predictpos.y, 4, 0, Constants.TWO_PI);
|
||||
this.ctx.fill();
|
||||
this.ctx.stroke();
|
||||
this.ctx.beginPath();
|
||||
this.ctx.arc(normal.x, normal.y, 4, 0, Constants.TWO_PI);
|
||||
this.ctx.fill();
|
||||
this.ctx.stroke();
|
||||
this.ctx.beginPath();
|
||||
this.ctx.moveTo(predictpos.x, predictpos.y);
|
||||
this.ctx.lineTo(target.x, target.y);
|
||||
this.ctx.stroke();
|
||||
this.ctx.beginPath();
|
||||
this.ctx.arc(target.x, target.y, 8, 0, Constants.TWO_PI);
|
||||
this.ctx.fill();
|
||||
this.ctx.stroke();
|
||||
}
|
||||
}
|
||||
}
|
||||
seek(target, strength = 1) {
|
||||
const desired = Vector.sub(target, this.position);
|
||||
desired.normalize();
|
||||
desired.mult(this.maxSpeed);
|
||||
const steer = Vector.sub(desired, this.velocity);
|
||||
steer.limit(this.maxForce);
|
||||
this.applyForce(steer.mult(strength));
|
||||
}
|
||||
link(target) {
|
||||
this.position = target.trailingPoint;
|
||||
this.seek(target.trailingPoint);
|
||||
}
|
||||
arrive(target) {
|
||||
const desired = Vector.sub(target, this.position);
|
||||
const d = desired.mag();
|
||||
let speed = this.maxSpeed;
|
||||
if (d < 10) {
|
||||
speed = map(d, 0, 100, 0, this.maxSpeed);
|
||||
}
|
||||
desired.setMag(speed);
|
||||
const steer = Vector.sub(desired, this.velocity);
|
||||
steer.limit(this.maxForce);
|
||||
this.applyForce(steer);
|
||||
}
|
||||
}
|
||||
function getNormalPoint(p, a, b) {
|
||||
const ap = Vector.sub(p, a);
|
||||
const ab = Vector.sub(b, a);
|
||||
ab.normalize();
|
||||
ab.mult(ap.dot(ab));
|
||||
const normalPoint = Vector.add(a, ab);
|
||||
return normalPoint;
|
||||
}
|
||||
class Train extends Follower {
|
||||
nodes;
|
||||
currentTrack;
|
||||
speed;
|
||||
follower;
|
||||
followers;
|
||||
constructor(track, length){
|
||||
super(track.points[0].copy());
|
||||
this.maxSpeed = 2;
|
||||
this.speed = 1;
|
||||
this.currentTrack = track;
|
||||
this.velocity = this.currentTrack.tangent(0).normalize().mult(this.maxSpeed);
|
||||
this.addCar(length);
|
||||
this.maxForce = .2;
|
||||
}
|
||||
init() {
|
||||
this.boundingBox.size.set(30, 10);
|
||||
this._trailingPoint = 30;
|
||||
}
|
||||
move() {
|
||||
this.follow();
|
||||
super.move();
|
||||
this.follower?.move();
|
||||
}
|
||||
follow() {
|
||||
const [_, t] = this.currentTrack.followTrack(this);
|
||||
this.velocity = this.currentTrack.tangent(t);
|
||||
this.velocity.normalize().mult(this.speed || this.maxSpeed);
|
||||
}
|
||||
setContext(ctx) {
|
||||
super.setContext(ctx);
|
||||
this.follower?.setContext(ctx);
|
||||
}
|
||||
addCar(length) {
|
||||
console.log(length);
|
||||
if (length) this.follower = new TrainCar(this.currentTrack, length - 1);
|
||||
this.follower?.setTarget(this);
|
||||
this.follower?.position.set(this.trailingPoint);
|
||||
this._trailingPoint -= 2;
|
||||
}
|
||||
}
|
||||
class TrainCar extends Train {
|
||||
target;
|
||||
setTarget(t) {
|
||||
this.target = t;
|
||||
}
|
||||
init() {
|
||||
this.boundingBox.size.set(20, 10);
|
||||
this._trailingPoint = 25;
|
||||
this.maxSpeed = this.maxSpeed * 2;
|
||||
this.maxForce = this.maxForce * 2;
|
||||
}
|
||||
move() {
|
||||
if (this.target) {
|
||||
if (this.position.dist(this.target.position) > this.target.position.dist(this.target.trailingPoint)) {
|
||||
this.arrive(this.currentTrack.getNearestPoint(this.target.trailingPoint));
|
||||
this.speed = this.target.speed;
|
||||
super.move();
|
||||
} else {
|
||||
this.draw();
|
||||
this.follower?.draw();
|
||||
}
|
||||
}
|
||||
}
|
||||
edges() {}
|
||||
}
|
||||
class Track extends PathSegment {
|
||||
editable = false;
|
||||
next;
|
||||
@ -897,38 +727,6 @@ class Track extends PathSegment {
|
||||
this.next = next || this;
|
||||
this.prev = prev || this;
|
||||
}
|
||||
followTrack(train) {
|
||||
const predict = train.velocity.copy();
|
||||
predict.normalize();
|
||||
predict.mult(1);
|
||||
const predictpos = Vector.add(train.position, predict);
|
||||
let [closest, closestDistance, closestT] = this.getClosestPoint(predictpos);
|
||||
let mostValid = this;
|
||||
if (this.next !== this) {
|
||||
const [point, distance, t] = this.next.getClosestPoint(predictpos);
|
||||
if (distance < closestDistance) {
|
||||
closest = point;
|
||||
closestDistance = distance;
|
||||
mostValid = this.next;
|
||||
closestT = t;
|
||||
}
|
||||
}
|
||||
if (this.prev !== this) {
|
||||
const [point1, distance1, t1] = this.next.getClosestPoint(predictpos);
|
||||
if (distance1 < closestDistance) {
|
||||
closest = point1;
|
||||
closestDistance = distance1;
|
||||
mostValid = this.next;
|
||||
closestT = t1;
|
||||
}
|
||||
}
|
||||
train.currentTrack = mostValid;
|
||||
train.arrive(closest);
|
||||
return [
|
||||
closest,
|
||||
closestT
|
||||
];
|
||||
}
|
||||
getNearestPoint(p) {
|
||||
let [closest, closestDistance] = this.getClosestPoint(p);
|
||||
if (this.next !== this) {
|
||||
@ -953,8 +751,10 @@ class Track extends PathSegment {
|
||||
}
|
||||
draw() {
|
||||
super.draw();
|
||||
if (this.editable) for (const e of this.points){
|
||||
e.drawDot();
|
||||
if (this.editable) {
|
||||
const [a, b, c, d] = this.points;
|
||||
doodler.line(a, b);
|
||||
doodler.line(c, d);
|
||||
}
|
||||
}
|
||||
setNext(t) {
|
||||
@ -970,9 +770,28 @@ class Spline {
|
||||
segments = [];
|
||||
ctx;
|
||||
evenPoints;
|
||||
pointSpacing;
|
||||
get points() {
|
||||
return Array.from(new Set(this.segments.flatMap((s)=>s.points)));
|
||||
}
|
||||
nodes;
|
||||
constructor(segs){
|
||||
this.segments = segs;
|
||||
this.pointSpacing = 1;
|
||||
this.evenPoints = this.calculateEvenlySpacedPoints(1);
|
||||
this.nodes = [];
|
||||
for(let i = 0; i < this.points.length; i += 3){
|
||||
const node = {
|
||||
anchor: this.points[i],
|
||||
controls: [
|
||||
this.points.at(i - 1),
|
||||
this.points[(i + 1) % this.points.length]
|
||||
],
|
||||
mirrored: false,
|
||||
tangent: true
|
||||
};
|
||||
this.nodes.push(node);
|
||||
}
|
||||
}
|
||||
setContext(ctx) {
|
||||
this.ctx = ctx;
|
||||
@ -986,6 +805,7 @@ class Spline {
|
||||
}
|
||||
}
|
||||
calculateEvenlySpacedPoints(spacing, resolution = 1) {
|
||||
this.pointSpacing = 1;
|
||||
const points = [];
|
||||
points.push(this.segments[0].points[0]);
|
||||
let prev = points[0];
|
||||
@ -1007,6 +827,7 @@ class Spline {
|
||||
prev = point;
|
||||
}
|
||||
}
|
||||
this.evenPoints = points;
|
||||
return points;
|
||||
}
|
||||
followEvenPoints(t) {
|
||||
@ -1014,10 +835,35 @@ class Spline {
|
||||
const i = Math.floor(t);
|
||||
const a = this.evenPoints[i];
|
||||
const b = this.evenPoints[(i + 1) % this.evenPoints.length];
|
||||
try {
|
||||
return Vector.lerp(a, b, t % 1);
|
||||
} catch {
|
||||
console.log(t, i, a, b);
|
||||
return Vector.lerp(a, b, t % 1);
|
||||
}
|
||||
calculateApproxLength() {
|
||||
for (const s of this.segments){
|
||||
s.calculateApproxLength();
|
||||
}
|
||||
}
|
||||
toggleNodeTangent(p) {
|
||||
const node = this.nodes.find((n)=>n.anchor === p);
|
||||
node && (node.tangent = !node.tangent);
|
||||
}
|
||||
toggleNodeMirrored(p) {
|
||||
const node = this.nodes.find((n)=>n.anchor === p);
|
||||
node && (node.mirrored = !node.mirrored);
|
||||
}
|
||||
handleNodeEdit(p, movement) {
|
||||
const node = this.nodes.find((n)=>n.anchor === p || n.controls.includes(p));
|
||||
if (!node || !(node.mirrored || node.tangent)) return;
|
||||
if (node.anchor !== p) {
|
||||
if (node.mirrored || node.tangent) {
|
||||
const mover = node.controls.find((e)=>e !== p);
|
||||
const v = Vector.sub(node.anchor, p);
|
||||
if (!node.mirrored) v.setMag(Vector.sub(node.anchor, mover).mag());
|
||||
mover.set(Vector.add(v, node.anchor));
|
||||
}
|
||||
} else {
|
||||
for (const control of node.controls){
|
||||
control.add(movement.x, movement.y);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1108,25 +954,62 @@ const loadFromJson = ()=>{
|
||||
}
|
||||
return new Spline(segments);
|
||||
};
|
||||
const engineSprites = document.createElement('img');
|
||||
engineSprites.src = './sprites/EngineSprites.png';
|
||||
engineSprites.style.display = 'none';
|
||||
engineSprites.id = 'engine-sprites';
|
||||
document.body.append(engineSprites);
|
||||
init({
|
||||
width: 400,
|
||||
height: 400,
|
||||
bg: '#333'
|
||||
});
|
||||
const path = loadFromJson();
|
||||
let t = 0;
|
||||
let speed = 1;
|
||||
Array(1).fill(null).map((_, i)=>new Train(path.segments[i % path.segments.length], 5));
|
||||
const car = new TrainCar(55, engineSprites, 80, 20, {
|
||||
at: new Vector(0, 80),
|
||||
height: 20,
|
||||
width: 80
|
||||
});
|
||||
const train = new Train(path, [
|
||||
car
|
||||
]);
|
||||
let dragEndCounter = 0;
|
||||
let selectedNode;
|
||||
doodler.createLayer(()=>{
|
||||
path.draw();
|
||||
const points = Array(5).fill(null).map((_, i)=>path.followEvenPoints(t - i * 15));
|
||||
for (const point of points){
|
||||
point && doodler.drawCircle(point, 5, {
|
||||
strokeColor: 'green'
|
||||
for(let i = 0; i < path.evenPoints.length; i += 10){
|
||||
const p = path.evenPoints[i];
|
||||
const next = path.evenPoints[(i + 1) % path.evenPoints.length];
|
||||
const last = path.evenPoints.at(i - 1);
|
||||
if (!last) break;
|
||||
const tan = Vector.sub(last, next);
|
||||
doodler.drawRotated(p, tan.heading(), ()=>{
|
||||
doodler.line(p, p.copy().add(0, 10), {
|
||||
color: '#291b17',
|
||||
weight: 4
|
||||
});
|
||||
doodler.line(p, p.copy().add(0, -10), {
|
||||
color: '#291b17',
|
||||
weight: 4
|
||||
});
|
||||
doodler.line(p.copy().add(-6, 5), p.copy().add(6, 5), {
|
||||
color: 'grey',
|
||||
weight: 2
|
||||
});
|
||||
doodler.line(p.copy().add(-6, -5), p.copy().add(6, -5), {
|
||||
color: 'grey',
|
||||
weight: 2
|
||||
});
|
||||
});
|
||||
}
|
||||
t = (t + speed / 2) % path.evenPoints.length;
|
||||
path.draw();
|
||||
train.move();
|
||||
selectedNode?.anchor.drawDot();
|
||||
selectedNode?.controls.forEach((e)=>e.drawDot());
|
||||
});
|
||||
let editable = false;
|
||||
const clickables = new Map();
|
||||
let selectedPoint;
|
||||
document.addEventListener('keyup', (e)=>{
|
||||
if (e.key === 'd') {}
|
||||
if (e.key === 'ArrowUp') {
|
||||
@ -1135,7 +1018,23 @@ document.addEventListener('keyup', (e)=>{
|
||||
if (e.key === 'ArrowDown') {
|
||||
speed -= .1;
|
||||
}
|
||||
if (e.key === 'm' && selectedPoint) {
|
||||
const points = path.points;
|
||||
const index = points.findIndex((p)=>p === selectedPoint);
|
||||
if (index > -1) {
|
||||
const prev = points.at(index - 1);
|
||||
const next = points[(index + 1) % points.length];
|
||||
const toPrev = Vector.sub(prev, selectedPoint);
|
||||
toPrev.setMag(next.dist(selectedPoint));
|
||||
toPrev.rotate(Math.PI);
|
||||
const toNext = Vector.add(toPrev, selectedPoint);
|
||||
next.set(toNext);
|
||||
path.calculateApproxLength();
|
||||
path.calculateEvenlySpacedPoints(1);
|
||||
}
|
||||
}
|
||||
if (e.key === 'e') {
|
||||
editable = !editable;
|
||||
for (const t of path.segments){
|
||||
t.editable = !t.editable;
|
||||
for (const p of t.points){
|
||||
@ -1144,12 +1043,30 @@ document.addEventListener('keyup', (e)=>{
|
||||
doodler.addDragEvents({
|
||||
point: p,
|
||||
onDragEnd: ()=>{
|
||||
console.log('dragend');
|
||||
dragEndCounter++;
|
||||
t.length = t.calculateApproxLength(100);
|
||||
path.evenPoints = path.calculateEvenlySpacedPoints(1);
|
||||
},
|
||||
onDrag: (movement)=>{
|
||||
path.handleNodeEdit(p, movement);
|
||||
}
|
||||
});
|
||||
} else doodler.unregisterDraggable(p);
|
||||
} else {
|
||||
doodler.unregisterDraggable(p);
|
||||
}
|
||||
}
|
||||
}
|
||||
for (const p1 of path.points){
|
||||
if (editable) {
|
||||
const onClick = ()=>{
|
||||
selectedPoint = p1;
|
||||
selectedNode = path.nodes.find((e)=>e.anchor === p1 || e.controls.includes(p1));
|
||||
};
|
||||
clickables.set(p1, onClick);
|
||||
doodler.registerClickable(p1.copy().sub(10, 10), p1.copy().add(10, 10), onClick);
|
||||
} else {
|
||||
const the = clickables.get(p1);
|
||||
doodler.unregisterClickable(the);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -12,6 +12,6 @@
|
||||
},
|
||||
"imports": {
|
||||
"drawing": "./drawing/index.ts",
|
||||
"doodler": "https://git.cyborggrizzly.com/emma/doodler/raw/tag/0.0.4a/mod.ts"
|
||||
"doodler": "https://git.cyborggrizzly.com/emma/doodler/raw/tag/0.0.7a/mod.ts"
|
||||
}
|
||||
}
|
115
main.ts
115
main.ts
@ -1,18 +1,19 @@
|
||||
import { lerp } from "./math/lerp.ts";
|
||||
import { ComplexPath, PathSegment } from "./math/path.ts";
|
||||
import { Mover } from "./physics/mover.ts";
|
||||
import { Train } from "./train.ts";
|
||||
import { Train, TrainCar } from "./train.ts";
|
||||
import { fillCircle, drawCircle } from 'drawing';
|
||||
import { generateSquareTrack, loadFromJson } from "./track.ts";
|
||||
import { generateSquareTrack, IControlNode, loadFromJson } from "./track.ts";
|
||||
import { drawLine } from "./drawing/line.ts";
|
||||
import { initializeDoodler, Vector } from 'doodler';
|
||||
|
||||
|
||||
|
||||
// for (const mover of trains) {
|
||||
// mover.setContext(ctx);
|
||||
// mover.velocity.add(Vector.random2D())
|
||||
// }
|
||||
const engineSprites = document.createElement('img');
|
||||
engineSprites.src = './sprites/EngineSprites.png';
|
||||
engineSprites.style.display = 'none';
|
||||
engineSprites.id = 'engine-sprites';
|
||||
document.body.append(engineSprites);
|
||||
|
||||
initializeDoodler({
|
||||
width: 400,
|
||||
@ -33,43 +34,42 @@ let t = 0;
|
||||
let currentSeg = 0;
|
||||
let speed = 1;
|
||||
|
||||
const trainCount = 1;
|
||||
const trains = Array(trainCount).fill(null).map((_, i) => new Train(path.segments[i % path.segments.length], 5));
|
||||
// const trainCount = 1;
|
||||
// const trains = Array(trainCount).fill(null).map((_, i) => new Train(path.segments[i % path.segments.length], 5));
|
||||
const car = new TrainCar(55, engineSprites, 80, 20, {at: new Vector(0, 80), height: 20, width: 80})
|
||||
const train = new Train(path, [car]);
|
||||
|
||||
let dragEndCounter = 0
|
||||
let selectedNode: IControlNode | undefined;
|
||||
|
||||
doodler.createLayer(() => {
|
||||
path.draw();
|
||||
|
||||
// for (const train of trains) {
|
||||
// train.move();
|
||||
// }
|
||||
|
||||
// ctx.strokeStyle = 'red';
|
||||
// ctx.lineWidth = 4;
|
||||
// const seg = path.segments[currentSeg];
|
||||
// const start = seg.getPointAtT(t);
|
||||
// const tan = seg.tangent(t).normalize().mult(25);
|
||||
// const tan = seg.tangent(t);
|
||||
|
||||
// for (const p of path.evenPoints) {
|
||||
// p.drawDot();
|
||||
// }
|
||||
// doodler.line(start, new Vector(start.x + tan.x, start.y + tan.y), {color: 'blue'});
|
||||
// doodler.fillCircle(start, 5, {fillColor: 'blue'})
|
||||
|
||||
const points = Array(5).fill(null).map((_,i) => path.followEvenPoints(t - (i * 15)))
|
||||
for (const point of points) {
|
||||
point &&
|
||||
doodler.drawCircle(point, 5, { strokeColor: 'green' })
|
||||
for (let i = 0; i < path.evenPoints.length; i+=10) {
|
||||
const p = path.evenPoints[i];
|
||||
const next = path.evenPoints[(i + 1)%path.evenPoints.length];
|
||||
const last = path.evenPoints.at(i - 1);
|
||||
if (!last) break;
|
||||
const tan = Vector.sub(last, next);
|
||||
|
||||
doodler.drawRotated(p, tan.heading(), () => {
|
||||
doodler.line(p, p.copy().add(0,10), {color: '#291b17', weight: 4})
|
||||
doodler.line(p, p.copy().add(0,-10), {color: '#291b17', weight: 4})
|
||||
doodler.line(p.copy().add(-6,5), p.copy().add(6,5), {color: 'grey', weight: 2})
|
||||
doodler.line(p.copy().add(-6,-5), p.copy().add(6,-5), {color: 'grey', weight: 2})
|
||||
})
|
||||
}
|
||||
path.draw();
|
||||
train.move();
|
||||
|
||||
// const point = path.followEvenPoints(t);
|
||||
|
||||
t = (t + (speed / 2)) % path.evenPoints.length;
|
||||
|
||||
// path.segments.forEach(s => s.calculateApproxLength(10000))
|
||||
selectedNode?.anchor.drawDot();
|
||||
selectedNode?.controls.forEach(e => e.drawDot());
|
||||
})
|
||||
|
||||
let editable = false;
|
||||
|
||||
const clickables = new Map()
|
||||
|
||||
let selectedPoint: Vector;
|
||||
|
||||
document.addEventListener('keyup', e => {
|
||||
if (e.key === 'd') {
|
||||
// console.log(trains)
|
||||
@ -90,7 +90,26 @@ document.addEventListener('keyup', e => {
|
||||
speed -= .1
|
||||
}
|
||||
|
||||
if (e.key === 'm' && selectedPoint) {
|
||||
const points = path.points;
|
||||
const index = points.findIndex(p => p === selectedPoint);
|
||||
if (index > -1) {
|
||||
const prev = points.at(index - 1)!;
|
||||
const next = points[(index + 1) % points.length];
|
||||
|
||||
const toPrev = Vector.sub(prev, selectedPoint);
|
||||
toPrev.setMag(next.dist(selectedPoint));
|
||||
toPrev.rotate(Math.PI)
|
||||
const toNext = Vector.add(toPrev, selectedPoint);
|
||||
next.set(toNext);
|
||||
|
||||
path.calculateApproxLength();
|
||||
path.calculateEvenlySpacedPoints(1);
|
||||
}
|
||||
}
|
||||
|
||||
if (e.key === 'e') {
|
||||
editable = !editable;
|
||||
for (const t of path.segments) {
|
||||
t.editable = !t.editable;
|
||||
for (const p of t.points) {
|
||||
@ -99,14 +118,34 @@ document.addEventListener('keyup', e => {
|
||||
doodler.addDragEvents({
|
||||
point: p,
|
||||
onDragEnd: () => {
|
||||
console.log('dragend');
|
||||
dragEndCounter++
|
||||
t.length = t.calculateApproxLength(100)
|
||||
path.evenPoints = path.calculateEvenlySpacedPoints(1)
|
||||
},
|
||||
onDrag: (movement) => {
|
||||
// todo - remove ! after updating doodler
|
||||
path.handleNodeEdit(p, movement!)
|
||||
}
|
||||
})
|
||||
}
|
||||
else
|
||||
else {
|
||||
doodler.unregisterDraggable(p)
|
||||
}
|
||||
}
|
||||
}
|
||||
for (const p of path.points) {
|
||||
if (editable) {
|
||||
const onClick = () => {
|
||||
selectedPoint = p;
|
||||
selectedNode = path.nodes.find(e => e.anchor === p || e.controls.includes(p));
|
||||
}
|
||||
|
||||
clickables.set(p, onClick);
|
||||
doodler.registerClickable(p.copy().sub(10, 10), p.copy().add(10, 10), onClick);
|
||||
}
|
||||
else {
|
||||
const the = clickables.get(p);
|
||||
doodler.unregisterClickable(the);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -184,13 +184,14 @@ export class PathSegment {
|
||||
const current = stepSize * i;
|
||||
points.push(this.getPointAtT(current))
|
||||
}
|
||||
return points.reduce((acc: { prev?: Vector, length: number }, cur) => {
|
||||
this.length = points.reduce((acc: { prev?: Vector, length: number }, cur) => {
|
||||
const prev = acc.prev;
|
||||
acc.prev = cur;
|
||||
if (!prev) return acc;
|
||||
acc.length += cur.dist(prev);
|
||||
return acc;
|
||||
}, { prev: undefined, length: 0 }).length
|
||||
return this.length;
|
||||
}
|
||||
|
||||
calculateEvenlySpacedPoints(spacing: number, resolution = 1) {
|
||||
|
BIN
sprites/BlueEngine.png
Normal file
BIN
sprites/BlueEngine.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 1.5 KiB |
BIN
sprites/Engine.png
Normal file
BIN
sprites/Engine.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 1.3 KiB |
BIN
sprites/EngineSprites.png
Normal file
BIN
sprites/EngineSprites.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 22 KiB |
BIN
sprites/GreenEngine.png
Normal file
BIN
sprites/GreenEngine.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 1.5 KiB |
BIN
sprites/PurpleEngine.png
Normal file
BIN
sprites/PurpleEngine.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 1.5 KiB |
BIN
sprites/RedEngine.png
Normal file
BIN
sprites/RedEngine.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 1.5 KiB |
155
track.ts
155
track.ts
@ -18,43 +18,43 @@ export class Track extends PathSegment {
|
||||
this.prev = prev || this;
|
||||
}
|
||||
|
||||
followTrack(train: Train): [Vector, number] {
|
||||
const predict = train.velocity.copy();
|
||||
predict.normalize();
|
||||
predict.mult(1);
|
||||
const predictpos = Vector.add(train.position, predict)
|
||||
// followTrack(train: Train): [Vector, number] {
|
||||
// const predict = train.velocity.copy();
|
||||
// predict.normalize();
|
||||
// predict.mult(1);
|
||||
// const predictpos = Vector.add(train.position, predict)
|
||||
|
||||
// const leading = train.leadingPoint;
|
||||
// let closest = this.points[0];
|
||||
// let closestDistance = this.getClosestPoint(leading);
|
||||
let [closest, closestDistance, closestT] = this.getClosestPoint(predictpos);
|
||||
// deno-lint-ignore no-this-alias
|
||||
let mostValid: Track = this;
|
||||
// // const leading = train.leadingPoint;
|
||||
// // let closest = this.points[0];
|
||||
// // let closestDistance = this.getClosestPoint(leading);
|
||||
// let [closest, closestDistance, closestT] = this.getClosestPoint(predictpos);
|
||||
// // deno-lint-ignore no-this-alias
|
||||
// let mostValid: Track = this;
|
||||
|
||||
if (this.next !== this) {
|
||||
const [point, distance, t] = this.next.getClosestPoint(predictpos);
|
||||
if (distance < closestDistance) {
|
||||
closest = point;
|
||||
closestDistance = distance;
|
||||
mostValid = this.next;
|
||||
closestT = t;
|
||||
}
|
||||
}
|
||||
if (this.prev !== this) {
|
||||
const [point, distance, t] = this.next.getClosestPoint(predictpos);
|
||||
if (distance < closestDistance) {
|
||||
closest = point;
|
||||
closestDistance = distance;
|
||||
mostValid = this.next;
|
||||
closestT = t;
|
||||
}
|
||||
}
|
||||
// if (this.next !== this) {
|
||||
// const [point, distance, t] = this.next.getClosestPoint(predictpos);
|
||||
// if (distance < closestDistance) {
|
||||
// closest = point;
|
||||
// closestDistance = distance;
|
||||
// mostValid = this.next;
|
||||
// closestT = t;
|
||||
// }
|
||||
// }
|
||||
// if (this.prev !== this) {
|
||||
// const [point, distance, t] = this.next.getClosestPoint(predictpos);
|
||||
// if (distance < closestDistance) {
|
||||
// closest = point;
|
||||
// closestDistance = distance;
|
||||
// mostValid = this.next;
|
||||
// closestT = t;
|
||||
// }
|
||||
// }
|
||||
|
||||
train.currentTrack = mostValid;
|
||||
train.arrive(closest);
|
||||
// if (predictpos.dist(closest) > 2) train.arrive(closest);
|
||||
return [closest, closestT];
|
||||
}
|
||||
// train.currentTrack = mostValid;
|
||||
// train.arrive(closest);
|
||||
// // if (predictpos.dist(closest) > 2) train.arrive(closest);
|
||||
// return [closest, closestT];
|
||||
// }
|
||||
|
||||
getNearestPoint(p: Vector) {
|
||||
let [closest, closestDistance] = this.getClosestPoint(p);
|
||||
@ -85,10 +85,11 @@ export class Track extends PathSegment {
|
||||
|
||||
draw(): void {
|
||||
super.draw();
|
||||
if (this.editable)
|
||||
for (const e of this.points) {
|
||||
e.drawDot();
|
||||
}
|
||||
if (this.editable) {
|
||||
const [a, b, c, d] = this.points;
|
||||
doodler.line(a, b);
|
||||
doodler.line(c, d);
|
||||
}
|
||||
}
|
||||
|
||||
setNext(t: Track) {
|
||||
@ -107,9 +108,28 @@ export class Spline<T extends PathSegment = PathSegment> {
|
||||
ctx?: CanvasRenderingContext2D;
|
||||
|
||||
evenPoints: Vector[];
|
||||
pointSpacing: number;
|
||||
|
||||
get points() {
|
||||
return Array.from(new Set(this.segments.flatMap(s => s.points)));
|
||||
}
|
||||
|
||||
nodes: IControlNode[];
|
||||
|
||||
constructor(segs: T[]) {
|
||||
this.segments = segs;
|
||||
this.pointSpacing = 1;
|
||||
this.evenPoints = this.calculateEvenlySpacedPoints(1);
|
||||
this.nodes = [];
|
||||
for (let i = 0; i < this.points.length; i += 3) {
|
||||
const node: IControlNode = {
|
||||
anchor: this.points[i],
|
||||
controls: [this.points.at(i - 1)!, this.points[(i + 1) % this.points.length]],
|
||||
mirrored: false,
|
||||
tangent: true
|
||||
}
|
||||
this.nodes.push(node);
|
||||
}
|
||||
}
|
||||
|
||||
setContext(ctx: CanvasRenderingContext2D) {
|
||||
@ -126,6 +146,7 @@ export class Spline<T extends PathSegment = PathSegment> {
|
||||
}
|
||||
|
||||
calculateEvenlySpacedPoints(spacing: number, resolution = 1) {
|
||||
this.pointSpacing = 1;
|
||||
// return this.segments.flatMap(s => s.calculateEvenlySpacedPoints(spacing, resolution));
|
||||
const points: Vector[] = []
|
||||
|
||||
@ -155,20 +176,51 @@ export class Spline<T extends PathSegment = PathSegment> {
|
||||
}
|
||||
}
|
||||
|
||||
this.evenPoints = points;
|
||||
|
||||
return points;
|
||||
}
|
||||
|
||||
followEvenPoints(t: number) {
|
||||
if (t < 0) t+= this.evenPoints.length
|
||||
if (t < 0) t += this.evenPoints.length
|
||||
const i = Math.floor(t);
|
||||
const a = this.evenPoints[i]
|
||||
const b = this.evenPoints[(i + 1) % this.evenPoints.length]
|
||||
|
||||
try {
|
||||
return Vector.lerp(a, b, t % 1);
|
||||
return Vector.lerp(a, b, t % 1);
|
||||
}
|
||||
|
||||
} catch {
|
||||
console.log(t, i, a, b);
|
||||
calculateApproxLength() {
|
||||
for (const s of this.segments) {
|
||||
s.calculateApproxLength();
|
||||
}
|
||||
}
|
||||
|
||||
toggleNodeTangent(p: Vector) {
|
||||
const node = this.nodes.find(n => n.anchor === p);
|
||||
|
||||
node && (node.tangent = !node.tangent);
|
||||
}
|
||||
toggleNodeMirrored(p: Vector) {
|
||||
const node = this.nodes.find(n => n.anchor === p);
|
||||
|
||||
node && (node.mirrored = !node.mirrored);
|
||||
}
|
||||
handleNodeEdit(p: Vector, movement: { x: number, y: number }) {
|
||||
const node = this.nodes.find(n => n.anchor === p || n.controls.includes(p));
|
||||
if (!node || !(node.mirrored || node.tangent)) return;
|
||||
|
||||
if (node.anchor !== p) {
|
||||
if (node.mirrored || node.tangent) {
|
||||
const mover = node.controls.find(e => e !== p)!;
|
||||
const v = Vector.sub(node.anchor, p);
|
||||
if (!node.mirrored) v.setMag(Vector.sub(node.anchor, mover).mag());
|
||||
mover.set(Vector.add(v, node.anchor));
|
||||
}
|
||||
} else {
|
||||
for (const control of node.controls) {
|
||||
control.add(movement.x, movement.y)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -209,14 +261,21 @@ export const loadFromJson = () => {
|
||||
if (!json) return generateSquareTrack();
|
||||
const segments: Track[] = [];
|
||||
|
||||
for (const {points} of json.segments) {
|
||||
segments.push(new Track(points.map((p:{x:number,y:number}) => new Vector(p.x, p.y))));
|
||||
for (const { points } of json.segments) {
|
||||
segments.push(new Track(points.map((p: { x: number, y: number }) => new Vector(p.x, p.y))));
|
||||
}
|
||||
|
||||
for (const [i,s] of segments.entries()) {
|
||||
s.setNext(segments[(i+1)%segments.length])
|
||||
s.setPrev(segments.at(i-1)!)
|
||||
for (const [i, s] of segments.entries()) {
|
||||
s.setNext(segments[(i + 1) % segments.length])
|
||||
s.setPrev(segments.at(i - 1)!)
|
||||
}
|
||||
|
||||
return new Spline<Track>(segments);
|
||||
}
|
||||
|
||||
export interface IControlNode {
|
||||
anchor: Vector;
|
||||
controls: [Vector, Vector];
|
||||
tangent: boolean;
|
||||
mirrored: boolean;
|
||||
}
|
||||
|
312
train.old.ts
Normal file
312
train.old.ts
Normal file
@ -0,0 +1,312 @@
|
||||
import { drawLine } from "./drawing/line.ts";
|
||||
import { ComplexPath, PathSegment } from "./math/path.ts";
|
||||
import { Vector } from "doodler";
|
||||
import { Follower } from "./physics/follower.ts";
|
||||
import { Mover } from "./physics/mover.ts";
|
||||
import { Track } from "./track.ts";
|
||||
|
||||
export class Train extends Follower {
|
||||
nodes?: Vector[];
|
||||
|
||||
currentTrack: Track;
|
||||
|
||||
speed: number;
|
||||
|
||||
follower?: TrainCar;
|
||||
|
||||
followers?: TrainCar[];
|
||||
|
||||
constructor(track: Track, length: number) {
|
||||
super(track.points[0].copy());
|
||||
this.maxSpeed = 2;
|
||||
this.speed = 1;
|
||||
this.currentTrack = track;
|
||||
this.velocity = this.currentTrack.tangent(0).normalize().mult(this.maxSpeed);
|
||||
|
||||
this.addCar(length);
|
||||
|
||||
this.maxForce = .2;
|
||||
}
|
||||
|
||||
init(): void {
|
||||
this.boundingBox.size.set(30, 10);
|
||||
this._trailingPoint = 30;
|
||||
}
|
||||
|
||||
move(): void {
|
||||
this.follow();
|
||||
|
||||
super.move();
|
||||
this.follower?.move()
|
||||
// this.draw();
|
||||
}
|
||||
|
||||
follow(): void {
|
||||
// const [_, t] = this.currentTrack.followTrack(this);
|
||||
|
||||
// this.position = this.currentTrack.getPointAtT(t);
|
||||
// this.velocity = this.currentTrack.tangent(t);
|
||||
this.velocity.normalize().mult(this.speed || this.maxSpeed);
|
||||
// if (nearest.dist(this.position) > 10)
|
||||
// this.seek(nearest);
|
||||
}
|
||||
|
||||
// draw(): void {
|
||||
// if (!this.ctx) return;
|
||||
// const ctx = this.ctx;
|
||||
// // const [a, b] = this.nodes;
|
||||
|
||||
// ctx.strokeStyle = 'blue'
|
||||
// ctx.lineWidth = 10;
|
||||
// // drawLine(ctx, a.x, a.y, b.x, b.y);
|
||||
// super.draw()
|
||||
// }
|
||||
|
||||
setContext(ctx: CanvasRenderingContext2D): void {
|
||||
super.setContext(ctx);
|
||||
this.follower?.setContext(ctx);
|
||||
}
|
||||
|
||||
addCar(length: number,) {
|
||||
console.log(length);
|
||||
if (length)
|
||||
this.follower = new TrainCar(this.currentTrack, length - 1);
|
||||
this.follower?.setTarget(this);
|
||||
this.follower?.position.set(this.trailingPoint);
|
||||
this._trailingPoint -= 2;
|
||||
}
|
||||
}
|
||||
|
||||
class TrainCar extends Train {
|
||||
// constructor(n: [Vector, Vector], track: Track) {
|
||||
// super(track);
|
||||
// this.nodes = n;
|
||||
// }
|
||||
target?: Train;
|
||||
setTarget(t: Train) {
|
||||
this.target = t;
|
||||
}
|
||||
|
||||
init(): void {
|
||||
this.boundingBox.size.set(20, 10)
|
||||
this._trailingPoint = 25;
|
||||
this.maxSpeed = this.maxSpeed * 2;
|
||||
this.maxForce = this.maxForce * 2;
|
||||
// this.speed = 0;
|
||||
}
|
||||
|
||||
// follow(): void {
|
||||
// if (!this.target) return;
|
||||
|
||||
// const points = this.currentTrack.getAllPointsInRange(this.target.position, this.target._trailingPoint);
|
||||
// let closest = this.target.position;
|
||||
// let closestTan = this.target.velocity;
|
||||
// for (const [t, path] of points) {
|
||||
// const point = path.getPointAtT(t);
|
||||
// if (point.dist(this.target.trailingPoint) < this.target.trailingPoint.dist(closest)) {
|
||||
// closest = point;
|
||||
// closestTan = path.tangent(t);
|
||||
// }
|
||||
// }
|
||||
|
||||
// // this.position.set(closest);
|
||||
// this.seek(closest);
|
||||
// this.velocity.set(closestTan.normalize().mult(this.target.speed));
|
||||
// }
|
||||
|
||||
move(): void {
|
||||
// if (!this.target) return;
|
||||
|
||||
// const r = 30;
|
||||
// const points = this.currentTrack.getAllPointsInRange(this.target.position, this.target._trailingPoint);
|
||||
// let closest = this.target.position;
|
||||
// let closestTan = this.target.velocity;
|
||||
// for (const [t, path] of points) {
|
||||
// const point = path.getPointAtT(t);
|
||||
// if (point.dist(this.target.trailingPoint) < this.target.trailingPoint.dist(closest)) {
|
||||
// closest = point;
|
||||
// closestTan = path.tangent(t);
|
||||
// }
|
||||
// }
|
||||
|
||||
// // this.position.set(closest);
|
||||
// // this.seek(closest);
|
||||
// this.velocity.set(closestTan.normalize().mult(this.target.speed));
|
||||
// super.move();
|
||||
// if (this.target && this.position.dist(this.target.trailingPoint) < 2) {
|
||||
// this.velocity.setMag(0);
|
||||
// } else if (this.target) {
|
||||
// this.velocity.setMag(this.target.velocity.mag());
|
||||
// }
|
||||
|
||||
// if (this.target) {
|
||||
// this.position.set(this.target.trailingPoint);
|
||||
// this.speed = this.target.speed;
|
||||
// }
|
||||
// const [pos,t] = this.currentTrack.followTrack(this);
|
||||
|
||||
// this.position = pos.copy()
|
||||
// if (this.target) {
|
||||
// const points = this.currentTrack.getPointWithinRadius(this.target.position, 30);
|
||||
|
||||
// let closest = this.target.position;
|
||||
// for (const [i,point] of points.entries()) {
|
||||
// if (typeof point !== "number") break;
|
||||
|
||||
// const tracks = [this.currentTrack, this.currentTrack.next, this.currentTrack.prev];
|
||||
|
||||
// const a = tracks[i].getPointAtT(point);
|
||||
|
||||
// if (a.dist(this.target.trailingPoint) < closest.dist(this.target.trailingPoint)) {
|
||||
// closest = a;
|
||||
// }
|
||||
// }
|
||||
|
||||
// this.position = closest;
|
||||
// }
|
||||
// this.draw();
|
||||
if (this.target) {
|
||||
if (this.position.dist(this.target.position) > this.target.position.dist(this.target.trailingPoint)) {
|
||||
|
||||
// this.velocity = this.currentTrack.tangent(t);
|
||||
// this.velocity.normalize().mult(this.speed);
|
||||
|
||||
this.arrive(this.currentTrack.getNearestPoint(this.target.trailingPoint));
|
||||
// if (this.position.dist())
|
||||
// this.move()
|
||||
this.speed = this.target.speed;
|
||||
super.move();
|
||||
} else {
|
||||
this.draw()
|
||||
this.follower?.draw();
|
||||
}
|
||||
}
|
||||
// this.draw()
|
||||
// this.follower?.move()
|
||||
}
|
||||
|
||||
// draw(): void {
|
||||
// if (!this.ctx) return;
|
||||
// super.draw()
|
||||
// this.ctx.fillStyle = 'red';
|
||||
// this.position.drawDot(this.ctx);
|
||||
// this.ctx.fillStyle = 'green';
|
||||
// this.target?.trailingPoint.drawDot(this.ctx);
|
||||
// }
|
||||
|
||||
edges(): void {
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
// export class Train extends Follower {
|
||||
|
||||
// currentSegment: Track;
|
||||
// cars: TrainCar[] = [];
|
||||
|
||||
// id: string;
|
||||
// constructor(path: Track);
|
||||
// constructor(x: number, y: number, segment: Track);
|
||||
// constructor(x: number | Track, y?: number, segment?: Track) {
|
||||
|
||||
// super(x instanceof Track ? x.points[0].copy() : new Vector(x, y))
|
||||
|
||||
// if (x instanceof Track) {
|
||||
// this.currentSegment = x;
|
||||
// } else if (segment) {
|
||||
// this.currentSegment = segment;
|
||||
// } else {
|
||||
// throw new Error('Path not provided for train construction')
|
||||
// }
|
||||
// // super(new Vector(Math.floor(Math.random() * 200),Math.floor(Math.random() * 200)), Vector.random2D());
|
||||
// this.id = crypto.randomUUID()
|
||||
// this.boundingBox.size.set(40, 10)
|
||||
|
||||
// this.maxSpeed = 3;
|
||||
// this.maxForce = .3;
|
||||
|
||||
// this.addCar();
|
||||
|
||||
// this._trailingPoint = 40;
|
||||
// this._leadingPoint = 15;
|
||||
// }
|
||||
|
||||
// move(): void {
|
||||
// for (const car of this.cars) {
|
||||
// car.move();
|
||||
// }
|
||||
// this.follow(this.currentSegment)
|
||||
// super.move();
|
||||
// }
|
||||
|
||||
// draw(): void {
|
||||
// if (!this.ctx) return;
|
||||
// // this.ctx.save();
|
||||
// this.ctx.fillStyle = 'white';
|
||||
// this.ctx.strokeStyle = 'red';
|
||||
// super.draw();
|
||||
// // this.ctx.restore();
|
||||
// }
|
||||
|
||||
// addCar() {
|
||||
// const last = this.cars[this.cars.length - 1];
|
||||
// this.cars.push(new TrainCar(this, (last || this).velocity.copy().normalize().mult(-30), last));
|
||||
// }
|
||||
|
||||
// setContext(ctx: CanvasRenderingContext2D): void {
|
||||
// super.setContext(ctx);
|
||||
// for (const car of this.cars) {
|
||||
// car.setContext(ctx);
|
||||
// }
|
||||
// }
|
||||
|
||||
// follow(toFollow: Track): void {
|
||||
// // const predict = this.velocity.copy();
|
||||
// // predict.normalize();
|
||||
// // predict.mult(25);
|
||||
// // const predictpos = Vector.add(this.position, predict)
|
||||
|
||||
// const nearest = toFollow.getMostValidTrack(this);
|
||||
|
||||
// this.seek(nearest);
|
||||
// }
|
||||
// }
|
||||
|
||||
// export class TrainCar extends Follower {
|
||||
// train?: Train;
|
||||
// prevCar?: Mover;
|
||||
// constructor(train: Train, pos: Vector, prevCar: Mover) {
|
||||
// super(pos);
|
||||
// this.train = train;
|
||||
|
||||
// this.boundingBox.size.set(20, 15);
|
||||
|
||||
// this.prevCar = prevCar || train;
|
||||
|
||||
// this.maxSpeed = 2;
|
||||
// this.maxForce = .3;
|
||||
|
||||
// this._trailingPoint = 25;
|
||||
// this._leadingPoint = 25;
|
||||
|
||||
// }
|
||||
|
||||
// move(): void {
|
||||
// if (this.train && this.prevCar) {
|
||||
// this.link(this.prevCar);
|
||||
// // super.move();
|
||||
// this.edges();
|
||||
// this.ctx && (this.ctx.fillStyle = 'orange')
|
||||
// this.draw();
|
||||
// }
|
||||
// else super.move();
|
||||
// }
|
||||
|
||||
// edges(): void {
|
||||
// if (!this.ctx || !this.train) return;
|
||||
// if (this.train.position.x > this.ctx.canvas.width) this.position.x -= this.ctx.canvas.width;
|
||||
// if (this.train.position.y > this.ctx.canvas.height) this.position.y -= this.ctx.canvas.height;
|
||||
// if (this.train.position.x < 0) this.position.x += this.ctx.canvas.width;
|
||||
// if (this.train.position.y < 0) this.position.y += this.ctx.canvas.height;
|
||||
// }
|
||||
// }
|
370
train.ts
370
train.ts
@ -3,310 +3,104 @@ import { ComplexPath, PathSegment } from "./math/path.ts";
|
||||
import { Vector } from "doodler";
|
||||
import { Follower } from "./physics/follower.ts";
|
||||
import { Mover } from "./physics/mover.ts";
|
||||
import { Track } from "./track.ts";
|
||||
import { Spline, Track } from "./track.ts";
|
||||
|
||||
export class Train extends Follower {
|
||||
nodes?: Vector[];
|
||||
export class Train {
|
||||
nodes: Vector[] = [];
|
||||
|
||||
currentTrack: Track;
|
||||
cars: TrainCar[] = [];
|
||||
|
||||
speed: number;
|
||||
path: Spline<Track>;
|
||||
t: number;
|
||||
|
||||
follower?: TrainCar;
|
||||
engineLength = 40;
|
||||
spacing = 30;
|
||||
|
||||
followers?: TrainCar[];
|
||||
|
||||
constructor(track: Track, length: number) {
|
||||
super(track.points[0].copy());
|
||||
this.maxSpeed = 2;
|
||||
this.speed = 1;
|
||||
this.currentTrack = track;
|
||||
this.velocity = this.currentTrack.tangent(0).normalize().mult(this.maxSpeed);
|
||||
|
||||
this.addCar(length);
|
||||
|
||||
this.maxForce = .2;
|
||||
}
|
||||
|
||||
init(): void {
|
||||
this.boundingBox.size.set(30, 10);
|
||||
this._trailingPoint = 30;
|
||||
}
|
||||
|
||||
move(): void {
|
||||
this.follow();
|
||||
|
||||
super.move();
|
||||
this.follower?.move()
|
||||
// this.draw();
|
||||
}
|
||||
|
||||
follow(): void {
|
||||
const [_, t] = this.currentTrack.followTrack(this);
|
||||
|
||||
// this.position = this.currentTrack.getPointAtT(t);
|
||||
this.velocity = this.currentTrack.tangent(t);
|
||||
this.velocity.normalize().mult(this.speed || this.maxSpeed);
|
||||
// if (nearest.dist(this.position) > 10)
|
||||
// this.seek(nearest);
|
||||
}
|
||||
|
||||
// draw(): void {
|
||||
// if (!this.ctx) return;
|
||||
// const ctx = this.ctx;
|
||||
// // const [a, b] = this.nodes;
|
||||
|
||||
// ctx.strokeStyle = 'blue'
|
||||
// ctx.lineWidth = 10;
|
||||
// // drawLine(ctx, a.x, a.y, b.x, b.y);
|
||||
// super.draw()
|
||||
// }
|
||||
|
||||
setContext(ctx: CanvasRenderingContext2D): void {
|
||||
super.setContext(ctx);
|
||||
this.follower?.setContext(ctx);
|
||||
}
|
||||
|
||||
addCar(length: number,) {
|
||||
console.log(length);
|
||||
if (length)
|
||||
this.follower = new TrainCar(this.currentTrack, length - 1);
|
||||
this.follower?.setTarget(this);
|
||||
this.follower?.position.set(this.trailingPoint);
|
||||
this._trailingPoint -= 2;
|
||||
}
|
||||
}
|
||||
|
||||
class TrainCar extends Train {
|
||||
// constructor(n: [Vector, Vector], track: Track) {
|
||||
// super(track);
|
||||
// this.nodes = n;
|
||||
// }
|
||||
target?: Train;
|
||||
setTarget(t: Train) {
|
||||
this.target = t;
|
||||
}
|
||||
|
||||
init(): void {
|
||||
this.boundingBox.size.set(20, 10)
|
||||
this._trailingPoint = 25;
|
||||
this.maxSpeed = this.maxSpeed * 2;
|
||||
this.maxForce = this.maxForce * 2;
|
||||
// this.speed = 0;
|
||||
}
|
||||
|
||||
// follow(): void {
|
||||
// if (!this.target) return;
|
||||
|
||||
// const points = this.currentTrack.getAllPointsInRange(this.target.position, this.target._trailingPoint);
|
||||
// let closest = this.target.position;
|
||||
// let closestTan = this.target.velocity;
|
||||
// for (const [t, path] of points) {
|
||||
// const point = path.getPointAtT(t);
|
||||
// if (point.dist(this.target.trailingPoint) < this.target.trailingPoint.dist(closest)) {
|
||||
// closest = point;
|
||||
// closestTan = path.tangent(t);
|
||||
// }
|
||||
// }
|
||||
|
||||
// // this.position.set(closest);
|
||||
// this.seek(closest);
|
||||
// this.velocity.set(closestTan.normalize().mult(this.target.speed));
|
||||
// }
|
||||
|
||||
move(): void {
|
||||
// if (!this.target) return;
|
||||
|
||||
// const r = 30;
|
||||
// const points = this.currentTrack.getAllPointsInRange(this.target.position, this.target._trailingPoint);
|
||||
// let closest = this.target.position;
|
||||
// let closestTan = this.target.velocity;
|
||||
// for (const [t, path] of points) {
|
||||
// const point = path.getPointAtT(t);
|
||||
// if (point.dist(this.target.trailingPoint) < this.target.trailingPoint.dist(closest)) {
|
||||
// closest = point;
|
||||
// closestTan = path.tangent(t);
|
||||
// }
|
||||
// }
|
||||
|
||||
// // this.position.set(closest);
|
||||
// // this.seek(closest);
|
||||
// this.velocity.set(closestTan.normalize().mult(this.target.speed));
|
||||
// super.move();
|
||||
// if (this.target && this.position.dist(this.target.trailingPoint) < 2) {
|
||||
// this.velocity.setMag(0);
|
||||
// } else if (this.target) {
|
||||
// this.velocity.setMag(this.target.velocity.mag());
|
||||
// }
|
||||
|
||||
// if (this.target) {
|
||||
// this.position.set(this.target.trailingPoint);
|
||||
// this.speed = this.target.speed;
|
||||
// }
|
||||
// const [pos,t] = this.currentTrack.followTrack(this);
|
||||
|
||||
// this.position = pos.copy()
|
||||
// if (this.target) {
|
||||
// const points = this.currentTrack.getPointWithinRadius(this.target.position, 30);
|
||||
|
||||
// let closest = this.target.position;
|
||||
// for (const [i,point] of points.entries()) {
|
||||
// if (typeof point !== "number") break;
|
||||
|
||||
// const tracks = [this.currentTrack, this.currentTrack.next, this.currentTrack.prev];
|
||||
|
||||
// const a = tracks[i].getPointAtT(point);
|
||||
|
||||
// if (a.dist(this.target.trailingPoint) < closest.dist(this.target.trailingPoint)) {
|
||||
// closest = a;
|
||||
// }
|
||||
// }
|
||||
|
||||
// this.position = closest;
|
||||
// }
|
||||
// this.draw();
|
||||
if (this.target) {
|
||||
if (this.position.dist(this.target.position) > this.target.position.dist(this.target.trailingPoint)) {
|
||||
|
||||
// this.velocity = this.currentTrack.tangent(t);
|
||||
// this.velocity.normalize().mult(this.speed);
|
||||
|
||||
this.arrive(this.currentTrack.getNearestPoint(this.target.trailingPoint));
|
||||
// if (this.position.dist())
|
||||
// this.move()
|
||||
this.speed = this.target.speed;
|
||||
super.move();
|
||||
} else {
|
||||
this.draw()
|
||||
this.follower?.draw();
|
||||
}
|
||||
constructor(track: Spline<Track>, cars: TrainCar[] = []) {
|
||||
this.path = track;
|
||||
this.t = 0;
|
||||
this.nodes.push(this.path.followEvenPoints(this.t),)
|
||||
this.nodes.push(this.path.followEvenPoints(this.t - this.real2Track(40)));
|
||||
this.cars.push(new TrainCar(55, document.getElementById('engine-sprites')! as HTMLImageElement, 80, 20, { at: new Vector(0, 60), width: 80, height: 20 }));
|
||||
this.cars[0].points = this.nodes.map(n => n) as [Vector, Vector];
|
||||
let currentOffset = 40;
|
||||
for (const car of cars) {
|
||||
currentOffset += this.spacing;
|
||||
const a = this.path.followEvenPoints(this.t - currentOffset);
|
||||
currentOffset += car.length;
|
||||
const b = this.path.followEvenPoints(this.t - currentOffset);
|
||||
car.points = [a,b];
|
||||
this.cars.push(car);
|
||||
}
|
||||
// this.draw()
|
||||
// this.follower?.move()
|
||||
}
|
||||
|
||||
// draw(): void {
|
||||
// if (!this.ctx) return;
|
||||
// super.draw()
|
||||
// this.ctx.fillStyle = 'red';
|
||||
// this.position.drawDot(this.ctx);
|
||||
// this.ctx.fillStyle = 'green';
|
||||
// this.target?.trailingPoint.drawDot(this.ctx);
|
||||
move() {
|
||||
this.t = (this.t + 1) % this.path.evenPoints.length;
|
||||
let currentOffset = 0;
|
||||
for (const car of this.cars) {
|
||||
if (!car.points) return;
|
||||
const [a,b] = car.points;
|
||||
a.set(this.path.followEvenPoints(this.t - currentOffset));
|
||||
currentOffset += car.length;
|
||||
b.set(this.path.followEvenPoints(this.t - currentOffset));
|
||||
currentOffset += this.spacing;
|
||||
car.draw();
|
||||
}
|
||||
// this.draw();
|
||||
}
|
||||
|
||||
// draw() {
|
||||
// for (const [i, node] of this.nodes.entries()) {
|
||||
// doodler.drawCircle(node.point, 10, { color: 'purple', weight: 3 })
|
||||
// // const next = this.nodes[i + 1];
|
||||
// // if (next) {
|
||||
// // const to = Vector.sub(node.point, next.point);
|
||||
// // to.setMag(40);
|
||||
// // doodler.line(next.point, Vector.add(to, next.point))
|
||||
// // }
|
||||
// }
|
||||
// }
|
||||
|
||||
edges(): void {
|
||||
|
||||
real2Track(length: number) {
|
||||
return length / this.path.pointSpacing
|
||||
}
|
||||
}
|
||||
|
||||
// export class Train extends Follower {
|
||||
export class TrainCar {
|
||||
img: HTMLImageElement;
|
||||
imgWidth: number;
|
||||
imgHeight: number;
|
||||
sprite?: ISprite;
|
||||
|
||||
// currentSegment: Track;
|
||||
// cars: TrainCar[] = [];
|
||||
points?: [Vector, Vector];
|
||||
length: number;
|
||||
|
||||
// id: string;
|
||||
// constructor(path: Track);
|
||||
// constructor(x: number, y: number, segment: Track);
|
||||
// constructor(x: number | Track, y?: number, segment?: Track) {
|
||||
constructor(length: number, img: HTMLImageElement, w: number, h: number, sprite?: ISprite) {
|
||||
this.img = img;
|
||||
this.sprite = sprite;
|
||||
this.imgWidth = w;
|
||||
this.imgHeight = h;
|
||||
this.length = length;
|
||||
}
|
||||
|
||||
// super(x instanceof Track ? x.points[0].copy() : new Vector(x, y))
|
||||
draw() {
|
||||
if (!this.points) return;
|
||||
const [a, b] = this.points;
|
||||
const origin = Vector.add(Vector.sub(a, b).div(2), b);
|
||||
const angle = Vector.sub(b, a).heading();
|
||||
|
||||
// if (x instanceof Track) {
|
||||
// this.currentSegment = x;
|
||||
// } else if (segment) {
|
||||
// this.currentSegment = segment;
|
||||
// } else {
|
||||
// throw new Error('Path not provided for train construction')
|
||||
// }
|
||||
// // super(new Vector(Math.floor(Math.random() * 200),Math.floor(Math.random() * 200)), Vector.random2D());
|
||||
// this.id = crypto.randomUUID()
|
||||
// this.boundingBox.size.set(40, 10)
|
||||
doodler.drawCircle(origin, 4, {color: 'blue'})
|
||||
|
||||
// this.maxSpeed = 3;
|
||||
// this.maxForce = .3;
|
||||
doodler.drawRotated(origin, angle, () => {
|
||||
this.sprite ?
|
||||
doodler.drawSprite(this.img, this.sprite.at, this.sprite.width, this.sprite.height, origin.copy().sub(this.imgWidth / 2, this.imgHeight / 2), this.imgWidth, this.imgHeight) :
|
||||
doodler.drawImage(this.img, origin.copy().sub(this.imgWidth / 2, this.imgHeight / 2));
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// this.addCar();
|
||||
|
||||
// this._trailingPoint = 40;
|
||||
// this._leadingPoint = 15;
|
||||
// }
|
||||
|
||||
// move(): void {
|
||||
// for (const car of this.cars) {
|
||||
// car.move();
|
||||
// }
|
||||
// this.follow(this.currentSegment)
|
||||
// super.move();
|
||||
// }
|
||||
|
||||
// draw(): void {
|
||||
// if (!this.ctx) return;
|
||||
// // this.ctx.save();
|
||||
// this.ctx.fillStyle = 'white';
|
||||
// this.ctx.strokeStyle = 'red';
|
||||
// super.draw();
|
||||
// // this.ctx.restore();
|
||||
// }
|
||||
|
||||
// addCar() {
|
||||
// const last = this.cars[this.cars.length - 1];
|
||||
// this.cars.push(new TrainCar(this, (last || this).velocity.copy().normalize().mult(-30), last));
|
||||
// }
|
||||
|
||||
// setContext(ctx: CanvasRenderingContext2D): void {
|
||||
// super.setContext(ctx);
|
||||
// for (const car of this.cars) {
|
||||
// car.setContext(ctx);
|
||||
// }
|
||||
// }
|
||||
|
||||
// follow(toFollow: Track): void {
|
||||
// // const predict = this.velocity.copy();
|
||||
// // predict.normalize();
|
||||
// // predict.mult(25);
|
||||
// // const predictpos = Vector.add(this.position, predict)
|
||||
|
||||
// const nearest = toFollow.getMostValidTrack(this);
|
||||
|
||||
// this.seek(nearest);
|
||||
// }
|
||||
// }
|
||||
|
||||
// export class TrainCar extends Follower {
|
||||
// train?: Train;
|
||||
// prevCar?: Mover;
|
||||
// constructor(train: Train, pos: Vector, prevCar: Mover) {
|
||||
// super(pos);
|
||||
// this.train = train;
|
||||
|
||||
// this.boundingBox.size.set(20, 15);
|
||||
|
||||
// this.prevCar = prevCar || train;
|
||||
|
||||
// this.maxSpeed = 2;
|
||||
// this.maxForce = .3;
|
||||
|
||||
// this._trailingPoint = 25;
|
||||
// this._leadingPoint = 25;
|
||||
|
||||
// }
|
||||
|
||||
// move(): void {
|
||||
// if (this.train && this.prevCar) {
|
||||
// this.link(this.prevCar);
|
||||
// // super.move();
|
||||
// this.edges();
|
||||
// this.ctx && (this.ctx.fillStyle = 'orange')
|
||||
// this.draw();
|
||||
// }
|
||||
// else super.move();
|
||||
// }
|
||||
|
||||
// edges(): void {
|
||||
// if (!this.ctx || !this.train) return;
|
||||
// if (this.train.position.x > this.ctx.canvas.width) this.position.x -= this.ctx.canvas.width;
|
||||
// if (this.train.position.y > this.ctx.canvas.height) this.position.y -= this.ctx.canvas.height;
|
||||
// if (this.train.position.x < 0) this.position.x += this.ctx.canvas.width;
|
||||
// if (this.train.position.y < 0) this.position.y += this.ctx.canvas.height;
|
||||
// }
|
||||
// }
|
||||
interface ISprite {
|
||||
at: Vector;
|
||||
width: number;
|
||||
height: number;
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user