gpu.js/examples/fluid.html
Robert Plummer f88d92cc40 feat: Bring back kernel.immutable and kernel.setImmutable() with defaults to false
Along with tests and documentation on which versions are affected.  Sorry for the inconvenience!
feat: Add more advanced-typescript.ts
fix: Alter documentation mentioning v2
fix: Example of fluid.html, to use `immutable`
2020-03-11 07:36:50 -04:00

1143 lines
35 KiB
HTML

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>SSPS - Chadams Studios (slightly upgraded)</title>
<script src="../dist/gpu-browser.min.js"></script>
<script>
function XSSPS(particleCount, renderSize, onAfterInput) {
this.dt = 0;
this.renderSize = renderSize || 512;
this.particleCount = particleCount || 512;
this.onAfterInput = onAfterInput;
this.hashLength = 128;
this.hashWSize = 3.0;
this.hashBinLen = 128;
this.rayCount = 128;
this.rayDist = 20;
this.rfScale = 2.0;
this.restDensity = 0.1;
this.fieldLen = 4;
this.gConst = 0.02;
this.bound = 8;
this.shootIndex = 0;
this.keys = {};
this.canvas = document.createElement('canvas');
const gpu = this.gpu = new GPU({
canvas: this.canvas,
mode: 'gpu'
});
/*
RENDER SUPPORT FUNCTIONS (GLSL)
*/
gpu.addNativeFunction('raySphere',
`vec2 raySphere(vec3 r0, vec3 rd, vec3 s0, float sr) {
float a = dot(rd, rd);
vec3 s0_r0 = r0 - s0;
float b = 2.0 * dot(rd, s0_r0);
float c = dot(s0_r0, s0_r0) - (sr * sr);
float test = b*b - 4.0*a*c;
if (test < 0.0) {
return vec2(-1.0, 0.);
}
test = sqrt(test);
float X = (-b - test)/(2.0*a);
float Y = (-b + test)/(2.0*a);
return vec2(
X,
Y-X
);
}`
);
gpu.addNativeFunction('getRay0',
`vec3 getRay0(vec2 uv, vec3 c0, vec3 cd, vec3 up, float s0, float s1, float slen) {
up = normalize(cross(cross(cd, up), cd));
vec3 vup = normalize(cross(cd, up));
vec3 vleft = up;
vec2 X = (uv - vec2(0.5, 0.5)) * s0;
return c0 + vup * X.y + vleft * X.x;
}`
);
gpu.addNativeFunction('getRayDir',
`vec3 getRayDir(vec3 R0, vec2 uv, vec3 c0, vec3 cd, vec3 up, float s0, float s1, float slen) {
up = normalize(cross(cross(cd, up), cd));
vec3 vup = normalize(cross(cd, up));
vec3 vleft = up;
vec2 X = (uv - vec2(0.5, 0.5)) * s1;
vec3 far = vup * X.y + vleft * X.x;
return normalize((normalize(cd) * slen + far + c0) - R0);
}`
);
gpu.addNativeFunction('reflectWrap',
`vec3 reflectWrap(vec3 I, vec3 N) {
return normalize(reflect(normalize(I), normalize(N)));
}`
);
gpu.addNativeFunction('refractWrap',
`vec3 refractWrap(vec3 I, vec3 N, float eta) {
return normalize(refract(normalize(I), normalize(N), eta));
}`
);
gpu.addNativeFunction('distanceWrap',
`float distanceWrap(vec3 A, vec3 B) {
return length(A - B);
}`
);
/*
UPDATE SUPPORT FUNCTIONS (GLSL)
*/
gpu.addNativeFunction('compGravity',
`vec3 compGravity(vec3 M, vec3 J, float jMass, float f) {
vec3 delta = J - M;
float dlen = length(delta);
if (dlen > 0.05) {
float dlen2 = jMass / (max(dlen*dlen, 1.) * 0.1);
return dlen2 * (delta / dlen) * f;
}
return vec3(0., 0., 0.);
}`
);
gpu.addNativeFunction('partDensity',
`vec2 partDensity(vec3 M, vec3 J, float fLen) {
vec3 delta = J - M;
float len = length(delta);
if (len < fLen) {
float t = 1. - (len / fLen);
return vec2(t*t, t*t*t);
}
return vec2(0., 0.);
}`
);
gpu.addNativeFunction('pressForce',
`vec3 pressForce(vec3 M, vec3 J, vec3 Mv, vec3 Jv, float fLen, float dt, float spressure, float snpressure, float viscdt) {
vec3 delta = J - M;
float len = length(delta);
if (len < fLen) {
float t = 1. - (len / fLen);
delta *= dt * t * (spressure + snpressure * t) / (2. * len);
vec3 deltaV = Jv - Mv;
deltaV *= dt * t * viscdt;
return -(delta - deltaV);
}
return vec3(0., 0., 0.);
}`
);
/*
INIT SUPPORT FUNCTIONS
*/
gpu.addNativeFunction('random',
`float random(float sequence, float seed) {
return fract(sin(dot(vec2(seed, sequence), vec2(12.9898, 78.233))) * 43758.5453);
}`
);
/*
VELOCITY UPDATE KERNEL
*/
this.updateVelocity = gpu.createKernel(function(
positions,
velocities,
attrs,
pullMass,
camP,
camDir,
moveTPullR,
oDensity,
oVisc,
oMass,
oIncomp,
dt
) {
var playerPos = [
camP[0] + camDir[0] * moveTPullR,
camP[1] + camDir[1] * moveTPullR,
camP[2] + camDir[2] * moveTPullR,
];
// Unpack particle
var comp = this.thread.x % 3;
var me = (this.thread.x - comp) / 3;
var mx = positions[me*3],
my = positions[me*3+1],
mz = positions[me*3+2];
var mpos = [mx, my, mz];
var mvx = velocities[me*3],
mvy = velocities[me*3+1],
mvz = velocities[me*3+2];
var mvel = [mvx, mvy, mvz];
var mmass = oMass;
var incomp = oIncomp;
var viscdt = oVisc;
var ddensity = 0.0,
nddensity = 0.0;
// Compute pressure on this particle
for (var i=0; i<this.constants.particleCount; i++) {
var opos = [positions[i*3], positions[i*3+1], positions[i*3+2]];
var density = oDensity;
var dret = partDensity(mpos, opos, this.constants.fieldLen);
ddensity += dret[0] * density;
nddensity += dret[1] * density;
}
// Interact with player by adding pressure
var opos = [playerPos[0], playerPos[1], playerPos[2]];
var fl2 = this.constants.fieldLen;
var pf = [0, 0];
if (pullMass > 0.0) {
pf = partDensity(mpos, opos, fl2);
}
ddensity += pf[0] * (pullMass / 10.0 * this.constants.restDensity);
nddensity += pf[1] * (pullMass / 10.0 * this.constants.restDensity);
var spressure = (ddensity - this.constants.restDensity) * incomp;
var snpressure = nddensity * incomp;
// Compute force from pressure on this particle
var ret = [mvx, mvy, mvz];
for (var i=0; i<this.constants.particleCount; i++) {
if (i - me > 0.01) {
var opos = [positions[i*3], positions[i*3+1], positions[i*3+2]];
var ovel = [velocities[i*3], velocities[i*3+1], velocities[i*3+2]];
var mass = oMass;
var jmf = (2. * mass) / (mass + mmass);
var dret = pressForce(mpos, opos, mvel, ovel, this.constants.fieldLen, dt, spressure, snpressure, viscdt);
ret[0] += dret[0] * jmf; ret[1] += dret[1] * jmf; ret[2] += dret[2] * jmf;
}
}
// Player pressure
if (pullMass > 0.0) {
var opos = [playerPos[0], playerPos[1], playerPos[2]];
var ovel = [0, 0, 0];
var mass = 1.0;
var jmf = (2. * mass) / (mass + mmass);
var dret = pressForce(mpos, opos, mvel, ovel, this.constants.fieldLen, dt, spressure, snpressure, viscdt);
ret[0] += dret[0] * jmf;
ret[1] += dret[1] * jmf;
ret[2] += dret[2] * jmf;
}
// Compute gravitational force on this particle
var gf = this.constants.gConst * dt * 0.1;
for (var i=0; i<this.constants.particleCount; i++) {
var opos = [positions[i*3], positions[i*3+1], positions[i*3+2]];
var mass = attrs[i*6+2];
var dret = compGravity(mpos, opos, mass, gf);
ret[0] += dret[0]; ret[1] += dret[1]; ret[2] += dret[2];
}
// Enforce scene boundaries
if (ret[0] < 0 && mpos[0] < -this.constants.bound) {
ret[0] = -ret[0];
} else if (ret[0] > 0 && mpos[0] > this.constants.bound) {
ret[0] = -ret[0];
}
if (ret[1] < 0 && mpos[1] < -this.constants.bound) {
ret[1] = -ret[1];
} else if (ret[1] > 0 && mpos[1] > this.constants.bound) {
ret[1] = -ret[1];
}
if (ret[2] < 0 && mpos[2] < -this.constants.bound) {
ret[2] = -ret[2];
} else if (ret[2] > 0 && mpos[2] > this.constants.bound) {
ret[2] = -ret[2];
}
if (comp < 0.01) {
return ret[0];
} else if (comp < 1.01) {
return ret[1];
} else if (comp < 2.01) {
return ret[2];
}
}, {
constants: {
particleCount: this.particleCount,
bound: this.bound,
fieldLen: this.fieldLen,
gConst: this.gConst,
restDensity: ((4 / 3) * Math.PI * Math.pow(this.fieldLen, 3)) * this.restDensity
},
loopMaxIterations: this.particleCount,
output: [ this.particleCount*3 ],
pipeline: true,
tactic: 'speed',
immutable: true,
});
/*
SET POSITION/VELOCITES
*/
this.setPosition = gpu.createKernel(function(list, index, camP, camDir, amount) {
if (Math.abs(index - Math.floor(this.thread.x / this.constants.pitch)) < 0.01) {
const i = this.thread.x % this.constants.pitch;
return camP[i] + camDir[i] * amount;
} else {
return list[this.thread.x];
}
}, {
output: [ this.particleCount * 3 ],
pipeline: true,
constants: { particleCount: this.particleCount, pitch: 3 },
loopMaxIterations: this.particleCount,
tactic: 'speed',
immutable: true,
});
this.setVelocity = gpu.createKernel(function(list, index, camDir, amount) {
if (Math.abs(index - Math.floor(this.thread.x / this.constants.pitch)) < 0.01) {
return camDir[this.thread.x % this.constants.pitch] * amount;
} else {
return list[this.thread.x];
}
}, {
output: [ this.particleCount * 3 ],
pipeline: true,
constants: { particleCount: this.particleCount, pitch: 3 },
loopMaxIterations: this.particleCount,
tactic: 'speed',
immutable: true,
});
/*
POSITION UPDATE KERNEL
*/
this.updatePosition = gpu.createKernel(function(positions, velocities, dt) {
return positions[this.thread.x] + velocities[this.thread.x] * dt;
}, {
output: [ this.particleCount * 3 ],
pipeline: true,
loopMaxIterations: this.particleCount,
tactic: 'speed',
immutable: true,
});
this.rotateCamera = gpu.createKernel(function(rotLR, rotUD, camCenter, camDir, camUp, camNearWidth, camFarWidth, camDist) {
var uv = [0.5+rotLR, 0.5+rotUD];
var CC = [camCenter[0], camCenter[1], camCenter[2]],
CD = [camDir[0], camDir[1], camDir[2]],
CU = [camUp[0], camUp[1], camUp[2]];
var ray0 = getRay0(uv, CC, CD, CU, camNearWidth, camFarWidth, camDist);
var rayDir = getRayDir(ray0, uv, CC, CD, CU, camNearWidth, camFarWidth, camDist);
if (this.thread.x === 0) {
return rayDir[0];
} else if (this.thread.x === 1) {
return rayDir[1];
} else {
return rayDir[2];
}
}, {
output: [ 3 ],
tactic: 'speed',
pipeline: true,
immutable: true,
});
this.updateCameraPosition = gpu.createKernel(function(camDir, camP, moveTFR, dt) {
var dlen = Math.sqrt(
(camDir[0] * camDir[0])
+ (camDir[1] * camDir[1])
+ (camDir[2] * camDir[2])
);
return camP[this.thread.x] + (camDir[this.thread.x] / dlen) * moveTFR * dt * 6;
}, {
output: [3],
tactic: 'speed',
pipeline: true,
immutable: true,
});
/*
RENDER KERNEL
*/
this.renderMode = 0;
this.renderKernel = [
gpu.createKernel(function(positions, camCenter, camDir, camUp, camNearWidth, camFarWidth, camDist) {
let uv = [this.thread.x / (this.constants.renderSize-1), this.thread.y / (this.constants.renderSize-1)];
let CC = [camCenter[0], camCenter[1], camCenter[2]],
CD = [camDir[0], camDir[1], camDir[2]],
CU = [camUp[0], camUp[1], camUp[2]];
let ray0 = getRay0(uv, CC, CD, CU, camNearWidth, camFarWidth, camDist);
let rayDir = getRayDir(ray0, uv, CC, CD, CU, camNearWidth, camFarWidth, camDist);
this.color(0., 0., 0., 1.);
let outClr = [
0., 0., 0.
];
let ds = 0.0;
let done = 0.0;
let norm = [0., 0., 0.];
for (let i=0; i<this.constants.ICOUNT; i++) {
if (done < 0.5) {
let rnow = [
ray0[0] + rayDir[0] * ds,
ray0[1] + rayDir[1] * ds,
ray0[2] + rayDir[2] * ds
];
norm[0] = 0.; norm[1] = 0.; norm[2] = 0.;
let m = 0.0;
let fq = 0.0;
let nt = 0.0;
let dmin = 1000.0;
for (let j=0; j<this.constants.particleCount; j++) {
let off = j * 3;
let dx = positions[off], dy = positions[off+1.], dz = positions[off+2.];
dx -= rnow[0]; dy -= rnow[1]; dz -= rnow[2];
let r = Math.sqrt(dx*dx + dy*dy + dz*dz);
let x = r / this.constants.rfScale;
if (x < 1.0) {
let q = 1.0 - x*x*x*(x*(x*6.0-15.0)+10.0);
norm[0] += (dx/r) * q;
norm[1] += (dy/r) * q;
norm[2] += (dz/r) * q;
fq += q;
nt += q;
m += 1.0;
}
else {
dmin = Math.min(dmin, r - this.constants.rfScale);
}
}
let dist = dmin + 0.1;
norm[0] /= nt; norm[1] /= nt; norm[2] /= nt;
if (m > 0.5) {
dist = (0.5333 * this.constants.rfScale) * (0.5 - fq);
}
ds += dist;
if (dist < 0.01) {
done = 1.0;
}
if (ds >= this.constants.rayDist) {
done = 1.0;
}
}
}
ds = Math.min(ds, this.constants.rayDist);
let ref = refractWrap(norm, rayDir, 1./1.5);
let dot = ref[0] * rayDir[0] + ref[1] * rayDir[1] + ref[2] * rayDir[2];
let int = (1. - Math.pow(ds / this.constants.rayDist, 1.5));
let light = Math.min(1., Math.max(dot*dot, 0.)) * int;
outClr[2] = int + light;
outClr[1] = light;
outClr[0] = light;
this.color(Math.min(outClr[0], 1.), Math.min(outClr[1], 1.), Math.min(outClr[2], 1.), 1.);
}, {
graphical: true,
constants: {
rayDist: this.rayDist,
rfScale: this.rfScale,
bound: this.bound,
renderSize: this.renderSize,
particleCount: this.particleCount,
ICOUNT: 48
},
loopMaxIterations: 48 * this.particleCount,
output: [this.renderSize, this.renderSize],
tactic: 'speed',
}),
gpu.createKernel(function(positions, camCenter, camDir, camUp, camNearWidth, camFarWidth, camDist) {
let uv = [this.thread.x / (this.constants.renderSize-1), this.thread.y / (this.constants.renderSize-1)];
let CC = [camCenter[0], camCenter[1], camCenter[2]],
CD = [camDir[0], camDir[1], camDir[2]],
CU = [camUp[0], camUp[1], camUp[2]];
let ray0 = getRay0(uv, CC, CD, CU, camNearWidth, camFarWidth, camDist);
let rayDir = getRayDir(ray0, uv, CC, CD, CU, camNearWidth, camFarWidth, camDist);
this.color(0., 0., 0., 1.);
let outClr = [
0., 0., 0.
];
let ds = 0.0;
let done = 0.0;
let norm = [0., 0., 0.];
for (let i=0; i<this.constants.ICOUNT; i++) {
if (done < 0.5) {
let rnow = [
ray0[0] + rayDir[0] * ds,
ray0[1] + rayDir[1] * ds,
ray0[2] + rayDir[2] * ds
];
norm[0] = 0.; norm[1] = 0.; norm[2] = 0.;
let m = 0.0;
let fq = 0.0;
let nt = 0.0;
let dmin = 1000.0;
for (let j=0; j<this.constants.particleCount; j++) {
let off = j * 3;
let dx = positions[off], dy = positions[off+1.], dz = positions[off+2.];
dx -= rnow[0]; dy -= rnow[1]; dz -= rnow[2];
let r = Math.sqrt(dx*dx + dy*dy + dz*dz);
let x = r / this.constants.rfScale;
if (x < 1.0) {
let q = 1.0 - x*x*x*(x*(x*6.0-15.0)+10.0);
norm[0] += (dx/r) * q;
norm[1] += (dy/r) * q;
norm[2] += (dz/r) * q;
fq += q;
nt += q;
m += 1.0;
}
else {
dmin = Math.min(dmin, r - this.constants.rfScale);
}
}
let dist = dmin + 0.1;
norm[0] /= nt; norm[1] /= nt; norm[2] /= nt;
if (m > 0.5) {
dist = (0.5333 * this.constants.rfScale) * (0.5 - fq);
}
ds += dist;
if (dist < 0.01) {
done = 1.0;
}
if (ds >= this.constants.rayDist) {
done = 1.0;
}
}
}
ds = Math.min(ds, this.constants.rayDist);
let ref = refractWrap(norm, rayDir, 1./1.5);
let dot = ref[0] * rayDir[0] + ref[1] * rayDir[1] + ref[2] * rayDir[2];
let int = (1. - Math.pow(ds / this.constants.rayDist, 1.5));
let light = Math.min(1., Math.max(dot*dot, 0.)) * int;
outClr[2] = int + light;
outClr[1] = light;
outClr[0] = light;
if (ds < (this.constants.rayDist - 0.001)) {
ray0[0] += rayDir[0] * ds;
ray0[1] += rayDir[1] * ds;
ray0[2] += rayDir[2] * ds;
let ds = 0.00;
let done = 0.0;
let nrayDir = [-norm[0], -norm[1], -norm[2]];
nrayDir = reflectWrap(nrayDir, rayDir);
norm = [0., 0., 0.];
ray0[0] += nrayDir[0] * 0.01;
ray0[1] += nrayDir[1] * 0.01;
ray0[2] += nrayDir[2] * 0.01;
for (let i=0; i<this.constants.ICOUNTR; i++) {
if (done < 0.5) {
let rnow = [
ray0[0] + nrayDir[0] * ds,
ray0[1] + nrayDir[1] * ds,
ray0[2] + nrayDir[2] * ds
];
norm[0] = 0.; norm[1] = 0.; norm[2] = 0.;
let m = 0.0;
let fq = 0.0;
let nt = 0.0;
let dmin = 1000.0;
for (let j=0; j<this.constants.particleCount; j++) {
let off = j * 3;
let dx = positions[off], dy = positions[off+1.], dz = positions[off+2.];
dx -= rnow[0]; dy -= rnow[1]; dz -= rnow[2];
let r = Math.sqrt(dx*dx + dy*dy + dz*dz);
let x = r / this.constants.rfScale;
if (x < 1.0) {
let q = 1.0 - x*x*x*(x*(x*6.0-15.0)+10.0);
norm[0] += (dx/r) * q;
norm[1] += (dy/r) * q;
norm[2] += (dz/r) * q;
fq += q;
nt += q;
m += 1.0;
} else {
dmin = Math.min(dmin, r - this.constants.rfScale);
}
}
let dist = dmin + 0.1;
norm[0] /= nt; norm[1] /= nt; norm[2] /= nt;
if (m > 0.5) {
dist = (0.5333 * this.constants.rfScale) * (0.5 - fq);
}
ds += dist;
if (dist < 0.001) {
done = 1.0;
}
if (ds >= this.constants.rayDistRef) {
done = 1.0;
}
}
}
ds = Math.min(ds, this.constants.rayDistRef);
if (ds < (this.constants.rayDistRef - 0.001)) {
let ref = refractWrap(norm, nrayDir, 1./1.5);
let dot = ref[0] * nrayDir[0] + ref[1] * nrayDir[1] + ref[2] * nrayDir[2];
let int = 1. - Math.pow(Math.abs(ds) / this.constants.rayDistRef, 1.5);
let light = Math.min(1., Math.max(dot*dot, 0.)) * int;
outClr[2] += (int + light) * 0.1;
outClr[1] += (light) * 0.1;
outClr[0] += (light) * 0.1;
}
}
this.color(Math.min(outClr[0], 1.), Math.min(outClr[1], 1.), Math.min(outClr[2], 1.), 1.);
}, {
graphical: true,
constants: {
rayCount: this.rayCount,
rayDist: this.rayDist,
rayDistRef: Math.floor(this.rayDist / 4),
rfScale: this.rfScale,
bound: this.bound,
renderSize: this.renderSize,
particleCount: this.particleCount,
ICOUNT: 48,
ICOUNTR: 24
},
loopMaxIterations: 48 * this.particleCount,
output: [this.renderSize, this.renderSize],
tactic: 'speed',
})
];
/*
INIT KERNEL
*/
const seedPositions = gpu.createKernel(function(){
var t = random(Math.floor(this.thread.x / 3), this.constants.seed + 0.5);
var r = t * this.constants.maxr;
var a = 3.141592 * 2.0 * random(Math.floor(this.thread.x / 3), this.constants.seed + 1.5);
var comp = this.thread.x % 3;
if (comp < 0.01) {
return Math.cos(a) * r;
} else if (comp < 1.01) {
return Math.sin(a) * r;
} else {
return 0.0;
}
}, {
constants: {
maxr: 25,
seed: Math.random() * 1e6,
particleCount: this.particleCount
},
pipeline: true,
output: [this.particleCount * 3],
tactic: 'speed',
});
const seedVelocities = gpu.createKernel(function(){
var t = random(this.thread.x, this.constants.seed + 0.5);
return (t - 0.5) * this.constants.iv;
}, {
constants: {
iv: 1.5,
seed: Math.random() * 1e6,
particleCount: this.particleCount,
},
pipeline: true,
output: [this.particleCount * 3],
tactic: 'speed',
});
this.sDensity = [0.125, 0.25, 0.5, 1.0, 1.5, 2.0];
this.sDensityI = 5;
this.sMass = [0.05, 0.1, 0.2, 0.4, 0.8];
this.sMassI = 2;
this.sIncomp = [1.0, 0.8, 0.6, 0.4, 0.2];
this.sIncompI = 3;
this.sVisc = [0.05, 0.1, 0.25, 0.35, 0.5, 0.75, 0.95];
this.sViscI = 0;
const seedAttrs = gpu.createKernel(function(){
var comp = this.thread.x % 6;
if (comp < 0.01) {
return 0.25; // radius
} else if (comp < 1.01) {
return 0.5; // density
} else if (comp < 2.01) {
return 0.2; // mass
} else if (comp < 3.01) {
return 0.8; // incompress
} else if (comp < 4.01) {
return 0.35; // visc
} else {
// type
if (Math.floor(this.thread.x / 6) > (this.constants.particleCount-33)) {
return 1.0;
} else {
return 0.0;
}
}
}, {
constants: {
iv: 50,
seed: Math.random() * 1e6,
particleCount: this.particleCount
},
pipeline: true,
output: [this.particleCount * 6],
tactic: 'speed',
});
/*
* Seed Particles
*/
this.reset = () => {
this.cam = {
p: [0, 0, 17.5],
dir: [0, 0, -1],
up: [0, 1, 0],
nearW: 0.0001,
farW: 1000,
farDist: 1000
};
this.move = {
toLR: 0,
toUD: 0,
toFR: 0,
tLR: 0,
tUD: 0,
tFR: 0,
toPull: 0,
tPull: 0,
toPullR: 0,
tPullR: 0
};
this.data = {
pos: seedPositions(),
vel: seedVelocities(),
attr: seedAttrs()
};
};
this.reset();
this.hash = [];
for (let i=0; i<this.hashLength; i++) {
for (let j=0; j<this.hashBinLen; j++) {
this.hash.push(0); this.hash.push(0); this.hash.push(0); this.hash.push(-1);
}
}
this.normv = gpu.createKernel(function(a) {
const length = Math.sqrt(Math.abs(a[0]*a[0]) + Math.abs(a[1]*a[1]) + Math.abs(a[2]*a[2]));
return a[this.thread.x] / length;
}, {
output: [3],
pipeline: true,
tactic: 'speed',
immutable: true,
});
this.crossv = gpu.createKernel(function(a, b) {
const a1 = a[0], a2 = a[1], a3 = a[2];
const b1 = b[0], b2 = b[1], b3 = b[2];
if (this.thread.x === 0) {
return a2 * b3 - a3 * b2;
} else if (this.thread.x === 1) {
return a3 * b1 - a1 * b3;
} else {
return a1 * b2 - a2 * b1;
}
}, {
output: [3],
pipeline: true,
tactic: 'speed',
immutable: true,
});
}
XSSPS.prototype.hashFn = function(x, y, z) {
const max2 = Math.floor(this.bound * 2);
const ix = (Math.floor(x/this.hashWSize)) + max2,
iy = (Math.floor(y/this.hashWSize)) + max2,
iz = (Math.floor(z/this.hashWSize)) + max2;
return ((ix * 137) + (iy * 197) + (iz * 167)) % this.hashLength;
};
XSSPS.prototype.updateRender = function(keys, dt) {
this.dt = dt;
this.handleInput(keys);
this.sDensityI = this.sDensityI % this.sDensity.length;
this.sViscI = this.sViscI % this.sVisc.length;
this.sMassI = this.sMassI % this.sMass.length;
this.sIncompI = this.sIncompI % this.sIncomp.length;
const SUBSTEPS = 2;
for (let i=0; i<SUBSTEPS; i++) {
// Update velocities via gravity & pressure
const prevVel = this.data.vel;
this.data.vel = this.updateVelocity(
this.data.pos,
this.data.vel,
this.data.attr,
this.move.tPull,
this.cam.p,
this.cam.dir,
this.move.tPullR,
this.sDensity[this.sDensityI],
this.sVisc[this.sViscI],
this.sMass[this.sMassI],
this.sIncomp[this.sIncompI],
this.dt / SUBSTEPS
);
deleteTexture(prevVel);
// Update positions
const prevPos = this.data.pos;
this.data.pos = this.updatePosition(
this.data.pos,
this.data.vel,
this.dt / SUBSTEPS
);
deleteTexture(prevPos);
}
// Render
this.renderMode = this.renderMode % this.renderKernel.length;
this.renderKernel[this.renderMode](
this.data.pos,
this.cam.p,
this.cam.dir,
this.cam.up,
this.cam.nearW, this.cam.farW, this.cam.farDist
);
};
XSSPS.prototype.handleInput = function(keys) {
this.move.toLR = this.move.toFR = this.move.toUD = 0;
if (keys[37]) {
this.move.toLR -= 1;
}
if (keys[39]) {
this.move.toLR += 1;
}
if (keys[38]) {
this.move.toUD -= 1;
}
if (keys[40]) {
this.move.toUD += 1;
}
if (keys[83]) {
this.move.toFR -= 1;
}
if (keys[87]) {
this.move.toFR += 1;
}
if (this.keys[82] && !keys[82]) {
this.renderMode += 1;
}
if (this.keys[27] && !keys[27]) {
this.reset();
}
if (this.keys[49] && !keys[49]) {
this.sDensityI += 1;
}
if (this.keys[50] && !keys[50]) {
this.sViscI += 1;
}
if (this.keys[51] && !keys[51]) {
this.sMassI += 1;
}
if (this.keys[52] && !keys[52]) {
this.sIncompI += 1;
}
if (this.keys[32] && !keys[32]) {
const prevPos = this.data.pos;
this.data.pos = this.setPosition(
this.data.pos,
(this.particleCount-1) - this.shootIndex,
this.cam.p,
this.cam.dir,
0.5
);
deleteTexture(prevPos);
const prevVel = this.data.vel;
this.data.vel = this.setVelocity(
this.data.vel,
(this.particleCount-1) - this.shootIndex,
this.cam.dir,
15
);
deleteTexture(prevVel);
this.shootIndex = (this.shootIndex + 1) % 32;
}
if (keys[69]) {
this.move.toPull = 10;
this.move.toPullR = 5;
}
else {
this.move.toPull = -1;
this.move.toPullR = 0.25;
}
this.keys = {...keys};
this.move.tLR += (this.move.toLR - this.move.tLR) * this.dt * 1.5;
this.move.tUD += (this.move.toUD - this.move.tUD) * this.dt * 1.5;
this.move.tFR += (this.move.toFR - this.move.tFR) * this.dt * 1.5;
this.move.tPull += (this.move.toPull - this.move.tPull) * this.dt * 1.5;
this.move.tPullR += (this.move.toPullR - this.move.tPullR) * this.dt * 1.5;
this.updateCamera();
if (this.onAfterInput) this.onAfterInput();
};
XSSPS.prototype.updateCamera = function() {
const prevUp = this.cam.up;
const crossv1 = this.crossv(
this.cam.dir,
this.cam.up
);
const crossv2 = this.crossv(
crossv1,
this.cam.dir
);
deleteTexture(this.cam.up);
this.cam.up = this.normv(crossv2);
deleteTexture(crossv1);
deleteTexture(crossv2);
deleteTexture(prevUp);
const prevDir = this.cam.dir;
this.cam.dir = this.rotateCamera(
this.move.tLR / 35,
-this.move.tUD / 35,
this.cam.p,
this.cam.dir,
this.cam.up,
1, 3.5, 2
);
deleteTexture(prevDir);
const prevP = this.cam.p;
this.cam.p = this.updateCameraPosition(
this.cam.dir,
this.cam.p,
this.move.tFR,
this.dt
);
deleteTexture(prevP);
};
</script>
<script>
function SSPS(psim) {
this.dt = 0;
this.psim = psim;
this.keys = {};
this.controls = {};
this.buildControls();
}
SSPS.prototype.buildControls = function() {
let html = `<div>
SSPS by Chadams - <span id="fps"></span> fps - <span id="particle-count"></span> particles @ <span id="size"></span><br />
[W] - Forward, [S] - Reverse, [ARROWS] - Look<br />
[E] - Grab, [SPACE] - Shoot Particle<br />
[R] - Cycle Render Mode: <span id="render-mode"></span><br />
[1] - Cycle Particle Density: <span id="particle-density"></span><br />
[2] - Cycle Particle Viscosity: <span id="particle-viscosity"></span><br />
[3] - Cycle Particle Mass: <span id="particle-mass"></span><br />
[4] - Cycle Particle Incompressiveness: <span id="particle-incompressiveness"></span><br />
[ESC] - Reset Particles & Camera<br />
[P] - Pause<br />
</div>`;
const renderer = document.createElement('div');
renderer.innerHTML = html;
html = this.controls.html = renderer.children[0];
this.controls.fps = html.querySelector('#fps');
this.controls.particleCount = html.querySelector('#particle-count');
this.controls.particleCount.innerHTML = this.psim.particleCount;
this.controls.size = html.querySelector('#size');
this.controls.size.innerHTML = this.psim.renderSize + 'x' + this.psim.renderSize;
this.controls.renderMode = html.querySelector('#render-mode');
this.controls.particleDensity = html.querySelector('#particle-density');
this.controls.particleViscosity = html.querySelector('#particle-viscosity');
this.controls.particleMass = html.querySelector('#particle-mass');
this.controls.particleIncompressiveness = html.querySelector('#particle-incompressiveness');
this.updateControls();
};
SSPS.prototype.mkOpt = function(lst, i) {
i = i % lst.length;
let ret = '';
for (let j=0; j<lst.length; j++) {
if (j === i) {
ret += '>>' + lst[j] + '<< ';
}
else {
ret += ' ' + lst[j] + ' ';
}
}
return ret;
};
SSPS.prototype.updateFPS = function() {
this.controls.fps.innerHTML = Math.round(1 / this.dt);
};
SSPS.prototype.updateControls = function() {
this.controls.renderMode.innerHTML = this.mkOpt([1, 2], this.psim.renderMode);
this.controls.particleDensity.innerHTML = this.mkOpt(this.psim.sDensity, this.psim.sDensityI);
this.controls.particleViscosity.innerHTML = this.mkOpt(this.psim.sVisc, this.psim.sViscI);
this.controls.particleMass.innerHTML = this.mkOpt(this.psim.sMass, this.psim.sMassI);
this.controls.particleIncompressiveness.innerHTML = this.mkOpt(this.psim.sIncomp, this.psim.sIncompI);
};
SSPS.prototype.updateRender = function() {
this.psim.updateRender(this.keys, this.dt);
this.updateFPS();
};
SSPS.prototype.start = function(tick) {
if (this.running) return;
document.title = 'SSPS - Chadams Studios (slightly upgraded)';
this.running = true;
requestAnimationFrame(tick);
};
SSPS.prototype.init = function () {
let lTime = Date.timeStamp();
let lDt = 1/60;
this.time = 0;
document.body.addEventListener('keydown', (e) => {
e = e || window.event;
this.keys[e.keyCode] = true;
});
document.body.addEventListener('keyup', (e) => {
e = e || window.event;
if (e.keyCode === 80) {
if (this.running) {
this.stop();
} else {
this.start(tick);
}
return;
}
this.keys[e.keyCode] = false;
});
const tick = () => {
if (!this.running) {
return;
}
const cTime = Date.timeStamp();
const dt = (Math.max(Math.min(cTime - lTime, 1/10), 1/240) || (1/60)) * 0.1 + lDt * 0.9;
this.dt = dt;
lDt = dt;
lTime = cTime;
this.time += dt;
this.updateRender();
requestAnimationFrame(tick);
};
this.start(tick);
};
SSPS.prototype.stop = function () {
this.running = false;
document.title = 'SSPS Stopped - Chadams Studios (slightly upgraded)';
};
Date.timeStamp = function() {
return new Date().getTime() / 1000.0;
};
function deleteTexture(texture) {
if (texture && texture.delete) {
texture.delete();
}
}
</script>
</head>
<body style="background-color: black; padding: 0; margin: 0;"></body>
<script>
const psim = new XSSPS(64, 256, () => {
inst.updateControls();
});
const inst = new SSPS(psim);
const { html } = inst.controls;
html.style.color = '#78788b';
html.style.position = 'absolute';
document.body.appendChild(html);
psim.canvas.style.margin = '0 auto';
psim.canvas.style.display = 'block';
psim.canvas.style.width = '100vh';
psim.canvas.style.padding = '0';
document.body.appendChild(psim.canvas);
inst.init();
</script>
</html>