projectile
This commit is contained in:
parent
e53d88c991
commit
4c093c1fad
245
draw.js
245
draw.js
|
@ -1,19 +1,18 @@
|
||||||
class Drawing {
|
class Drawing {
|
||||||
constructor() {
|
constructor(divId) {
|
||||||
const div = document.createElement('div');
|
this.div = document.getElementById(divId);
|
||||||
const buttonsDiv = document.createElement('div');
|
this.div.classList.add('drawing');
|
||||||
this.startButton = document.createElement('button');
|
this.div.classList.add('container');
|
||||||
this.startButton.onclick = () => this.start();
|
this.buttonsDiv = document.createElement('div');
|
||||||
this.startButton.innerHTML = "Start";
|
this.titleDiv = document.createElement('div');
|
||||||
this.stopButton = document.createElement('button');
|
this.titleDiv.classList.add('title');
|
||||||
this.stopButton.onclick = () => this.stop();
|
this.captionDiv = document.createElement('div');
|
||||||
this.stopButton.innerHTML = "Stop";
|
this.captionDiv.classList.add('caption');
|
||||||
this.canvas = document.createElement('canvas');
|
this.canvas = document.createElement('canvas');
|
||||||
buttonsDiv.appendChild(this.startButton);
|
this.div.appendChild(this.titleDiv);
|
||||||
buttonsDiv.appendChild(this.stopButton);
|
this.div.appendChild(this.buttonsDiv);
|
||||||
div.appendChild(buttonsDiv);
|
this.div.appendChild(this.canvas);
|
||||||
div.appendChild(this.canvas);
|
this.div.appendChild(this.captionDiv);
|
||||||
document.body.appendChild(div);
|
|
||||||
this.ctx = this.canvas.getContext('2d');
|
this.ctx = this.canvas.getContext('2d');
|
||||||
this.sequence = [];
|
this.sequence = [];
|
||||||
this.t = 0;
|
this.t = 0;
|
||||||
|
@ -21,34 +20,128 @@ class Drawing {
|
||||||
this.dt = 0;
|
this.dt = 0;
|
||||||
this.points = {};
|
this.points = {};
|
||||||
this.stopped = true;
|
this.stopped = true;
|
||||||
this.frame = [[-50, -50], [150, 150]];
|
this.frame = [[-10, -10], [110, 110]];
|
||||||
|
this.frameMargin = 10;
|
||||||
this.scale = 1.0;
|
this.scale = 1.0;
|
||||||
this.canvas.width = (this.frame[1][0] - this.frame[0][0]) * this.scale;
|
|
||||||
this.canvas.height = (this.frame[1][1] - this.frame[0][1]) * this.scale;
|
|
||||||
this.speed = 1.0;
|
this.speed = 1.0;
|
||||||
|
this.rendered = false;
|
||||||
|
this.updateCanvasSize();
|
||||||
|
}
|
||||||
|
|
||||||
|
addButtons() {
|
||||||
|
if (!this.buttonsAdded) {
|
||||||
|
this.startButton = document.createElement('button');
|
||||||
|
this.startButton.onclick = () => this.start();
|
||||||
|
this.startButton.innerHTML = "Start";
|
||||||
|
this.stopButton = document.createElement('button');
|
||||||
|
this.stopButton.onclick = () => this.stop();
|
||||||
|
this.stopButton.innerHTML = "Stop";
|
||||||
|
this.buttonsDiv.appendChild(this.startButton);
|
||||||
|
this.buttonsDiv.appendChild(this.stopButton);
|
||||||
|
this.buttonsAdded = true;
|
||||||
|
this.updateButtonStates();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
updateButtonStates() {
|
||||||
|
if (this.stopped) {
|
||||||
|
this.stopButton.disabled = true;
|
||||||
|
this.startButton.disabled = false;
|
||||||
|
} else {
|
||||||
|
this.stopButton.disabled = false;
|
||||||
|
this.startButton.disabled = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
setTitle(title) {
|
||||||
|
this.titleDiv.innerHTML = title;
|
||||||
|
}
|
||||||
|
|
||||||
|
setCaption(caption) {
|
||||||
|
this.captionDiv.innerHTML = caption;
|
||||||
|
}
|
||||||
|
|
||||||
|
start() {
|
||||||
|
if (this.stopped) {
|
||||||
|
this.elideInterval = true;
|
||||||
|
this.stopped = false;
|
||||||
|
}
|
||||||
|
if (this.onStartFn) {
|
||||||
|
this.onStartFn();
|
||||||
|
}
|
||||||
|
this.addButtons();
|
||||||
|
this.updateButtonStates();
|
||||||
|
this.animate();
|
||||||
|
}
|
||||||
|
|
||||||
|
stop() {
|
||||||
|
this.stopped = true;
|
||||||
|
this.updateButtonStates();
|
||||||
|
}
|
||||||
|
|
||||||
|
render() {
|
||||||
|
for (let action of this.sequence) {
|
||||||
|
action();
|
||||||
|
}
|
||||||
|
this.rendered = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
animate() {
|
||||||
|
this.ctx.reset();
|
||||||
|
this.render();
|
||||||
|
if (!this.stopped) {
|
||||||
|
requestAnimationFrame((prevt) => {
|
||||||
|
const rt = document.timeline.currentTime;
|
||||||
|
const elapsed = rt - prevt;
|
||||||
|
if (this.elideInterval) {
|
||||||
|
this.dt = 0;
|
||||||
|
this.elideInterval = false;
|
||||||
|
} else {
|
||||||
|
this.dt = (rt - this.rt + elapsed) * this.speed;
|
||||||
|
}
|
||||||
|
this.t += this.dt;
|
||||||
|
this.rt = rt;
|
||||||
|
this.animate();
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pixel([x, y]) {
|
pixel([x, y]) {
|
||||||
return [
|
return [
|
||||||
(x - this.frame[0][0]) * this.scale,
|
(x - this.frame[0][0]) * this.scale + this.frameMargin,
|
||||||
this.canvas.height - (y - this.frame[0][1]) * this.scale
|
this.canvas.height - (y - this.frame[0][1]) * this.scale - this.frameMargin
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
onStart(fn) {
|
||||||
|
this.onStartFn = fn;
|
||||||
|
}
|
||||||
|
|
||||||
setSpeed(speed) {
|
setSpeed(speed) {
|
||||||
this.speed = speed;
|
this.speed = speed;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
updateCanvasSize() {
|
||||||
|
this.canvas.width = (this.frame[1][0] - this.frame[0][0]) * this.scale + 2 * this.frameMargin;
|
||||||
|
this.canvas.height = (this.frame[1][1] - this.frame[0][1]) * this.scale + 2 * this.frameMargin;
|
||||||
|
if (this.rendered) {
|
||||||
|
this.render();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
setFrame([x1, y1], [x2, y2]) {
|
setFrame([x1, y1], [x2, y2]) {
|
||||||
this.frame = [[x1, y1], [x2, y2]];
|
this.frame = [[x1, y1], [x2, y2]];
|
||||||
this.canvas.width = (this.frame[1][0] - this.frame[0][0]) * this.scale;
|
this.updateCanvasSize();
|
||||||
this.canvas.height = (this.frame[1][1] - this.frame[0][1]) * this.scale;
|
}
|
||||||
|
|
||||||
|
setFrameMargin(frameMargin) {
|
||||||
|
this.frameMargin = frameMargin;
|
||||||
|
this.updateCanvasSize();
|
||||||
}
|
}
|
||||||
|
|
||||||
setScale(zoom) {
|
setScale(zoom) {
|
||||||
this.scale = zoom;
|
this.scale = zoom;
|
||||||
this.canvas.width = (this.frame[1][0] - this.frame[0][0]) * this.scale;
|
this.updateCanvasSize();
|
||||||
this.canvas.height = (this.frame[1][1] - this.frame[0][1]) * this.scale;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
setStroke(style, width) {
|
setStroke(style, width) {
|
||||||
|
@ -64,6 +157,24 @@ class Drawing {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
definePoint(name, fn) {
|
||||||
|
this.points[name] = fn;
|
||||||
|
}
|
||||||
|
|
||||||
|
getPoint(p) {
|
||||||
|
if (typeof p === 'string') {
|
||||||
|
const fn = this.points[p];
|
||||||
|
if (!fn) {
|
||||||
|
const e = new Error;
|
||||||
|
e.message = `Point '${p}' is not defined`;
|
||||||
|
throw e;
|
||||||
|
}
|
||||||
|
return fn();
|
||||||
|
} else {
|
||||||
|
return p;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
line(p1, p2) {
|
line(p1, p2) {
|
||||||
this.sequence.push(() => {
|
this.sequence.push(() => {
|
||||||
this.ctx.beginPath();
|
this.ctx.beginPath();
|
||||||
|
@ -72,6 +183,15 @@ class Drawing {
|
||||||
this.ctx.stroke();
|
this.ctx.stroke();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
arrow(p1, p2) {
|
||||||
|
this.sequence.push(() => {
|
||||||
|
this.ctx.beginPath();
|
||||||
|
this.ctx.moveTo(...this.pixel(this.getPoint(p1)));
|
||||||
|
this.ctx.lineTo(...this.pixel(this.getPoint(p2)));
|
||||||
|
this.ctx.stroke();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
polyline(...points) {
|
polyline(...points) {
|
||||||
this.sequence.push(() => {
|
this.sequence.push(() => {
|
||||||
|
@ -115,7 +235,7 @@ class Drawing {
|
||||||
if (oldest >= 0) {
|
if (oldest >= 0) {
|
||||||
history = history.slice(oldest);
|
history = history.slice(oldest);
|
||||||
}
|
}
|
||||||
this.ctx.strokeStyle = 'black';
|
this.ctx.strokeStyle = 'gray';
|
||||||
this.ctx.lineWidth = 1;
|
this.ctx.lineWidth = 1;
|
||||||
this.ctx.beginPath();
|
this.ctx.beginPath();
|
||||||
this.ctx.moveTo(...this.pixel(history[0].p));
|
this.ctx.moveTo(...this.pixel(history[0].p));
|
||||||
|
@ -128,78 +248,43 @@ class Drawing {
|
||||||
}
|
}
|
||||||
this.ctx.stroke();
|
this.ctx.stroke();
|
||||||
fn(x, y);
|
fn(x, y);
|
||||||
})
|
});
|
||||||
|
return {
|
||||||
|
reset: () => {
|
||||||
|
history = [];
|
||||||
|
distance = 0;
|
||||||
|
}
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
square(p, opts) {
|
square(p, opts) {
|
||||||
const size = opts?.size ?? 10;
|
const size = opts?.size ?? 10;
|
||||||
this.object(p, opts, (x, y) => {
|
return this.object(p, opts, (x, y) => {
|
||||||
this.ctx.fillRect(...this.pixel([x - size / 2, y + size / 2]), size * this.scale, size * this.scale);
|
this.ctx.fillRect(...this.pixel([x - size / 2, y + size / 2]), size * this.scale, size * this.scale);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
circle(p, opts) {
|
circle(p, opts) {
|
||||||
const radius = opts?.radius ?? 5;
|
const radius = opts?.radius ?? 5;
|
||||||
this.object(p, opts, (x, y) => {
|
return this.object(p, opts, (x, y) => {
|
||||||
this.ctx.beginPath();
|
this.ctx.beginPath();
|
||||||
this.ctx.ellipse(...this.pixel([x, y]), radius * this.scale, radius * this.scale, 0, 0, 2*Math.PI);
|
this.ctx.ellipse(...this.pixel([x, y]), radius * this.scale, radius * this.scale, 0, 0, 2*Math.PI);
|
||||||
this.ctx.fill();
|
this.ctx.fill();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
definePoint(name, fn) {
|
func(opts, fn) {
|
||||||
this.points[name] = fn;
|
const origin = opts?.origin ?? [0, 0];
|
||||||
}
|
const domain = opts?.domain ?? [this.frame[0][0], this.frame[1][0]];
|
||||||
|
const step = opts?.step ?? 1;
|
||||||
getPoint(p) {
|
this.sequence.push(() => {
|
||||||
if (typeof p === 'string') {
|
this.ctx.beginPath();
|
||||||
const fn = this.points[p];
|
this.ctx.moveTo(...this.pixel([domain[0], fn(domain[0])]));
|
||||||
if (!fn) {
|
for (let x = domain[0] + step; x <= domain[1]; x += step) {
|
||||||
const e = new Error;
|
this.ctx.lineTo(...this.pixel([x, fn(x)]));
|
||||||
e.message = `Point '${p}' is not defined`;
|
|
||||||
throw e;
|
|
||||||
}
|
}
|
||||||
return fn();
|
this.ctx.lineTo(...this.pixel([domain[1], fn(domain[1])]));
|
||||||
} else {
|
this.ctx.stroke();
|
||||||
return p;
|
})
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
render() {
|
|
||||||
for (let action of this.sequence) {
|
|
||||||
action();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
animate() {
|
|
||||||
this.ctx.reset();
|
|
||||||
this.render();
|
|
||||||
if (!this.stopped) {
|
|
||||||
requestAnimationFrame((prevt) => {
|
|
||||||
const rt = document.timeline.currentTime;
|
|
||||||
const elapsed = rt - prevt;
|
|
||||||
if (this.elideInterval) {
|
|
||||||
this.dt = 0;
|
|
||||||
this.elideInterval = false;
|
|
||||||
} else {
|
|
||||||
this.dt = (rt - this.rt + elapsed) * this.speed;
|
|
||||||
}
|
|
||||||
this.t += this.dt;
|
|
||||||
this.rt = rt;
|
|
||||||
this.animate();
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
stop() {
|
|
||||||
this.stopped = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
start() {
|
|
||||||
if (this.stopped) {
|
|
||||||
this.elideInterval = true;
|
|
||||||
this.stopped = false;
|
|
||||||
}
|
|
||||||
this.animate();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
25
main.css
25
main.css
|
@ -1,6 +1,27 @@
|
||||||
body {
|
body {
|
||||||
margin: 50px;
|
margin: 1em;
|
||||||
}
|
}
|
||||||
|
|
||||||
canvas {
|
canvas {
|
||||||
border: 1px red dashed;
|
/* border: 1px red dashed; */
|
||||||
|
margin: 0.5em;
|
||||||
|
}
|
||||||
|
|
||||||
|
button {
|
||||||
|
margin: 0.25em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.drawing.container {
|
||||||
|
/* border: 1px black solid; */
|
||||||
|
display: inline-block;
|
||||||
|
margin: 1em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.drawing > .title {
|
||||||
|
font-size: large;
|
||||||
|
}
|
||||||
|
|
||||||
|
.drawing > .caption {
|
||||||
|
font-size: small;
|
||||||
|
text-align: center;
|
||||||
}
|
}
|
126
test.html
126
test.html
|
@ -8,23 +8,119 @@
|
||||||
</head>
|
</head>
|
||||||
|
|
||||||
<body>
|
<body>
|
||||||
|
<div id="d1"></div>
|
||||||
|
<p>
|
||||||
|
Some body text
|
||||||
|
</p>
|
||||||
|
<div id="d2"></div>
|
||||||
|
<div id="d3"></div>
|
||||||
|
<div id="d4"></div>
|
||||||
|
<div id="d5"></div>
|
||||||
<script>
|
<script>
|
||||||
const d = new Drawing();
|
let d1, d2, d3, d4;
|
||||||
d.setStroke('black', 4);
|
{
|
||||||
d.polyline([0, 100], [0, 0], [100, 0]);
|
const d = new Drawing('d1');
|
||||||
d.definePoint('p1', () => d.oscillatingPoint([25, 50], [100, 100], 5000));
|
d1 = d;
|
||||||
d.setStroke('red', 2);
|
d.setTitle('Example Animation');
|
||||||
d.line([0, 0], 'p1');
|
d.setCaption('Lissajou figure (skewed)');
|
||||||
d.definePoint('p2', () => {
|
d.addButtons();
|
||||||
const [x, y] = d.getPoint('p1');
|
d.setStroke('black', 4);
|
||||||
return [x, y - d.oscillatingValue(10, 40, 5000 / 3, Math.PI / 2)];
|
d.polyline([0, 100], [0, 0], [100, 0]);
|
||||||
});
|
d.definePoint('p1', () => d.oscillatingPoint([25, 50], [100, 100], 5000));
|
||||||
d.setFill('blue');
|
d.setStroke('red', 2);
|
||||||
d.square('p2', {trace: {age: 5000}});
|
d.line([0, 0], 'p1');
|
||||||
d.setFill('cyan');
|
d.definePoint('p2', () => {
|
||||||
d.circle('p1');
|
const [x, y] = d.getPoint('p1');
|
||||||
d.start();
|
return [x, y - d.oscillatingValue(10, 40, 5000 / 3, Math.PI / 2)];
|
||||||
|
});
|
||||||
|
d.setFill('blue');
|
||||||
|
d.square('p2', {trace: {age: 5000}});
|
||||||
|
d.setFill('cyan');
|
||||||
|
d.circle('p1');
|
||||||
|
d.start();
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
const d = new Drawing('d2');
|
||||||
|
d2 = d;
|
||||||
|
d.setTitle('Sine Wave');
|
||||||
|
d.setCaption('y = sin(x)');
|
||||||
|
d.setStroke('black', 4);
|
||||||
|
d.line([0, 0], [2*Math.PI, 0]);
|
||||||
|
d.line([0, -1], [0, 1]);
|
||||||
|
d.setStroke('red', 2);
|
||||||
|
d.setFrame([0, -1], [2*Math.PI, 1]);
|
||||||
|
d.setScale(100 / Math.PI);
|
||||||
|
d.func({step: 0.1}, (x) => Math.sin(x));
|
||||||
|
d.render();
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
const d = new Drawing('d3');
|
||||||
|
d3 = d;
|
||||||
|
d.setTitle('Oscillating Sine Wave');
|
||||||
|
d.setCaption('y = sin(x) * sin(t)');
|
||||||
|
d.setStroke('black', 4);
|
||||||
|
d.line([0, 0], [2*Math.PI, 0]);
|
||||||
|
d.line([0, -1], [0, 1]);
|
||||||
|
d.setStroke('red', 2);
|
||||||
|
d.setFrame([0, -1], [2*Math.PI, 1]);
|
||||||
|
d.setScale(100 / Math.PI);
|
||||||
|
d.func({step: 0.1}, (x) => Math.sin(x) * d.oscillatingValue(-1, 1, 500));
|
||||||
|
d.start();
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
const d = new Drawing('d4');
|
||||||
|
d4 = d;
|
||||||
|
d.setTitle('Travelling Sine Wave');
|
||||||
|
d.setCaption('y = sin(x + t)');
|
||||||
|
d.setStroke('black', 4);
|
||||||
|
d.line([0, 0], [2*Math.PI, 0]);
|
||||||
|
d.line([0, -1], [0, 1]);
|
||||||
|
d.setStroke('red', 2);
|
||||||
|
d.setFrame([0, -1], [2*Math.PI, 1]);
|
||||||
|
d.setScale(100 / Math.PI);
|
||||||
|
d.func({step: 0.1}, (x) => Math.sin(x + 2*Math.PI*d.t / 1000));
|
||||||
|
d.start();
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
const d = new Drawing('d5');
|
||||||
|
d5 = d;
|
||||||
|
d.setTitle('Projectile');
|
||||||
|
d.setCaption('y = v0y * t - g * t^2<br>x = v0x * t');
|
||||||
|
d.setFrame([0, 0], [300, 100]);
|
||||||
|
d.setStroke('black', 4);
|
||||||
|
d.polyline([0, 100], [0, 0], [300, 0]);
|
||||||
|
const v0 = 80;
|
||||||
|
const angle = Math.PI / 4;
|
||||||
|
const v0x = v0 * Math.cos(angle);
|
||||||
|
const v0y = v0 * Math.sin(angle);
|
||||||
|
d.definePoint('p1', () => {
|
||||||
|
const t = d.t / 1000;
|
||||||
|
const x = v0x * t;
|
||||||
|
const y = v0y * t - 9.81 * t**2;
|
||||||
|
if (t > 0 && (y <= 0 || x >= d.frame[1][0])) {
|
||||||
|
d.stop();
|
||||||
|
d.t = 0;
|
||||||
|
}
|
||||||
|
return [x, y];
|
||||||
|
})
|
||||||
|
d.setStroke('green', 1);
|
||||||
|
d.line([0, 0], [v0x, v0y]);
|
||||||
|
d.setFill('blue');
|
||||||
|
const projectile = d.circle('p1', {trace: {age: -1}})
|
||||||
|
d.onStart(() => {
|
||||||
|
if (d.t == 0) {
|
||||||
|
projectile.reset();
|
||||||
|
}
|
||||||
|
})
|
||||||
|
d.start();
|
||||||
|
}
|
||||||
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
</body>
|
</body>
|
||||||
|
|
||||||
</html>
|
</html>
|
Loading…
Reference in New Issue