3d-spinner 0.9.4 → 0.9.6
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.d.ts +1 -1
- package/dist/animations/ghost-train.js +3 -1
- package/dist/animations/grid-assembly.d.ts +1 -1
- package/dist/animations/spin.d.ts +3 -1
- package/dist/animations/spin.js +6 -1
- package/dist/cjs/animations/charged-orb.cjs +177 -30
- package/dist/cjs/animations/grid-assembly.cjs +171 -28
- package/dist/cjs/animations/object-motion.cjs +163 -26
- package/dist/cjs/animations/particles.cjs +181 -30
- package/dist/cjs/animations/spin.cjs +176 -29
- package/dist/cjs/engines/little-3d-engine/little-3d-engine.cjs +200 -47
- 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 +69 -10
- package/dist/cjs/engines/little-3d-engine/renderers/webgpu-textured.cjs +64 -11
- package/dist/cjs/prefabs/prefabs.cjs +200 -44
- 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-textured.js +8 -3
- package/dist/engines/little-3d-engine/renderers/webgl.js +41 -7
- 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 +271 -67
- 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;
|
|
@@ -4,7 +4,7 @@ import type { MotionController } from "../motion/controller.js";
|
|
|
4
4
|
export interface GhostTrainOptions {
|
|
5
5
|
/** How the convoy moves. Default a tilted square track. */
|
|
6
6
|
motion?: MotionController;
|
|
7
|
-
/** Uniform car size in scene units. Default `0.
|
|
7
|
+
/** Uniform car size in scene units. Default `0.15`. */
|
|
8
8
|
size?: number;
|
|
9
9
|
/** Rendering backend. Default `"canvas2d"`. */
|
|
10
10
|
backend?: Backend;
|
|
@@ -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,7 +1,7 @@
|
|
|
1
1
|
import type { AnimationFrame, AnimationLabel, SpinnerAnimation } from "../animation.js";
|
|
2
2
|
import { type Backend, type Mesh } from "../engines/little-3d-engine/little-3d-engine.js";
|
|
3
3
|
export interface GridAssemblyOptions {
|
|
4
|
-
/** Meshes (or factories) cycled across the 25 shapes. Defaults to a
|
|
4
|
+
/** Meshes (or factories) cycled across the 25 shapes. Defaults to a single pastel dark-blue cube. */
|
|
5
5
|
meshes?: Array<Mesh | (() => Mesh)>;
|
|
6
6
|
/** Uniform shape size in scene units. Default `0.34`. */
|
|
7
7
|
size?: number;
|
|
@@ -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,19 +414,23 @@ void main() {
|
|
|
339
414
|
const data = expandToTriangles(mesh);
|
|
340
415
|
const vao = gl.createVertexArray();
|
|
341
416
|
gl.bindVertexArray(vao);
|
|
342
|
-
const
|
|
417
|
+
const buffers = [];
|
|
418
|
+
const attribute = (location, array, size = 3) => {
|
|
343
419
|
if (location < 0) return;
|
|
344
420
|
const buffer = gl.createBuffer();
|
|
421
|
+
buffers.push(buffer);
|
|
345
422
|
gl.bindBuffer(gl.ARRAY_BUFFER, buffer);
|
|
346
423
|
gl.bufferData(gl.ARRAY_BUFFER, array, gl.STATIC_DRAW);
|
|
347
424
|
gl.enableVertexAttribArray(location);
|
|
348
|
-
gl.vertexAttribPointer(location,
|
|
425
|
+
gl.vertexAttribPointer(location, size, gl.FLOAT, false, 0, 0);
|
|
349
426
|
};
|
|
350
427
|
attribute(loc.aPos, data.positions);
|
|
351
428
|
attribute(loc.aNormal, data.normals);
|
|
352
429
|
attribute(loc.aColor, data.colors);
|
|
430
|
+
attribute(loc.aEmissive, data.emissives);
|
|
431
|
+
attribute(loc.aSpecular, data.speculars, 4);
|
|
353
432
|
gl.bindVertexArray(null);
|
|
354
|
-
const result = { vao, count: data.count };
|
|
433
|
+
const result = { vao, buffers, count: data.count };
|
|
355
434
|
this.cache.set(mesh, result);
|
|
356
435
|
return result;
|
|
357
436
|
}
|
|
@@ -364,6 +443,7 @@ void main() {
|
|
|
364
443
|
gl.useProgram(this.program);
|
|
365
444
|
gl.uniformMatrix4fv(loc.uViewProj, false, new Float32Array(frame.viewProjection));
|
|
366
445
|
gl.uniform3f(loc.uToLight, frame.light.toLight.x, frame.light.toLight.y, frame.light.toLight.z);
|
|
446
|
+
gl.uniform3f(loc.uEye, frame.eye.x, frame.eye.y, frame.eye.z);
|
|
367
447
|
gl.uniform1f(loc.uIntensity, frame.light.intensity);
|
|
368
448
|
gl.uniform1f(loc.uAmbient, frame.light.ambient);
|
|
369
449
|
gl.disable(gl.BLEND);
|
|
@@ -408,7 +488,10 @@ void main() {
|
|
|
408
488
|
destroy() {
|
|
409
489
|
const gl = this.gl;
|
|
410
490
|
if (gl) {
|
|
411
|
-
for (const mesh of this.cache.values())
|
|
491
|
+
for (const mesh of this.cache.values()) {
|
|
492
|
+
gl.deleteVertexArray(mesh.vao);
|
|
493
|
+
for (const buffer of mesh.buffers) gl.deleteBuffer(buffer);
|
|
494
|
+
}
|
|
412
495
|
if (this.program) gl.deleteProgram(this.program);
|
|
413
496
|
}
|
|
414
497
|
this.cache.clear();
|
|
@@ -438,6 +521,7 @@ struct Uniforms {
|
|
|
438
521
|
model: mat4x4<f32>,
|
|
439
522
|
toLight: vec4<f32>,
|
|
440
523
|
params: vec4<f32>,
|
|
524
|
+
eye: vec4<f32>,
|
|
441
525
|
};
|
|
442
526
|
@group(0) @binding(0) var<uniform> u: Uniforms;
|
|
443
527
|
|
|
@@ -445,23 +529,40 @@ struct VSOut {
|
|
|
445
529
|
@builtin(position) position: vec4<f32>,
|
|
446
530
|
@location(0) normal: vec3<f32>,
|
|
447
531
|
@location(1) color: vec3<f32>,
|
|
532
|
+
@location(2) emissive: vec3<f32>,
|
|
533
|
+
@location(3) specular: vec4<f32>,
|
|
534
|
+
@location(4) worldPos: vec3<f32>,
|
|
448
535
|
};
|
|
449
536
|
|
|
450
537
|
@vertex
|
|
451
|
-
fn vs(@location(0) pos: vec3<f32>, @location(1) normal: vec3<f32>, @location(2) color: vec3<f32>) -> VSOut {
|
|
538
|
+
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
539
|
var out: VSOut;
|
|
453
540
|
let m = mat3x3<f32>(u.model[0].xyz, u.model[1].xyz, u.model[2].xyz);
|
|
454
541
|
out.normal = m * normal;
|
|
455
542
|
out.color = color;
|
|
456
|
-
out.
|
|
543
|
+
out.emissive = emissive;
|
|
544
|
+
out.specular = specular;
|
|
545
|
+
let world = u.model * vec4<f32>(pos, 1.0);
|
|
546
|
+
out.worldPos = world.xyz;
|
|
547
|
+
out.position = u.viewProj * world;
|
|
457
548
|
return out;
|
|
458
549
|
}
|
|
459
550
|
|
|
460
551
|
@fragment
|
|
461
552
|
fn fs(in: VSOut) -> @location(0) vec4<f32> {
|
|
462
|
-
let
|
|
553
|
+
let normal = normalize(in.normal);
|
|
554
|
+
let toLight = normalize(u.toLight.xyz);
|
|
555
|
+
let lambert = max(dot(normal, toLight), 0.0);
|
|
463
556
|
let brightness = clamp(u.params.y + u.params.x * lambert, 0.0, 1.0);
|
|
464
|
-
|
|
557
|
+
var lit = in.color * brightness;
|
|
558
|
+
if (lambert > 0.0) {
|
|
559
|
+
let viewDir = normalize(u.eye.xyz - in.worldPos);
|
|
560
|
+
let halfVec = normalize(toLight + viewDir);
|
|
561
|
+
let highlight = pow(max(dot(normal, halfVec), 0.0), in.specular.w) * u.params.x;
|
|
562
|
+
lit = lit + highlight * in.specular.xyz;
|
|
563
|
+
}
|
|
564
|
+
lit = lit + in.emissive;
|
|
565
|
+
return vec4<f32>(lit, u.params.z);
|
|
465
566
|
}
|
|
466
567
|
`;
|
|
467
568
|
CLIP_Z_FIX = [1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0.5, 0, 0, 0, 0.5, 1];
|
|
@@ -502,13 +603,15 @@ fn fs(in: VSOut) -> @location(0) vec4<f32> {
|
|
|
502
603
|
{
|
|
503
604
|
binding: 0,
|
|
504
605
|
visibility: stage.VERTEX | stage.FRAGMENT,
|
|
505
|
-
buffer: { type: "uniform", hasDynamicOffset: true, minBindingSize:
|
|
606
|
+
buffer: { type: "uniform", hasDynamicOffset: true, minBindingSize: 176 }
|
|
506
607
|
}
|
|
507
608
|
]
|
|
508
609
|
});
|
|
509
|
-
const vertexBuffer = (location) => ({
|
|
510
|
-
arrayStride:
|
|
511
|
-
attributes: [
|
|
610
|
+
const vertexBuffer = (location, components = 3) => ({
|
|
611
|
+
arrayStride: components * 4,
|
|
612
|
+
attributes: [
|
|
613
|
+
{ shaderLocation: location, offset: 0, format: `float32x${components}` }
|
|
614
|
+
]
|
|
512
615
|
});
|
|
513
616
|
const pipelineLayout = device.createPipelineLayout({ bindGroupLayouts: [layout] });
|
|
514
617
|
const blend = {
|
|
@@ -524,7 +627,13 @@ fn fs(in: VSOut) -> @location(0) vec4<f32> {
|
|
|
524
627
|
vertex: {
|
|
525
628
|
module: module2,
|
|
526
629
|
entryPoint: "vs",
|
|
527
|
-
buffers: [
|
|
630
|
+
buffers: [
|
|
631
|
+
vertexBuffer(0),
|
|
632
|
+
vertexBuffer(1),
|
|
633
|
+
vertexBuffer(2),
|
|
634
|
+
vertexBuffer(3),
|
|
635
|
+
vertexBuffer(4, 4)
|
|
636
|
+
]
|
|
528
637
|
},
|
|
529
638
|
fragment: {
|
|
530
639
|
module: module2,
|
|
@@ -577,6 +686,8 @@ fn fs(in: VSOut) -> @location(0) vec4<f32> {
|
|
|
577
686
|
position: upload(data.positions),
|
|
578
687
|
normal: upload(data.normals),
|
|
579
688
|
color: upload(data.colors),
|
|
689
|
+
emissive: upload(data.emissives),
|
|
690
|
+
specular: upload(data.speculars),
|
|
580
691
|
count: data.count
|
|
581
692
|
};
|
|
582
693
|
this.cache.set(mesh, result);
|
|
@@ -628,7 +739,7 @@ fn fs(in: VSOut) -> @location(0) vec4<f32> {
|
|
|
628
739
|
const bindGroup = this.device.createBindGroup({
|
|
629
740
|
layout,
|
|
630
741
|
entries: [
|
|
631
|
-
{ binding: 0, resource: { buffer: this.uniformBuffer, offset: 0, size:
|
|
742
|
+
{ binding: 0, resource: { buffer: this.uniformBuffer, offset: 0, size: 176 } }
|
|
632
743
|
]
|
|
633
744
|
});
|
|
634
745
|
draws.forEach((draw, i) => {
|
|
@@ -637,6 +748,7 @@ fn fs(in: VSOut) -> @location(0) vec4<f32> {
|
|
|
637
748
|
data.set(draw.item.model, 16);
|
|
638
749
|
data.set([frame.light.toLight.x, frame.light.toLight.y, frame.light.toLight.z, 0], 32);
|
|
639
750
|
data.set([frame.light.intensity, frame.light.ambient, draw.opacity, 0], 36);
|
|
751
|
+
data.set([frame.eye.x, frame.eye.y, frame.eye.z, 0], 40);
|
|
640
752
|
this.device.queue.writeBuffer(this.uniformBuffer, i * UNIFORM_STRIDE, data);
|
|
641
753
|
});
|
|
642
754
|
const encoder = this.device.createCommandEncoder();
|
|
@@ -663,6 +775,8 @@ fn fs(in: VSOut) -> @location(0) vec4<f32> {
|
|
|
663
775
|
pass.setVertexBuffer(0, mesh.position);
|
|
664
776
|
pass.setVertexBuffer(1, mesh.normal);
|
|
665
777
|
pass.setVertexBuffer(2, mesh.color);
|
|
778
|
+
pass.setVertexBuffer(3, mesh.emissive);
|
|
779
|
+
pass.setVertexBuffer(4, mesh.specular);
|
|
666
780
|
pass.draw(mesh.count);
|
|
667
781
|
});
|
|
668
782
|
pass.end();
|
|
@@ -674,6 +788,8 @@ fn fs(in: VSOut) -> @location(0) vec4<f32> {
|
|
|
674
788
|
mesh.position.destroy?.();
|
|
675
789
|
mesh.normal.destroy?.();
|
|
676
790
|
mesh.color.destroy?.();
|
|
791
|
+
mesh.emissive.destroy?.();
|
|
792
|
+
mesh.specular.destroy?.();
|
|
677
793
|
}
|
|
678
794
|
this.cache.clear();
|
|
679
795
|
this.uniformBuffer?.destroy?.();
|
|
@@ -756,9 +872,30 @@ var init_canvas2d = __esm({
|
|
|
756
872
|
depth += dot(d, d);
|
|
757
873
|
}
|
|
758
874
|
depth /= face.indices.length;
|
|
875
|
+
let surface;
|
|
876
|
+
const material = face.material;
|
|
877
|
+
if (material) {
|
|
878
|
+
if (material.specular) {
|
|
879
|
+
let cx = 0;
|
|
880
|
+
let cy = 0;
|
|
881
|
+
let cz = 0;
|
|
882
|
+
for (const i of face.indices) {
|
|
883
|
+
cx += world[i].x;
|
|
884
|
+
cy += world[i].y;
|
|
885
|
+
cz += world[i].z;
|
|
886
|
+
}
|
|
887
|
+
const inv = 1 / face.indices.length;
|
|
888
|
+
const viewDir = normalize(
|
|
889
|
+
subtract(frame.eye, { x: cx * inv, y: cy * inv, z: cz * inv })
|
|
890
|
+
);
|
|
891
|
+
surface = { material, viewDir };
|
|
892
|
+
} else {
|
|
893
|
+
surface = { material };
|
|
894
|
+
}
|
|
895
|
+
}
|
|
759
896
|
polygons.push({
|
|
760
897
|
points,
|
|
761
|
-
color: shadeColor(normal, face.color, frame.light),
|
|
898
|
+
color: shadeColor(normal, face.color, frame.light, surface),
|
|
762
899
|
depth,
|
|
763
900
|
opacity: faceOpacity
|
|
764
901
|
});
|
|
@@ -888,6 +1025,12 @@ init_light();
|
|
|
888
1025
|
init_math();
|
|
889
1026
|
|
|
890
1027
|
// src/engines/little-3d-engine/core/mesh.ts
|
|
1028
|
+
function attachMaterial(mesh, material) {
|
|
1029
|
+
if (material) {
|
|
1030
|
+
for (const face of mesh.faces) face.material = material;
|
|
1031
|
+
}
|
|
1032
|
+
return mesh;
|
|
1033
|
+
}
|
|
891
1034
|
function transform(init) {
|
|
892
1035
|
return {
|
|
893
1036
|
position: init?.position ?? { x: 0, y: 0, z: 0 },
|
|
@@ -940,8 +1083,11 @@ var SEED_FACES = [
|
|
|
940
1083
|
[8, 6, 7],
|
|
941
1084
|
[9, 8, 1]
|
|
942
1085
|
];
|
|
943
|
-
function icosphere(size = 1, detail = 1, colors = DEFAULT_COLORS) {
|
|
944
|
-
return
|
|
1086
|
+
function icosphere(size = 1, detail = 1, colors = DEFAULT_COLORS, material) {
|
|
1087
|
+
return attachMaterial(
|
|
1088
|
+
sphereFromTriangles(SEED_VERTICES, SEED_FACES, size, detail, colors),
|
|
1089
|
+
material
|
|
1090
|
+
);
|
|
945
1091
|
}
|
|
946
1092
|
|
|
947
1093
|
// src/engines/little-3d-engine/shapes/primitives/spheres/octa-sphere.ts
|
|
@@ -1148,6 +1294,7 @@ var REENTER_STAGGER_MS = 45;
|
|
|
1148
1294
|
var CENTER_POP_OUT_AT = REENTER_MS + (MINIS - 1) * REENTER_STAGGER_MS + 150;
|
|
1149
1295
|
var CENTER_POP_OUT_MS = 420;
|
|
1150
1296
|
var PARKED = { x: 0, y: 0, z: 50 };
|
|
1297
|
+
var ORB_MATERIAL = { specular: [1, 1, 1], shininess: 28 };
|
|
1151
1298
|
var CENTER_COLORS = ["#67e8f9", "#22d3ee", "#0ea5e9", "#38bdf8", "#7dd3fc"];
|
|
1152
1299
|
var MINI_COLORS = [
|
|
1153
1300
|
["#e0f2fe", "#bae6fd", "#7dd3fc"],
|
|
@@ -1176,9 +1323,9 @@ var ChargedOrbAnimation = class {
|
|
|
1176
1323
|
backend: this.backend,
|
|
1177
1324
|
camera: { position: { x: 0, y: 0, z: CAMERA_Z } }
|
|
1178
1325
|
});
|
|
1179
|
-
this.center = engine.add(icosphere(1, 2, CENTER_COLORS), { scale: 0 });
|
|
1326
|
+
this.center = engine.add(icosphere(1, 2, CENTER_COLORS, ORB_MATERIAL), { scale: 0 });
|
|
1180
1327
|
for (let i = 0; i < MINIS; i++) {
|
|
1181
|
-
const mesh = icosphere(1, 1, MINI_COLORS[i % MINI_COLORS.length]);
|
|
1328
|
+
const mesh = icosphere(1, 1, MINI_COLORS[i % MINI_COLORS.length], ORB_MATERIAL);
|
|
1182
1329
|
this.minis.push(engine.add(mesh, { scale: 0, transparency: { ...MINI_TRANSPARENCY } }));
|
|
1183
1330
|
}
|
|
1184
1331
|
this.engine = engine;
|