Changes
2 changed files (+534/-225)
-
-
@@ -1,250 +1,276 @@"use strict"; // See script.ts for original source code // Compile using tsc script.ts --lib esnext,dom --alwaysStrict // Inject SVG into DOM synchronously because we can't access the DOM of SVGs inside img tags document.querySelectorAll("svg").forEach(function(svg) { let req = new XMLHttpRequest() req.open("GET", svg.id + ".svg", false) req.send() svg.outerHTML = req.responseText }) let rad = 10 // Stroke radius let A = [] // Objects let idcnt = 0 let ix = 0 let iy = 0 let ny = 0 document.querySelectorAll("svg").forEach(function(svg) { // Position objects so they aren't overlapping if (ix + svg.width.baseVal.value > window.innerWidth) { ix = 0 iy += ny ny = 0 } svg.style.left = ix + "px" svg.style.top = iy + "px" ix += svg.width.baseVal.value ny = Math.max(svg.height.baseVal.value, ny) svg.id = idcnt++ const rect = svg.getBoundingClientRect() let a = { id: svg.id, // Unique ID p: [], // Collision circles cm: svg.createSVGPoint(), // Position of center of mass relative to top left corner x: rect.x, // x position of center of mass y: rect.y, // y position of center of mass vx: Math.random(), // x velocity vy: Math.random(), // y velocity th: 0, // Angular position w: Math.random() / 100, // Angular velocity m: 0, // Mass mi: 0, // Moment of inertia r: 0 // Distance to farthest point from center of mass } svg.querySelectorAll("path").forEach(function(path) { // Get circles on path for collision checking let num = Math.floor(path.getTotalLength() * 2 / rad) for (let i = 0; i <= num; i++) { const p = path.getPointAtLength(i / num * path.getTotalLength()) a.cm.x += p.x a.cm.y += p.y a.p.push(p) // Show circles for debugging /* let circle = document.createElementNS("http://www.w3.org/2000/svg", "circle") circle.setAttribute("cx", p.x) circle.setAttribute("cy", p.y) circle.setAttribute("r", 10) circle.setAttribute("fill", "red") svg.appendChild(circle) */ } }) a.cm.x /= a.p.length a.cm.y /= a.p.length // Change origin to center of mass a.x += a.cm.x a.y += a.cm.y for (const p of a.p) { p.x -= a.cm.x p.y -= a.cm.y } svg.style.transformOrigin = a.cm.x + "px " + a.cm.y + "px" a.m = a.p.length for (const p of a.p) a.mi += p.x ** 2 + p.y ** 2 for (const p of a.p) a.r = Math.max(Math.sqrt(p.x ** 2 + p.y ** 2), a.r) A.push(a) }) document.querySelectorAll("svg").forEach(function (svg) { var req = new XMLHttpRequest(); req.open("GET", svg.id + ".svg", false); req.send(); svg.outerHTML = req.responseText; }); var rad = 10; // Stroke radius var A = new Array(); // Objects var idcnt = 0; var ix = 0; var iy = 0; var ny = 0; document.querySelectorAll("svg").forEach(function (svg) { // Position objects so they aren't overlapping if (ix + svg.width.baseVal.value > window.innerWidth) { ix = 0; iy += ny; ny = 0; } svg.style.left = ix + "px"; svg.style.top = iy + "px"; ix += svg.width.baseVal.value; ny = Math.max(svg.height.baseVal.value, ny); svg.id = String(idcnt++); var rect = svg.getBoundingClientRect(); var a = { id: svg.id, p: [], cm: svg.createSVGPoint(), x: rect.x, y: rect.y, vx: Math.random(), vy: Math.random(), th: 0, w: Math.random() / 100, m: 0, mi: 0, r: 0 }; svg.querySelectorAll("path").forEach(function (path) { // Get circles on path for collision checking var num = Math.floor(path.getTotalLength() * 2 / rad); for (var i = 0; i <= num; i++) { var p = path.getPointAtLength(i / num * path.getTotalLength()); a.cm.x += p.x; a.cm.y += p.y; a.p.push(p); // Show circles for debugging /* let circle = document.createElementNS("http://www.w3.org/2000/svg", "circle") circle.setAttribute("cx", p.x) circle.setAttribute("cy", p.y) circle.setAttribute("r", 10) circle.setAttribute("fill", "red") svg.appendChild(circle) */ } }); a.cm.x /= a.p.length; a.cm.y /= a.p.length; // Change origin to center of mass a.x += a.cm.x; a.y += a.cm.y; for (var _i = 0, _a = a.p; _i < _a.length; _i++) { var p = _a[_i]; p.x -= a.cm.x; p.y -= a.cm.y; } svg.style.transformOrigin = a.cm.x + "px " + a.cm.y + "px"; a.m = a.p.length; for (var _b = 0, _c = a.p; _b < _c.length; _b++) { var p = _c[_b]; a.mi += Math.pow(p.x, 2) + Math.pow(p.y, 2); } for (var _d = 0, _e = a.p; _d < _e.length; _d++) { var p = _e[_d]; a.r = Math.max(Math.sqrt(Math.pow(p.x, 2) + Math.pow(p.y, 2)), a.r); } A.push(a); }); // Actual position of p in object a function rot(a, p) { const c = Math.cos(a.th) const s = Math.sin(a.th) return {x: a.x + p.x * c - p.y * s, y: a.y + p.x * s + p.y * c} var c = Math.cos(a.th); var s = Math.sin(a.th); return { x: a.x + p.x * c - p.y * s, y: a.y + p.x * s + p.y * c }; } // Distance squared between a and b function ds(a, b) { return (a.x - b.x) ** 2 + (a.y - b.y) ** 2 return Math.pow((a.x - b.x), 2) + Math.pow((a.y - b.y), 2); } // Cross product function cr(a, b) { return a.x * b.y - a.y * b.x return a.x * b.y - a.y * b.x; } // Collision of object a with b at point c with normal n function collide(a, b, c, n) { // https://physics.stackexchange.com/questions/783524/angular-motion-in-collisions/783565#783565 // https://physics.stackexchange.com/questions/786641/collision-calculation-in-2d/786969#786969 // https://physics.stackexchange.com/questions/686640/resolving-angular-components-in-2d-circular-rigid-body-collision-response // I still don't know how to derive this magic but I'm convinced it works // No idea if there's a sign error // It looks fine though const ca = {x: a.x - c.x, y: a.y - c.y} const cb = {x: b.x - c.x, y: b.y - c.y} const v = n.x * (a.vx - b.vx) + n.y * (a.vy - b.vy) - a.w * cr(ca, n) + b.w * cr(cb, n) const m = 1 / (1 / a.m + 1 / b.m + cr(ca, n) ** 2 / a.mi + cr(cb, n) ** 2 / b.mi) const j = 2 * m * v a.vx += -n.x * j / a.m a.vy += -n.y * j / a.m a.w += cr(ca, n) * j / a.mi b.vx += n.x * j / b.m b.vy += n.y * j / b.m b.w += -cr(cb, n) * j / b.mi // console.log('boop') // https://physics.stackexchange.com/questions/783524/angular-motion-in-collisions/783565#783565 // https://physics.stackexchange.com/questions/786641/collision-calculation-in-2d/786969#786969 // https://physics.stackexchange.com/questions/686640/resolving-angular-components-in-2d-circular-rigid-body-collision-response // I still don't know how to derive this magic but I'm convinced it works // No idea if there's a sign error // It looks fine though var ca = { x: a.x - c.x, y: a.y - c.y }; var cb = { x: b.x - c.x, y: b.y - c.y }; var v = n.x * (a.vx - b.vx) + n.y * (a.vy - b.vy) - a.w * cr(ca, n) + b.w * cr(cb, n); var m = 1 / (1 / a.m + 1 / b.m + Math.pow(cr(ca, n), 2) / a.mi + Math.pow(cr(cb, n), 2) / b.mi); var j = 2 * m * v; a.vx += -n.x * j / a.m; a.vy += -n.y * j / a.m; a.w += cr(ca, n) * j / a.mi; b.vx += n.x * j / b.m; b.vy += n.y * j / b.m; b.w += -cr(cb, n) * j / b.mi; // console.log('boop') } // Collision of object a with wall at position k and direction d function wallCollide(a, k, d) { if ((!d && Math.abs(a.x - k) < a.r + rad) || (d && Math.abs(a.y - k) < a.r + rad)) { let c = {x: 0, y: 0, cnt: 0} for (const p of a.p.map(x => rot(a, x))) { if ((!d && Math.abs(p.x - k) < rad) || (d && Math.abs(p.y - k) < rad)) { c.x += p.x c.y += p.y c.cnt++ } } if (c.cnt > 0) { c.x /= c.cnt c.y /= c.cnt let b = c b.vx = b.vy = b.w = 0 b.m = b.mi = 1e9 collide(a, b, c, {x: 1 - d, y: d}) } } if ((!d && Math.abs(a.x - k) < a.r + rad) || (d && Math.abs(a.y - k) < a.r + rad)) { var c = { x: 0, y: 0, cnt: 0 }; for (var _i = 0, _a = a.p.map(function (x) { return rot(a, x); }); _i < _a.length; _i++) { var p = _a[_i]; if ((!d && Math.abs(p.x - k) < rad) || (d && Math.abs(p.y - k) < rad)) { c.x += p.x; c.y += p.y; c.cnt++; } } if (c.cnt > 0) { c.x /= c.cnt; c.y /= c.cnt; var b = { id: "", p: [], cm: { x: 0, y: 0 }, x: c.x, y: c.y, vx: 0, vy: 0, th: 0, w: 0, m: 1e9, mi: 1e9, r: 0 }; collide(a, b, c, { x: 1 - Number(d), y: Number(d) }); } } } // Collision of object a with object b function objectsCollide(a, b) { if (ds(a, b) < (a.r + b.r + 2 * rad) ** 2) { // Objects are close let c = {x: 0, y: 0, cnt: 0} let n = {x: 0, y: 0} // Slight performance optimization? // Only consider points close to other object let aa = [] let bb = [] for (const p of a.p.map(x => rot(a, x))) { if (ds(p, b) < (a.r + b.r + 2 * rad) ** 2) aa.push(p) } for (const p of b.p.map(x => rot(b, x))) { if (ds(p, a) < (a.r + b.r + 2 * rad) ** 2) bb.push(p) } for (const p of aa) { for (const q of bb) { const d = ds(p, q) if (d < (2 * rad) ** 2) { // Collision! // These calculations are a bit sketchy but I guess they work? c.x += p.x + q.x c.y += p.y + q.y c.cnt++ n.x += (p.x - q.x) / d n.y += (p.y - q.y) / d } } } if (c.cnt > 0) { c.x /= 2 * c.cnt c.y /= 2 * c.cnt // Normalize n let norm = Math.sqrt(n.x ** 2 + n.y ** 2) n.x /= norm n.y /= norm collide(a, b, c, n) } } if (ds(a, b) < Math.pow((a.r + b.r + 2 * rad), 2)) { // Objects are close var c = { x: 0, y: 0, cnt: 0 }; var n = { x: 0, y: 0 }; // Slight performance optimization? // Only consider points close to other object var aa = []; var bb = []; for (var _i = 0, _a = a.p.map(function (x) { return rot(a, x); }); _i < _a.length; _i++) { var p = _a[_i]; if (ds(p, b) < Math.pow((a.r + b.r + 2 * rad), 2)) aa.push(p); } for (var _b = 0, _c = b.p.map(function (x) { return rot(b, x); }); _b < _c.length; _b++) { var p = _c[_b]; if (ds(p, a) < Math.pow((a.r + b.r + 2 * rad), 2)) bb.push(p); } for (var _d = 0, aa_1 = aa; _d < aa_1.length; _d++) { var p = aa_1[_d]; for (var _e = 0, bb_1 = bb; _e < bb_1.length; _e++) { var q = bb_1[_e]; var d = ds(p, q); if (d < Math.pow((2 * rad), 2)) { // Collision! // These calculations are a bit sketchy but I guess they work? c.x += p.x + q.x; c.y += p.y + q.y; c.cnt++; n.x += (p.x - q.x) / d; n.y += (p.y - q.y) / d; } } } if (c.cnt > 0) { c.x /= 2 * c.cnt; c.y /= 2 * c.cnt; // Normalize n var norm = Math.sqrt(Math.pow(n.x, 2) + Math.pow(n.y, 2)); n.x /= norm; n.y /= norm; collide(a, b, c, n); } } } // Move stuff, check collisions, and render function tick() { // Move each object one step for (let a of A) { a.x += a.vx a.y += a.vy a.th += a.w // Don't allow glitching into walls /* const px = a.p.map(x => rot(a, x).x) let k = Math.min(...px) - rad if (k < 0) a.x -= k k = window.innerWidth - Math.max(...px) - rad if (k < 0) a.x += k const py = a.p.map(x => rot(a, x).y) k = Math.min(...py) - rad if (k < 0) a.y -= k k = window.innerHeight - Math.max(...py) - rad if (k < 0) a.y += k */ // Friction if (Math.abs(a.vx) > 0.001) a.vx -= 0.001 * Math.sign(a.vx) if (Math.abs(a.vy) > 0.001) a.vy -= 0.001 * Math.sign(a.vy) if (Math.abs(a.w) > 0.00001) a.w -= 0.00001 * Math.sign(a.w) } // Check wall collisions for (let a of A) { wallCollide(a, 0, 0) wallCollide(a, window.innerWidth, 0) wallCollide(a, 0, 1) wallCollide(a, window.innerHeight, 1) } // Check collisions between objects for (let i = 0; i < A.length; i++) { for (let j = i + 1; j < A.length; j++) { objectsCollide(A[i], A[j]) } } // Render every 10ms tickcnt++ if (tickcnt == 10) { tickcnt = 0 for (a of A) { let e = document.getElementById(a.id) e.style.left = a.x - a.cm.x + "px" e.style.top = a.y - a.cm.y + "px" e.style.rotate = a.th + "rad" } } // Move each object one step for (var _i = 0, A_1 = A; _i < A_1.length; _i++) { var a = A_1[_i]; a.x += a.vx; a.y += a.vy; a.th += a.w; // Don't allow glitching into walls /* const px = a.p.map(x => rot(a, x).x) let k = Math.min(...px) - rad if (k < 0) a.x -= k k = window.innerWidth - Math.max(...px) - rad if (k < 0) a.x += k const py = a.p.map(x => rot(a, x).y) k = Math.min(...py) - rad if (k < 0) a.y -= k k = window.innerHeight - Math.max(...py) - rad if (k < 0) a.y += k */ // Friction if (Math.abs(a.vx) > 0.001) a.vx -= 0.001 * Math.sign(a.vx); if (Math.abs(a.vy) > 0.001) a.vy -= 0.001 * Math.sign(a.vy); if (Math.abs(a.w) > 0.00001) a.w -= 0.00001 * Math.sign(a.w); } // Check wall collisions for (var _a = 0, A_2 = A; _a < A_2.length; _a++) { var a = A_2[_a]; wallCollide(a, 0, false); wallCollide(a, window.innerWidth, false); wallCollide(a, 0, true); wallCollide(a, window.innerHeight, true); } // Check collisions between objects for (var i = 0; i < A.length; i++) { for (var j = i + 1; j < A.length; j++) { objectsCollide(A[i], A[j]); } } // Render every 10ms tickcnt++; if (tickcnt == 10) { tickcnt = 0; for (var _b = 0, A_3 = A; _b < A_3.length; _b++) { var a = A_3[_b]; var e = document.getElementById(a.id); if (e != null) { e.style.left = a.x - a.cm.x + "px"; e.style.top = a.y - a.cm.y + "px"; e.style.rotate = a.th + "rad"; } } } } // Use click to update velocities function updatev(event) { for (a of A) { let d = Math.max(ds(a, {x: event.clientX, y: event.clientY}), 100) a.vx += 100 * (a.x - event.clientX) / d a.vy += 100 * (a.y - event.clientY) / d } // Display spreading out circles let circle = document.createElement("div") circle.style.width = circle.style.height = "10px" circle.style.left = event.clientX - 5 + "px" circle.style.top = event.clientY - 5 + "px" document.body.appendChild(circle) circle.offsetWidth circle.style.transform = "scale(500)" circle.style.opacity = "0" setTimeout(function () { document.body.removeChild(circle) }, 1000) for (var _i = 0, A_4 = A; _i < A_4.length; _i++) { var a = A_4[_i]; var d = Math.max(ds(a, { x: event.clientX, y: event.clientY }), 100); a.vx += 100 * (a.x - event.clientX) / d; a.vy += 100 * (a.y - event.clientY) / d; } // Display spreading out circles var circle = document.createElement("div"); circle.style.width = circle.style.height = "10px"; circle.style.left = event.clientX - 5 + "px"; circle.style.top = event.clientY - 5 + "px"; document.body.appendChild(circle); circle.style.transform = "scale(500)"; circle.style.opacity = "0"; setTimeout(function () { document.body.removeChild(circle); }, 1000); } let tickcnt = 0 setInterval(tick, 1) document.addEventListener("click", updatev) var tickcnt = 0; setInterval(tick, 1); document.addEventListener("click", updatev);
-
-
script.ts (new)
-
@@ -0,0 +1,283 @@// See script.ts for original source code // Compile using tsc script.ts --lib esnext,dom --alwaysStrict // Inject SVG into DOM synchronously because we can't access the DOM of SVGs inside img tags document.querySelectorAll("svg").forEach(function (svg) { let req = new XMLHttpRequest() req.open("GET", svg.id + ".svg", false) req.send() svg.outerHTML = req.responseText }) type Pt = { x: number // x position of center of mass y: number // y position of center of mass } interface Obj extends Pt { id: string // Unique ID p: Pt[] // Collision circles cm: Pt // Position of center of mass relative to top left corner vx: number // x velocity vy: number // y velocity th: number // Angular position w: number // Angular velocity m: number // Mass mi: number // Moment of inertia r: number // Distance to farthest point from center of mass } const rad = 10 // Stroke radius let A = new Array<Obj>() // Objects let idcnt = 0 let ix = 0 let iy = 0 let ny = 0 document.querySelectorAll("svg").forEach(function (svg) { // Position objects so they aren't overlapping if (ix + svg.width.baseVal.value > window.innerWidth) { ix = 0 iy += ny ny = 0 } svg.style.left = ix + "px" svg.style.top = iy + "px" ix += svg.width.baseVal.value ny = Math.max(svg.height.baseVal.value, ny) svg.id = String(idcnt++) const rect = svg.getBoundingClientRect() const a: Obj = { id: svg.id, p: [], cm: svg.createSVGPoint(), x: rect.x, y: rect.y, vx: Math.random(), vy: Math.random(), th: 0, w: Math.random() / 100, m: 0, mi: 0, r: 0 } svg.querySelectorAll("path").forEach(function (path) { // Get circles on path for collision checking const num = Math.floor(path.getTotalLength() * 2 / rad) for (let i = 0; i <= num; i++) { const p = path.getPointAtLength(i / num * path.getTotalLength()) a.cm.x += p.x a.cm.y += p.y a.p.push(p) // Show circles for debugging /* let circle = document.createElementNS("http://www.w3.org/2000/svg", "circle") circle.setAttribute("cx", p.x) circle.setAttribute("cy", p.y) circle.setAttribute("r", 10) circle.setAttribute("fill", "red") svg.appendChild(circle) */ } }) a.cm.x /= a.p.length a.cm.y /= a.p.length // Change origin to center of mass a.x += a.cm.x a.y += a.cm.y for (const p of a.p) { p.x -= a.cm.x p.y -= a.cm.y } svg.style.transformOrigin = a.cm.x + "px " + a.cm.y + "px" a.m = a.p.length for (const p of a.p) a.mi += p.x ** 2 + p.y ** 2 for (const p of a.p) a.r = Math.max(Math.sqrt(p.x ** 2 + p.y ** 2), a.r) A.push(a) }) // Actual position of p in object a function rot(a: Obj, p: Pt) { const c = Math.cos(a.th) const s = Math.sin(a.th) return { x: a.x + p.x * c - p.y * s, y: a.y + p.x * s + p.y * c } } // Distance squared between a and b function ds(a: Pt, b: Pt) { return (a.x - b.x) ** 2 + (a.y - b.y) ** 2 } // Cross product function cr(a: Pt, b: Pt) { return a.x * b.y - a.y * b.x } // Collision of object a with b at point c with normal n function collide(a: Obj, b: Obj, c: Pt, n: Pt) { // https://physics.stackexchange.com/questions/783524/angular-motion-in-collisions/783565#783565 // https://physics.stackexchange.com/questions/786641/collision-calculation-in-2d/786969#786969 // https://physics.stackexchange.com/questions/686640/resolving-angular-components-in-2d-circular-rigid-body-collision-response // I still don't know how to derive this magic but I'm convinced it works // No idea if there's a sign error // It looks fine though const ca = { x: a.x - c.x, y: a.y - c.y } const cb = { x: b.x - c.x, y: b.y - c.y } const v = n.x * (a.vx - b.vx) + n.y * (a.vy - b.vy) - a.w * cr(ca, n) + b.w * cr(cb, n) const m = 1 / (1 / a.m + 1 / b.m + cr(ca, n) ** 2 / a.mi + cr(cb, n) ** 2 / b.mi) const j = 2 * m * v a.vx += -n.x * j / a.m a.vy += -n.y * j / a.m a.w += cr(ca, n) * j / a.mi b.vx += n.x * j / b.m b.vy += n.y * j / b.m b.w += -cr(cb, n) * j / b.mi // console.log('boop') } // Collision of object a with wall at position k and direction d function wallCollide(a: Obj, k: number, d: Boolean) { if ((!d && Math.abs(a.x - k) < a.r + rad) || (d && Math.abs(a.y - k) < a.r + rad)) { let c = { x: 0, y: 0, cnt: 0 } for (const p of a.p.map(x => rot(a, x))) { if ((!d && Math.abs(p.x - k) < rad) || (d && Math.abs(p.y - k) < rad)) { c.x += p.x c.y += p.y c.cnt++ } } if (c.cnt > 0) { c.x /= c.cnt c.y /= c.cnt const b: Obj = { id: "", p: [], cm: { x: 0, y: 0 }, x: c.x, y: c.y, vx: 0, vy: 0, th: 0, w: 0, m: 1e9, mi: 1e9, r: 0 } collide(a, b, c, { x: 1 - Number(d), y: Number(d) }) } } } // Collision of object a with object b function objectsCollide(a: Obj, b: Obj) { if (ds(a, b) < (a.r + b.r + 2 * rad) ** 2) { // Objects are close let c = { x: 0, y: 0, cnt: 0 } let n = { x: 0, y: 0 } // Slight performance optimization? // Only consider points close to other object let aa = [] let bb = [] for (const p of a.p.map(x => rot(a, x))) { if (ds(p, b) < (a.r + b.r + 2 * rad) ** 2) aa.push(p) } for (const p of b.p.map(x => rot(b, x))) { if (ds(p, a) < (a.r + b.r + 2 * rad) ** 2) bb.push(p) } for (const p of aa) { for (const q of bb) { const d = ds(p, q) if (d < (2 * rad) ** 2) { // Collision! // These calculations are a bit sketchy but I guess they work? c.x += p.x + q.x c.y += p.y + q.y c.cnt++ n.x += (p.x - q.x) / d n.y += (p.y - q.y) / d } } } if (c.cnt > 0) { c.x /= 2 * c.cnt c.y /= 2 * c.cnt // Normalize n const norm = Math.sqrt(n.x ** 2 + n.y ** 2) n.x /= norm n.y /= norm collide(a, b, c, n) } } } // Move stuff, check collisions, and render function tick() { // Move each object one step for (let a of A) { a.x += a.vx a.y += a.vy a.th += a.w // Don't allow glitching into walls /* const px = a.p.map(x => rot(a, x).x) let k = Math.min(...px) - rad if (k < 0) a.x -= k k = window.innerWidth - Math.max(...px) - rad if (k < 0) a.x += k const py = a.p.map(x => rot(a, x).y) k = Math.min(...py) - rad if (k < 0) a.y -= k k = window.innerHeight - Math.max(...py) - rad if (k < 0) a.y += k */ // Friction if (Math.abs(a.vx) > 0.001) a.vx -= 0.001 * Math.sign(a.vx) if (Math.abs(a.vy) > 0.001) a.vy -= 0.001 * Math.sign(a.vy) if (Math.abs(a.w) > 0.00001) a.w -= 0.00001 * Math.sign(a.w) } // Check wall collisions for (const a of A) { wallCollide(a, 0, false) wallCollide(a, window.innerWidth, false) wallCollide(a, 0, true) wallCollide(a, window.innerHeight, true) } // Check collisions between objects for (let i = 0; i < A.length; i++) { for (let j = i + 1; j < A.length; j++) { objectsCollide(A[i], A[j]) } } // Render every 10ms tickcnt++ if (tickcnt == 10) { tickcnt = 0 for (let a of A) { let e = document.getElementById(a.id) if (e != null) { e.style.left = a.x - a.cm.x + "px" e.style.top = a.y - a.cm.y + "px" e.style.rotate = a.th + "rad" } } } } // Use click to update velocities function updatev(event: PointerEvent) { for (let a of A) { const d = Math.max(ds(a, { x: event.clientX, y: event.clientY }), 100) a.vx += 100 * (a.x - event.clientX) / d a.vy += 100 * (a.y - event.clientY) / d } // Display spreading out circles let circle = document.createElement("div") circle.style.width = circle.style.height = "10px" circle.style.left = event.clientX - 5 + "px" circle.style.top = event.clientY - 5 + "px" document.body.appendChild(circle) circle.style.transform = "scale(500)" circle.style.opacity = "0" setTimeout(function () { document.body.removeChild(circle) }, 1000) } let tickcnt = 0 setInterval(tick, 1) document.addEventListener("click", updatev)
-