PHYSICS
This commit is contained in:
commit
bf7131f7b2
6 changed files with 428 additions and 0 deletions
54
hiragana-a.svg
Normal file
54
hiragana-a.svg
Normal file
|
@ -0,0 +1,54 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||||
|
<!-- Created with Inkscape (http://www.inkscape.org/) -->
|
||||||
|
|
||||||
|
<svg
|
||||||
|
width="200"
|
||||||
|
height="200"
|
||||||
|
viewBox="0 0 200 200"
|
||||||
|
version="1.1"
|
||||||
|
id="hiragana-a"
|
||||||
|
inkscape:version="1.3.2 (091e20ef0f, 2023-11-25, custom)"
|
||||||
|
sodipodi:docname="hiragana-a.svg"
|
||||||
|
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||||
|
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
xmlns:svg="http://www.w3.org/2000/svg">
|
||||||
|
<sodipodi:namedview
|
||||||
|
id="namedview1"
|
||||||
|
pagecolor="#ffffff"
|
||||||
|
bordercolor="#000000"
|
||||||
|
borderopacity="0.25"
|
||||||
|
inkscape:showpageshadow="2"
|
||||||
|
inkscape:pageopacity="0.0"
|
||||||
|
inkscape:pagecheckerboard="0"
|
||||||
|
inkscape:deskcolor="#d1d1d1"
|
||||||
|
inkscape:document-units="mm"
|
||||||
|
inkscape:zoom="1.3575939"
|
||||||
|
inkscape:cx="-103.49192"
|
||||||
|
inkscape:cy="160.57821"
|
||||||
|
inkscape:window-width="1536"
|
||||||
|
inkscape:window-height="902"
|
||||||
|
inkscape:window-x="0"
|
||||||
|
inkscape:window-y="0"
|
||||||
|
inkscape:window-maximized="1"
|
||||||
|
inkscape:current-layer="layer1" />
|
||||||
|
<defs
|
||||||
|
id="defs1" />
|
||||||
|
<g
|
||||||
|
inkscape:label="Layer 1"
|
||||||
|
inkscape:groupmode="layer"
|
||||||
|
id="layer1">
|
||||||
|
<path
|
||||||
|
style="fill:none;stroke:currentColor;stroke-width:20;stroke-linejoin:round"
|
||||||
|
d="m 12.784166,48.2633 c 6.810296,-0.0161 13.608098,-0.70428 20.417184,-0.656946 8.339073,0.05797 16.662758,0.833983 25.001807,0.895073 34.613369,0.253565 37.757077,-1.380655 75.497083,-1.862985 9.91666,-0.126739 19.84279,-0.01771 29.74891,0.456642 24.06701,1.15225 14.13856,4.282188 22.44406,1.479181"
|
||||||
|
id="path1" />
|
||||||
|
<path
|
||||||
|
style="fill:none;stroke:currentColor;stroke-width:20;stroke-linejoin:round"
|
||||||
|
d="M 77.515495,8.2192151 C 75.516564,32.199537 75.530182,56.33753 75.541992,80.400969 c 0.0056,11.348185 -0.02805,22.701441 0.317652,34.042571 0.353335,11.59151 1.100709,23.16309 1.778856,34.73524 1.053836,17.98307 4.207218,12.37774 0.282507,18.48794"
|
||||||
|
id="path2" />
|
||||||
|
<path
|
||||||
|
style="fill:none;stroke:currentColor;stroke-width:20;stroke-linejoin:round"
|
||||||
|
d="m 125.70777,69.526531 c -0.69486,3.385925 -3.63293,17.853348 -4.07195,19.06547 -8.71461,24.060549 -19.52078,47.577849 -34.680923,68.388329 -5.193067,7.12857 -11.141846,14.13769 -18.645251,18.77272 -12.126114,7.49057 -27.245849,4.86443 -34.058991,-9.14824 -7.660344,-15.75512 -5.83572,-36.61123 3.067622,-51.35112 3.931406,-6.50862 9.029684,-12.95456 15.761122,-16.490912 16.544317,-8.691539 41.019976,-8.7624 58.602761,-4.619642 24.18531,5.698413 40.76727,10.598274 54.53221,31.135644 3.54089,5.28304 7.29148,11.80223 6.15485,18.05974 -1.42005,7.81773 -7.43918,14.32401 -13.0577,19.94234 -15.76644,15.76589 -18.71398,16.59872 -31.88644,22.27876"
|
||||||
|
id="path4" />
|
||||||
|
</g>
|
||||||
|
</svg>
|
After Width: | Height: | Size: 2.7 KiB |
54
hiragana-ni.svg
Normal file
54
hiragana-ni.svg
Normal file
|
@ -0,0 +1,54 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||||
|
<!-- Created with Inkscape (http://www.inkscape.org/) -->
|
||||||
|
|
||||||
|
<svg
|
||||||
|
width="200"
|
||||||
|
height="200"
|
||||||
|
viewBox="0 0 200 200"
|
||||||
|
version="1.1"
|
||||||
|
id="hiragana-ni"
|
||||||
|
inkscape:version="1.3.2 (091e20ef0f, 2023-11-25, custom)"
|
||||||
|
sodipodi:docname="hiragana-ni.svg"
|
||||||
|
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||||
|
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
xmlns:svg="http://www.w3.org/2000/svg">
|
||||||
|
<sodipodi:namedview
|
||||||
|
id="namedview1"
|
||||||
|
pagecolor="#ffffff"
|
||||||
|
bordercolor="#000000"
|
||||||
|
borderopacity="0.25"
|
||||||
|
inkscape:showpageshadow="2"
|
||||||
|
inkscape:pageopacity="0.0"
|
||||||
|
inkscape:pagecheckerboard="0"
|
||||||
|
inkscape:deskcolor="#d1d1d1"
|
||||||
|
inkscape:document-units="mm"
|
||||||
|
inkscape:zoom="1.7276112"
|
||||||
|
inkscape:cx="-45.438465"
|
||||||
|
inkscape:cy="107.08428"
|
||||||
|
inkscape:window-width="1536"
|
||||||
|
inkscape:window-height="902"
|
||||||
|
inkscape:window-x="0"
|
||||||
|
inkscape:window-y="0"
|
||||||
|
inkscape:window-maximized="1"
|
||||||
|
inkscape:current-layer="layer1" />
|
||||||
|
<defs
|
||||||
|
id="defs1" />
|
||||||
|
<g
|
||||||
|
inkscape:label="Layer 1"
|
||||||
|
inkscape:groupmode="layer"
|
||||||
|
id="layer1">
|
||||||
|
<path
|
||||||
|
style="fill:none;fill-opacity:1;stroke:currentColor;stroke-width:20;stroke-linecap:butt;stroke-linejoin:round;stroke-dasharray:none;stroke-opacity:1"
|
||||||
|
d="m 27.966735,16.083962 c -1.000438,1.124839 -2.372955,2.006558 -3.001316,3.374511 -0.890902,1.939519 -1.163631,4.125026 -1.417007,6.244278 -0.773668,6.470962 -1.184352,12.980894 -1.65866,19.480661 -1.990683,27.279717 -2.379566,30.990179 -1.454185,59.744588 0.793536,24.65759 1.788458,47.0885 5.760979,71.28514 0.564082,3.43584 1.988484,6.67372 2.982725,10.01058"
|
||||||
|
id="path7" />
|
||||||
|
<path
|
||||||
|
style="fill:none;fill-opacity:1;stroke:currentColor;stroke-width:20;stroke-linecap:butt;stroke-linejoin:round;stroke-dasharray:none;stroke-opacity:1"
|
||||||
|
d="m 61.803371,47.19668 c 1.248756,-0.907498 2.287696,-2.21703 3.746279,-2.722487 3.4894,-1.209225 7.188566,-1.722283 10.831328,-2.329273 9.225962,-1.537319 18.442605,-3.263583 27.758222,-4.100892 28.83951,-2.592158 58.01616,-0.884141 86.89226,-0.03718"
|
||||||
|
id="path8" />
|
||||||
|
<path
|
||||||
|
style="fill:none;fill-opacity:1;stroke:currentColor;stroke-width:20;stroke-linecap:butt;stroke-linejoin:round;stroke-dasharray:none;stroke-opacity:1"
|
||||||
|
d="m 87.045007,122.0222 c -1.118643,1.0562 -2.453734,1.92242 -3.355924,3.1686 -5.781083,7.98538 -13.117144,21.43086 -4.884465,30.77813 4.323739,4.90911 10.049889,8.7611 16.126165,11.18451 7.326277,2.92194 15.374477,3.78432 23.228367,4.51127 11.20859,1.03744 22.50545,0.60682 33.76085,0.76354 26.66878,0.37137 14.44172,-1.00144 26.91889,0.59627"
|
||||||
|
id="path9" />
|
||||||
|
</g>
|
||||||
|
</svg>
|
After Width: | Height: | Size: 2.7 KiB |
54
hiragana-small-ya.svg
Normal file
54
hiragana-small-ya.svg
Normal file
|
@ -0,0 +1,54 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||||
|
<!-- Created with Inkscape (http://www.inkscape.org/) -->
|
||||||
|
|
||||||
|
<svg
|
||||||
|
width="200"
|
||||||
|
height="200"
|
||||||
|
viewBox="0 0 200 200"
|
||||||
|
version="1.1"
|
||||||
|
id="hiragana-small-ya"
|
||||||
|
inkscape:version="1.3.2 (091e20ef0f, 2023-11-25, custom)"
|
||||||
|
sodipodi:docname="hiragana-small-ya.svg"
|
||||||
|
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||||
|
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
xmlns:svg="http://www.w3.org/2000/svg">
|
||||||
|
<sodipodi:namedview
|
||||||
|
id="namedview1"
|
||||||
|
pagecolor="#ffffff"
|
||||||
|
bordercolor="#000000"
|
||||||
|
borderopacity="0.25"
|
||||||
|
inkscape:showpageshadow="2"
|
||||||
|
inkscape:pageopacity="0.0"
|
||||||
|
inkscape:pagecheckerboard="0"
|
||||||
|
inkscape:deskcolor="#d1d1d1"
|
||||||
|
inkscape:document-units="mm"
|
||||||
|
inkscape:zoom="1.381533"
|
||||||
|
inkscape:cx="-121.24213"
|
||||||
|
inkscape:cy="98.441371"
|
||||||
|
inkscape:window-width="1536"
|
||||||
|
inkscape:window-height="902"
|
||||||
|
inkscape:window-x="0"
|
||||||
|
inkscape:window-y="0"
|
||||||
|
inkscape:window-maximized="1"
|
||||||
|
inkscape:current-layer="layer1" />
|
||||||
|
<defs
|
||||||
|
id="defs1" />
|
||||||
|
<g
|
||||||
|
inkscape:label="Layer 1"
|
||||||
|
inkscape:groupmode="layer"
|
||||||
|
id="layer1">
|
||||||
|
<path
|
||||||
|
style="fill:none;stroke:currentColor;stroke-width:20;stroke-linejoin:round"
|
||||||
|
d="m 22.318281,66.024742 c 3.522764,-5.501248 1.310196,-3.074172 12.511103,-6.879934 18.486357,-6.281155 31.10176,-9.525165 51.481112,-11.33282 17.781294,-1.577204 56.822334,-3.535437 75.702734,3.133648 12.25208,4.327774 13.15313,16.159869 5.86791,25.022203 -9.89215,12.033627 -30.4193,21.729881 -44.13131,27.517921 -1.60362,0.6769 -3.45533,0.42408 -5.18299,0.63612"
|
||||||
|
id="path1" />
|
||||||
|
<path
|
||||||
|
style="fill:none;stroke:currentColor;stroke-width:20;stroke-linejoin:round"
|
||||||
|
d="m 39.84223,19.392328 c 2.395717,4.499876 5.306115,8.761481 7.187149,13.499629 2.029237,5.111447 3.134124,10.546481 4.476386,15.879684 3.417985,13.580674 6.331658,27.285733 9.802138,40.853088 7.475495,29.224441 16.236122,57.535541 27.425749,85.593431"
|
||||||
|
id="path2" />
|
||||||
|
<path
|
||||||
|
style="fill:none;stroke:currentColor;stroke-width:20;stroke-linejoin:round"
|
||||||
|
d="m 95.63708,19.155587 c 2.853869,14.957057 9.67911,28.437868 14.84236,42.573525 2.10119,5.752511 3.81797,11.638235 5.72695,17.457352"
|
||||||
|
id="path3" />
|
||||||
|
</g>
|
||||||
|
</svg>
|
After Width: | Height: | Size: 2.3 KiB |
11
index.html
Normal file
11
index.html
Normal file
|
@ -0,0 +1,11 @@
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<title>dumb physics engine</title>
|
||||||
|
<meta name="description" content="Converts SVGs into a list of circles and use those circles for dumb collision checking">
|
||||||
|
<link rel="stylesheet" href="style.css">
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<script src="script.js"></script>
|
||||||
|
</body>
|
||||||
|
</html>
|
210
script.js
Normal file
210
script.js
Normal file
|
@ -0,0 +1,210 @@
|
||||||
|
// Inject SVG into DOM synchronously because we can't access the DOM of SVGs inside img tags
|
||||||
|
function injectSVG(svg) {
|
||||||
|
let req = new XMLHttpRequest()
|
||||||
|
req.open("GET", svg, false)
|
||||||
|
req.send()
|
||||||
|
document.body.innerHTML += req.responseText
|
||||||
|
}
|
||||||
|
|
||||||
|
// Draw 200x200 SVGs in Inkscape with the pencil tool and a stroke width of 20
|
||||||
|
// Make sure the scale is set to 1 and that the id is set to a unique value
|
||||||
|
// Also, change the stroke to currentColor so we can style the color with CSS
|
||||||
|
injectSVG("hiragana-ni.svg")
|
||||||
|
injectSVG("hiragana-small-ya.svg")
|
||||||
|
injectSVG("hiragana-a.svg")
|
||||||
|
|
||||||
|
let rad = 10
|
||||||
|
let size = 200
|
||||||
|
let A = []
|
||||||
|
let cnt = 0
|
||||||
|
document.querySelectorAll("svg").forEach(function(svg) {
|
||||||
|
svg.style.left = 1.5 * size * cnt++ + "px"
|
||||||
|
svg.style.top = "50px"
|
||||||
|
let a = {
|
||||||
|
id: svg.id, // Unique ID
|
||||||
|
p: [], // Collision circles of Array instances creates a new array populated with the results of calling a provided function on every element in the calling array. Try it Syntax js map(callbackFn) map(callbackFn, thisArg) Parameters callbackFn A function to execute for each element in the array.
|
||||||
|
|
||||||
|
cm: svg.createSVGPoint(), // Center of mass
|
||||||
|
vx: Math.random(), // x velocity
|
||||||
|
vy: Math.random(), // y velocity
|
||||||
|
th: 0, // Angular position
|
||||||
|
w: Math.random() / 100 // Angular velocity
|
||||||
|
}
|
||||||
|
svg.querySelectorAll("path").forEach(function(path) {
|
||||||
|
// Get circles on path for collision checking
|
||||||
|
for (let i = 0; i < path.getTotalLength(); i += rad) {
|
||||||
|
const p = path.getPointAtLength(i)
|
||||||
|
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", rad);
|
||||||
|
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
|
||||||
|
const rect = svg.getBoundingClientRect()
|
||||||
|
a.x = rect.x + a.cm.x // Position of center of mass
|
||||||
|
a.y = rect.y + a.cm.y // Position of center of mass
|
||||||
|
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 // Mass
|
||||||
|
a.mi = 0 // Moment of inertia
|
||||||
|
for (const p of a.p) a.mi += p.x ** 2 + p.y ** 2
|
||||||
|
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}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Distance squared between a and b
|
||||||
|
function ds(a, b) {
|
||||||
|
return (a.x - b.x) ** 2 + (a.y - b.y) ** 2
|
||||||
|
}
|
||||||
|
|
||||||
|
// Cross product
|
||||||
|
function cr(a, b) {
|
||||||
|
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
|
||||||
|
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('hi')
|
||||||
|
}
|
||||||
|
|
||||||
|
// Collision of object a with wall at position k and direction d
|
||||||
|
function wallCollide(a, k, d) {
|
||||||
|
if ((d == 0 && Math.abs(a.x - k) < size) || (d == 1 && Math.abs(a.y - k) < size)) {
|
||||||
|
let c = {x: 0, y: 0, cnt: 0}
|
||||||
|
for (const p of a.p.map(x => rot(a, x))) {
|
||||||
|
if ((d == 0 && Math.abs(p.x - k) < rad) || (d == 1 && 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})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function tick() {
|
||||||
|
// Move each object one step
|
||||||
|
for (let a of A) {
|
||||||
|
a.x += a.vx
|
||||||
|
a.y += a.vy
|
||||||
|
a.th += a.w
|
||||||
|
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++) {
|
||||||
|
let a = A[i]
|
||||||
|
let b = A[j]
|
||||||
|
if (ds(a, b) < size * size) {
|
||||||
|
// Objects are close
|
||||||
|
let c = {x: 0, y: 0, cnt: 0}
|
||||||
|
let n = {x: 0, y: 0}
|
||||||
|
for (const p of a.p.map(x => rot(a, x))) {
|
||||||
|
// p is close to object b
|
||||||
|
if (ds(p, b) < size * size) {
|
||||||
|
for (const q of b.p.map(x => rot(b, x))) {
|
||||||
|
const d = ds(p, q)
|
||||||
|
if (d < 4 * rad * rad) {
|
||||||
|
// Collision!
|
||||||
|
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
|
||||||
|
let norm = Math.sqrt(n.x ** 2 + n.y ** 2)
|
||||||
|
n.x /= norm
|
||||||
|
n.y /= norm
|
||||||
|
console.log(c.cnt, c, n)
|
||||||
|
collide(a, b, c, n)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Render every 10ms
|
||||||
|
cnt++
|
||||||
|
if (cnt == 10) {
|
||||||
|
cnt = 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"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Use click to update velocities
|
||||||
|
function updatev(event) {
|
||||||
|
for (a of A) {
|
||||||
|
let d = ds(a, {x: event.clientX, y: event.clientY})
|
||||||
|
a.vx += 100 * (a.x - event.clientX) / d
|
||||||
|
a.vy += 100 * (a.y - event.clientY) / d
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: Display spreading out circles
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
cnt = 0
|
||||||
|
setInterval(tick, 1)
|
||||||
|
document.addEventListener("click", updatev)
|
45
style.css
Normal file
45
style.css
Normal file
|
@ -0,0 +1,45 @@
|
||||||
|
body {
|
||||||
|
/* background-image: url("catgirl.png"); */
|
||||||
|
background-size: auto 100vh;
|
||||||
|
background-position: center;
|
||||||
|
background-repeat: no-repeat;
|
||||||
|
background-attachment: fixed;
|
||||||
|
touch-action: manipulation;
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes color {
|
||||||
|
10% {
|
||||||
|
color: #a43535;
|
||||||
|
}
|
||||||
|
20% {
|
||||||
|
color: #ff7b00;
|
||||||
|
}
|
||||||
|
30% {
|
||||||
|
color: #ffff51;
|
||||||
|
}
|
||||||
|
40% {
|
||||||
|
color: #a7ff4e;
|
||||||
|
}
|
||||||
|
50% {
|
||||||
|
color: #7addfe;
|
||||||
|
}
|
||||||
|
60% {
|
||||||
|
color: #6b6bfd;
|
||||||
|
}
|
||||||
|
70% {
|
||||||
|
color: #ca61ff;
|
||||||
|
}
|
||||||
|
80% {
|
||||||
|
color: #ff54af;
|
||||||
|
}
|
||||||
|
90% {
|
||||||
|
color: #f3bbff;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
svg {
|
||||||
|
color: #f3bbff;
|
||||||
|
font-size: 240px;
|
||||||
|
animation: color 10s infinite linear;
|
||||||
|
position: absolute;
|
||||||
|
}
|
Loading…
Reference in a new issue