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 @@ 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,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: module2,
|
|
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: module2,
|
|
@@ -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 @@ var init_canvas2d = __esm({
|
|
|
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
|
});
|
|
@@ -852,6 +989,7 @@ __export(little_3d_engine_exports, {
|
|
|
852
989
|
Camera: () => Camera,
|
|
853
990
|
Light: () => Light,
|
|
854
991
|
Little3dEngine: () => Little3dEngine,
|
|
992
|
+
attachMaterial: () => attachMaterial,
|
|
855
993
|
cross: () => cross,
|
|
856
994
|
cube: () => cube,
|
|
857
995
|
cubeSphere: () => cubeSphere,
|
|
@@ -915,6 +1053,12 @@ init_light();
|
|
|
915
1053
|
init_math();
|
|
916
1054
|
|
|
917
1055
|
// src/engines/little-3d-engine/core/mesh.ts
|
|
1056
|
+
function attachMaterial(mesh, material) {
|
|
1057
|
+
if (material) {
|
|
1058
|
+
for (const face of mesh.faces) face.material = material;
|
|
1059
|
+
}
|
|
1060
|
+
return mesh;
|
|
1061
|
+
}
|
|
918
1062
|
function transform(init) {
|
|
919
1063
|
return {
|
|
920
1064
|
position: init?.position ?? { x: 0, y: 0, z: 0 },
|
|
@@ -929,7 +1073,7 @@ init_light();
|
|
|
929
1073
|
|
|
930
1074
|
// src/engines/little-3d-engine/shapes/primitives/cube.ts
|
|
931
1075
|
var DEFAULT_COLORS = ["#3b82f6", "#8b5cf6", "#ec4899", "#f59e0b", "#10b981", "#ef4444"];
|
|
932
|
-
function cube(size = 1, colors = DEFAULT_COLORS) {
|
|
1076
|
+
function cube(size = 1, colors = DEFAULT_COLORS, material) {
|
|
933
1077
|
const h = size / 2;
|
|
934
1078
|
const vertices = [
|
|
935
1079
|
{ x: -h, y: -h, z: h },
|
|
@@ -949,12 +1093,12 @@ function cube(size = 1, colors = DEFAULT_COLORS) {
|
|
|
949
1093
|
{ indices: [1, 5, 6, 2], color: colors[4 % colors.length] },
|
|
950
1094
|
{ indices: [4, 0, 3, 7], color: colors[5 % colors.length] }
|
|
951
1095
|
];
|
|
952
|
-
return { vertices, faces };
|
|
1096
|
+
return attachMaterial({ vertices, faces }, material);
|
|
953
1097
|
}
|
|
954
1098
|
|
|
955
1099
|
// src/engines/little-3d-engine/shapes/primitives/quad.ts
|
|
956
1100
|
var DEFAULT_COLORS2 = ["#3b82f6"];
|
|
957
|
-
function quad(size = 1, colors = DEFAULT_COLORS2) {
|
|
1101
|
+
function quad(size = 1, colors = DEFAULT_COLORS2, material) {
|
|
958
1102
|
const s = size / 2;
|
|
959
1103
|
const vertices = [
|
|
960
1104
|
{ x: -s, y: -s, z: 0 },
|
|
@@ -962,12 +1106,15 @@ function quad(size = 1, colors = DEFAULT_COLORS2) {
|
|
|
962
1106
|
{ x: s, y: s, z: 0 },
|
|
963
1107
|
{ x: -s, y: s, z: 0 }
|
|
964
1108
|
];
|
|
965
|
-
return
|
|
1109
|
+
return attachMaterial(
|
|
1110
|
+
{ vertices, faces: [{ indices: [0, 1, 2, 3], color: colors[0] }] },
|
|
1111
|
+
material
|
|
1112
|
+
);
|
|
966
1113
|
}
|
|
967
1114
|
|
|
968
1115
|
// src/engines/little-3d-engine/shapes/primitives/tetrahedron.ts
|
|
969
1116
|
var DEFAULT_COLORS3 = ["#3b82f6", "#8b5cf6", "#ec4899", "#f59e0b"];
|
|
970
|
-
function tetrahedron(size = 1, colors = DEFAULT_COLORS3) {
|
|
1117
|
+
function tetrahedron(size = 1, colors = DEFAULT_COLORS3, material) {
|
|
971
1118
|
const s = size / 2;
|
|
972
1119
|
const vertices = [
|
|
973
1120
|
{ x: s, y: s, z: s },
|
|
@@ -981,7 +1128,7 @@ function tetrahedron(size = 1, colors = DEFAULT_COLORS3) {
|
|
|
981
1128
|
{ indices: [0, 2, 3], color: colors[2 % colors.length] },
|
|
982
1129
|
{ indices: [1, 3, 2], color: colors[3 % colors.length] }
|
|
983
1130
|
];
|
|
984
|
-
return { vertices, faces };
|
|
1131
|
+
return attachMaterial({ vertices, faces }, material);
|
|
985
1132
|
}
|
|
986
1133
|
|
|
987
1134
|
// src/engines/little-3d-engine/shapes/primitives/octahedron.ts
|
|
@@ -995,7 +1142,7 @@ var DEFAULT_COLORS4 = [
|
|
|
995
1142
|
"#06b6d4",
|
|
996
1143
|
"#eab308"
|
|
997
1144
|
];
|
|
998
|
-
function octahedron(size = 1, colors = DEFAULT_COLORS4) {
|
|
1145
|
+
function octahedron(size = 1, colors = DEFAULT_COLORS4, material) {
|
|
999
1146
|
const r = size / 2;
|
|
1000
1147
|
const vertices = [
|
|
1001
1148
|
{ x: r, y: 0, z: 0 },
|
|
@@ -1015,12 +1162,12 @@ function octahedron(size = 1, colors = DEFAULT_COLORS4) {
|
|
|
1015
1162
|
{ indices: [5, 3, 1], color: colors[6 % colors.length] },
|
|
1016
1163
|
{ indices: [5, 0, 3], color: colors[7 % colors.length] }
|
|
1017
1164
|
];
|
|
1018
|
-
return { vertices, faces };
|
|
1165
|
+
return attachMaterial({ vertices, faces }, material);
|
|
1019
1166
|
}
|
|
1020
1167
|
|
|
1021
1168
|
// src/engines/little-3d-engine/shapes/primitives/pyramid.ts
|
|
1022
1169
|
var DEFAULT_COLORS5 = ["#3b82f6", "#8b5cf6", "#ec4899", "#f59e0b", "#10b981"];
|
|
1023
|
-
function pyramid(size = 1, colors = DEFAULT_COLORS5) {
|
|
1170
|
+
function pyramid(size = 1, colors = DEFAULT_COLORS5, material) {
|
|
1024
1171
|
const h = size / 2;
|
|
1025
1172
|
const vertices = [
|
|
1026
1173
|
{ x: -h, y: -h, z: h },
|
|
@@ -1036,12 +1183,12 @@ function pyramid(size = 1, colors = DEFAULT_COLORS5) {
|
|
|
1036
1183
|
{ indices: [4, 2, 3], color: colors[3 % colors.length] },
|
|
1037
1184
|
{ indices: [4, 3, 0], color: colors[4 % colors.length] }
|
|
1038
1185
|
];
|
|
1039
|
-
return { vertices, faces };
|
|
1186
|
+
return attachMaterial({ vertices, faces }, material);
|
|
1040
1187
|
}
|
|
1041
1188
|
|
|
1042
1189
|
// src/engines/little-3d-engine/shapes/primitives/spheres/uv-sphere.ts
|
|
1043
1190
|
var DEFAULT_COLORS6 = ["#3b82f6", "#8b5cf6", "#ec4899", "#f59e0b", "#10b981", "#ef4444"];
|
|
1044
|
-
function uvSphere(size = 1, detail = 1, colors = DEFAULT_COLORS6) {
|
|
1191
|
+
function uvSphere(size = 1, detail = 1, colors = DEFAULT_COLORS6, material) {
|
|
1045
1192
|
const r = size / 2;
|
|
1046
1193
|
const d = Math.max(1, Math.floor(detail));
|
|
1047
1194
|
const slices = Math.max(4, d * 4);
|
|
@@ -1080,7 +1227,7 @@ function uvSphere(size = 1, detail = 1, colors = DEFAULT_COLORS6) {
|
|
|
1080
1227
|
color: color()
|
|
1081
1228
|
});
|
|
1082
1229
|
}
|
|
1083
|
-
return { vertices, faces };
|
|
1230
|
+
return attachMaterial({ vertices, faces }, material);
|
|
1084
1231
|
}
|
|
1085
1232
|
|
|
1086
1233
|
// src/engines/little-3d-engine/shapes/primitives/spheres/icosphere.ts
|
|
@@ -1123,8 +1270,11 @@ var SEED_FACES = [
|
|
|
1123
1270
|
[8, 6, 7],
|
|
1124
1271
|
[9, 8, 1]
|
|
1125
1272
|
];
|
|
1126
|
-
function icosphere(size = 1, detail = 1, colors = DEFAULT_COLORS7) {
|
|
1127
|
-
return
|
|
1273
|
+
function icosphere(size = 1, detail = 1, colors = DEFAULT_COLORS7, material) {
|
|
1274
|
+
return attachMaterial(
|
|
1275
|
+
sphereFromTriangles(SEED_VERTICES, SEED_FACES, size, detail, colors),
|
|
1276
|
+
material
|
|
1277
|
+
);
|
|
1128
1278
|
}
|
|
1129
1279
|
|
|
1130
1280
|
// src/engines/little-3d-engine/shapes/primitives/spheres/octa-sphere.ts
|
|
@@ -1148,8 +1298,11 @@ var SEED_FACES2 = [
|
|
|
1148
1298
|
[5, 3, 1],
|
|
1149
1299
|
[5, 0, 3]
|
|
1150
1300
|
];
|
|
1151
|
-
function octaSphere(size = 1, detail = 1, colors = DEFAULT_COLORS8) {
|
|
1152
|
-
return
|
|
1301
|
+
function octaSphere(size = 1, detail = 1, colors = DEFAULT_COLORS8, material) {
|
|
1302
|
+
return attachMaterial(
|
|
1303
|
+
sphereFromTriangles(SEED_VERTICES2, SEED_FACES2, size, detail, colors),
|
|
1304
|
+
material
|
|
1305
|
+
);
|
|
1153
1306
|
}
|
|
1154
1307
|
|
|
1155
1308
|
// src/engines/little-3d-engine/shapes/primitives/spheres/cube-sphere.ts
|
|
@@ -1162,7 +1315,7 @@ var CUBE_FACES = [
|
|
|
1162
1315
|
{ normal: [0, 1, 0], right: [1, 0, 0], up: [0, 0, -1] },
|
|
1163
1316
|
{ normal: [0, -1, 0], right: [1, 0, 0], up: [0, 0, 1] }
|
|
1164
1317
|
];
|
|
1165
|
-
function cubeSphere(size = 1, detail = 1, colors = DEFAULT_COLORS9) {
|
|
1318
|
+
function cubeSphere(size = 1, detail = 1, colors = DEFAULT_COLORS9, material) {
|
|
1166
1319
|
const r = size / 2;
|
|
1167
1320
|
const n = Math.max(1, Math.floor(detail));
|
|
1168
1321
|
const vertices = [];
|
|
@@ -1191,13 +1344,13 @@ function cubeSphere(size = 1, detail = 1, colors = DEFAULT_COLORS9) {
|
|
|
1191
1344
|
}
|
|
1192
1345
|
}
|
|
1193
1346
|
}
|
|
1194
|
-
return { vertices, faces };
|
|
1347
|
+
return attachMaterial({ vertices, faces }, material);
|
|
1195
1348
|
}
|
|
1196
1349
|
|
|
1197
1350
|
// src/engines/little-3d-engine/shapes/complex/plane.ts
|
|
1198
1351
|
var DEFAULT_COLORS10 = ["#e0f2fe", "#7dd3fc", "#38bdf8", "#f8fafc"];
|
|
1199
|
-
function planeMesh(colors = DEFAULT_COLORS10) {
|
|
1200
|
-
return {
|
|
1352
|
+
function planeMesh(colors = DEFAULT_COLORS10, material) {
|
|
1353
|
+
return attachMaterial({
|
|
1201
1354
|
vertices: [
|
|
1202
1355
|
{ x: 0.9, y: 0, z: 0 },
|
|
1203
1356
|
{ x: -0.2, y: 0, z: 0.82 },
|
|
@@ -1225,7 +1378,7 @@ function planeMesh(colors = DEFAULT_COLORS10) {
|
|
|
1225
1378
|
{ indices: [3, 6, 8], color: colors[0] ?? DEFAULT_COLORS10[0] },
|
|
1226
1379
|
{ indices: [3, 8, 6], color: colors[1] ?? DEFAULT_COLORS10[1] }
|
|
1227
1380
|
]
|
|
1228
|
-
};
|
|
1381
|
+
}, material);
|
|
1229
1382
|
}
|
|
1230
1383
|
|
|
1231
1384
|
// 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
|
}
|