projectile
This commit is contained in:
parent
e53d88c991
commit
4c093c1fad
245
draw.js
245
draw.js
|
@ -1,19 +1,18 @@
|
|||
class Drawing {
|
||||
constructor() {
|
||||
const div = document.createElement('div');
|
||||
const buttonsDiv = document.createElement('div');
|
||||
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";
|
||||
constructor(divId) {
|
||||
this.div = document.getElementById(divId);
|
||||
this.div.classList.add('drawing');
|
||||
this.div.classList.add('container');
|
||||
this.buttonsDiv = document.createElement('div');
|
||||
this.titleDiv = document.createElement('div');
|
||||
this.titleDiv.classList.add('title');
|
||||
this.captionDiv = document.createElement('div');
|
||||
this.captionDiv.classList.add('caption');
|
||||
this.canvas = document.createElement('canvas');
|
||||
buttonsDiv.appendChild(this.startButton);
|
||||
buttonsDiv.appendChild(this.stopButton);
|
||||
div.appendChild(buttonsDiv);
|
||||
div.appendChild(this.canvas);
|
||||
document.body.appendChild(div);
|
||||
this.div.appendChild(this.titleDiv);
|
||||
this.div.appendChild(this.buttonsDiv);
|
||||
this.div.appendChild(this.canvas);
|
||||
this.div.appendChild(this.captionDiv);
|
||||
this.ctx = this.canvas.getContext('2d');
|
||||
this.sequence = [];
|
||||
this.t = 0;
|
||||
|
@ -21,34 +20,128 @@ class Drawing {
|
|||
this.dt = 0;
|
||||
this.points = {};
|
||||
this.stopped = true;
|
||||
this.frame = [[-50, -50], [150, 150]];
|
||||
this.frame = [[-10, -10], [110, 110]];
|
||||
this.frameMargin = 10;
|
||||
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.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]) {
|
||||
return [
|
||||
(x - this.frame[0][0]) * this.scale,
|
||||
this.canvas.height - (y - this.frame[0][1]) * this.scale
|
||||
(x - this.frame[0][0]) * this.scale + this.frameMargin,
|
||||
this.canvas.height - (y - this.frame[0][1]) * this.scale - this.frameMargin
|
||||
];
|
||||
}
|
||||
|
||||
onStart(fn) {
|
||||
this.onStartFn = fn;
|
||||
}
|
||||
|
||||
setSpeed(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]) {
|
||||
this.frame = [[x1, y1], [x2, y2]];
|
||||
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.updateCanvasSize();
|
||||
}
|
||||
|
||||
setFrameMargin(frameMargin) {
|
||||
this.frameMargin = frameMargin;
|
||||
this.updateCanvasSize();
|
||||
}
|
||||
|
||||
setScale(zoom) {
|
||||
this.scale = zoom;
|
||||
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.updateCanvasSize();
|
||||
}
|
||||
|
||||
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) {
|
||||
this.sequence.push(() => {
|
||||
this.ctx.beginPath();
|
||||
|
@ -73,6 +184,15 @@ class Drawing {
|
|||
});
|
||||
}
|
||||
|
||||
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) {
|
||||
this.sequence.push(() => {
|
||||
this.ctx.beginPath();
|
||||
|
@ -115,7 +235,7 @@ class Drawing {
|
|||
if (oldest >= 0) {
|
||||
history = history.slice(oldest);
|
||||
}
|
||||
this.ctx.strokeStyle = 'black';
|
||||
this.ctx.strokeStyle = 'gray';
|
||||
this.ctx.lineWidth = 1;
|
||||
this.ctx.beginPath();
|
||||
this.ctx.moveTo(...this.pixel(history[0].p));
|
||||
|
@ -128,78 +248,43 @@ class Drawing {
|
|||
}
|
||||
this.ctx.stroke();
|
||||
fn(x, y);
|
||||
})
|
||||
});
|
||||
return {
|
||||
reset: () => {
|
||||
history = [];
|
||||
distance = 0;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
square(p, opts) {
|
||||
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);
|
||||
});
|
||||
}
|
||||
|
||||
circle(p, opts) {
|
||||
const radius = opts?.radius ?? 5;
|
||||
this.object(p, opts, (x, y) => {
|
||||
return this.object(p, opts, (x, y) => {
|
||||
this.ctx.beginPath();
|
||||
this.ctx.ellipse(...this.pixel([x, y]), radius * this.scale, radius * this.scale, 0, 0, 2*Math.PI);
|
||||
this.ctx.fill();
|
||||
});
|
||||
}
|
||||
|
||||
definePoint(name, fn) {
|
||||
this.points[name] = fn;
|
||||
func(opts, fn) {
|
||||
const origin = opts?.origin ?? [0, 0];
|
||||
const domain = opts?.domain ?? [this.frame[0][0], this.frame[1][0]];
|
||||
const step = opts?.step ?? 1;
|
||||
this.sequence.push(() => {
|
||||
this.ctx.beginPath();
|
||||
this.ctx.moveTo(...this.pixel([domain[0], fn(domain[0])]));
|
||||
for (let x = domain[0] + step; x <= domain[1]; x += step) {
|
||||
this.ctx.lineTo(...this.pixel([x, fn(x)]));
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
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();
|
||||
this.ctx.lineTo(...this.pixel([domain[1], fn(domain[1])]));
|
||||
this.ctx.stroke();
|
||||
})
|
||||
}
|
||||
}
|
25
main.css
25
main.css
|
@ -1,6 +1,27 @@
|
|||
body {
|
||||
margin: 50px;
|
||||
margin: 1em;
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
98
test.html
98
test.html
|
@ -8,8 +8,22 @@
|
|||
</head>
|
||||
|
||||
<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>
|
||||
const d = new Drawing();
|
||||
let d1, d2, d3, d4;
|
||||
{
|
||||
const d = new Drawing('d1');
|
||||
d1 = d;
|
||||
d.setTitle('Example Animation');
|
||||
d.setCaption('Lissajou figure (skewed)');
|
||||
d.addButtons();
|
||||
d.setStroke('black', 4);
|
||||
d.polyline([0, 100], [0, 0], [100, 0]);
|
||||
d.definePoint('p1', () => d.oscillatingPoint([25, 50], [100, 100], 5000));
|
||||
|
@ -24,7 +38,89 @@
|
|||
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>
|
||||
|
||||
</body>
|
||||
|
||||
</html>
|
Loading…
Reference in New Issue