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
|
@@ -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
|
});
|
|
@@ -1296,9 +1433,11 @@ void main() {
|
|
|
1296
1433
|
const data = expandToTriangles(mesh);
|
|
1297
1434
|
const vao = gl.createVertexArray();
|
|
1298
1435
|
gl.bindVertexArray(vao);
|
|
1436
|
+
const buffers = [];
|
|
1299
1437
|
const attribute = (location, array, size) => {
|
|
1300
1438
|
if (location < 0) return;
|
|
1301
1439
|
const buffer = gl.createBuffer();
|
|
1440
|
+
buffers.push(buffer);
|
|
1302
1441
|
gl.bindBuffer(gl.ARRAY_BUFFER, buffer);
|
|
1303
1442
|
gl.bufferData(gl.ARRAY_BUFFER, array, gl.STATIC_DRAW);
|
|
1304
1443
|
gl.enableVertexAttribArray(location);
|
|
@@ -1308,7 +1447,7 @@ void main() {
|
|
|
1308
1447
|
attribute(loc.aColor, data.colors, 3);
|
|
1309
1448
|
attribute(loc.aUV, planarUVs(mesh), 2);
|
|
1310
1449
|
gl.bindVertexArray(null);
|
|
1311
|
-
const result = { vao, count: data.count };
|
|
1450
|
+
const result = { vao, buffers, count: data.count };
|
|
1312
1451
|
this.buffers.set(mesh, result);
|
|
1313
1452
|
return result;
|
|
1314
1453
|
}
|
|
@@ -1346,7 +1485,10 @@ void main() {
|
|
|
1346
1485
|
const gl = this.gl;
|
|
1347
1486
|
if (gl) {
|
|
1348
1487
|
for (const texture of this.textures.values()) gl.deleteTexture(texture);
|
|
1349
|
-
for (const
|
|
1488
|
+
for (const cached of this.buffers.values()) {
|
|
1489
|
+
gl.deleteVertexArray(cached.vao);
|
|
1490
|
+
for (const buffer of cached.buffers) gl.deleteBuffer(buffer);
|
|
1491
|
+
}
|
|
1350
1492
|
if (this.program) gl.deleteProgram(this.program);
|
|
1351
1493
|
}
|
|
1352
1494
|
this.textures.clear();
|
|
@@ -1624,6 +1766,12 @@ init_light();
|
|
|
1624
1766
|
init_math();
|
|
1625
1767
|
|
|
1626
1768
|
// src/engines/little-3d-engine/core/mesh.ts
|
|
1769
|
+
function attachMaterial(mesh, material) {
|
|
1770
|
+
if (material) {
|
|
1771
|
+
for (const face of mesh.faces) face.material = material;
|
|
1772
|
+
}
|
|
1773
|
+
return mesh;
|
|
1774
|
+
}
|
|
1627
1775
|
function transform(init) {
|
|
1628
1776
|
return {
|
|
1629
1777
|
position: init?.position ?? { x: 0, y: 0, z: 0 },
|
|
@@ -1638,7 +1786,7 @@ init_light();
|
|
|
1638
1786
|
|
|
1639
1787
|
// src/engines/little-3d-engine/shapes/primitives/cube.ts
|
|
1640
1788
|
var DEFAULT_COLORS = ["#3b82f6", "#8b5cf6", "#ec4899", "#f59e0b", "#10b981", "#ef4444"];
|
|
1641
|
-
function cube(size = 1, colors = DEFAULT_COLORS) {
|
|
1789
|
+
function cube(size = 1, colors = DEFAULT_COLORS, material) {
|
|
1642
1790
|
const h = size / 2;
|
|
1643
1791
|
const vertices = [
|
|
1644
1792
|
{ x: -h, y: -h, z: h },
|
|
@@ -1658,12 +1806,12 @@ function cube(size = 1, colors = DEFAULT_COLORS) {
|
|
|
1658
1806
|
{ indices: [1, 5, 6, 2], color: colors[4 % colors.length] },
|
|
1659
1807
|
{ indices: [4, 0, 3, 7], color: colors[5 % colors.length] }
|
|
1660
1808
|
];
|
|
1661
|
-
return { vertices, faces };
|
|
1809
|
+
return attachMaterial({ vertices, faces }, material);
|
|
1662
1810
|
}
|
|
1663
1811
|
|
|
1664
1812
|
// src/engines/little-3d-engine/shapes/primitives/quad.ts
|
|
1665
1813
|
var DEFAULT_COLORS2 = ["#3b82f6"];
|
|
1666
|
-
function quad(size = 1, colors = DEFAULT_COLORS2) {
|
|
1814
|
+
function quad(size = 1, colors = DEFAULT_COLORS2, material) {
|
|
1667
1815
|
const s = size / 2;
|
|
1668
1816
|
const vertices = [
|
|
1669
1817
|
{ x: -s, y: -s, z: 0 },
|
|
@@ -1671,12 +1819,15 @@ function quad(size = 1, colors = DEFAULT_COLORS2) {
|
|
|
1671
1819
|
{ x: s, y: s, z: 0 },
|
|
1672
1820
|
{ x: -s, y: s, z: 0 }
|
|
1673
1821
|
];
|
|
1674
|
-
return
|
|
1822
|
+
return attachMaterial(
|
|
1823
|
+
{ vertices, faces: [{ indices: [0, 1, 2, 3], color: colors[0] }] },
|
|
1824
|
+
material
|
|
1825
|
+
);
|
|
1675
1826
|
}
|
|
1676
1827
|
|
|
1677
1828
|
// src/engines/little-3d-engine/shapes/primitives/tetrahedron.ts
|
|
1678
1829
|
var DEFAULT_COLORS3 = ["#3b82f6", "#8b5cf6", "#ec4899", "#f59e0b"];
|
|
1679
|
-
function tetrahedron(size = 1, colors = DEFAULT_COLORS3) {
|
|
1830
|
+
function tetrahedron(size = 1, colors = DEFAULT_COLORS3, material) {
|
|
1680
1831
|
const s = size / 2;
|
|
1681
1832
|
const vertices = [
|
|
1682
1833
|
{ x: s, y: s, z: s },
|
|
@@ -1690,12 +1841,12 @@ function tetrahedron(size = 1, colors = DEFAULT_COLORS3) {
|
|
|
1690
1841
|
{ indices: [0, 2, 3], color: colors[2 % colors.length] },
|
|
1691
1842
|
{ indices: [1, 3, 2], color: colors[3 % colors.length] }
|
|
1692
1843
|
];
|
|
1693
|
-
return { vertices, faces };
|
|
1844
|
+
return attachMaterial({ vertices, faces }, material);
|
|
1694
1845
|
}
|
|
1695
1846
|
|
|
1696
1847
|
// src/engines/little-3d-engine/shapes/primitives/pyramid.ts
|
|
1697
1848
|
var DEFAULT_COLORS4 = ["#3b82f6", "#8b5cf6", "#ec4899", "#f59e0b", "#10b981"];
|
|
1698
|
-
function pyramid(size = 1, colors = DEFAULT_COLORS4) {
|
|
1849
|
+
function pyramid(size = 1, colors = DEFAULT_COLORS4, material) {
|
|
1699
1850
|
const h = size / 2;
|
|
1700
1851
|
const vertices = [
|
|
1701
1852
|
{ x: -h, y: -h, z: h },
|
|
@@ -1711,7 +1862,7 @@ function pyramid(size = 1, colors = DEFAULT_COLORS4) {
|
|
|
1711
1862
|
{ indices: [4, 2, 3], color: colors[3 % colors.length] },
|
|
1712
1863
|
{ indices: [4, 3, 0], color: colors[4 % colors.length] }
|
|
1713
1864
|
];
|
|
1714
|
-
return { vertices, faces };
|
|
1865
|
+
return attachMaterial({ vertices, faces }, material);
|
|
1715
1866
|
}
|
|
1716
1867
|
|
|
1717
1868
|
// src/engines/little-3d-engine/shapes/primitives/spheres/icosphere.ts
|
|
@@ -1754,8 +1905,11 @@ var SEED_FACES = [
|
|
|
1754
1905
|
[8, 6, 7],
|
|
1755
1906
|
[9, 8, 1]
|
|
1756
1907
|
];
|
|
1757
|
-
function icosphere(size = 1, detail = 1, colors = DEFAULT_COLORS5) {
|
|
1758
|
-
return
|
|
1908
|
+
function icosphere(size = 1, detail = 1, colors = DEFAULT_COLORS5, material) {
|
|
1909
|
+
return attachMaterial(
|
|
1910
|
+
sphereFromTriangles(SEED_VERTICES, SEED_FACES, size, detail, colors),
|
|
1911
|
+
material
|
|
1912
|
+
);
|
|
1759
1913
|
}
|
|
1760
1914
|
|
|
1761
1915
|
// src/engines/little-3d-engine/shapes/primitives/spheres/octa-sphere.ts
|
|
@@ -1763,8 +1917,8 @@ init_geometry();
|
|
|
1763
1917
|
|
|
1764
1918
|
// src/engines/little-3d-engine/shapes/complex/plane.ts
|
|
1765
1919
|
var DEFAULT_COLORS6 = ["#e0f2fe", "#7dd3fc", "#38bdf8", "#f8fafc"];
|
|
1766
|
-
function planeMesh(colors = DEFAULT_COLORS6) {
|
|
1767
|
-
return {
|
|
1920
|
+
function planeMesh(colors = DEFAULT_COLORS6, material) {
|
|
1921
|
+
return attachMaterial({
|
|
1768
1922
|
vertices: [
|
|
1769
1923
|
{ x: 0.9, y: 0, z: 0 },
|
|
1770
1924
|
{ x: -0.2, y: 0, z: 0.82 },
|
|
@@ -1792,7 +1946,7 @@ function planeMesh(colors = DEFAULT_COLORS6) {
|
|
|
1792
1946
|
{ indices: [3, 6, 8], color: colors[0] ?? DEFAULT_COLORS6[0] },
|
|
1793
1947
|
{ indices: [3, 8, 6], color: colors[1] ?? DEFAULT_COLORS6[1] }
|
|
1794
1948
|
]
|
|
1795
|
-
};
|
|
1949
|
+
}, material);
|
|
1796
1950
|
}
|
|
1797
1951
|
|
|
1798
1952
|
// src/engines/little-3d-engine/textures/dynamic/canvas-texture.ts
|
|
@@ -2252,6 +2406,7 @@ var REENTER_STAGGER_MS = 45;
|
|
|
2252
2406
|
var CENTER_POP_OUT_AT = REENTER_MS + (MINIS - 1) * REENTER_STAGGER_MS + 150;
|
|
2253
2407
|
var CENTER_POP_OUT_MS = 420;
|
|
2254
2408
|
var PARKED = { x: 0, y: 0, z: 50 };
|
|
2409
|
+
var ORB_MATERIAL = { specular: [1, 1, 1], shininess: 28 };
|
|
2255
2410
|
var CENTER_COLORS = ["#67e8f9", "#22d3ee", "#0ea5e9", "#38bdf8", "#7dd3fc"];
|
|
2256
2411
|
var MINI_COLORS = [
|
|
2257
2412
|
["#e0f2fe", "#bae6fd", "#7dd3fc"],
|
|
@@ -2280,9 +2435,9 @@ var ChargedOrbAnimation = class {
|
|
|
2280
2435
|
backend: this.backend,
|
|
2281
2436
|
camera: { position: { x: 0, y: 0, z: CAMERA_Z } }
|
|
2282
2437
|
});
|
|
2283
|
-
this.center = engine.add(icosphere(1, 2, CENTER_COLORS), { scale: 0 });
|
|
2438
|
+
this.center = engine.add(icosphere(1, 2, CENTER_COLORS, ORB_MATERIAL), { scale: 0 });
|
|
2284
2439
|
for (let i = 0; i < MINIS; i++) {
|
|
2285
|
-
const mesh = icosphere(1, 1, MINI_COLORS[i % MINI_COLORS.length]);
|
|
2440
|
+
const mesh = icosphere(1, 1, MINI_COLORS[i % MINI_COLORS.length], ORB_MATERIAL);
|
|
2286
2441
|
this.minis.push(engine.add(mesh, { scale: 0, transparency: { ...MINI_TRANSPARENCY } }));
|
|
2287
2442
|
}
|
|
2288
2443
|
this.engine = engine;
|
|
@@ -2944,6 +3099,7 @@ var WARP_ACCEL = 1e3;
|
|
|
2944
3099
|
var TRAIL_OUTRO_MS = 1200;
|
|
2945
3100
|
var TRANSPARENCY = { mode: "two-sided", opacity: 0.275 };
|
|
2946
3101
|
var CAR_COLORS = ["#bae6fd", "#7dd3fc", "#38bdf8", "#0ea5e9", "#a5f3fc", "#e0f2fe"];
|
|
3102
|
+
var CAR_MATERIAL = { emissive: [0.1, 0.2, 0.3] };
|
|
2947
3103
|
var WORLD_UP2 = { x: 0, y: 1, z: 0 };
|
|
2948
3104
|
function clamp014(value) {
|
|
2949
3105
|
return Math.max(0, Math.min(1, value));
|
|
@@ -3004,7 +3160,7 @@ var GhostTrainAnimation = class {
|
|
|
3004
3160
|
backend: this.backend,
|
|
3005
3161
|
camera: { position: { x: 0, y: 0, z: CAMERA_Z2 }, fov: FOV }
|
|
3006
3162
|
});
|
|
3007
|
-
const mesh = cube(1, CAR_COLORS);
|
|
3163
|
+
const mesh = cube(1, CAR_COLORS, CAR_MATERIAL);
|
|
3008
3164
|
for (let i = 0; i < MAX_CARS; i++) {
|
|
3009
3165
|
this.cars.push(engine.add(mesh, { scale: 0, transparency: { ...TRANSPARENCY } }));
|
|
3010
3166
|
}
|
|
@@ -7,13 +7,26 @@ export interface TriangleData {
|
|
|
7
7
|
positions: Float32Array;
|
|
8
8
|
normals: Float32Array;
|
|
9
9
|
colors: Float32Array;
|
|
10
|
+
/**
|
|
11
|
+
* Per-vertex emissive (`Ke`) as linear `0..1` RGB, duplicated across each
|
|
12
|
+
* face's vertices. Faces with no material emit `(0,0,0)`, so a GPU shader can
|
|
13
|
+
* add this term unconditionally and unlit meshes stay identical.
|
|
14
|
+
*/
|
|
15
|
+
emissives: Float32Array;
|
|
16
|
+
/**
|
|
17
|
+
* Per-vertex specular packed as 4 floats: `Ks` linear RGB in `xyz` and the
|
|
18
|
+
* `Ns` shininess exponent in `w`. Faces with no specular emit `(0,0,0,1)`, so
|
|
19
|
+
* the highlight term multiplies to zero and unlit meshes stay identical.
|
|
20
|
+
* Note the stride is 4 floats here, not 3 like the other arrays.
|
|
21
|
+
*/
|
|
22
|
+
speculars: Float32Array;
|
|
10
23
|
/** Number of vertices (positions.length / 3). */
|
|
11
24
|
count: number;
|
|
12
25
|
}
|
|
13
26
|
/**
|
|
14
27
|
* Triangulate a mesh into a non-indexed soup where every vertex carries its
|
|
15
|
-
* face's normal and
|
|
16
|
-
* shading without per-primitive state.
|
|
28
|
+
* face's normal, color, and emissive. This is what GPU backends upload to
|
|
29
|
+
* render flat shading without per-primitive state.
|
|
17
30
|
*/
|
|
18
31
|
export declare function expandToTriangles(mesh: Mesh): TriangleData;
|
|
19
32
|
/**
|
|
@@ -13,8 +13,8 @@ export function parseColor(color) {
|
|
|
13
13
|
}
|
|
14
14
|
/**
|
|
15
15
|
* Triangulate a mesh into a non-indexed soup where every vertex carries its
|
|
16
|
-
* face's normal and
|
|
17
|
-
* shading without per-primitive state.
|
|
16
|
+
* face's normal, color, and emissive. This is what GPU backends upload to
|
|
17
|
+
* render flat shading without per-primitive state.
|
|
18
18
|
*/
|
|
19
19
|
export function expandToTriangles(mesh) {
|
|
20
20
|
let triangles = 0;
|
|
@@ -23,7 +23,10 @@ export function expandToTriangles(mesh) {
|
|
|
23
23
|
const positions = new Float32Array(triangles * 9);
|
|
24
24
|
const normals = new Float32Array(triangles * 9);
|
|
25
25
|
const colors = new Float32Array(triangles * 9);
|
|
26
|
+
const emissives = new Float32Array(triangles * 9);
|
|
27
|
+
const speculars = new Float32Array(triangles * 12);
|
|
26
28
|
let o = 0;
|
|
29
|
+
let so = 0;
|
|
27
30
|
for (const face of mesh.faces) {
|
|
28
31
|
const v0 = mesh.vertices[face.indices[0]];
|
|
29
32
|
const v1 = mesh.vertices[face.indices[1]];
|
|
@@ -33,6 +36,17 @@ export function expandToTriangles(mesh) {
|
|
|
33
36
|
const cr = r / 255;
|
|
34
37
|
const cg = g / 255;
|
|
35
38
|
const cb = b / 255;
|
|
39
|
+
const emissive = face.material?.emissive;
|
|
40
|
+
const er = emissive ? emissive[0] : 0;
|
|
41
|
+
const eg = emissive ? emissive[1] : 0;
|
|
42
|
+
const eb = emissive ? emissive[2] : 0;
|
|
43
|
+
const specular = face.material?.specular;
|
|
44
|
+
const sr = specular ? specular[0] : 0;
|
|
45
|
+
const sg = specular ? specular[1] : 0;
|
|
46
|
+
const sb = specular ? specular[2] : 0;
|
|
47
|
+
// Ns only matters where Ks is non-zero; default the exponent to 1 (32 is
|
|
48
|
+
// the flat-shading default when a specular is present but Ns is omitted).
|
|
49
|
+
const sn = specular ? face.material?.shininess ?? 32 : 1;
|
|
36
50
|
for (let k = 1; k < face.indices.length - 1; k++) {
|
|
37
51
|
const tri = [face.indices[0], face.indices[k], face.indices[k + 1]];
|
|
38
52
|
for (const index of tri) {
|
|
@@ -46,11 +60,19 @@ export function expandToTriangles(mesh) {
|
|
|
46
60
|
colors[o] = cr;
|
|
47
61
|
colors[o + 1] = cg;
|
|
48
62
|
colors[o + 2] = cb;
|
|
63
|
+
emissives[o] = er;
|
|
64
|
+
emissives[o + 1] = eg;
|
|
65
|
+
emissives[o + 2] = eb;
|
|
66
|
+
speculars[so] = sr;
|
|
67
|
+
speculars[so + 1] = sg;
|
|
68
|
+
speculars[so + 2] = sb;
|
|
69
|
+
speculars[so + 3] = sn;
|
|
49
70
|
o += 3;
|
|
71
|
+
so += 4;
|
|
50
72
|
}
|
|
51
73
|
}
|
|
52
74
|
}
|
|
53
|
-
return { positions, normals, colors, count: positions.length / 3 };
|
|
75
|
+
return { positions, normals, colors, emissives, speculars, count: positions.length / 3 };
|
|
54
76
|
}
|
|
55
77
|
function midpoint(a, b) {
|
|
56
78
|
return { x: (a.x + b.x) / 2, y: (a.y + b.y) / 2, z: (a.z + b.z) / 2 };
|