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
|
@@ -139,7 +139,10 @@ var Spinner3D = (() => {
|
|
|
139
139
|
const positions = new Float32Array(triangles * 9);
|
|
140
140
|
const normals = new Float32Array(triangles * 9);
|
|
141
141
|
const colors = new Float32Array(triangles * 9);
|
|
142
|
+
const emissives = new Float32Array(triangles * 9);
|
|
143
|
+
const speculars = new Float32Array(triangles * 12);
|
|
142
144
|
let o = 0;
|
|
145
|
+
let so = 0;
|
|
143
146
|
for (const face of mesh.faces) {
|
|
144
147
|
const v0 = mesh.vertices[face.indices[0]];
|
|
145
148
|
const v1 = mesh.vertices[face.indices[1]];
|
|
@@ -149,6 +152,15 @@ var Spinner3D = (() => {
|
|
|
149
152
|
const cr = r / 255;
|
|
150
153
|
const cg = g / 255;
|
|
151
154
|
const cb = b / 255;
|
|
155
|
+
const emissive = face.material?.emissive;
|
|
156
|
+
const er = emissive ? emissive[0] : 0;
|
|
157
|
+
const eg = emissive ? emissive[1] : 0;
|
|
158
|
+
const eb = emissive ? emissive[2] : 0;
|
|
159
|
+
const specular = face.material?.specular;
|
|
160
|
+
const sr = specular ? specular[0] : 0;
|
|
161
|
+
const sg = specular ? specular[1] : 0;
|
|
162
|
+
const sb = specular ? specular[2] : 0;
|
|
163
|
+
const sn = specular ? face.material?.shininess ?? 32 : 1;
|
|
152
164
|
for (let k = 1; k < face.indices.length - 1; k++) {
|
|
153
165
|
const tri = [face.indices[0], face.indices[k], face.indices[k + 1]];
|
|
154
166
|
for (const index of tri) {
|
|
@@ -162,11 +174,19 @@ var Spinner3D = (() => {
|
|
|
162
174
|
colors[o] = cr;
|
|
163
175
|
colors[o + 1] = cg;
|
|
164
176
|
colors[o + 2] = cb;
|
|
177
|
+
emissives[o] = er;
|
|
178
|
+
emissives[o + 1] = eg;
|
|
179
|
+
emissives[o + 2] = eb;
|
|
180
|
+
speculars[so] = sr;
|
|
181
|
+
speculars[so + 1] = sg;
|
|
182
|
+
speculars[so + 2] = sb;
|
|
183
|
+
speculars[so + 3] = sn;
|
|
165
184
|
o += 3;
|
|
185
|
+
so += 4;
|
|
166
186
|
}
|
|
167
187
|
}
|
|
168
188
|
}
|
|
169
|
-
return { positions, normals, colors, count: positions.length / 3 };
|
|
189
|
+
return { positions, normals, colors, emissives, speculars, count: positions.length / 3 };
|
|
170
190
|
}
|
|
171
191
|
function midpoint(a, b) {
|
|
172
192
|
return { x: (a.x + b.x) / 2, y: (a.y + b.y) / 2, z: (a.z + b.z) / 2 };
|
|
@@ -204,13 +224,42 @@ var Spinner3D = (() => {
|
|
|
204
224
|
function clamp012(value) {
|
|
205
225
|
return Math.min(1, Math.max(0, value));
|
|
206
226
|
}
|
|
207
|
-
function
|
|
227
|
+
function clamp255(value) {
|
|
228
|
+
return Math.round(Math.min(255, Math.max(0, value)));
|
|
229
|
+
}
|
|
230
|
+
function shade(normal, color, light, surface) {
|
|
208
231
|
const lambert = Math.max(0, dot(normal, light.toLight));
|
|
209
232
|
const brightness = clamp012(light.ambient + light.intensity * lambert);
|
|
210
|
-
const [
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
233
|
+
const [baseR, baseG, baseB] = parseColor(color);
|
|
234
|
+
let r = baseR * brightness;
|
|
235
|
+
let g = baseG * brightness;
|
|
236
|
+
let b = baseB * brightness;
|
|
237
|
+
const material = surface?.material;
|
|
238
|
+
const specular = material?.specular;
|
|
239
|
+
const viewDir = surface?.viewDir;
|
|
240
|
+
if (specular && viewDir && lambert > 0) {
|
|
241
|
+
const half = normalize({
|
|
242
|
+
x: light.toLight.x + viewDir.x,
|
|
243
|
+
y: light.toLight.y + viewDir.y,
|
|
244
|
+
z: light.toLight.z + viewDir.z
|
|
245
|
+
});
|
|
246
|
+
const shininess = material?.shininess ?? 32;
|
|
247
|
+
const highlight = Math.pow(Math.max(0, dot(normal, half)), shininess) * light.intensity * 255;
|
|
248
|
+
r += highlight * specular[0];
|
|
249
|
+
g += highlight * specular[1];
|
|
250
|
+
b += highlight * specular[2];
|
|
251
|
+
}
|
|
252
|
+
const emissive = material?.emissive;
|
|
253
|
+
if (emissive) {
|
|
254
|
+
r += emissive[0] * 255;
|
|
255
|
+
g += emissive[1] * 255;
|
|
256
|
+
b += emissive[2] * 255;
|
|
257
|
+
}
|
|
258
|
+
return [clamp255(r), clamp255(g), clamp255(b)];
|
|
259
|
+
}
|
|
260
|
+
function shadeColor(normal, color, light, surface) {
|
|
261
|
+
const [r, g, b] = shade(normal, color, light, surface);
|
|
262
|
+
return `rgb(${r}, ${g}, ${b})`;
|
|
214
263
|
}
|
|
215
264
|
var DEFAULTS2, Light;
|
|
216
265
|
var init_light = __esm({
|
|
@@ -233,8 +282,8 @@ var Spinner3D = (() => {
|
|
|
233
282
|
};
|
|
234
283
|
}
|
|
235
284
|
/** Convenience wrapper around {@link shadeColor} using this light. */
|
|
236
|
-
shade(normal, color) {
|
|
237
|
-
return shadeColor(normal, color, this.params);
|
|
285
|
+
shade(normal, color, surface) {
|
|
286
|
+
return shadeColor(normal, color, this.params, surface);
|
|
238
287
|
}
|
|
239
288
|
};
|
|
240
289
|
}
|
|
@@ -274,28 +323,51 @@ var Spinner3D = (() => {
|
|
|
274
323
|
in vec3 aPos;
|
|
275
324
|
in vec3 aNormal;
|
|
276
325
|
in vec3 aColor;
|
|
326
|
+
in vec3 aEmissive;
|
|
327
|
+
in vec4 aSpecular;
|
|
277
328
|
uniform mat4 uViewProj;
|
|
278
329
|
uniform mat4 uModel;
|
|
279
330
|
out vec3 vNormal;
|
|
280
331
|
out vec3 vColor;
|
|
332
|
+
out vec3 vEmissive;
|
|
333
|
+
out vec4 vSpecular;
|
|
334
|
+
out vec3 vWorldPos;
|
|
281
335
|
void main() {
|
|
282
336
|
vNormal = mat3(uModel) * aNormal;
|
|
283
337
|
vColor = aColor;
|
|
284
|
-
|
|
338
|
+
vEmissive = aEmissive;
|
|
339
|
+
vSpecular = aSpecular;
|
|
340
|
+
vec4 world = uModel * vec4(aPos, 1.0);
|
|
341
|
+
vWorldPos = world.xyz;
|
|
342
|
+
gl_Position = uViewProj * world;
|
|
285
343
|
}`;
|
|
286
344
|
FRAGMENT_SHADER = `#version 300 es
|
|
287
345
|
precision mediump float;
|
|
288
346
|
in vec3 vNormal;
|
|
289
347
|
in vec3 vColor;
|
|
348
|
+
in vec3 vEmissive;
|
|
349
|
+
in vec4 vSpecular;
|
|
350
|
+
in vec3 vWorldPos;
|
|
290
351
|
uniform vec3 uToLight;
|
|
352
|
+
uniform vec3 uEye;
|
|
291
353
|
uniform float uIntensity;
|
|
292
354
|
uniform float uAmbient;
|
|
293
355
|
uniform float uOpacity;
|
|
294
356
|
out vec4 fragColor;
|
|
295
357
|
void main() {
|
|
296
|
-
|
|
358
|
+
vec3 normal = normalize(vNormal);
|
|
359
|
+
vec3 toLight = normalize(uToLight);
|
|
360
|
+
float lambert = max(dot(normal, toLight), 0.0);
|
|
297
361
|
float brightness = clamp(uAmbient + uIntensity * lambert, 0.0, 1.0);
|
|
298
|
-
|
|
362
|
+
vec3 lit = vColor * brightness;
|
|
363
|
+
if (lambert > 0.0) {
|
|
364
|
+
vec3 viewDir = normalize(uEye - vWorldPos);
|
|
365
|
+
vec3 halfVec = normalize(toLight + viewDir);
|
|
366
|
+
float highlight = pow(max(dot(normal, halfVec), 0.0), vSpecular.w) * uIntensity;
|
|
367
|
+
lit += highlight * vSpecular.xyz;
|
|
368
|
+
}
|
|
369
|
+
lit += vEmissive;
|
|
370
|
+
fragColor = vec4(lit, uOpacity);
|
|
299
371
|
}`;
|
|
300
372
|
WebGLRenderer = class {
|
|
301
373
|
constructor(options = {}) {
|
|
@@ -316,9 +388,12 @@ void main() {
|
|
|
316
388
|
aPos: gl.getAttribLocation(this.program, "aPos"),
|
|
317
389
|
aNormal: gl.getAttribLocation(this.program, "aNormal"),
|
|
318
390
|
aColor: gl.getAttribLocation(this.program, "aColor"),
|
|
391
|
+
aEmissive: gl.getAttribLocation(this.program, "aEmissive"),
|
|
392
|
+
aSpecular: gl.getAttribLocation(this.program, "aSpecular"),
|
|
319
393
|
uViewProj: gl.getUniformLocation(this.program, "uViewProj"),
|
|
320
394
|
uModel: gl.getUniformLocation(this.program, "uModel"),
|
|
321
395
|
uToLight: gl.getUniformLocation(this.program, "uToLight"),
|
|
396
|
+
uEye: gl.getUniformLocation(this.program, "uEye"),
|
|
322
397
|
uIntensity: gl.getUniformLocation(this.program, "uIntensity"),
|
|
323
398
|
uAmbient: gl.getUniformLocation(this.program, "uAmbient"),
|
|
324
399
|
uOpacity: gl.getUniformLocation(this.program, "uOpacity")
|
|
@@ -342,19 +417,23 @@ void main() {
|
|
|
342
417
|
const data = expandToTriangles(mesh);
|
|
343
418
|
const vao = gl.createVertexArray();
|
|
344
419
|
gl.bindVertexArray(vao);
|
|
345
|
-
const
|
|
420
|
+
const buffers = [];
|
|
421
|
+
const attribute = (location, array, size = 3) => {
|
|
346
422
|
if (location < 0) return;
|
|
347
423
|
const buffer = gl.createBuffer();
|
|
424
|
+
buffers.push(buffer);
|
|
348
425
|
gl.bindBuffer(gl.ARRAY_BUFFER, buffer);
|
|
349
426
|
gl.bufferData(gl.ARRAY_BUFFER, array, gl.STATIC_DRAW);
|
|
350
427
|
gl.enableVertexAttribArray(location);
|
|
351
|
-
gl.vertexAttribPointer(location,
|
|
428
|
+
gl.vertexAttribPointer(location, size, gl.FLOAT, false, 0, 0);
|
|
352
429
|
};
|
|
353
430
|
attribute(loc.aPos, data.positions);
|
|
354
431
|
attribute(loc.aNormal, data.normals);
|
|
355
432
|
attribute(loc.aColor, data.colors);
|
|
433
|
+
attribute(loc.aEmissive, data.emissives);
|
|
434
|
+
attribute(loc.aSpecular, data.speculars, 4);
|
|
356
435
|
gl.bindVertexArray(null);
|
|
357
|
-
const result = { vao, count: data.count };
|
|
436
|
+
const result = { vao, buffers, count: data.count };
|
|
358
437
|
this.cache.set(mesh, result);
|
|
359
438
|
return result;
|
|
360
439
|
}
|
|
@@ -367,6 +446,7 @@ void main() {
|
|
|
367
446
|
gl.useProgram(this.program);
|
|
368
447
|
gl.uniformMatrix4fv(loc.uViewProj, false, new Float32Array(frame.viewProjection));
|
|
369
448
|
gl.uniform3f(loc.uToLight, frame.light.toLight.x, frame.light.toLight.y, frame.light.toLight.z);
|
|
449
|
+
gl.uniform3f(loc.uEye, frame.eye.x, frame.eye.y, frame.eye.z);
|
|
370
450
|
gl.uniform1f(loc.uIntensity, frame.light.intensity);
|
|
371
451
|
gl.uniform1f(loc.uAmbient, frame.light.ambient);
|
|
372
452
|
gl.disable(gl.BLEND);
|
|
@@ -411,7 +491,10 @@ void main() {
|
|
|
411
491
|
destroy() {
|
|
412
492
|
const gl = this.gl;
|
|
413
493
|
if (gl) {
|
|
414
|
-
for (const mesh of this.cache.values())
|
|
494
|
+
for (const mesh of this.cache.values()) {
|
|
495
|
+
gl.deleteVertexArray(mesh.vao);
|
|
496
|
+
for (const buffer of mesh.buffers) gl.deleteBuffer(buffer);
|
|
497
|
+
}
|
|
415
498
|
if (this.program) gl.deleteProgram(this.program);
|
|
416
499
|
}
|
|
417
500
|
this.cache.clear();
|
|
@@ -441,6 +524,7 @@ struct Uniforms {
|
|
|
441
524
|
model: mat4x4<f32>,
|
|
442
525
|
toLight: vec4<f32>,
|
|
443
526
|
params: vec4<f32>,
|
|
527
|
+
eye: vec4<f32>,
|
|
444
528
|
};
|
|
445
529
|
@group(0) @binding(0) var<uniform> u: Uniforms;
|
|
446
530
|
|
|
@@ -448,23 +532,40 @@ struct VSOut {
|
|
|
448
532
|
@builtin(position) position: vec4<f32>,
|
|
449
533
|
@location(0) normal: vec3<f32>,
|
|
450
534
|
@location(1) color: vec3<f32>,
|
|
535
|
+
@location(2) emissive: vec3<f32>,
|
|
536
|
+
@location(3) specular: vec4<f32>,
|
|
537
|
+
@location(4) worldPos: vec3<f32>,
|
|
451
538
|
};
|
|
452
539
|
|
|
453
540
|
@vertex
|
|
454
|
-
fn vs(@location(0) pos: vec3<f32>, @location(1) normal: vec3<f32>, @location(2) color: vec3<f32>) -> VSOut {
|
|
541
|
+
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 {
|
|
455
542
|
var out: VSOut;
|
|
456
543
|
let m = mat3x3<f32>(u.model[0].xyz, u.model[1].xyz, u.model[2].xyz);
|
|
457
544
|
out.normal = m * normal;
|
|
458
545
|
out.color = color;
|
|
459
|
-
out.
|
|
546
|
+
out.emissive = emissive;
|
|
547
|
+
out.specular = specular;
|
|
548
|
+
let world = u.model * vec4<f32>(pos, 1.0);
|
|
549
|
+
out.worldPos = world.xyz;
|
|
550
|
+
out.position = u.viewProj * world;
|
|
460
551
|
return out;
|
|
461
552
|
}
|
|
462
553
|
|
|
463
554
|
@fragment
|
|
464
555
|
fn fs(in: VSOut) -> @location(0) vec4<f32> {
|
|
465
|
-
let
|
|
556
|
+
let normal = normalize(in.normal);
|
|
557
|
+
let toLight = normalize(u.toLight.xyz);
|
|
558
|
+
let lambert = max(dot(normal, toLight), 0.0);
|
|
466
559
|
let brightness = clamp(u.params.y + u.params.x * lambert, 0.0, 1.0);
|
|
467
|
-
|
|
560
|
+
var lit = in.color * brightness;
|
|
561
|
+
if (lambert > 0.0) {
|
|
562
|
+
let viewDir = normalize(u.eye.xyz - in.worldPos);
|
|
563
|
+
let halfVec = normalize(toLight + viewDir);
|
|
564
|
+
let highlight = pow(max(dot(normal, halfVec), 0.0), in.specular.w) * u.params.x;
|
|
565
|
+
lit = lit + highlight * in.specular.xyz;
|
|
566
|
+
}
|
|
567
|
+
lit = lit + in.emissive;
|
|
568
|
+
return vec4<f32>(lit, u.params.z);
|
|
468
569
|
}
|
|
469
570
|
`;
|
|
470
571
|
CLIP_Z_FIX = [1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0.5, 0, 0, 0, 0.5, 1];
|
|
@@ -505,13 +606,15 @@ fn fs(in: VSOut) -> @location(0) vec4<f32> {
|
|
|
505
606
|
{
|
|
506
607
|
binding: 0,
|
|
507
608
|
visibility: stage.VERTEX | stage.FRAGMENT,
|
|
508
|
-
buffer: { type: "uniform", hasDynamicOffset: true, minBindingSize:
|
|
609
|
+
buffer: { type: "uniform", hasDynamicOffset: true, minBindingSize: 176 }
|
|
509
610
|
}
|
|
510
611
|
]
|
|
511
612
|
});
|
|
512
|
-
const vertexBuffer = (location) => ({
|
|
513
|
-
arrayStride:
|
|
514
|
-
attributes: [
|
|
613
|
+
const vertexBuffer = (location, components = 3) => ({
|
|
614
|
+
arrayStride: components * 4,
|
|
615
|
+
attributes: [
|
|
616
|
+
{ shaderLocation: location, offset: 0, format: `float32x${components}` }
|
|
617
|
+
]
|
|
515
618
|
});
|
|
516
619
|
const pipelineLayout = device.createPipelineLayout({ bindGroupLayouts: [layout] });
|
|
517
620
|
const blend = {
|
|
@@ -527,7 +630,13 @@ fn fs(in: VSOut) -> @location(0) vec4<f32> {
|
|
|
527
630
|
vertex: {
|
|
528
631
|
module,
|
|
529
632
|
entryPoint: "vs",
|
|
530
|
-
buffers: [
|
|
633
|
+
buffers: [
|
|
634
|
+
vertexBuffer(0),
|
|
635
|
+
vertexBuffer(1),
|
|
636
|
+
vertexBuffer(2),
|
|
637
|
+
vertexBuffer(3),
|
|
638
|
+
vertexBuffer(4, 4)
|
|
639
|
+
]
|
|
531
640
|
},
|
|
532
641
|
fragment: {
|
|
533
642
|
module,
|
|
@@ -580,6 +689,8 @@ fn fs(in: VSOut) -> @location(0) vec4<f32> {
|
|
|
580
689
|
position: upload(data.positions),
|
|
581
690
|
normal: upload(data.normals),
|
|
582
691
|
color: upload(data.colors),
|
|
692
|
+
emissive: upload(data.emissives),
|
|
693
|
+
specular: upload(data.speculars),
|
|
583
694
|
count: data.count
|
|
584
695
|
};
|
|
585
696
|
this.cache.set(mesh, result);
|
|
@@ -631,7 +742,7 @@ fn fs(in: VSOut) -> @location(0) vec4<f32> {
|
|
|
631
742
|
const bindGroup = this.device.createBindGroup({
|
|
632
743
|
layout,
|
|
633
744
|
entries: [
|
|
634
|
-
{ binding: 0, resource: { buffer: this.uniformBuffer, offset: 0, size:
|
|
745
|
+
{ binding: 0, resource: { buffer: this.uniformBuffer, offset: 0, size: 176 } }
|
|
635
746
|
]
|
|
636
747
|
});
|
|
637
748
|
draws.forEach((draw, i) => {
|
|
@@ -640,6 +751,7 @@ fn fs(in: VSOut) -> @location(0) vec4<f32> {
|
|
|
640
751
|
data.set(draw.item.model, 16);
|
|
641
752
|
data.set([frame.light.toLight.x, frame.light.toLight.y, frame.light.toLight.z, 0], 32);
|
|
642
753
|
data.set([frame.light.intensity, frame.light.ambient, draw.opacity, 0], 36);
|
|
754
|
+
data.set([frame.eye.x, frame.eye.y, frame.eye.z, 0], 40);
|
|
643
755
|
this.device.queue.writeBuffer(this.uniformBuffer, i * UNIFORM_STRIDE, data);
|
|
644
756
|
});
|
|
645
757
|
const encoder = this.device.createCommandEncoder();
|
|
@@ -666,6 +778,8 @@ fn fs(in: VSOut) -> @location(0) vec4<f32> {
|
|
|
666
778
|
pass.setVertexBuffer(0, mesh.position);
|
|
667
779
|
pass.setVertexBuffer(1, mesh.normal);
|
|
668
780
|
pass.setVertexBuffer(2, mesh.color);
|
|
781
|
+
pass.setVertexBuffer(3, mesh.emissive);
|
|
782
|
+
pass.setVertexBuffer(4, mesh.specular);
|
|
669
783
|
pass.draw(mesh.count);
|
|
670
784
|
});
|
|
671
785
|
pass.end();
|
|
@@ -677,6 +791,8 @@ fn fs(in: VSOut) -> @location(0) vec4<f32> {
|
|
|
677
791
|
mesh.position.destroy?.();
|
|
678
792
|
mesh.normal.destroy?.();
|
|
679
793
|
mesh.color.destroy?.();
|
|
794
|
+
mesh.emissive.destroy?.();
|
|
795
|
+
mesh.specular.destroy?.();
|
|
680
796
|
}
|
|
681
797
|
this.cache.clear();
|
|
682
798
|
this.uniformBuffer?.destroy?.();
|
|
@@ -759,9 +875,30 @@ fn fs(in: VSOut) -> @location(0) vec4<f32> {
|
|
|
759
875
|
depth += dot(d, d);
|
|
760
876
|
}
|
|
761
877
|
depth /= face.indices.length;
|
|
878
|
+
let surface;
|
|
879
|
+
const material = face.material;
|
|
880
|
+
if (material) {
|
|
881
|
+
if (material.specular) {
|
|
882
|
+
let cx = 0;
|
|
883
|
+
let cy = 0;
|
|
884
|
+
let cz = 0;
|
|
885
|
+
for (const i of face.indices) {
|
|
886
|
+
cx += world[i].x;
|
|
887
|
+
cy += world[i].y;
|
|
888
|
+
cz += world[i].z;
|
|
889
|
+
}
|
|
890
|
+
const inv = 1 / face.indices.length;
|
|
891
|
+
const viewDir = normalize(
|
|
892
|
+
subtract(frame.eye, { x: cx * inv, y: cy * inv, z: cz * inv })
|
|
893
|
+
);
|
|
894
|
+
surface = { material, viewDir };
|
|
895
|
+
} else {
|
|
896
|
+
surface = { material };
|
|
897
|
+
}
|
|
898
|
+
}
|
|
762
899
|
polygons.push({
|
|
763
900
|
points,
|
|
764
|
-
color: shadeColor(normal, face.color, frame.light),
|
|
901
|
+
color: shadeColor(normal, face.color, frame.light, surface),
|
|
765
902
|
depth,
|
|
766
903
|
opacity: faceOpacity
|
|
767
904
|
});
|
|
@@ -1299,9 +1436,11 @@ void main() {
|
|
|
1299
1436
|
const data = expandToTriangles(mesh);
|
|
1300
1437
|
const vao = gl.createVertexArray();
|
|
1301
1438
|
gl.bindVertexArray(vao);
|
|
1439
|
+
const buffers = [];
|
|
1302
1440
|
const attribute = (location, array, size) => {
|
|
1303
1441
|
if (location < 0) return;
|
|
1304
1442
|
const buffer = gl.createBuffer();
|
|
1443
|
+
buffers.push(buffer);
|
|
1305
1444
|
gl.bindBuffer(gl.ARRAY_BUFFER, buffer);
|
|
1306
1445
|
gl.bufferData(gl.ARRAY_BUFFER, array, gl.STATIC_DRAW);
|
|
1307
1446
|
gl.enableVertexAttribArray(location);
|
|
@@ -1311,7 +1450,7 @@ void main() {
|
|
|
1311
1450
|
attribute(loc.aColor, data.colors, 3);
|
|
1312
1451
|
attribute(loc.aUV, planarUVs(mesh), 2);
|
|
1313
1452
|
gl.bindVertexArray(null);
|
|
1314
|
-
const result = { vao, count: data.count };
|
|
1453
|
+
const result = { vao, buffers, count: data.count };
|
|
1315
1454
|
this.buffers.set(mesh, result);
|
|
1316
1455
|
return result;
|
|
1317
1456
|
}
|
|
@@ -1349,7 +1488,10 @@ void main() {
|
|
|
1349
1488
|
const gl = this.gl;
|
|
1350
1489
|
if (gl) {
|
|
1351
1490
|
for (const texture of this.textures.values()) gl.deleteTexture(texture);
|
|
1352
|
-
for (const
|
|
1491
|
+
for (const cached of this.buffers.values()) {
|
|
1492
|
+
gl.deleteVertexArray(cached.vao);
|
|
1493
|
+
for (const buffer of cached.buffers) gl.deleteBuffer(buffer);
|
|
1494
|
+
}
|
|
1353
1495
|
if (this.program) gl.deleteProgram(this.program);
|
|
1354
1496
|
}
|
|
1355
1497
|
this.textures.clear();
|
|
@@ -1501,6 +1643,7 @@ void main() {
|
|
|
1501
1643
|
var stdin_exports = {};
|
|
1502
1644
|
__export(stdin_exports, {
|
|
1503
1645
|
Camera: () => Camera,
|
|
1646
|
+
Canvas2DTexturedRenderer: () => Canvas2DTexturedRenderer,
|
|
1504
1647
|
ChargedOrbAnimation: () => ChargedOrbAnimation,
|
|
1505
1648
|
CompositeAnimation: () => CompositeAnimation,
|
|
1506
1649
|
GridAssemblyAnimation: () => GridAssemblyAnimation,
|
|
@@ -1512,6 +1655,7 @@ void main() {
|
|
|
1512
1655
|
SpinAnimation: () => SpinAnimation,
|
|
1513
1656
|
WebGLTexturedRenderer: () => WebGLTexturedRenderer,
|
|
1514
1657
|
WebGPUTexturedRenderer: () => WebGPUTexturedRenderer,
|
|
1658
|
+
attachMaterial: () => attachMaterial,
|
|
1515
1659
|
centerAndScaleMesh: () => centerAndScaleMesh,
|
|
1516
1660
|
chargedOrb: () => chargedOrb,
|
|
1517
1661
|
circleMotion: () => circleMotion,
|
|
@@ -1729,6 +1873,12 @@ void main() {
|
|
|
1729
1873
|
init_math();
|
|
1730
1874
|
|
|
1731
1875
|
// src/engines/little-3d-engine/core/mesh.ts
|
|
1876
|
+
function attachMaterial(mesh, material) {
|
|
1877
|
+
if (material) {
|
|
1878
|
+
for (const face of mesh.faces) face.material = material;
|
|
1879
|
+
}
|
|
1880
|
+
return mesh;
|
|
1881
|
+
}
|
|
1732
1882
|
function transform(init) {
|
|
1733
1883
|
return {
|
|
1734
1884
|
position: init?.position ?? { x: 0, y: 0, z: 0 },
|
|
@@ -1743,7 +1893,7 @@ void main() {
|
|
|
1743
1893
|
|
|
1744
1894
|
// src/engines/little-3d-engine/shapes/primitives/cube.ts
|
|
1745
1895
|
var DEFAULT_COLORS = ["#3b82f6", "#8b5cf6", "#ec4899", "#f59e0b", "#10b981", "#ef4444"];
|
|
1746
|
-
function cube(size = 1, colors = DEFAULT_COLORS) {
|
|
1896
|
+
function cube(size = 1, colors = DEFAULT_COLORS, material) {
|
|
1747
1897
|
const h = size / 2;
|
|
1748
1898
|
const vertices = [
|
|
1749
1899
|
{ x: -h, y: -h, z: h },
|
|
@@ -1763,12 +1913,12 @@ void main() {
|
|
|
1763
1913
|
{ indices: [1, 5, 6, 2], color: colors[4 % colors.length] },
|
|
1764
1914
|
{ indices: [4, 0, 3, 7], color: colors[5 % colors.length] }
|
|
1765
1915
|
];
|
|
1766
|
-
return { vertices, faces };
|
|
1916
|
+
return attachMaterial({ vertices, faces }, material);
|
|
1767
1917
|
}
|
|
1768
1918
|
|
|
1769
1919
|
// src/engines/little-3d-engine/shapes/primitives/quad.ts
|
|
1770
1920
|
var DEFAULT_COLORS2 = ["#3b82f6"];
|
|
1771
|
-
function quad(size = 1, colors = DEFAULT_COLORS2) {
|
|
1921
|
+
function quad(size = 1, colors = DEFAULT_COLORS2, material) {
|
|
1772
1922
|
const s = size / 2;
|
|
1773
1923
|
const vertices = [
|
|
1774
1924
|
{ x: -s, y: -s, z: 0 },
|
|
@@ -1776,12 +1926,15 @@ void main() {
|
|
|
1776
1926
|
{ x: s, y: s, z: 0 },
|
|
1777
1927
|
{ x: -s, y: s, z: 0 }
|
|
1778
1928
|
];
|
|
1779
|
-
return
|
|
1929
|
+
return attachMaterial(
|
|
1930
|
+
{ vertices, faces: [{ indices: [0, 1, 2, 3], color: colors[0] }] },
|
|
1931
|
+
material
|
|
1932
|
+
);
|
|
1780
1933
|
}
|
|
1781
1934
|
|
|
1782
1935
|
// src/engines/little-3d-engine/shapes/primitives/tetrahedron.ts
|
|
1783
1936
|
var DEFAULT_COLORS3 = ["#3b82f6", "#8b5cf6", "#ec4899", "#f59e0b"];
|
|
1784
|
-
function tetrahedron(size = 1, colors = DEFAULT_COLORS3) {
|
|
1937
|
+
function tetrahedron(size = 1, colors = DEFAULT_COLORS3, material) {
|
|
1785
1938
|
const s = size / 2;
|
|
1786
1939
|
const vertices = [
|
|
1787
1940
|
{ x: s, y: s, z: s },
|
|
@@ -1795,7 +1948,7 @@ void main() {
|
|
|
1795
1948
|
{ indices: [0, 2, 3], color: colors[2 % colors.length] },
|
|
1796
1949
|
{ indices: [1, 3, 2], color: colors[3 % colors.length] }
|
|
1797
1950
|
];
|
|
1798
|
-
return { vertices, faces };
|
|
1951
|
+
return attachMaterial({ vertices, faces }, material);
|
|
1799
1952
|
}
|
|
1800
1953
|
|
|
1801
1954
|
// src/engines/little-3d-engine/shapes/primitives/octahedron.ts
|
|
@@ -1809,7 +1962,7 @@ void main() {
|
|
|
1809
1962
|
"#06b6d4",
|
|
1810
1963
|
"#eab308"
|
|
1811
1964
|
];
|
|
1812
|
-
function octahedron(size = 1, colors = DEFAULT_COLORS4) {
|
|
1965
|
+
function octahedron(size = 1, colors = DEFAULT_COLORS4, material) {
|
|
1813
1966
|
const r = size / 2;
|
|
1814
1967
|
const vertices = [
|
|
1815
1968
|
{ x: r, y: 0, z: 0 },
|
|
@@ -1829,12 +1982,12 @@ void main() {
|
|
|
1829
1982
|
{ indices: [5, 3, 1], color: colors[6 % colors.length] },
|
|
1830
1983
|
{ indices: [5, 0, 3], color: colors[7 % colors.length] }
|
|
1831
1984
|
];
|
|
1832
|
-
return { vertices, faces };
|
|
1985
|
+
return attachMaterial({ vertices, faces }, material);
|
|
1833
1986
|
}
|
|
1834
1987
|
|
|
1835
1988
|
// src/engines/little-3d-engine/shapes/primitives/pyramid.ts
|
|
1836
1989
|
var DEFAULT_COLORS5 = ["#3b82f6", "#8b5cf6", "#ec4899", "#f59e0b", "#10b981"];
|
|
1837
|
-
function pyramid(size = 1, colors = DEFAULT_COLORS5) {
|
|
1990
|
+
function pyramid(size = 1, colors = DEFAULT_COLORS5, material) {
|
|
1838
1991
|
const h = size / 2;
|
|
1839
1992
|
const vertices = [
|
|
1840
1993
|
{ x: -h, y: -h, z: h },
|
|
@@ -1850,12 +2003,12 @@ void main() {
|
|
|
1850
2003
|
{ indices: [4, 2, 3], color: colors[3 % colors.length] },
|
|
1851
2004
|
{ indices: [4, 3, 0], color: colors[4 % colors.length] }
|
|
1852
2005
|
];
|
|
1853
|
-
return { vertices, faces };
|
|
2006
|
+
return attachMaterial({ vertices, faces }, material);
|
|
1854
2007
|
}
|
|
1855
2008
|
|
|
1856
2009
|
// src/engines/little-3d-engine/shapes/primitives/spheres/uv-sphere.ts
|
|
1857
2010
|
var DEFAULT_COLORS6 = ["#3b82f6", "#8b5cf6", "#ec4899", "#f59e0b", "#10b981", "#ef4444"];
|
|
1858
|
-
function uvSphere(size = 1, detail = 1, colors = DEFAULT_COLORS6) {
|
|
2011
|
+
function uvSphere(size = 1, detail = 1, colors = DEFAULT_COLORS6, material) {
|
|
1859
2012
|
const r = size / 2;
|
|
1860
2013
|
const d = Math.max(1, Math.floor(detail));
|
|
1861
2014
|
const slices = Math.max(4, d * 4);
|
|
@@ -1894,7 +2047,7 @@ void main() {
|
|
|
1894
2047
|
color: color()
|
|
1895
2048
|
});
|
|
1896
2049
|
}
|
|
1897
|
-
return { vertices, faces };
|
|
2050
|
+
return attachMaterial({ vertices, faces }, material);
|
|
1898
2051
|
}
|
|
1899
2052
|
|
|
1900
2053
|
// src/engines/little-3d-engine/shapes/primitives/spheres/icosphere.ts
|
|
@@ -1937,8 +2090,11 @@ void main() {
|
|
|
1937
2090
|
[8, 6, 7],
|
|
1938
2091
|
[9, 8, 1]
|
|
1939
2092
|
];
|
|
1940
|
-
function icosphere(size = 1, detail = 1, colors = DEFAULT_COLORS7) {
|
|
1941
|
-
return
|
|
2093
|
+
function icosphere(size = 1, detail = 1, colors = DEFAULT_COLORS7, material) {
|
|
2094
|
+
return attachMaterial(
|
|
2095
|
+
sphereFromTriangles(SEED_VERTICES, SEED_FACES, size, detail, colors),
|
|
2096
|
+
material
|
|
2097
|
+
);
|
|
1942
2098
|
}
|
|
1943
2099
|
|
|
1944
2100
|
// src/engines/little-3d-engine/shapes/primitives/spheres/octa-sphere.ts
|
|
@@ -1962,8 +2118,11 @@ void main() {
|
|
|
1962
2118
|
[5, 3, 1],
|
|
1963
2119
|
[5, 0, 3]
|
|
1964
2120
|
];
|
|
1965
|
-
function octaSphere(size = 1, detail = 1, colors = DEFAULT_COLORS8) {
|
|
1966
|
-
return
|
|
2121
|
+
function octaSphere(size = 1, detail = 1, colors = DEFAULT_COLORS8, material) {
|
|
2122
|
+
return attachMaterial(
|
|
2123
|
+
sphereFromTriangles(SEED_VERTICES2, SEED_FACES2, size, detail, colors),
|
|
2124
|
+
material
|
|
2125
|
+
);
|
|
1967
2126
|
}
|
|
1968
2127
|
|
|
1969
2128
|
// src/engines/little-3d-engine/shapes/primitives/spheres/cube-sphere.ts
|
|
@@ -1976,7 +2135,7 @@ void main() {
|
|
|
1976
2135
|
{ normal: [0, 1, 0], right: [1, 0, 0], up: [0, 0, -1] },
|
|
1977
2136
|
{ normal: [0, -1, 0], right: [1, 0, 0], up: [0, 0, 1] }
|
|
1978
2137
|
];
|
|
1979
|
-
function cubeSphere(size = 1, detail = 1, colors = DEFAULT_COLORS9) {
|
|
2138
|
+
function cubeSphere(size = 1, detail = 1, colors = DEFAULT_COLORS9, material) {
|
|
1980
2139
|
const r = size / 2;
|
|
1981
2140
|
const n = Math.max(1, Math.floor(detail));
|
|
1982
2141
|
const vertices = [];
|
|
@@ -2005,13 +2164,13 @@ void main() {
|
|
|
2005
2164
|
}
|
|
2006
2165
|
}
|
|
2007
2166
|
}
|
|
2008
|
-
return { vertices, faces };
|
|
2167
|
+
return attachMaterial({ vertices, faces }, material);
|
|
2009
2168
|
}
|
|
2010
2169
|
|
|
2011
2170
|
// src/engines/little-3d-engine/shapes/complex/plane.ts
|
|
2012
2171
|
var DEFAULT_COLORS10 = ["#e0f2fe", "#7dd3fc", "#38bdf8", "#f8fafc"];
|
|
2013
|
-
function planeMesh(colors = DEFAULT_COLORS10) {
|
|
2014
|
-
return {
|
|
2172
|
+
function planeMesh(colors = DEFAULT_COLORS10, material) {
|
|
2173
|
+
return attachMaterial({
|
|
2015
2174
|
vertices: [
|
|
2016
2175
|
{ x: 0.9, y: 0, z: 0 },
|
|
2017
2176
|
{ x: -0.2, y: 0, z: 0.82 },
|
|
@@ -2039,7 +2198,7 @@ void main() {
|
|
|
2039
2198
|
{ indices: [3, 6, 8], color: colors[0] ?? DEFAULT_COLORS10[0] },
|
|
2040
2199
|
{ indices: [3, 8, 6], color: colors[1] ?? DEFAULT_COLORS10[1] }
|
|
2041
2200
|
]
|
|
2042
|
-
};
|
|
2201
|
+
}, material);
|
|
2043
2202
|
}
|
|
2044
2203
|
|
|
2045
2204
|
// src/engines/little-3d-engine/textures/dynamic/canvas-texture.ts
|
|
@@ -2596,10 +2755,14 @@ void main() {
|
|
|
2596
2755
|
const pick = Array.isArray(color) ? (i) => color[i % color.length] : () => color;
|
|
2597
2756
|
return { vertices: mesh.vertices, faces: mesh.faces.map((f, i) => ({ ...f, color: pick(i) })) };
|
|
2598
2757
|
}
|
|
2758
|
+
function applyMaterial(mesh, material) {
|
|
2759
|
+
if (!material) return mesh;
|
|
2760
|
+
return { vertices: mesh.vertices, faces: mesh.faces.map((f) => ({ ...f, material })) };
|
|
2761
|
+
}
|
|
2599
2762
|
var SpinAnimation = class {
|
|
2600
2763
|
constructor(options = {}) {
|
|
2601
2764
|
this.exited = false;
|
|
2602
|
-
this.mesh = applyColor(resolveMesh(options.shape), options.color);
|
|
2765
|
+
this.mesh = applyMaterial(applyColor(resolveMesh(options.shape), options.color), options.material);
|
|
2603
2766
|
this.spinX = options.spinX ?? 7e-4;
|
|
2604
2767
|
this.spinY = options.spinY ?? 11e-4;
|
|
2605
2768
|
this.backend = options.backend;
|
|
@@ -3292,6 +3455,7 @@ void main() {
|
|
|
3292
3455
|
var CENTER_POP_OUT_AT = REENTER_MS + (MINIS - 1) * REENTER_STAGGER_MS + 150;
|
|
3293
3456
|
var CENTER_POP_OUT_MS = 420;
|
|
3294
3457
|
var PARKED = { x: 0, y: 0, z: 50 };
|
|
3458
|
+
var ORB_MATERIAL = { specular: [1, 1, 1], shininess: 28 };
|
|
3295
3459
|
var CENTER_COLORS = ["#67e8f9", "#22d3ee", "#0ea5e9", "#38bdf8", "#7dd3fc"];
|
|
3296
3460
|
var MINI_COLORS = [
|
|
3297
3461
|
["#e0f2fe", "#bae6fd", "#7dd3fc"],
|
|
@@ -3320,9 +3484,9 @@ void main() {
|
|
|
3320
3484
|
backend: this.backend,
|
|
3321
3485
|
camera: { position: { x: 0, y: 0, z: CAMERA_Z } }
|
|
3322
3486
|
});
|
|
3323
|
-
this.center = engine.add(icosphere(1, 2, CENTER_COLORS), { scale: 0 });
|
|
3487
|
+
this.center = engine.add(icosphere(1, 2, CENTER_COLORS, ORB_MATERIAL), { scale: 0 });
|
|
3324
3488
|
for (let i = 0; i < MINIS; i++) {
|
|
3325
|
-
const mesh = icosphere(1, 1, MINI_COLORS[i % MINI_COLORS.length]);
|
|
3489
|
+
const mesh = icosphere(1, 1, MINI_COLORS[i % MINI_COLORS.length], ORB_MATERIAL);
|
|
3326
3490
|
this.minis.push(engine.add(mesh, { scale: 0, transparency: { ...MINI_TRANSPARENCY } }));
|
|
3327
3491
|
}
|
|
3328
3492
|
this.engine = engine;
|
|
@@ -3907,6 +4071,7 @@ void main() {
|
|
|
3907
4071
|
var TRAIL_OUTRO_MS = 1200;
|
|
3908
4072
|
var TRANSPARENCY = { mode: "two-sided", opacity: 0.275 };
|
|
3909
4073
|
var CAR_COLORS = ["#bae6fd", "#7dd3fc", "#38bdf8", "#0ea5e9", "#a5f3fc", "#e0f2fe"];
|
|
4074
|
+
var CAR_MATERIAL = { emissive: [0.1, 0.2, 0.3] };
|
|
3910
4075
|
var WORLD_UP2 = { x: 0, y: 1, z: 0 };
|
|
3911
4076
|
function clamp016(value) {
|
|
3912
4077
|
return Math.max(0, Math.min(1, value));
|
|
@@ -3967,7 +4132,7 @@ void main() {
|
|
|
3967
4132
|
backend: this.backend,
|
|
3968
4133
|
camera: { position: { x: 0, y: 0, z: CAMERA_Z3 }, fov: FOV2 }
|
|
3969
4134
|
});
|
|
3970
|
-
const mesh = cube(1, CAR_COLORS);
|
|
4135
|
+
const mesh = cube(1, CAR_COLORS, CAR_MATERIAL);
|
|
3971
4136
|
for (let i = 0; i < MAX_CARS; i++) {
|
|
3972
4137
|
this.cars.push(engine.add(mesh, { scale: 0, transparency: { ...TRANSPARENCY } }));
|
|
3973
4138
|
}
|
|
@@ -4649,32 +4814,69 @@ void main() {
|
|
|
4649
4814
|
|
|
4650
4815
|
// <stdin>
|
|
4651
4816
|
init_webgl_textured();
|
|
4817
|
+
init_canvas2d_textured();
|
|
4652
4818
|
init_webgpu_textured();
|
|
4653
4819
|
|
|
4654
4820
|
// src/engines/little-3d-engine/loaders/obj.ts
|
|
4655
4821
|
var DEFAULT_COLORS12 = ["#3b82f6", "#8b5cf6", "#ec4899", "#f59e0b", "#10b981", "#ef4444"];
|
|
4822
|
+
function clamp018(value) {
|
|
4823
|
+
return Math.min(1, Math.max(0, value));
|
|
4824
|
+
}
|
|
4656
4825
|
function channelToHex(value) {
|
|
4657
4826
|
const channel = Number.parseFloat(value);
|
|
4658
4827
|
if (!Number.isFinite(channel)) return void 0;
|
|
4659
|
-
return Math.round(
|
|
4660
|
-
}
|
|
4661
|
-
function
|
|
4662
|
-
const
|
|
4663
|
-
|
|
4828
|
+
return Math.round(clamp018(channel) * 255).toString(16).padStart(2, "0");
|
|
4829
|
+
}
|
|
4830
|
+
function parseRgb(parts) {
|
|
4831
|
+
const channels = parts.slice(1, 4).map(Number.parseFloat);
|
|
4832
|
+
if (channels.length !== 3 || !channels.every(Number.isFinite)) return void 0;
|
|
4833
|
+
return [clamp018(channels[0]), clamp018(channels[1]), clamp018(channels[2])];
|
|
4834
|
+
}
|
|
4835
|
+
function toMaterial(surface) {
|
|
4836
|
+
const material = {};
|
|
4837
|
+
if (surface.specular) material.specular = surface.specular;
|
|
4838
|
+
if (surface.shininess !== void 0) material.shininess = surface.shininess;
|
|
4839
|
+
if (surface.emissive) material.emissive = surface.emissive;
|
|
4840
|
+
return Object.keys(material).length > 0 ? material : void 0;
|
|
4841
|
+
}
|
|
4842
|
+
function parseMtl(text) {
|
|
4843
|
+
const materials = /* @__PURE__ */ new Map();
|
|
4844
|
+
const surfaces = /* @__PURE__ */ new Map();
|
|
4845
|
+
let name;
|
|
4664
4846
|
for (const line of text.split("\n")) {
|
|
4665
4847
|
const trimmed = line.trim();
|
|
4666
4848
|
if (trimmed === "" || trimmed.startsWith("#")) continue;
|
|
4667
4849
|
const parts = trimmed.split(/\s+/);
|
|
4668
|
-
|
|
4669
|
-
|
|
4670
|
-
|
|
4850
|
+
const keyword = parts[0];
|
|
4851
|
+
if (keyword === "newmtl") {
|
|
4852
|
+
name = parts.slice(1).join(" ");
|
|
4853
|
+
if (name && !materials.has(name)) {
|
|
4854
|
+
materials.set(name, {});
|
|
4855
|
+
surfaces.set(name, {});
|
|
4856
|
+
}
|
|
4857
|
+
continue;
|
|
4858
|
+
}
|
|
4859
|
+
if (!name) continue;
|
|
4860
|
+
const entry = materials.get(name);
|
|
4861
|
+
const surface = surfaces.get(name);
|
|
4862
|
+
if (keyword === "Kd") {
|
|
4671
4863
|
const channels = parts.slice(1, 4).map(channelToHex);
|
|
4672
4864
|
if (channels.length === 3 && channels.every((channel) => channel !== void 0)) {
|
|
4673
|
-
|
|
4865
|
+
entry.color = `#${channels.join("")}`;
|
|
4674
4866
|
}
|
|
4867
|
+
} else if (keyword === "Ks") {
|
|
4868
|
+
surface.specular = parseRgb(parts);
|
|
4869
|
+
} else if (keyword === "Ns") {
|
|
4870
|
+
const ns = Number.parseFloat(parts[1]);
|
|
4871
|
+
if (Number.isFinite(ns)) surface.shininess = Math.max(0, ns);
|
|
4872
|
+
} else if (keyword === "Ke") {
|
|
4873
|
+
surface.emissive = parseRgb(parts);
|
|
4675
4874
|
}
|
|
4676
4875
|
}
|
|
4677
|
-
|
|
4876
|
+
for (const [key, surface] of surfaces) {
|
|
4877
|
+
materials.get(key).material = toMaterial(surface);
|
|
4878
|
+
}
|
|
4879
|
+
return materials;
|
|
4678
4880
|
}
|
|
4679
4881
|
function resolveIndex(token, vertexCount) {
|
|
4680
4882
|
const n = parseInt(token, 10);
|
|
@@ -4682,7 +4884,7 @@ void main() {
|
|
|
4682
4884
|
}
|
|
4683
4885
|
function parseObj(text, options = {}) {
|
|
4684
4886
|
const colors = options.colors ?? DEFAULT_COLORS12;
|
|
4685
|
-
const
|
|
4887
|
+
const materials = options.useMtlColors && options.mtl ? parseMtl(options.mtl) : void 0;
|
|
4686
4888
|
const vertices = [];
|
|
4687
4889
|
const faces = [];
|
|
4688
4890
|
let material;
|
|
@@ -4706,9 +4908,11 @@ void main() {
|
|
|
4706
4908
|
indices.push(resolveIndex(vertexToken, vertices.length));
|
|
4707
4909
|
}
|
|
4708
4910
|
if (indices.length >= 3) {
|
|
4709
|
-
const
|
|
4710
|
-
const color =
|
|
4711
|
-
|
|
4911
|
+
const entry = material ? materials?.get(material) : void 0;
|
|
4912
|
+
const color = entry?.color ?? (materials ? colors[0] ?? "#888888" : colors[faces.length % colors.length]);
|
|
4913
|
+
const face = { indices, color };
|
|
4914
|
+
if (entry?.material) face.material = entry.material;
|
|
4915
|
+
faces.push(face);
|
|
4712
4916
|
}
|
|
4713
4917
|
}
|
|
4714
4918
|
}
|