-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
1 parent
4c6a41e
commit 997b42f
Showing
5 changed files
with
420 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,153 @@ | ||
/* | ||
* 2024 Tarpeeksi Hyvae Soft | ||
* | ||
*/ | ||
|
||
import {icons} from "./icons.js"; | ||
import background from "./bg.texture.js"; | ||
import * as fluid from "./fluid.js"; | ||
|
||
const fluidDomainSize = 150; | ||
|
||
export default { | ||
Meta: { | ||
name: "Xmas '94", | ||
version: "1.0", | ||
author: "Tarpeeksi Hyvae Soft", | ||
}, | ||
App() { | ||
const width = w95.state(150); | ||
const height = w95.state(150); | ||
const x = w95.state( | ||
~~(0.5 * (w95.shell.display.width - width.now)), | ||
w95.reRenderOnly | ||
); | ||
const y = w95.state( | ||
~~(0.5 * (w95.shell.display.visibleHeight - height.now)), | ||
w95.reRenderOnly | ||
); | ||
|
||
const redraw = w95.state(0); | ||
const flakes = w95.state(new Array(100).fill().map(f=>({ | ||
x: Math.max(1, Math.min((width.now - 10), ~~(Math.random() * width.now))), | ||
y: ~~(Math.random() * (height.now / 2)), | ||
color: w95.color(200 + ~~(55 * Math.random())), | ||
}))); | ||
|
||
return { | ||
get x() { return x.now }, | ||
get y() { return y.now }, | ||
get width() { return width.now }, | ||
get height() { return height.now }, | ||
Opened() { | ||
setInterval(()=>{ | ||
flakes.now.push({ | ||
x: Math.max(1, Math.min((width.now - 10), ~~(Math.random() * width.now))), | ||
y: 0, | ||
color: w95.color(200 + ~~(55 * Math.random())), | ||
}); | ||
}, 50); | ||
}, | ||
Mounted() { | ||
redraw.set(redraw.now+1); | ||
|
||
if (fluid.is_initialized()) { | ||
fluid.enforce_boundaries(); | ||
|
||
skip_flake_position_update: | ||
for (const flake of flakes.now) { | ||
const xm = (fluidDomainSize / width.now); | ||
const ym = (fluidDomainSize / height.now); | ||
const vel = fluid.velocity_at(~~(flake.x*xm), ~~(flake.y*ym)); | ||
const gravity = Math.max(0.3, Math.random()/((height.now / flake.y) / 2))/4; | ||
const ru = (-Math.random() + Math.random()); | ||
const rv = (-Math.random() + Math.random()); | ||
const newX = Math.min((width.now - 10), Math.max(1, flake.x + ru + (vel.u * (1 + Math.random() / 2)))); | ||
const newY = Math.min((height.now - 30), Math.max(1, flake.y + rv + gravity + (vel.v * (1 + Math.random() / 2)))) + Math.random(); | ||
|
||
if (background.pixels[((~~newX + (background.height - 1 - ~~newY) * background.width) * 4) + 3]) { | ||
continue skip_flake_position_update; | ||
} | ||
|
||
for (const flake of flakes.now) { | ||
if (~~flake.x === ~~newX && ~~flake.y === ~~newY) { | ||
continue skip_flake_position_update; | ||
} | ||
} | ||
|
||
flake.x = newX; | ||
flake.y = newY; | ||
} | ||
|
||
fluid.tick(); | ||
} | ||
else { | ||
fluid.initialize({ | ||
domainSize: fluidDomainSize, | ||
vorticityStrength: 1, | ||
}); | ||
} | ||
}, | ||
Form() { | ||
return w95.widget.window({ | ||
parent: this, | ||
title: this.$app.Meta.name, | ||
icon: icons.app16, | ||
move(deltaX, deltaY) { | ||
x.set(x.now + deltaX); | ||
y.set(y.now + deltaY); | ||
for (let y = 1; y < fluidDomainSize - 1; y++) { | ||
for (let x = 1; x < fluidDomainSize - 1; x++) { | ||
if (Math.random() < 0.25) { | ||
fluid.apply_force({x, y, dU: deltaX*Math.random()*2.25, dV: deltaY*Math.random()*2.25}); | ||
} | ||
} | ||
} | ||
}, | ||
close() { | ||
w95.windowManager.release_window(this) | ||
}, | ||
children: [ | ||
w95.widget.frame({ | ||
y: 1, | ||
width: "pw", | ||
height: "ph", | ||
shape: w95.frameShape.box, | ||
children: [ | ||
w95.widget.renderSurface({ | ||
x: 1, | ||
y: 1, | ||
width: "pw - 2", | ||
height: "ph - 2", | ||
backgroundColor: w95.color(0, 0, 0,155), | ||
meshes: [ | ||
Rngon.mesh([ | ||
Rngon.ngon([ | ||
Rngon.vertex(0, 0), | ||
Rngon.vertex(142, 0), | ||
Rngon.vertex(142, 121), | ||
Rngon.vertex(0, 121), | ||
], { | ||
texture: background, | ||
allowAlphaReject: true, | ||
isInScreenSpace: true, | ||
}) | ||
]), | ||
Rngon.mesh(flakes.now.map(f=>( | ||
Rngon.ngon([ | ||
Rngon.vertex(~~f.x, ~~f.y) | ||
], { | ||
color: f.color, | ||
isInScreenSpace: true, | ||
}) | ||
))), | ||
], | ||
}), | ||
], | ||
}), | ||
], | ||
}); | ||
}, | ||
}; | ||
}, | ||
}; |
Large diffs are not rendered by default.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,227 @@ | ||
/* | ||
* 2022 Tarpeeksi Hyvae Soft | ||
* | ||
* Adapted from Stam: Real-time fluid dynamics for games | ||
* (http://graphics.cs.cmu.edu/nsp/course/15-464/Fall09/papers/StamFluidforGames.pdf). | ||
* | ||
*/ | ||
|
||
"use strict"; | ||
|
||
const TICK_RATE = 0.05; | ||
const NUM_PROJECT_LOOPS = 15; | ||
let IS_INITIALIZED = false, | ||
DOMAIN_SIZE = 0, | ||
VORTICITY_STRENGTH = 0, | ||
VEL_GRID_SIZE = 0, | ||
VEL_U = [], | ||
VEL_V = [], | ||
U0 = [], | ||
V0 = []; | ||
|
||
function lerp(x, y, interval) | ||
{ | ||
return (x + (interval * (y - x))); | ||
} | ||
|
||
export function is_initialized() | ||
{ | ||
return IS_INITIALIZED; | ||
} | ||
|
||
export function velocity_at(x = 0, y = 0) | ||
{ | ||
console.assert(IS_INITIALIZED); | ||
|
||
const idx = vel_idx(x, y); | ||
|
||
return { | ||
u: VEL_U[idx], | ||
v: VEL_V[idx], | ||
}; | ||
} | ||
|
||
export function enforce_boundaries() | ||
{ | ||
for (let i = 0; i < DOMAIN_SIZE; i++) | ||
{ | ||
// Top. | ||
VEL_V[vel_idx(i, 0)] = 0; | ||
VEL_V[vel_idx(i, -1)] = 0; | ||
|
||
// Bottom. | ||
VEL_V[vel_idx(i, (DOMAIN_SIZE - 0))] = 0; | ||
VEL_V[vel_idx(i, (DOMAIN_SIZE - 1))] = 0; | ||
|
||
// Right wall. | ||
VEL_U[vel_idx((DOMAIN_SIZE - 0), i)] = 0; | ||
VEL_U[vel_idx((DOMAIN_SIZE - 1), i)] = 0; | ||
|
||
// Left wall. | ||
VEL_U[vel_idx(0, i)] = 0; | ||
VEL_U[vel_idx(-1, i)] = 0; | ||
} | ||
} | ||
|
||
export function initialize({domainSize, vorticityStrength}) | ||
{ | ||
console.assert(!IS_INITIALIZED); | ||
|
||
DOMAIN_SIZE = domainSize; | ||
VORTICITY_STRENGTH = vorticityStrength; | ||
VEL_GRID_SIZE = ((DOMAIN_SIZE + 2) ** 2); | ||
VEL_U = new Array(VEL_GRID_SIZE).fill(0); | ||
VEL_V = new Array(VEL_GRID_SIZE).fill(0); | ||
U0 = new Array(VEL_GRID_SIZE).fill(0); | ||
V0 = new Array(VEL_GRID_SIZE).fill(0); | ||
IS_INITIALIZED = true; | ||
|
||
return; | ||
} | ||
|
||
export function set_force({x = 0, y = 0, dU = 0, dV = 0} = {}) | ||
{ | ||
console.assert(IS_INITIALIZED); | ||
|
||
const idx = vel_idx(x, y); | ||
VEL_U[idx] = dU; | ||
VEL_V[idx] = dV; | ||
|
||
return; | ||
} | ||
|
||
export function apply_force({x = 0, y = 0, dU = 0, dV = 0} = {}) | ||
{ | ||
console.assert(IS_INITIALIZED); | ||
|
||
const idx = vel_idx(x, y); | ||
|
||
VEL_U[idx] = (dU != 0)? lerp(dU, VEL_U[idx], 0.85) : VEL_U[idx]; | ||
VEL_V[idx] = (dV != 0)? lerp(dV, VEL_V[idx], 0.85) : VEL_V[idx]; | ||
|
||
return; | ||
} | ||
|
||
export function tick() | ||
{ | ||
console.assert(IS_INITIALIZED); | ||
|
||
vorticify(); | ||
project(VEL_U, VEL_V, U0, V0); | ||
[U0, VEL_U] = [VEL_U, U0]; | ||
[V0, VEL_V] = [VEL_V, V0]; | ||
advect(VEL_U, U0, U0, V0, TICK_RATE); | ||
advect(VEL_V, V0, U0, V0, TICK_RATE); | ||
project(VEL_U, VEL_V, U0, V0); | ||
|
||
return; | ||
} | ||
|
||
function vel_idx(x, y) | ||
{ | ||
return Math.floor(x + y * (DOMAIN_SIZE + 2)); | ||
} | ||
|
||
function vorticify() | ||
{ | ||
for (let i = 1; i <= DOMAIN_SIZE; ++i) | ||
{ | ||
for (let j = 1; j <= DOMAIN_SIZE; ++j) | ||
{ | ||
const uy = (VEL_U[vel_idx(i,j+1)] - VEL_U[vel_idx(i,j-1)]) * 0.5; | ||
const vx = (VEL_V[vel_idx(i+1,j)] - VEL_V[vel_idx(i-1,j)]) * 0.5; | ||
|
||
U0[vel_idx(i, j)] = Math.abs(uy-vx); | ||
} | ||
} | ||
|
||
for (let i = 2; i < DOMAIN_SIZE; ++i) | ||
{ | ||
for (let j = 2; j < DOMAIN_SIZE; ++j) | ||
{ | ||
let wx = (U0[vel_idx(i+1,j)] - U0[vel_idx(i-1,j)]) * 0.5; | ||
let wy = (U0[vel_idx(i,j+1)] - U0[vel_idx(i,j-1)]) * 0.5; | ||
const len = (1 / (Math.sqrt(wx**2 + wy**2) + 0.000001)); | ||
|
||
wx *= len; | ||
wy *= len; | ||
|
||
const uy = (VEL_U[vel_idx(i,j+1)] - VEL_U[vel_idx(i,j-1)]) * 0.5; | ||
const vx = (VEL_V[vel_idx(i+1,j)] - VEL_V[vel_idx(i-1,j)]) * 0.5; | ||
const t = (uy - vx); | ||
|
||
VEL_U[vel_idx(i,j)] += (wy * -t * VORTICITY_STRENGTH); | ||
VEL_V[vel_idx(i,j)] += (wx * t * VORTICITY_STRENGTH); | ||
} | ||
} | ||
|
||
return; | ||
} | ||
|
||
function project(u, v, p, div) | ||
{ | ||
const h = 1.0 / DOMAIN_SIZE; | ||
|
||
for (let i = 1; i <= DOMAIN_SIZE; i++) | ||
{ | ||
for (let j = 1; j <= DOMAIN_SIZE; j++) | ||
{ | ||
div[vel_idx(i, j)] = | ||
-0.5 * h | ||
* (u[vel_idx(i+1,j)] - u[vel_idx(i-1,j)] + v[vel_idx(i,j+1)] - v[vel_idx(i,j-1)]); | ||
p[vel_idx(i,j)] = 0; | ||
} | ||
} | ||
|
||
for (let k = 0; k < NUM_PROJECT_LOOPS; k++) | ||
{ | ||
for (let i = 1; i <= DOMAIN_SIZE; i++) | ||
{ | ||
for (let j = 1; j <= DOMAIN_SIZE; j++) | ||
{ | ||
p[vel_idx(i, j)] = | ||
(div[vel_idx(i,j)] + p[vel_idx(i-1,j)] | ||
+ p[vel_idx(i+1,j)] + p[vel_idx(i,j-1)] + p[vel_idx(i,j+1)]) / 4; | ||
} | ||
} | ||
} | ||
|
||
for (let i = 1; i <= DOMAIN_SIZE; i++) | ||
{ | ||
for (let j = 1; j <= DOMAIN_SIZE; j++) | ||
{ | ||
u[vel_idx(i,j)] -= 0.5 * (p[vel_idx(i+1,j)] - p[vel_idx(i-1,j)]) / h; | ||
v[vel_idx(i,j)] -= 0.5 * (p[vel_idx(i,j+1)] - p[vel_idx(i,j-1)]) / h; | ||
} | ||
} | ||
|
||
return; | ||
} | ||
|
||
function advect(d, d0, u, v, dt) | ||
{ | ||
const dt0 = dt * DOMAIN_SIZE; | ||
|
||
for (let i = 1; i <= DOMAIN_SIZE; i++) | ||
{ | ||
for (let j = 1; j <= DOMAIN_SIZE; j++) | ||
{ | ||
const x = Math.max(0.5, Math.min((DOMAIN_SIZE + 0.5), i - dt0 * u[vel_idx(i,j)])); | ||
const y = Math.max(0.5, Math.min((DOMAIN_SIZE + 0.5), j - dt0 * v[vel_idx(i,j)])); | ||
const i0 = Math.floor(x); | ||
const i1 = i0 + 1; | ||
const j0 = Math.floor(y); | ||
const j1 = j0 + 1; | ||
const s1 = x - i0; | ||
const s0 = 1 - s1; | ||
const t1 = y - j0; | ||
const t0 = 1 - t1; | ||
|
||
d[vel_idx(i,j)] = | ||
s0 * (t0 * d0[vel_idx(i0,j0)] + t1 * d0[vel_idx(i0,j1)]) | ||
+ s1 * (t0 * d0[vel_idx(i1,j0)] + t1 * d0[vel_idx(i1,j1)]); | ||
} | ||
} | ||
|
||
return; | ||
} |
Oops, something went wrong.