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
|
@@ -139,7 +139,10 @@ function expandToTriangles(mesh) {
|
|
|
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 @@ function expandToTriangles(mesh) {
|
|
|
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 @@ function expandToTriangles(mesh) {
|
|
|
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 init_geometry = __esm({
|
|
|
204
224
|
function clamp01(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 = clamp01(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 init_light = __esm({
|
|
|
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 init_webgl = __esm({
|
|
|
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,17 +417,19 @@ void main() {
|
|
|
342
417
|
const data = expandToTriangles(mesh);
|
|
343
418
|
const vao = gl.createVertexArray();
|
|
344
419
|
gl.bindVertexArray(vao);
|
|
345
|
-
const attribute = (location, array) => {
|
|
420
|
+
const attribute = (location, array, size = 3) => {
|
|
346
421
|
if (location < 0) return;
|
|
347
422
|
const buffer = gl.createBuffer();
|
|
348
423
|
gl.bindBuffer(gl.ARRAY_BUFFER, buffer);
|
|
349
424
|
gl.bufferData(gl.ARRAY_BUFFER, array, gl.STATIC_DRAW);
|
|
350
425
|
gl.enableVertexAttribArray(location);
|
|
351
|
-
gl.vertexAttribPointer(location,
|
|
426
|
+
gl.vertexAttribPointer(location, size, gl.FLOAT, false, 0, 0);
|
|
352
427
|
};
|
|
353
428
|
attribute(loc.aPos, data.positions);
|
|
354
429
|
attribute(loc.aNormal, data.normals);
|
|
355
430
|
attribute(loc.aColor, data.colors);
|
|
431
|
+
attribute(loc.aEmissive, data.emissives);
|
|
432
|
+
attribute(loc.aSpecular, data.speculars, 4);
|
|
356
433
|
gl.bindVertexArray(null);
|
|
357
434
|
const result = { vao, count: data.count };
|
|
358
435
|
this.cache.set(mesh, result);
|
|
@@ -367,6 +444,7 @@ void main() {
|
|
|
367
444
|
gl.useProgram(this.program);
|
|
368
445
|
gl.uniformMatrix4fv(loc.uViewProj, false, new Float32Array(frame.viewProjection));
|
|
369
446
|
gl.uniform3f(loc.uToLight, frame.light.toLight.x, frame.light.toLight.y, frame.light.toLight.z);
|
|
447
|
+
gl.uniform3f(loc.uEye, frame.eye.x, frame.eye.y, frame.eye.z);
|
|
370
448
|
gl.uniform1f(loc.uIntensity, frame.light.intensity);
|
|
371
449
|
gl.uniform1f(loc.uAmbient, frame.light.ambient);
|
|
372
450
|
gl.disable(gl.BLEND);
|
|
@@ -441,6 +519,7 @@ struct Uniforms {
|
|
|
441
519
|
model: mat4x4<f32>,
|
|
442
520
|
toLight: vec4<f32>,
|
|
443
521
|
params: vec4<f32>,
|
|
522
|
+
eye: vec4<f32>,
|
|
444
523
|
};
|
|
445
524
|
@group(0) @binding(0) var<uniform> u: Uniforms;
|
|
446
525
|
|
|
@@ -448,23 +527,40 @@ struct VSOut {
|
|
|
448
527
|
@builtin(position) position: vec4<f32>,
|
|
449
528
|
@location(0) normal: vec3<f32>,
|
|
450
529
|
@location(1) color: vec3<f32>,
|
|
530
|
+
@location(2) emissive: vec3<f32>,
|
|
531
|
+
@location(3) specular: vec4<f32>,
|
|
532
|
+
@location(4) worldPos: vec3<f32>,
|
|
451
533
|
};
|
|
452
534
|
|
|
453
535
|
@vertex
|
|
454
|
-
fn vs(@location(0) pos: vec3<f32>, @location(1) normal: vec3<f32>, @location(2) color: vec3<f32>) -> VSOut {
|
|
536
|
+
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
537
|
var out: VSOut;
|
|
456
538
|
let m = mat3x3<f32>(u.model[0].xyz, u.model[1].xyz, u.model[2].xyz);
|
|
457
539
|
out.normal = m * normal;
|
|
458
540
|
out.color = color;
|
|
459
|
-
out.
|
|
541
|
+
out.emissive = emissive;
|
|
542
|
+
out.specular = specular;
|
|
543
|
+
let world = u.model * vec4<f32>(pos, 1.0);
|
|
544
|
+
out.worldPos = world.xyz;
|
|
545
|
+
out.position = u.viewProj * world;
|
|
460
546
|
return out;
|
|
461
547
|
}
|
|
462
548
|
|
|
463
549
|
@fragment
|
|
464
550
|
fn fs(in: VSOut) -> @location(0) vec4<f32> {
|
|
465
|
-
let
|
|
551
|
+
let normal = normalize(in.normal);
|
|
552
|
+
let toLight = normalize(u.toLight.xyz);
|
|
553
|
+
let lambert = max(dot(normal, toLight), 0.0);
|
|
466
554
|
let brightness = clamp(u.params.y + u.params.x * lambert, 0.0, 1.0);
|
|
467
|
-
|
|
555
|
+
var lit = in.color * brightness;
|
|
556
|
+
if (lambert > 0.0) {
|
|
557
|
+
let viewDir = normalize(u.eye.xyz - in.worldPos);
|
|
558
|
+
let halfVec = normalize(toLight + viewDir);
|
|
559
|
+
let highlight = pow(max(dot(normal, halfVec), 0.0), in.specular.w) * u.params.x;
|
|
560
|
+
lit = lit + highlight * in.specular.xyz;
|
|
561
|
+
}
|
|
562
|
+
lit = lit + in.emissive;
|
|
563
|
+
return vec4<f32>(lit, u.params.z);
|
|
468
564
|
}
|
|
469
565
|
`;
|
|
470
566
|
CLIP_Z_FIX = [1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0.5, 0, 0, 0, 0.5, 1];
|
|
@@ -505,13 +601,15 @@ fn fs(in: VSOut) -> @location(0) vec4<f32> {
|
|
|
505
601
|
{
|
|
506
602
|
binding: 0,
|
|
507
603
|
visibility: stage.VERTEX | stage.FRAGMENT,
|
|
508
|
-
buffer: { type: "uniform", hasDynamicOffset: true, minBindingSize:
|
|
604
|
+
buffer: { type: "uniform", hasDynamicOffset: true, minBindingSize: 176 }
|
|
509
605
|
}
|
|
510
606
|
]
|
|
511
607
|
});
|
|
512
|
-
const vertexBuffer = (location) => ({
|
|
513
|
-
arrayStride:
|
|
514
|
-
attributes: [
|
|
608
|
+
const vertexBuffer = (location, components = 3) => ({
|
|
609
|
+
arrayStride: components * 4,
|
|
610
|
+
attributes: [
|
|
611
|
+
{ shaderLocation: location, offset: 0, format: `float32x${components}` }
|
|
612
|
+
]
|
|
515
613
|
});
|
|
516
614
|
const pipelineLayout = device.createPipelineLayout({ bindGroupLayouts: [layout] });
|
|
517
615
|
const blend = {
|
|
@@ -527,7 +625,13 @@ fn fs(in: VSOut) -> @location(0) vec4<f32> {
|
|
|
527
625
|
vertex: {
|
|
528
626
|
module: module2,
|
|
529
627
|
entryPoint: "vs",
|
|
530
|
-
buffers: [
|
|
628
|
+
buffers: [
|
|
629
|
+
vertexBuffer(0),
|
|
630
|
+
vertexBuffer(1),
|
|
631
|
+
vertexBuffer(2),
|
|
632
|
+
vertexBuffer(3),
|
|
633
|
+
vertexBuffer(4, 4)
|
|
634
|
+
]
|
|
531
635
|
},
|
|
532
636
|
fragment: {
|
|
533
637
|
module: module2,
|
|
@@ -580,6 +684,8 @@ fn fs(in: VSOut) -> @location(0) vec4<f32> {
|
|
|
580
684
|
position: upload(data.positions),
|
|
581
685
|
normal: upload(data.normals),
|
|
582
686
|
color: upload(data.colors),
|
|
687
|
+
emissive: upload(data.emissives),
|
|
688
|
+
specular: upload(data.speculars),
|
|
583
689
|
count: data.count
|
|
584
690
|
};
|
|
585
691
|
this.cache.set(mesh, result);
|
|
@@ -631,7 +737,7 @@ fn fs(in: VSOut) -> @location(0) vec4<f32> {
|
|
|
631
737
|
const bindGroup = this.device.createBindGroup({
|
|
632
738
|
layout,
|
|
633
739
|
entries: [
|
|
634
|
-
{ binding: 0, resource: { buffer: this.uniformBuffer, offset: 0, size:
|
|
740
|
+
{ binding: 0, resource: { buffer: this.uniformBuffer, offset: 0, size: 176 } }
|
|
635
741
|
]
|
|
636
742
|
});
|
|
637
743
|
draws.forEach((draw, i) => {
|
|
@@ -640,6 +746,7 @@ fn fs(in: VSOut) -> @location(0) vec4<f32> {
|
|
|
640
746
|
data.set(draw.item.model, 16);
|
|
641
747
|
data.set([frame.light.toLight.x, frame.light.toLight.y, frame.light.toLight.z, 0], 32);
|
|
642
748
|
data.set([frame.light.intensity, frame.light.ambient, draw.opacity, 0], 36);
|
|
749
|
+
data.set([frame.eye.x, frame.eye.y, frame.eye.z, 0], 40);
|
|
643
750
|
this.device.queue.writeBuffer(this.uniformBuffer, i * UNIFORM_STRIDE, data);
|
|
644
751
|
});
|
|
645
752
|
const encoder = this.device.createCommandEncoder();
|
|
@@ -666,6 +773,8 @@ fn fs(in: VSOut) -> @location(0) vec4<f32> {
|
|
|
666
773
|
pass.setVertexBuffer(0, mesh.position);
|
|
667
774
|
pass.setVertexBuffer(1, mesh.normal);
|
|
668
775
|
pass.setVertexBuffer(2, mesh.color);
|
|
776
|
+
pass.setVertexBuffer(3, mesh.emissive);
|
|
777
|
+
pass.setVertexBuffer(4, mesh.specular);
|
|
669
778
|
pass.draw(mesh.count);
|
|
670
779
|
});
|
|
671
780
|
pass.end();
|
|
@@ -677,6 +786,8 @@ fn fs(in: VSOut) -> @location(0) vec4<f32> {
|
|
|
677
786
|
mesh.position.destroy?.();
|
|
678
787
|
mesh.normal.destroy?.();
|
|
679
788
|
mesh.color.destroy?.();
|
|
789
|
+
mesh.emissive.destroy?.();
|
|
790
|
+
mesh.specular.destroy?.();
|
|
680
791
|
}
|
|
681
792
|
this.cache.clear();
|
|
682
793
|
this.uniformBuffer?.destroy?.();
|
|
@@ -759,9 +870,30 @@ var init_canvas2d = __esm({
|
|
|
759
870
|
depth += dot(d, d);
|
|
760
871
|
}
|
|
761
872
|
depth /= face.indices.length;
|
|
873
|
+
let surface;
|
|
874
|
+
const material = face.material;
|
|
875
|
+
if (material) {
|
|
876
|
+
if (material.specular) {
|
|
877
|
+
let cx = 0;
|
|
878
|
+
let cy = 0;
|
|
879
|
+
let cz = 0;
|
|
880
|
+
for (const i of face.indices) {
|
|
881
|
+
cx += world[i].x;
|
|
882
|
+
cy += world[i].y;
|
|
883
|
+
cz += world[i].z;
|
|
884
|
+
}
|
|
885
|
+
const inv = 1 / face.indices.length;
|
|
886
|
+
const viewDir = normalize(
|
|
887
|
+
subtract(frame.eye, { x: cx * inv, y: cy * inv, z: cz * inv })
|
|
888
|
+
);
|
|
889
|
+
surface = { material, viewDir };
|
|
890
|
+
} else {
|
|
891
|
+
surface = { material };
|
|
892
|
+
}
|
|
893
|
+
}
|
|
762
894
|
polygons.push({
|
|
763
895
|
points,
|
|
764
|
-
color: shadeColor(normal, face.color, frame.light),
|
|
896
|
+
color: shadeColor(normal, face.color, frame.light, surface),
|
|
765
897
|
depth,
|
|
766
898
|
opacity: faceOpacity
|
|
767
899
|
});
|
|
@@ -852,6 +984,7 @@ __export(little_3d_engine_exports, {
|
|
|
852
984
|
Camera: () => Camera,
|
|
853
985
|
Light: () => Light,
|
|
854
986
|
Little3dEngine: () => Little3dEngine,
|
|
987
|
+
attachMaterial: () => attachMaterial,
|
|
855
988
|
cross: () => cross,
|
|
856
989
|
cube: () => cube,
|
|
857
990
|
cubeSphere: () => cubeSphere,
|
|
@@ -915,6 +1048,12 @@ init_light();
|
|
|
915
1048
|
init_math();
|
|
916
1049
|
|
|
917
1050
|
// src/engines/little-3d-engine/core/mesh.ts
|
|
1051
|
+
function attachMaterial(mesh, material) {
|
|
1052
|
+
if (material) {
|
|
1053
|
+
for (const face of mesh.faces) face.material = material;
|
|
1054
|
+
}
|
|
1055
|
+
return mesh;
|
|
1056
|
+
}
|
|
918
1057
|
function transform(init) {
|
|
919
1058
|
return {
|
|
920
1059
|
position: init?.position ?? { x: 0, y: 0, z: 0 },
|
|
@@ -929,7 +1068,7 @@ init_light();
|
|
|
929
1068
|
|
|
930
1069
|
// src/engines/little-3d-engine/shapes/primitives/cube.ts
|
|
931
1070
|
var DEFAULT_COLORS = ["#3b82f6", "#8b5cf6", "#ec4899", "#f59e0b", "#10b981", "#ef4444"];
|
|
932
|
-
function cube(size = 1, colors = DEFAULT_COLORS) {
|
|
1071
|
+
function cube(size = 1, colors = DEFAULT_COLORS, material) {
|
|
933
1072
|
const h = size / 2;
|
|
934
1073
|
const vertices = [
|
|
935
1074
|
{ x: -h, y: -h, z: h },
|
|
@@ -949,12 +1088,12 @@ function cube(size = 1, colors = DEFAULT_COLORS) {
|
|
|
949
1088
|
{ indices: [1, 5, 6, 2], color: colors[4 % colors.length] },
|
|
950
1089
|
{ indices: [4, 0, 3, 7], color: colors[5 % colors.length] }
|
|
951
1090
|
];
|
|
952
|
-
return { vertices, faces };
|
|
1091
|
+
return attachMaterial({ vertices, faces }, material);
|
|
953
1092
|
}
|
|
954
1093
|
|
|
955
1094
|
// src/engines/little-3d-engine/shapes/primitives/quad.ts
|
|
956
1095
|
var DEFAULT_COLORS2 = ["#3b82f6"];
|
|
957
|
-
function quad(size = 1, colors = DEFAULT_COLORS2) {
|
|
1096
|
+
function quad(size = 1, colors = DEFAULT_COLORS2, material) {
|
|
958
1097
|
const s = size / 2;
|
|
959
1098
|
const vertices = [
|
|
960
1099
|
{ x: -s, y: -s, z: 0 },
|
|
@@ -962,12 +1101,15 @@ function quad(size = 1, colors = DEFAULT_COLORS2) {
|
|
|
962
1101
|
{ x: s, y: s, z: 0 },
|
|
963
1102
|
{ x: -s, y: s, z: 0 }
|
|
964
1103
|
];
|
|
965
|
-
return
|
|
1104
|
+
return attachMaterial(
|
|
1105
|
+
{ vertices, faces: [{ indices: [0, 1, 2, 3], color: colors[0] }] },
|
|
1106
|
+
material
|
|
1107
|
+
);
|
|
966
1108
|
}
|
|
967
1109
|
|
|
968
1110
|
// src/engines/little-3d-engine/shapes/primitives/tetrahedron.ts
|
|
969
1111
|
var DEFAULT_COLORS3 = ["#3b82f6", "#8b5cf6", "#ec4899", "#f59e0b"];
|
|
970
|
-
function tetrahedron(size = 1, colors = DEFAULT_COLORS3) {
|
|
1112
|
+
function tetrahedron(size = 1, colors = DEFAULT_COLORS3, material) {
|
|
971
1113
|
const s = size / 2;
|
|
972
1114
|
const vertices = [
|
|
973
1115
|
{ x: s, y: s, z: s },
|
|
@@ -981,7 +1123,7 @@ function tetrahedron(size = 1, colors = DEFAULT_COLORS3) {
|
|
|
981
1123
|
{ indices: [0, 2, 3], color: colors[2 % colors.length] },
|
|
982
1124
|
{ indices: [1, 3, 2], color: colors[3 % colors.length] }
|
|
983
1125
|
];
|
|
984
|
-
return { vertices, faces };
|
|
1126
|
+
return attachMaterial({ vertices, faces }, material);
|
|
985
1127
|
}
|
|
986
1128
|
|
|
987
1129
|
// src/engines/little-3d-engine/shapes/primitives/octahedron.ts
|
|
@@ -995,7 +1137,7 @@ var DEFAULT_COLORS4 = [
|
|
|
995
1137
|
"#06b6d4",
|
|
996
1138
|
"#eab308"
|
|
997
1139
|
];
|
|
998
|
-
function octahedron(size = 1, colors = DEFAULT_COLORS4) {
|
|
1140
|
+
function octahedron(size = 1, colors = DEFAULT_COLORS4, material) {
|
|
999
1141
|
const r = size / 2;
|
|
1000
1142
|
const vertices = [
|
|
1001
1143
|
{ x: r, y: 0, z: 0 },
|
|
@@ -1015,12 +1157,12 @@ function octahedron(size = 1, colors = DEFAULT_COLORS4) {
|
|
|
1015
1157
|
{ indices: [5, 3, 1], color: colors[6 % colors.length] },
|
|
1016
1158
|
{ indices: [5, 0, 3], color: colors[7 % colors.length] }
|
|
1017
1159
|
];
|
|
1018
|
-
return { vertices, faces };
|
|
1160
|
+
return attachMaterial({ vertices, faces }, material);
|
|
1019
1161
|
}
|
|
1020
1162
|
|
|
1021
1163
|
// src/engines/little-3d-engine/shapes/primitives/pyramid.ts
|
|
1022
1164
|
var DEFAULT_COLORS5 = ["#3b82f6", "#8b5cf6", "#ec4899", "#f59e0b", "#10b981"];
|
|
1023
|
-
function pyramid(size = 1, colors = DEFAULT_COLORS5) {
|
|
1165
|
+
function pyramid(size = 1, colors = DEFAULT_COLORS5, material) {
|
|
1024
1166
|
const h = size / 2;
|
|
1025
1167
|
const vertices = [
|
|
1026
1168
|
{ x: -h, y: -h, z: h },
|
|
@@ -1036,12 +1178,12 @@ function pyramid(size = 1, colors = DEFAULT_COLORS5) {
|
|
|
1036
1178
|
{ indices: [4, 2, 3], color: colors[3 % colors.length] },
|
|
1037
1179
|
{ indices: [4, 3, 0], color: colors[4 % colors.length] }
|
|
1038
1180
|
];
|
|
1039
|
-
return { vertices, faces };
|
|
1181
|
+
return attachMaterial({ vertices, faces }, material);
|
|
1040
1182
|
}
|
|
1041
1183
|
|
|
1042
1184
|
// src/engines/little-3d-engine/shapes/primitives/spheres/uv-sphere.ts
|
|
1043
1185
|
var DEFAULT_COLORS6 = ["#3b82f6", "#8b5cf6", "#ec4899", "#f59e0b", "#10b981", "#ef4444"];
|
|
1044
|
-
function uvSphere(size = 1, detail = 1, colors = DEFAULT_COLORS6) {
|
|
1186
|
+
function uvSphere(size = 1, detail = 1, colors = DEFAULT_COLORS6, material) {
|
|
1045
1187
|
const r = size / 2;
|
|
1046
1188
|
const d = Math.max(1, Math.floor(detail));
|
|
1047
1189
|
const slices = Math.max(4, d * 4);
|
|
@@ -1080,7 +1222,7 @@ function uvSphere(size = 1, detail = 1, colors = DEFAULT_COLORS6) {
|
|
|
1080
1222
|
color: color()
|
|
1081
1223
|
});
|
|
1082
1224
|
}
|
|
1083
|
-
return { vertices, faces };
|
|
1225
|
+
return attachMaterial({ vertices, faces }, material);
|
|
1084
1226
|
}
|
|
1085
1227
|
|
|
1086
1228
|
// src/engines/little-3d-engine/shapes/primitives/spheres/icosphere.ts
|
|
@@ -1123,8 +1265,11 @@ var SEED_FACES = [
|
|
|
1123
1265
|
[8, 6, 7],
|
|
1124
1266
|
[9, 8, 1]
|
|
1125
1267
|
];
|
|
1126
|
-
function icosphere(size = 1, detail = 1, colors = DEFAULT_COLORS7) {
|
|
1127
|
-
return
|
|
1268
|
+
function icosphere(size = 1, detail = 1, colors = DEFAULT_COLORS7, material) {
|
|
1269
|
+
return attachMaterial(
|
|
1270
|
+
sphereFromTriangles(SEED_VERTICES, SEED_FACES, size, detail, colors),
|
|
1271
|
+
material
|
|
1272
|
+
);
|
|
1128
1273
|
}
|
|
1129
1274
|
|
|
1130
1275
|
// src/engines/little-3d-engine/shapes/primitives/spheres/octa-sphere.ts
|
|
@@ -1148,8 +1293,11 @@ var SEED_FACES2 = [
|
|
|
1148
1293
|
[5, 3, 1],
|
|
1149
1294
|
[5, 0, 3]
|
|
1150
1295
|
];
|
|
1151
|
-
function octaSphere(size = 1, detail = 1, colors = DEFAULT_COLORS8) {
|
|
1152
|
-
return
|
|
1296
|
+
function octaSphere(size = 1, detail = 1, colors = DEFAULT_COLORS8, material) {
|
|
1297
|
+
return attachMaterial(
|
|
1298
|
+
sphereFromTriangles(SEED_VERTICES2, SEED_FACES2, size, detail, colors),
|
|
1299
|
+
material
|
|
1300
|
+
);
|
|
1153
1301
|
}
|
|
1154
1302
|
|
|
1155
1303
|
// src/engines/little-3d-engine/shapes/primitives/spheres/cube-sphere.ts
|
|
@@ -1162,7 +1310,7 @@ var CUBE_FACES = [
|
|
|
1162
1310
|
{ normal: [0, 1, 0], right: [1, 0, 0], up: [0, 0, -1] },
|
|
1163
1311
|
{ normal: [0, -1, 0], right: [1, 0, 0], up: [0, 0, 1] }
|
|
1164
1312
|
];
|
|
1165
|
-
function cubeSphere(size = 1, detail = 1, colors = DEFAULT_COLORS9) {
|
|
1313
|
+
function cubeSphere(size = 1, detail = 1, colors = DEFAULT_COLORS9, material) {
|
|
1166
1314
|
const r = size / 2;
|
|
1167
1315
|
const n = Math.max(1, Math.floor(detail));
|
|
1168
1316
|
const vertices = [];
|
|
@@ -1191,13 +1339,13 @@ function cubeSphere(size = 1, detail = 1, colors = DEFAULT_COLORS9) {
|
|
|
1191
1339
|
}
|
|
1192
1340
|
}
|
|
1193
1341
|
}
|
|
1194
|
-
return { vertices, faces };
|
|
1342
|
+
return attachMaterial({ vertices, faces }, material);
|
|
1195
1343
|
}
|
|
1196
1344
|
|
|
1197
1345
|
// src/engines/little-3d-engine/shapes/complex/plane.ts
|
|
1198
1346
|
var DEFAULT_COLORS10 = ["#e0f2fe", "#7dd3fc", "#38bdf8", "#f8fafc"];
|
|
1199
|
-
function planeMesh(colors = DEFAULT_COLORS10) {
|
|
1200
|
-
return {
|
|
1347
|
+
function planeMesh(colors = DEFAULT_COLORS10, material) {
|
|
1348
|
+
return attachMaterial({
|
|
1201
1349
|
vertices: [
|
|
1202
1350
|
{ x: 0.9, y: 0, z: 0 },
|
|
1203
1351
|
{ x: -0.2, y: 0, z: 0.82 },
|
|
@@ -1225,7 +1373,7 @@ function planeMesh(colors = DEFAULT_COLORS10) {
|
|
|
1225
1373
|
{ indices: [3, 6, 8], color: colors[0] ?? DEFAULT_COLORS10[0] },
|
|
1226
1374
|
{ indices: [3, 8, 6], color: colors[1] ?? DEFAULT_COLORS10[1] }
|
|
1227
1375
|
]
|
|
1228
|
-
};
|
|
1376
|
+
}, material);
|
|
1229
1377
|
}
|
|
1230
1378
|
|
|
1231
1379
|
// src/engines/little-3d-engine/textures/dynamic/canvas-texture.ts
|
|
@@ -24,28 +24,64 @@ __export(obj_exports, {
|
|
|
24
24
|
});
|
|
25
25
|
module.exports = __toCommonJS(obj_exports);
|
|
26
26
|
var DEFAULT_COLORS = ["#3b82f6", "#8b5cf6", "#ec4899", "#f59e0b", "#10b981", "#ef4444"];
|
|
27
|
+
function clamp01(value) {
|
|
28
|
+
return Math.min(1, Math.max(0, value));
|
|
29
|
+
}
|
|
27
30
|
function channelToHex(value) {
|
|
28
31
|
const channel = Number.parseFloat(value);
|
|
29
32
|
if (!Number.isFinite(channel)) return void 0;
|
|
30
|
-
return Math.round(
|
|
33
|
+
return Math.round(clamp01(channel) * 255).toString(16).padStart(2, "0");
|
|
31
34
|
}
|
|
32
|
-
function
|
|
33
|
-
const
|
|
34
|
-
|
|
35
|
+
function parseRgb(parts) {
|
|
36
|
+
const channels = parts.slice(1, 4).map(Number.parseFloat);
|
|
37
|
+
if (channels.length !== 3 || !channels.every(Number.isFinite)) return void 0;
|
|
38
|
+
return [clamp01(channels[0]), clamp01(channels[1]), clamp01(channels[2])];
|
|
39
|
+
}
|
|
40
|
+
function toMaterial(surface) {
|
|
41
|
+
const material = {};
|
|
42
|
+
if (surface.specular) material.specular = surface.specular;
|
|
43
|
+
if (surface.shininess !== void 0) material.shininess = surface.shininess;
|
|
44
|
+
if (surface.emissive) material.emissive = surface.emissive;
|
|
45
|
+
return Object.keys(material).length > 0 ? material : void 0;
|
|
46
|
+
}
|
|
47
|
+
function parseMtl(text) {
|
|
48
|
+
const materials = /* @__PURE__ */ new Map();
|
|
49
|
+
const surfaces = /* @__PURE__ */ new Map();
|
|
50
|
+
let name;
|
|
35
51
|
for (const line of text.split("\n")) {
|
|
36
52
|
const trimmed = line.trim();
|
|
37
53
|
if (trimmed === "" || trimmed.startsWith("#")) continue;
|
|
38
54
|
const parts = trimmed.split(/\s+/);
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
55
|
+
const keyword = parts[0];
|
|
56
|
+
if (keyword === "newmtl") {
|
|
57
|
+
name = parts.slice(1).join(" ");
|
|
58
|
+
if (name && !materials.has(name)) {
|
|
59
|
+
materials.set(name, {});
|
|
60
|
+
surfaces.set(name, {});
|
|
61
|
+
}
|
|
62
|
+
continue;
|
|
63
|
+
}
|
|
64
|
+
if (!name) continue;
|
|
65
|
+
const entry = materials.get(name);
|
|
66
|
+
const surface = surfaces.get(name);
|
|
67
|
+
if (keyword === "Kd") {
|
|
42
68
|
const channels = parts.slice(1, 4).map(channelToHex);
|
|
43
69
|
if (channels.length === 3 && channels.every((channel) => channel !== void 0)) {
|
|
44
|
-
|
|
70
|
+
entry.color = `#${channels.join("")}`;
|
|
45
71
|
}
|
|
72
|
+
} else if (keyword === "Ks") {
|
|
73
|
+
surface.specular = parseRgb(parts);
|
|
74
|
+
} else if (keyword === "Ns") {
|
|
75
|
+
const ns = Number.parseFloat(parts[1]);
|
|
76
|
+
if (Number.isFinite(ns)) surface.shininess = Math.max(0, ns);
|
|
77
|
+
} else if (keyword === "Ke") {
|
|
78
|
+
surface.emissive = parseRgb(parts);
|
|
46
79
|
}
|
|
47
80
|
}
|
|
48
|
-
|
|
81
|
+
for (const [key, surface] of surfaces) {
|
|
82
|
+
materials.get(key).material = toMaterial(surface);
|
|
83
|
+
}
|
|
84
|
+
return materials;
|
|
49
85
|
}
|
|
50
86
|
function resolveIndex(token, vertexCount) {
|
|
51
87
|
const n = parseInt(token, 10);
|
|
@@ -53,7 +89,7 @@ function resolveIndex(token, vertexCount) {
|
|
|
53
89
|
}
|
|
54
90
|
function parseObj(text, options = {}) {
|
|
55
91
|
const colors = options.colors ?? DEFAULT_COLORS;
|
|
56
|
-
const
|
|
92
|
+
const materials = options.useMtlColors && options.mtl ? parseMtl(options.mtl) : void 0;
|
|
57
93
|
const vertices = [];
|
|
58
94
|
const faces = [];
|
|
59
95
|
let material;
|
|
@@ -77,9 +113,11 @@ function parseObj(text, options = {}) {
|
|
|
77
113
|
indices.push(resolveIndex(vertexToken, vertices.length));
|
|
78
114
|
}
|
|
79
115
|
if (indices.length >= 3) {
|
|
80
|
-
const
|
|
81
|
-
const color =
|
|
82
|
-
|
|
116
|
+
const entry = material ? materials?.get(material) : void 0;
|
|
117
|
+
const color = entry?.color ?? (materials ? colors[0] ?? "#888888" : colors[faces.length % colors.length]);
|
|
118
|
+
const face = { indices, color };
|
|
119
|
+
if (entry?.material) face.material = entry.material;
|
|
120
|
+
faces.push(face);
|
|
83
121
|
}
|
|
84
122
|
}
|
|
85
123
|
}
|