3d-spinner 0.9.4 → 0.9.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/animations/charged-orb.js +4 -2
- package/dist/animations/ghost-train.js +3 -1
- package/dist/animations/spin.d.ts +3 -1
- package/dist/animations/spin.js +6 -1
- package/dist/cjs/animations/charged-orb.cjs +170 -28
- package/dist/cjs/animations/grid-assembly.cjs +164 -26
- package/dist/cjs/animations/object-motion.cjs +156 -24
- package/dist/cjs/animations/particles.cjs +167 -26
- package/dist/cjs/animations/spin.cjs +169 -27
- package/dist/cjs/engines/little-3d-engine/little-3d-engine.cjs +193 -45
- package/dist/cjs/engines/little-3d-engine/loaders/obj.cjs +51 -13
- package/dist/cjs/engines/little-3d-engine/renderers/canvas2d-textured.cjs +56 -6
- package/dist/cjs/engines/little-3d-engine/renderers/webgl-textured.cjs +55 -6
- package/dist/cjs/engines/little-3d-engine/renderers/webgpu-textured.cjs +64 -11
- package/dist/cjs/prefabs/prefabs.cjs +186 -40
- package/dist/engines/little-3d-engine/core/geometry.d.ts +15 -2
- package/dist/engines/little-3d-engine/core/geometry.js +25 -3
- package/dist/engines/little-3d-engine/core/light.d.ts +28 -4
- package/dist/engines/little-3d-engine/core/light.js +48 -7
- package/dist/engines/little-3d-engine/core/mesh.d.ts +33 -0
- package/dist/engines/little-3d-engine/core/mesh.js +12 -0
- package/dist/engines/little-3d-engine/little-3d-engine.d.ts +2 -2
- package/dist/engines/little-3d-engine/little-3d-engine.js +1 -1
- package/dist/engines/little-3d-engine/loaders/obj.d.ts +8 -7
- package/dist/engines/little-3d-engine/loaders/obj.js +74 -20
- package/dist/engines/little-3d-engine/renderers/canvas2d.js +23 -1
- package/dist/engines/little-3d-engine/renderers/webgl.js +34 -5
- package/dist/engines/little-3d-engine/renderers/webgpu.js +43 -10
- package/dist/engines/little-3d-engine/shapes/complex/plane.d.ts +8 -3
- package/dist/engines/little-3d-engine/shapes/complex/plane.js +10 -4
- package/dist/engines/little-3d-engine/shapes/primitives/cube.d.ts +3 -2
- package/dist/engines/little-3d-engine/shapes/primitives/cube.js +4 -2
- package/dist/engines/little-3d-engine/shapes/primitives/octahedron.d.ts +3 -2
- package/dist/engines/little-3d-engine/shapes/primitives/octahedron.js +4 -2
- package/dist/engines/little-3d-engine/shapes/primitives/pyramid.d.ts +3 -2
- package/dist/engines/little-3d-engine/shapes/primitives/pyramid.js +4 -2
- package/dist/engines/little-3d-engine/shapes/primitives/quad.d.ts +3 -2
- package/dist/engines/little-3d-engine/shapes/primitives/quad.js +4 -2
- package/dist/engines/little-3d-engine/shapes/primitives/spheres/cube-sphere.d.ts +3 -2
- package/dist/engines/little-3d-engine/shapes/primitives/spheres/cube-sphere.js +4 -2
- package/dist/engines/little-3d-engine/shapes/primitives/spheres/icosphere.d.ts +3 -2
- package/dist/engines/little-3d-engine/shapes/primitives/spheres/icosphere.js +4 -2
- package/dist/engines/little-3d-engine/shapes/primitives/spheres/octa-sphere.d.ts +3 -2
- package/dist/engines/little-3d-engine/shapes/primitives/spheres/octa-sphere.js +4 -2
- package/dist/engines/little-3d-engine/shapes/primitives/spheres/uv-sphere.d.ts +3 -2
- package/dist/engines/little-3d-engine/shapes/primitives/spheres/uv-sphere.js +4 -2
- package/dist/engines/little-3d-engine/shapes/primitives/tetrahedron.d.ts +3 -2
- package/dist/engines/little-3d-engine/shapes/primitives/tetrahedron.js +4 -2
- package/dist/umd/spinner.global.js +255 -63
- package/dist/umd/spinner.global.min.js +58 -17
- package/package.json +1 -1
|
@@ -19,6 +19,8 @@ const REENTER_STAGGER_MS = 45;
|
|
|
19
19
|
const CENTER_POP_OUT_AT = REENTER_MS + (MINIS - 1) * REENTER_STAGGER_MS + 150;
|
|
20
20
|
const CENTER_POP_OUT_MS = 420;
|
|
21
21
|
const PARKED = { x: 0, y: 0, z: 50 };
|
|
22
|
+
// A glossy white highlight over the cyan orbs, for a charged, energetic sheen.
|
|
23
|
+
const ORB_MATERIAL = { specular: [1, 1, 1], shininess: 28 };
|
|
22
24
|
const CENTER_COLORS = ["#67e8f9", "#22d3ee", "#0ea5e9", "#38bdf8", "#7dd3fc"];
|
|
23
25
|
const MINI_COLORS = [
|
|
24
26
|
["#e0f2fe", "#bae6fd", "#7dd3fc"],
|
|
@@ -58,9 +60,9 @@ export class ChargedOrbAnimation {
|
|
|
58
60
|
backend: this.backend,
|
|
59
61
|
camera: { position: { x: 0, y: 0, z: CAMERA_Z } },
|
|
60
62
|
});
|
|
61
|
-
this.center = engine.add(icosphere(1, 2, CENTER_COLORS), { scale: 0 });
|
|
63
|
+
this.center = engine.add(icosphere(1, 2, CENTER_COLORS, ORB_MATERIAL), { scale: 0 });
|
|
62
64
|
for (let i = 0; i < MINIS; i++) {
|
|
63
|
-
const mesh = icosphere(1, 1, MINI_COLORS[i % MINI_COLORS.length]);
|
|
65
|
+
const mesh = icosphere(1, 1, MINI_COLORS[i % MINI_COLORS.length], ORB_MATERIAL);
|
|
64
66
|
this.minis.push(engine.add(mesh, { scale: 0, transparency: { ...MINI_TRANSPARENCY } }));
|
|
65
67
|
}
|
|
66
68
|
this.engine = engine;
|
|
@@ -18,6 +18,8 @@ const WARP_ACCEL = 1000; // extra path-milliseconds accrued, = 0.5 * WARP_ACCEL
|
|
|
18
18
|
const TRAIL_OUTRO_MS = 1200; // how long the lead keeps shedding stars into the outro
|
|
19
19
|
const TRANSPARENCY = { mode: "two-sided", opacity: 0.275 };
|
|
20
20
|
const CAR_COLORS = ["#bae6fd", "#7dd3fc", "#38bdf8", "#0ea5e9", "#a5f3fc", "#e0f2fe"];
|
|
21
|
+
// A faint icy self-glow so the translucent cars read as ghostly rather than flat.
|
|
22
|
+
const CAR_MATERIAL = { emissive: [0.1, 0.2, 0.3] };
|
|
21
23
|
const WORLD_UP = { x: 0, y: 1, z: 0 };
|
|
22
24
|
function clamp01(value) {
|
|
23
25
|
return Math.max(0, Math.min(1, value));
|
|
@@ -92,7 +94,7 @@ export class GhostTrainAnimation {
|
|
|
92
94
|
backend: this.backend,
|
|
93
95
|
camera: { position: { x: 0, y: 0, z: CAMERA_Z }, fov: FOV },
|
|
94
96
|
});
|
|
95
|
-
const mesh = cube(1, CAR_COLORS);
|
|
97
|
+
const mesh = cube(1, CAR_COLORS, CAR_MATERIAL);
|
|
96
98
|
for (let i = 0; i < MAX_CARS; i++) {
|
|
97
99
|
this.cars.push(engine.add(mesh, { scale: 0, transparency: { ...TRANSPARENCY } }));
|
|
98
100
|
}
|
|
@@ -1,11 +1,13 @@
|
|
|
1
1
|
import type { AnimationFrame, SpinnerAnimation } from "../animation.js";
|
|
2
|
-
import { type Backend, type Mesh, type Transparency } from "../engines/little-3d-engine/little-3d-engine.js";
|
|
2
|
+
import { type Backend, type Material, type Mesh, type Transparency } from "../engines/little-3d-engine/little-3d-engine.js";
|
|
3
3
|
import { type ProgressAnimationOptions } from "../progress-animation.js";
|
|
4
4
|
export interface SpinAnimationOptions {
|
|
5
5
|
/** Shape to spin: a mesh, or a factory that returns one. Default: a cube. */
|
|
6
6
|
shape?: Mesh | (() => Mesh);
|
|
7
7
|
/** Face color(s): one color for every face, or an array applied per face. */
|
|
8
8
|
color?: string | string[];
|
|
9
|
+
/** Surface material (specular, shininess, emissive) applied to every face. */
|
|
10
|
+
material?: Material;
|
|
9
11
|
/** Rotation speed around the X axis, in radians per millisecond. Default `0.0007`. */
|
|
10
12
|
spinX?: number;
|
|
11
13
|
/** Rotation speed around the Y axis, in radians per millisecond. Default `0.0011`. */
|
package/dist/animations/spin.js
CHANGED
|
@@ -24,6 +24,11 @@ function applyColor(mesh, color) {
|
|
|
24
24
|
const pick = Array.isArray(color) ? (i) => color[i % color.length] : () => color;
|
|
25
25
|
return { vertices: mesh.vertices, faces: mesh.faces.map((f, i) => ({ ...f, color: pick(i) })) };
|
|
26
26
|
}
|
|
27
|
+
function applyMaterial(mesh, material) {
|
|
28
|
+
if (!material)
|
|
29
|
+
return mesh;
|
|
30
|
+
return { vertices: mesh.vertices, faces: mesh.faces.map((f) => ({ ...f, material })) };
|
|
31
|
+
}
|
|
27
32
|
/**
|
|
28
33
|
* A spinning, flat-lit 3D shape (a cube by default). With `progressAnimation`
|
|
29
34
|
* set it pops in/out and its scale tracks progress, with an optional label;
|
|
@@ -32,7 +37,7 @@ function applyColor(mesh, color) {
|
|
|
32
37
|
export class SpinAnimation {
|
|
33
38
|
constructor(options = {}) {
|
|
34
39
|
this.exited = false;
|
|
35
|
-
this.mesh = applyColor(resolveMesh(options.shape), options.color);
|
|
40
|
+
this.mesh = applyMaterial(applyColor(resolveMesh(options.shape), options.color), options.material);
|
|
36
41
|
this.spinX = options.spinX ?? 0.0007;
|
|
37
42
|
this.spinY = options.spinY ?? 0.0011;
|
|
38
43
|
this.backend = options.backend;
|
|
@@ -136,7 +136,10 @@ function expandToTriangles(mesh) {
|
|
|
136
136
|
const positions = new Float32Array(triangles * 9);
|
|
137
137
|
const normals = new Float32Array(triangles * 9);
|
|
138
138
|
const colors = new Float32Array(triangles * 9);
|
|
139
|
+
const emissives = new Float32Array(triangles * 9);
|
|
140
|
+
const speculars = new Float32Array(triangles * 12);
|
|
139
141
|
let o = 0;
|
|
142
|
+
let so = 0;
|
|
140
143
|
for (const face of mesh.faces) {
|
|
141
144
|
const v0 = mesh.vertices[face.indices[0]];
|
|
142
145
|
const v1 = mesh.vertices[face.indices[1]];
|
|
@@ -146,6 +149,15 @@ function expandToTriangles(mesh) {
|
|
|
146
149
|
const cr = r / 255;
|
|
147
150
|
const cg = g / 255;
|
|
148
151
|
const cb = b / 255;
|
|
152
|
+
const emissive = face.material?.emissive;
|
|
153
|
+
const er = emissive ? emissive[0] : 0;
|
|
154
|
+
const eg = emissive ? emissive[1] : 0;
|
|
155
|
+
const eb = emissive ? emissive[2] : 0;
|
|
156
|
+
const specular = face.material?.specular;
|
|
157
|
+
const sr = specular ? specular[0] : 0;
|
|
158
|
+
const sg = specular ? specular[1] : 0;
|
|
159
|
+
const sb = specular ? specular[2] : 0;
|
|
160
|
+
const sn = specular ? face.material?.shininess ?? 32 : 1;
|
|
149
161
|
for (let k = 1; k < face.indices.length - 1; k++) {
|
|
150
162
|
const tri = [face.indices[0], face.indices[k], face.indices[k + 1]];
|
|
151
163
|
for (const index of tri) {
|
|
@@ -159,11 +171,19 @@ function expandToTriangles(mesh) {
|
|
|
159
171
|
colors[o] = cr;
|
|
160
172
|
colors[o + 1] = cg;
|
|
161
173
|
colors[o + 2] = cb;
|
|
174
|
+
emissives[o] = er;
|
|
175
|
+
emissives[o + 1] = eg;
|
|
176
|
+
emissives[o + 2] = eb;
|
|
177
|
+
speculars[so] = sr;
|
|
178
|
+
speculars[so + 1] = sg;
|
|
179
|
+
speculars[so + 2] = sb;
|
|
180
|
+
speculars[so + 3] = sn;
|
|
162
181
|
o += 3;
|
|
182
|
+
so += 4;
|
|
163
183
|
}
|
|
164
184
|
}
|
|
165
185
|
}
|
|
166
|
-
return { positions, normals, colors, count: positions.length / 3 };
|
|
186
|
+
return { positions, normals, colors, emissives, speculars, count: positions.length / 3 };
|
|
167
187
|
}
|
|
168
188
|
function midpoint(a, b) {
|
|
169
189
|
return { x: (a.x + b.x) / 2, y: (a.y + b.y) / 2, z: (a.z + b.z) / 2 };
|
|
@@ -201,13 +221,42 @@ var init_geometry = __esm({
|
|
|
201
221
|
function clamp01(value) {
|
|
202
222
|
return Math.min(1, Math.max(0, value));
|
|
203
223
|
}
|
|
204
|
-
function
|
|
224
|
+
function clamp255(value) {
|
|
225
|
+
return Math.round(Math.min(255, Math.max(0, value)));
|
|
226
|
+
}
|
|
227
|
+
function shade(normal, color, light, surface) {
|
|
205
228
|
const lambert = Math.max(0, dot(normal, light.toLight));
|
|
206
229
|
const brightness = clamp01(light.ambient + light.intensity * lambert);
|
|
207
|
-
const [
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
230
|
+
const [baseR, baseG, baseB] = parseColor(color);
|
|
231
|
+
let r = baseR * brightness;
|
|
232
|
+
let g = baseG * brightness;
|
|
233
|
+
let b = baseB * brightness;
|
|
234
|
+
const material = surface?.material;
|
|
235
|
+
const specular = material?.specular;
|
|
236
|
+
const viewDir = surface?.viewDir;
|
|
237
|
+
if (specular && viewDir && lambert > 0) {
|
|
238
|
+
const half = normalize({
|
|
239
|
+
x: light.toLight.x + viewDir.x,
|
|
240
|
+
y: light.toLight.y + viewDir.y,
|
|
241
|
+
z: light.toLight.z + viewDir.z
|
|
242
|
+
});
|
|
243
|
+
const shininess = material?.shininess ?? 32;
|
|
244
|
+
const highlight = Math.pow(Math.max(0, dot(normal, half)), shininess) * light.intensity * 255;
|
|
245
|
+
r += highlight * specular[0];
|
|
246
|
+
g += highlight * specular[1];
|
|
247
|
+
b += highlight * specular[2];
|
|
248
|
+
}
|
|
249
|
+
const emissive = material?.emissive;
|
|
250
|
+
if (emissive) {
|
|
251
|
+
r += emissive[0] * 255;
|
|
252
|
+
g += emissive[1] * 255;
|
|
253
|
+
b += emissive[2] * 255;
|
|
254
|
+
}
|
|
255
|
+
return [clamp255(r), clamp255(g), clamp255(b)];
|
|
256
|
+
}
|
|
257
|
+
function shadeColor(normal, color, light, surface) {
|
|
258
|
+
const [r, g, b] = shade(normal, color, light, surface);
|
|
259
|
+
return `rgb(${r}, ${g}, ${b})`;
|
|
211
260
|
}
|
|
212
261
|
var DEFAULTS2, Light;
|
|
213
262
|
var init_light = __esm({
|
|
@@ -230,8 +279,8 @@ var init_light = __esm({
|
|
|
230
279
|
};
|
|
231
280
|
}
|
|
232
281
|
/** Convenience wrapper around {@link shadeColor} using this light. */
|
|
233
|
-
shade(normal, color) {
|
|
234
|
-
return shadeColor(normal, color, this.params);
|
|
282
|
+
shade(normal, color, surface) {
|
|
283
|
+
return shadeColor(normal, color, this.params, surface);
|
|
235
284
|
}
|
|
236
285
|
};
|
|
237
286
|
}
|
|
@@ -271,28 +320,51 @@ var init_webgl = __esm({
|
|
|
271
320
|
in vec3 aPos;
|
|
272
321
|
in vec3 aNormal;
|
|
273
322
|
in vec3 aColor;
|
|
323
|
+
in vec3 aEmissive;
|
|
324
|
+
in vec4 aSpecular;
|
|
274
325
|
uniform mat4 uViewProj;
|
|
275
326
|
uniform mat4 uModel;
|
|
276
327
|
out vec3 vNormal;
|
|
277
328
|
out vec3 vColor;
|
|
329
|
+
out vec3 vEmissive;
|
|
330
|
+
out vec4 vSpecular;
|
|
331
|
+
out vec3 vWorldPos;
|
|
278
332
|
void main() {
|
|
279
333
|
vNormal = mat3(uModel) * aNormal;
|
|
280
334
|
vColor = aColor;
|
|
281
|
-
|
|
335
|
+
vEmissive = aEmissive;
|
|
336
|
+
vSpecular = aSpecular;
|
|
337
|
+
vec4 world = uModel * vec4(aPos, 1.0);
|
|
338
|
+
vWorldPos = world.xyz;
|
|
339
|
+
gl_Position = uViewProj * world;
|
|
282
340
|
}`;
|
|
283
341
|
FRAGMENT_SHADER = `#version 300 es
|
|
284
342
|
precision mediump float;
|
|
285
343
|
in vec3 vNormal;
|
|
286
344
|
in vec3 vColor;
|
|
345
|
+
in vec3 vEmissive;
|
|
346
|
+
in vec4 vSpecular;
|
|
347
|
+
in vec3 vWorldPos;
|
|
287
348
|
uniform vec3 uToLight;
|
|
349
|
+
uniform vec3 uEye;
|
|
288
350
|
uniform float uIntensity;
|
|
289
351
|
uniform float uAmbient;
|
|
290
352
|
uniform float uOpacity;
|
|
291
353
|
out vec4 fragColor;
|
|
292
354
|
void main() {
|
|
293
|
-
|
|
355
|
+
vec3 normal = normalize(vNormal);
|
|
356
|
+
vec3 toLight = normalize(uToLight);
|
|
357
|
+
float lambert = max(dot(normal, toLight), 0.0);
|
|
294
358
|
float brightness = clamp(uAmbient + uIntensity * lambert, 0.0, 1.0);
|
|
295
|
-
|
|
359
|
+
vec3 lit = vColor * brightness;
|
|
360
|
+
if (lambert > 0.0) {
|
|
361
|
+
vec3 viewDir = normalize(uEye - vWorldPos);
|
|
362
|
+
vec3 halfVec = normalize(toLight + viewDir);
|
|
363
|
+
float highlight = pow(max(dot(normal, halfVec), 0.0), vSpecular.w) * uIntensity;
|
|
364
|
+
lit += highlight * vSpecular.xyz;
|
|
365
|
+
}
|
|
366
|
+
lit += vEmissive;
|
|
367
|
+
fragColor = vec4(lit, uOpacity);
|
|
296
368
|
}`;
|
|
297
369
|
WebGLRenderer = class {
|
|
298
370
|
constructor(options = {}) {
|
|
@@ -313,9 +385,12 @@ void main() {
|
|
|
313
385
|
aPos: gl.getAttribLocation(this.program, "aPos"),
|
|
314
386
|
aNormal: gl.getAttribLocation(this.program, "aNormal"),
|
|
315
387
|
aColor: gl.getAttribLocation(this.program, "aColor"),
|
|
388
|
+
aEmissive: gl.getAttribLocation(this.program, "aEmissive"),
|
|
389
|
+
aSpecular: gl.getAttribLocation(this.program, "aSpecular"),
|
|
316
390
|
uViewProj: gl.getUniformLocation(this.program, "uViewProj"),
|
|
317
391
|
uModel: gl.getUniformLocation(this.program, "uModel"),
|
|
318
392
|
uToLight: gl.getUniformLocation(this.program, "uToLight"),
|
|
393
|
+
uEye: gl.getUniformLocation(this.program, "uEye"),
|
|
319
394
|
uIntensity: gl.getUniformLocation(this.program, "uIntensity"),
|
|
320
395
|
uAmbient: gl.getUniformLocation(this.program, "uAmbient"),
|
|
321
396
|
uOpacity: gl.getUniformLocation(this.program, "uOpacity")
|
|
@@ -339,17 +414,19 @@ void main() {
|
|
|
339
414
|
const data = expandToTriangles(mesh);
|
|
340
415
|
const vao = gl.createVertexArray();
|
|
341
416
|
gl.bindVertexArray(vao);
|
|
342
|
-
const attribute = (location, array) => {
|
|
417
|
+
const attribute = (location, array, size = 3) => {
|
|
343
418
|
if (location < 0) return;
|
|
344
419
|
const buffer = gl.createBuffer();
|
|
345
420
|
gl.bindBuffer(gl.ARRAY_BUFFER, buffer);
|
|
346
421
|
gl.bufferData(gl.ARRAY_BUFFER, array, gl.STATIC_DRAW);
|
|
347
422
|
gl.enableVertexAttribArray(location);
|
|
348
|
-
gl.vertexAttribPointer(location,
|
|
423
|
+
gl.vertexAttribPointer(location, size, gl.FLOAT, false, 0, 0);
|
|
349
424
|
};
|
|
350
425
|
attribute(loc.aPos, data.positions);
|
|
351
426
|
attribute(loc.aNormal, data.normals);
|
|
352
427
|
attribute(loc.aColor, data.colors);
|
|
428
|
+
attribute(loc.aEmissive, data.emissives);
|
|
429
|
+
attribute(loc.aSpecular, data.speculars, 4);
|
|
353
430
|
gl.bindVertexArray(null);
|
|
354
431
|
const result = { vao, count: data.count };
|
|
355
432
|
this.cache.set(mesh, result);
|
|
@@ -364,6 +441,7 @@ void main() {
|
|
|
364
441
|
gl.useProgram(this.program);
|
|
365
442
|
gl.uniformMatrix4fv(loc.uViewProj, false, new Float32Array(frame.viewProjection));
|
|
366
443
|
gl.uniform3f(loc.uToLight, frame.light.toLight.x, frame.light.toLight.y, frame.light.toLight.z);
|
|
444
|
+
gl.uniform3f(loc.uEye, frame.eye.x, frame.eye.y, frame.eye.z);
|
|
367
445
|
gl.uniform1f(loc.uIntensity, frame.light.intensity);
|
|
368
446
|
gl.uniform1f(loc.uAmbient, frame.light.ambient);
|
|
369
447
|
gl.disable(gl.BLEND);
|
|
@@ -438,6 +516,7 @@ struct Uniforms {
|
|
|
438
516
|
model: mat4x4<f32>,
|
|
439
517
|
toLight: vec4<f32>,
|
|
440
518
|
params: vec4<f32>,
|
|
519
|
+
eye: vec4<f32>,
|
|
441
520
|
};
|
|
442
521
|
@group(0) @binding(0) var<uniform> u: Uniforms;
|
|
443
522
|
|
|
@@ -445,23 +524,40 @@ struct VSOut {
|
|
|
445
524
|
@builtin(position) position: vec4<f32>,
|
|
446
525
|
@location(0) normal: vec3<f32>,
|
|
447
526
|
@location(1) color: vec3<f32>,
|
|
527
|
+
@location(2) emissive: vec3<f32>,
|
|
528
|
+
@location(3) specular: vec4<f32>,
|
|
529
|
+
@location(4) worldPos: vec3<f32>,
|
|
448
530
|
};
|
|
449
531
|
|
|
450
532
|
@vertex
|
|
451
|
-
fn vs(@location(0) pos: vec3<f32>, @location(1) normal: vec3<f32>, @location(2) color: vec3<f32>) -> VSOut {
|
|
533
|
+
fn vs(@location(0) pos: vec3<f32>, @location(1) normal: vec3<f32>, @location(2) color: vec3<f32>, @location(3) emissive: vec3<f32>, @location(4) specular: vec4<f32>) -> VSOut {
|
|
452
534
|
var out: VSOut;
|
|
453
535
|
let m = mat3x3<f32>(u.model[0].xyz, u.model[1].xyz, u.model[2].xyz);
|
|
454
536
|
out.normal = m * normal;
|
|
455
537
|
out.color = color;
|
|
456
|
-
out.
|
|
538
|
+
out.emissive = emissive;
|
|
539
|
+
out.specular = specular;
|
|
540
|
+
let world = u.model * vec4<f32>(pos, 1.0);
|
|
541
|
+
out.worldPos = world.xyz;
|
|
542
|
+
out.position = u.viewProj * world;
|
|
457
543
|
return out;
|
|
458
544
|
}
|
|
459
545
|
|
|
460
546
|
@fragment
|
|
461
547
|
fn fs(in: VSOut) -> @location(0) vec4<f32> {
|
|
462
|
-
let
|
|
548
|
+
let normal = normalize(in.normal);
|
|
549
|
+
let toLight = normalize(u.toLight.xyz);
|
|
550
|
+
let lambert = max(dot(normal, toLight), 0.0);
|
|
463
551
|
let brightness = clamp(u.params.y + u.params.x * lambert, 0.0, 1.0);
|
|
464
|
-
|
|
552
|
+
var lit = in.color * brightness;
|
|
553
|
+
if (lambert > 0.0) {
|
|
554
|
+
let viewDir = normalize(u.eye.xyz - in.worldPos);
|
|
555
|
+
let halfVec = normalize(toLight + viewDir);
|
|
556
|
+
let highlight = pow(max(dot(normal, halfVec), 0.0), in.specular.w) * u.params.x;
|
|
557
|
+
lit = lit + highlight * in.specular.xyz;
|
|
558
|
+
}
|
|
559
|
+
lit = lit + in.emissive;
|
|
560
|
+
return vec4<f32>(lit, u.params.z);
|
|
465
561
|
}
|
|
466
562
|
`;
|
|
467
563
|
CLIP_Z_FIX = [1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0.5, 0, 0, 0, 0.5, 1];
|
|
@@ -502,13 +598,15 @@ fn fs(in: VSOut) -> @location(0) vec4<f32> {
|
|
|
502
598
|
{
|
|
503
599
|
binding: 0,
|
|
504
600
|
visibility: stage.VERTEX | stage.FRAGMENT,
|
|
505
|
-
buffer: { type: "uniform", hasDynamicOffset: true, minBindingSize:
|
|
601
|
+
buffer: { type: "uniform", hasDynamicOffset: true, minBindingSize: 176 }
|
|
506
602
|
}
|
|
507
603
|
]
|
|
508
604
|
});
|
|
509
|
-
const vertexBuffer = (location) => ({
|
|
510
|
-
arrayStride:
|
|
511
|
-
attributes: [
|
|
605
|
+
const vertexBuffer = (location, components = 3) => ({
|
|
606
|
+
arrayStride: components * 4,
|
|
607
|
+
attributes: [
|
|
608
|
+
{ shaderLocation: location, offset: 0, format: `float32x${components}` }
|
|
609
|
+
]
|
|
512
610
|
});
|
|
513
611
|
const pipelineLayout = device.createPipelineLayout({ bindGroupLayouts: [layout] });
|
|
514
612
|
const blend = {
|
|
@@ -524,7 +622,13 @@ fn fs(in: VSOut) -> @location(0) vec4<f32> {
|
|
|
524
622
|
vertex: {
|
|
525
623
|
module: module2,
|
|
526
624
|
entryPoint: "vs",
|
|
527
|
-
buffers: [
|
|
625
|
+
buffers: [
|
|
626
|
+
vertexBuffer(0),
|
|
627
|
+
vertexBuffer(1),
|
|
628
|
+
vertexBuffer(2),
|
|
629
|
+
vertexBuffer(3),
|
|
630
|
+
vertexBuffer(4, 4)
|
|
631
|
+
]
|
|
528
632
|
},
|
|
529
633
|
fragment: {
|
|
530
634
|
module: module2,
|
|
@@ -577,6 +681,8 @@ fn fs(in: VSOut) -> @location(0) vec4<f32> {
|
|
|
577
681
|
position: upload(data.positions),
|
|
578
682
|
normal: upload(data.normals),
|
|
579
683
|
color: upload(data.colors),
|
|
684
|
+
emissive: upload(data.emissives),
|
|
685
|
+
specular: upload(data.speculars),
|
|
580
686
|
count: data.count
|
|
581
687
|
};
|
|
582
688
|
this.cache.set(mesh, result);
|
|
@@ -628,7 +734,7 @@ fn fs(in: VSOut) -> @location(0) vec4<f32> {
|
|
|
628
734
|
const bindGroup = this.device.createBindGroup({
|
|
629
735
|
layout,
|
|
630
736
|
entries: [
|
|
631
|
-
{ binding: 0, resource: { buffer: this.uniformBuffer, offset: 0, size:
|
|
737
|
+
{ binding: 0, resource: { buffer: this.uniformBuffer, offset: 0, size: 176 } }
|
|
632
738
|
]
|
|
633
739
|
});
|
|
634
740
|
draws.forEach((draw, i) => {
|
|
@@ -637,6 +743,7 @@ fn fs(in: VSOut) -> @location(0) vec4<f32> {
|
|
|
637
743
|
data.set(draw.item.model, 16);
|
|
638
744
|
data.set([frame.light.toLight.x, frame.light.toLight.y, frame.light.toLight.z, 0], 32);
|
|
639
745
|
data.set([frame.light.intensity, frame.light.ambient, draw.opacity, 0], 36);
|
|
746
|
+
data.set([frame.eye.x, frame.eye.y, frame.eye.z, 0], 40);
|
|
640
747
|
this.device.queue.writeBuffer(this.uniformBuffer, i * UNIFORM_STRIDE, data);
|
|
641
748
|
});
|
|
642
749
|
const encoder = this.device.createCommandEncoder();
|
|
@@ -663,6 +770,8 @@ fn fs(in: VSOut) -> @location(0) vec4<f32> {
|
|
|
663
770
|
pass.setVertexBuffer(0, mesh.position);
|
|
664
771
|
pass.setVertexBuffer(1, mesh.normal);
|
|
665
772
|
pass.setVertexBuffer(2, mesh.color);
|
|
773
|
+
pass.setVertexBuffer(3, mesh.emissive);
|
|
774
|
+
pass.setVertexBuffer(4, mesh.specular);
|
|
666
775
|
pass.draw(mesh.count);
|
|
667
776
|
});
|
|
668
777
|
pass.end();
|
|
@@ -674,6 +783,8 @@ fn fs(in: VSOut) -> @location(0) vec4<f32> {
|
|
|
674
783
|
mesh.position.destroy?.();
|
|
675
784
|
mesh.normal.destroy?.();
|
|
676
785
|
mesh.color.destroy?.();
|
|
786
|
+
mesh.emissive.destroy?.();
|
|
787
|
+
mesh.specular.destroy?.();
|
|
677
788
|
}
|
|
678
789
|
this.cache.clear();
|
|
679
790
|
this.uniformBuffer?.destroy?.();
|
|
@@ -756,9 +867,30 @@ var init_canvas2d = __esm({
|
|
|
756
867
|
depth += dot(d, d);
|
|
757
868
|
}
|
|
758
869
|
depth /= face.indices.length;
|
|
870
|
+
let surface;
|
|
871
|
+
const material = face.material;
|
|
872
|
+
if (material) {
|
|
873
|
+
if (material.specular) {
|
|
874
|
+
let cx = 0;
|
|
875
|
+
let cy = 0;
|
|
876
|
+
let cz = 0;
|
|
877
|
+
for (const i of face.indices) {
|
|
878
|
+
cx += world[i].x;
|
|
879
|
+
cy += world[i].y;
|
|
880
|
+
cz += world[i].z;
|
|
881
|
+
}
|
|
882
|
+
const inv = 1 / face.indices.length;
|
|
883
|
+
const viewDir = normalize(
|
|
884
|
+
subtract(frame.eye, { x: cx * inv, y: cy * inv, z: cz * inv })
|
|
885
|
+
);
|
|
886
|
+
surface = { material, viewDir };
|
|
887
|
+
} else {
|
|
888
|
+
surface = { material };
|
|
889
|
+
}
|
|
890
|
+
}
|
|
759
891
|
polygons.push({
|
|
760
892
|
points,
|
|
761
|
-
color: shadeColor(normal, face.color, frame.light),
|
|
893
|
+
color: shadeColor(normal, face.color, frame.light, surface),
|
|
762
894
|
depth,
|
|
763
895
|
opacity: faceOpacity
|
|
764
896
|
});
|
|
@@ -888,6 +1020,12 @@ init_light();
|
|
|
888
1020
|
init_math();
|
|
889
1021
|
|
|
890
1022
|
// src/engines/little-3d-engine/core/mesh.ts
|
|
1023
|
+
function attachMaterial(mesh, material) {
|
|
1024
|
+
if (material) {
|
|
1025
|
+
for (const face of mesh.faces) face.material = material;
|
|
1026
|
+
}
|
|
1027
|
+
return mesh;
|
|
1028
|
+
}
|
|
891
1029
|
function transform(init) {
|
|
892
1030
|
return {
|
|
893
1031
|
position: init?.position ?? { x: 0, y: 0, z: 0 },
|
|
@@ -940,8 +1078,11 @@ var SEED_FACES = [
|
|
|
940
1078
|
[8, 6, 7],
|
|
941
1079
|
[9, 8, 1]
|
|
942
1080
|
];
|
|
943
|
-
function icosphere(size = 1, detail = 1, colors = DEFAULT_COLORS) {
|
|
944
|
-
return
|
|
1081
|
+
function icosphere(size = 1, detail = 1, colors = DEFAULT_COLORS, material) {
|
|
1082
|
+
return attachMaterial(
|
|
1083
|
+
sphereFromTriangles(SEED_VERTICES, SEED_FACES, size, detail, colors),
|
|
1084
|
+
material
|
|
1085
|
+
);
|
|
945
1086
|
}
|
|
946
1087
|
|
|
947
1088
|
// src/engines/little-3d-engine/shapes/primitives/spheres/octa-sphere.ts
|
|
@@ -1148,6 +1289,7 @@ var REENTER_STAGGER_MS = 45;
|
|
|
1148
1289
|
var CENTER_POP_OUT_AT = REENTER_MS + (MINIS - 1) * REENTER_STAGGER_MS + 150;
|
|
1149
1290
|
var CENTER_POP_OUT_MS = 420;
|
|
1150
1291
|
var PARKED = { x: 0, y: 0, z: 50 };
|
|
1292
|
+
var ORB_MATERIAL = { specular: [1, 1, 1], shininess: 28 };
|
|
1151
1293
|
var CENTER_COLORS = ["#67e8f9", "#22d3ee", "#0ea5e9", "#38bdf8", "#7dd3fc"];
|
|
1152
1294
|
var MINI_COLORS = [
|
|
1153
1295
|
["#e0f2fe", "#bae6fd", "#7dd3fc"],
|
|
@@ -1176,9 +1318,9 @@ var ChargedOrbAnimation = class {
|
|
|
1176
1318
|
backend: this.backend,
|
|
1177
1319
|
camera: { position: { x: 0, y: 0, z: CAMERA_Z } }
|
|
1178
1320
|
});
|
|
1179
|
-
this.center = engine.add(icosphere(1, 2, CENTER_COLORS), { scale: 0 });
|
|
1321
|
+
this.center = engine.add(icosphere(1, 2, CENTER_COLORS, ORB_MATERIAL), { scale: 0 });
|
|
1180
1322
|
for (let i = 0; i < MINIS; i++) {
|
|
1181
|
-
const mesh = icosphere(1, 1, MINI_COLORS[i % MINI_COLORS.length]);
|
|
1323
|
+
const mesh = icosphere(1, 1, MINI_COLORS[i % MINI_COLORS.length], ORB_MATERIAL);
|
|
1182
1324
|
this.minis.push(engine.add(mesh, { scale: 0, transparency: { ...MINI_TRANSPARENCY } }));
|
|
1183
1325
|
}
|
|
1184
1326
|
this.engine = engine;
|