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
|
@@ -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
|
});
|
|
@@ -1624,6 +1756,12 @@ init_light();
|
|
|
1624
1756
|
init_math();
|
|
1625
1757
|
|
|
1626
1758
|
// src/engines/little-3d-engine/core/mesh.ts
|
|
1759
|
+
function attachMaterial(mesh, material) {
|
|
1760
|
+
if (material) {
|
|
1761
|
+
for (const face of mesh.faces) face.material = material;
|
|
1762
|
+
}
|
|
1763
|
+
return mesh;
|
|
1764
|
+
}
|
|
1627
1765
|
function transform(init) {
|
|
1628
1766
|
return {
|
|
1629
1767
|
position: init?.position ?? { x: 0, y: 0, z: 0 },
|
|
@@ -1638,7 +1776,7 @@ init_light();
|
|
|
1638
1776
|
|
|
1639
1777
|
// src/engines/little-3d-engine/shapes/primitives/cube.ts
|
|
1640
1778
|
var DEFAULT_COLORS = ["#3b82f6", "#8b5cf6", "#ec4899", "#f59e0b", "#10b981", "#ef4444"];
|
|
1641
|
-
function cube(size = 1, colors = DEFAULT_COLORS) {
|
|
1779
|
+
function cube(size = 1, colors = DEFAULT_COLORS, material) {
|
|
1642
1780
|
const h = size / 2;
|
|
1643
1781
|
const vertices = [
|
|
1644
1782
|
{ x: -h, y: -h, z: h },
|
|
@@ -1658,12 +1796,12 @@ function cube(size = 1, colors = DEFAULT_COLORS) {
|
|
|
1658
1796
|
{ indices: [1, 5, 6, 2], color: colors[4 % colors.length] },
|
|
1659
1797
|
{ indices: [4, 0, 3, 7], color: colors[5 % colors.length] }
|
|
1660
1798
|
];
|
|
1661
|
-
return { vertices, faces };
|
|
1799
|
+
return attachMaterial({ vertices, faces }, material);
|
|
1662
1800
|
}
|
|
1663
1801
|
|
|
1664
1802
|
// src/engines/little-3d-engine/shapes/primitives/quad.ts
|
|
1665
1803
|
var DEFAULT_COLORS2 = ["#3b82f6"];
|
|
1666
|
-
function quad(size = 1, colors = DEFAULT_COLORS2) {
|
|
1804
|
+
function quad(size = 1, colors = DEFAULT_COLORS2, material) {
|
|
1667
1805
|
const s = size / 2;
|
|
1668
1806
|
const vertices = [
|
|
1669
1807
|
{ x: -s, y: -s, z: 0 },
|
|
@@ -1671,12 +1809,15 @@ function quad(size = 1, colors = DEFAULT_COLORS2) {
|
|
|
1671
1809
|
{ x: s, y: s, z: 0 },
|
|
1672
1810
|
{ x: -s, y: s, z: 0 }
|
|
1673
1811
|
];
|
|
1674
|
-
return
|
|
1812
|
+
return attachMaterial(
|
|
1813
|
+
{ vertices, faces: [{ indices: [0, 1, 2, 3], color: colors[0] }] },
|
|
1814
|
+
material
|
|
1815
|
+
);
|
|
1675
1816
|
}
|
|
1676
1817
|
|
|
1677
1818
|
// src/engines/little-3d-engine/shapes/primitives/tetrahedron.ts
|
|
1678
1819
|
var DEFAULT_COLORS3 = ["#3b82f6", "#8b5cf6", "#ec4899", "#f59e0b"];
|
|
1679
|
-
function tetrahedron(size = 1, colors = DEFAULT_COLORS3) {
|
|
1820
|
+
function tetrahedron(size = 1, colors = DEFAULT_COLORS3, material) {
|
|
1680
1821
|
const s = size / 2;
|
|
1681
1822
|
const vertices = [
|
|
1682
1823
|
{ x: s, y: s, z: s },
|
|
@@ -1690,12 +1831,12 @@ function tetrahedron(size = 1, colors = DEFAULT_COLORS3) {
|
|
|
1690
1831
|
{ indices: [0, 2, 3], color: colors[2 % colors.length] },
|
|
1691
1832
|
{ indices: [1, 3, 2], color: colors[3 % colors.length] }
|
|
1692
1833
|
];
|
|
1693
|
-
return { vertices, faces };
|
|
1834
|
+
return attachMaterial({ vertices, faces }, material);
|
|
1694
1835
|
}
|
|
1695
1836
|
|
|
1696
1837
|
// src/engines/little-3d-engine/shapes/primitives/pyramid.ts
|
|
1697
1838
|
var DEFAULT_COLORS4 = ["#3b82f6", "#8b5cf6", "#ec4899", "#f59e0b", "#10b981"];
|
|
1698
|
-
function pyramid(size = 1, colors = DEFAULT_COLORS4) {
|
|
1839
|
+
function pyramid(size = 1, colors = DEFAULT_COLORS4, material) {
|
|
1699
1840
|
const h = size / 2;
|
|
1700
1841
|
const vertices = [
|
|
1701
1842
|
{ x: -h, y: -h, z: h },
|
|
@@ -1711,7 +1852,7 @@ function pyramid(size = 1, colors = DEFAULT_COLORS4) {
|
|
|
1711
1852
|
{ indices: [4, 2, 3], color: colors[3 % colors.length] },
|
|
1712
1853
|
{ indices: [4, 3, 0], color: colors[4 % colors.length] }
|
|
1713
1854
|
];
|
|
1714
|
-
return { vertices, faces };
|
|
1855
|
+
return attachMaterial({ vertices, faces }, material);
|
|
1715
1856
|
}
|
|
1716
1857
|
|
|
1717
1858
|
// src/engines/little-3d-engine/shapes/primitives/spheres/icosphere.ts
|
|
@@ -1754,8 +1895,11 @@ var SEED_FACES = [
|
|
|
1754
1895
|
[8, 6, 7],
|
|
1755
1896
|
[9, 8, 1]
|
|
1756
1897
|
];
|
|
1757
|
-
function icosphere(size = 1, detail = 1, colors = DEFAULT_COLORS5) {
|
|
1758
|
-
return
|
|
1898
|
+
function icosphere(size = 1, detail = 1, colors = DEFAULT_COLORS5, material) {
|
|
1899
|
+
return attachMaterial(
|
|
1900
|
+
sphereFromTriangles(SEED_VERTICES, SEED_FACES, size, detail, colors),
|
|
1901
|
+
material
|
|
1902
|
+
);
|
|
1759
1903
|
}
|
|
1760
1904
|
|
|
1761
1905
|
// src/engines/little-3d-engine/shapes/primitives/spheres/octa-sphere.ts
|
|
@@ -1763,8 +1907,8 @@ init_geometry();
|
|
|
1763
1907
|
|
|
1764
1908
|
// src/engines/little-3d-engine/shapes/complex/plane.ts
|
|
1765
1909
|
var DEFAULT_COLORS6 = ["#e0f2fe", "#7dd3fc", "#38bdf8", "#f8fafc"];
|
|
1766
|
-
function planeMesh(colors = DEFAULT_COLORS6) {
|
|
1767
|
-
return {
|
|
1910
|
+
function planeMesh(colors = DEFAULT_COLORS6, material) {
|
|
1911
|
+
return attachMaterial({
|
|
1768
1912
|
vertices: [
|
|
1769
1913
|
{ x: 0.9, y: 0, z: 0 },
|
|
1770
1914
|
{ x: -0.2, y: 0, z: 0.82 },
|
|
@@ -1792,7 +1936,7 @@ function planeMesh(colors = DEFAULT_COLORS6) {
|
|
|
1792
1936
|
{ indices: [3, 6, 8], color: colors[0] ?? DEFAULT_COLORS6[0] },
|
|
1793
1937
|
{ indices: [3, 8, 6], color: colors[1] ?? DEFAULT_COLORS6[1] }
|
|
1794
1938
|
]
|
|
1795
|
-
};
|
|
1939
|
+
}, material);
|
|
1796
1940
|
}
|
|
1797
1941
|
|
|
1798
1942
|
// src/engines/little-3d-engine/textures/dynamic/canvas-texture.ts
|
|
@@ -2252,6 +2396,7 @@ var REENTER_STAGGER_MS = 45;
|
|
|
2252
2396
|
var CENTER_POP_OUT_AT = REENTER_MS + (MINIS - 1) * REENTER_STAGGER_MS + 150;
|
|
2253
2397
|
var CENTER_POP_OUT_MS = 420;
|
|
2254
2398
|
var PARKED = { x: 0, y: 0, z: 50 };
|
|
2399
|
+
var ORB_MATERIAL = { specular: [1, 1, 1], shininess: 28 };
|
|
2255
2400
|
var CENTER_COLORS = ["#67e8f9", "#22d3ee", "#0ea5e9", "#38bdf8", "#7dd3fc"];
|
|
2256
2401
|
var MINI_COLORS = [
|
|
2257
2402
|
["#e0f2fe", "#bae6fd", "#7dd3fc"],
|
|
@@ -2280,9 +2425,9 @@ var ChargedOrbAnimation = class {
|
|
|
2280
2425
|
backend: this.backend,
|
|
2281
2426
|
camera: { position: { x: 0, y: 0, z: CAMERA_Z } }
|
|
2282
2427
|
});
|
|
2283
|
-
this.center = engine.add(icosphere(1, 2, CENTER_COLORS), { scale: 0 });
|
|
2428
|
+
this.center = engine.add(icosphere(1, 2, CENTER_COLORS, ORB_MATERIAL), { scale: 0 });
|
|
2284
2429
|
for (let i = 0; i < MINIS; i++) {
|
|
2285
|
-
const mesh = icosphere(1, 1, MINI_COLORS[i % MINI_COLORS.length]);
|
|
2430
|
+
const mesh = icosphere(1, 1, MINI_COLORS[i % MINI_COLORS.length], ORB_MATERIAL);
|
|
2286
2431
|
this.minis.push(engine.add(mesh, { scale: 0, transparency: { ...MINI_TRANSPARENCY } }));
|
|
2287
2432
|
}
|
|
2288
2433
|
this.engine = engine;
|
|
@@ -2944,6 +3089,7 @@ var WARP_ACCEL = 1e3;
|
|
|
2944
3089
|
var TRAIL_OUTRO_MS = 1200;
|
|
2945
3090
|
var TRANSPARENCY = { mode: "two-sided", opacity: 0.275 };
|
|
2946
3091
|
var CAR_COLORS = ["#bae6fd", "#7dd3fc", "#38bdf8", "#0ea5e9", "#a5f3fc", "#e0f2fe"];
|
|
3092
|
+
var CAR_MATERIAL = { emissive: [0.1, 0.2, 0.3] };
|
|
2947
3093
|
var WORLD_UP2 = { x: 0, y: 1, z: 0 };
|
|
2948
3094
|
function clamp014(value) {
|
|
2949
3095
|
return Math.max(0, Math.min(1, value));
|
|
@@ -3004,7 +3150,7 @@ var GhostTrainAnimation = class {
|
|
|
3004
3150
|
backend: this.backend,
|
|
3005
3151
|
camera: { position: { x: 0, y: 0, z: CAMERA_Z2 }, fov: FOV }
|
|
3006
3152
|
});
|
|
3007
|
-
const mesh = cube(1, CAR_COLORS);
|
|
3153
|
+
const mesh = cube(1, CAR_COLORS, CAR_MATERIAL);
|
|
3008
3154
|
for (let i = 0; i < MAX_CARS; i++) {
|
|
3009
3155
|
this.cars.push(engine.add(mesh, { scale: 0, transparency: { ...TRANSPARENCY } }));
|
|
3010
3156
|
}
|
|
@@ -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 };
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { type Vec3 } from "./math.js";
|
|
2
|
+
import type { Material } from "./mesh.js";
|
|
2
3
|
/** Directional light configuration. */
|
|
3
4
|
export interface LightOptions {
|
|
4
5
|
/** Direction the light travels. Default points down-forward into the scene. */
|
|
@@ -16,10 +17,33 @@ export interface LightParams {
|
|
|
16
17
|
ambient: number;
|
|
17
18
|
}
|
|
18
19
|
/**
|
|
19
|
-
*
|
|
20
|
-
*
|
|
20
|
+
* Per-face surface inputs for {@link shade}/{@link shadeColor} beyond the base
|
|
21
|
+
* color: its MTL {@link Material} and the direction from the face toward the
|
|
22
|
+
* camera. Both are needed for the specular highlight; without them shading
|
|
23
|
+
* falls back to flat Lambert plus any emissive term.
|
|
21
24
|
*/
|
|
22
|
-
export
|
|
25
|
+
export interface Surface {
|
|
26
|
+
/** MTL-derived specular/shininess/emissive for this face. */
|
|
27
|
+
material?: Material;
|
|
28
|
+
/** Unit vector from the face toward the eye, for the specular highlight. */
|
|
29
|
+
viewDir?: Vec3;
|
|
30
|
+
}
|
|
31
|
+
/**
|
|
32
|
+
* Shade a face and return `0..255` RGB channels.
|
|
33
|
+
*
|
|
34
|
+
* The diffuse term is flat Lambert: the base `color` brightened by how directly
|
|
35
|
+
* `normal` faces the light, floored at the ambient level. When a {@link Surface}
|
|
36
|
+
* supplies a specular material and a `viewDir`, a Blinn-Phong highlight (`Ks`
|
|
37
|
+
* tinted, tightened by `Ns`) is added; an emissive material (`Ke`) is always
|
|
38
|
+
* added on top. With no material this reduces exactly to the previous flat
|
|
39
|
+
* shading.
|
|
40
|
+
*/
|
|
41
|
+
export declare function shade(normal: Vec3, color: string, light: LightParams, surface?: Surface): [number, number, number];
|
|
42
|
+
/**
|
|
43
|
+
* Shade a face and return an `rgb(...)` string. Thin wrapper over {@link shade};
|
|
44
|
+
* see it for the shading model. Passing no `surface` gives flat Lambert shading.
|
|
45
|
+
*/
|
|
46
|
+
export declare function shadeColor(normal: Vec3, color: string, light: LightParams, surface?: Surface): string;
|
|
23
47
|
/** A single directional light with an ambient term, used for flat shading. */
|
|
24
48
|
export declare class Light {
|
|
25
49
|
readonly options: LightOptions;
|
|
@@ -27,5 +51,5 @@ export declare class Light {
|
|
|
27
51
|
readonly params: LightParams;
|
|
28
52
|
constructor(options?: Partial<LightOptions>);
|
|
29
53
|
/** Convenience wrapper around {@link shadeColor} using this light. */
|
|
30
|
-
shade(normal: Vec3, color: string): string;
|
|
54
|
+
shade(normal: Vec3, color: string, surface?: Surface): string;
|
|
31
55
|
}
|