reze-engine 0.1.3 → 0.1.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/engine.js CHANGED
@@ -24,14 +24,6 @@ export class Engine {
24
24
  this.stats = {
25
25
  fps: 0,
26
26
  frameTime: 0,
27
- memoryUsed: 0,
28
- vertices: 0,
29
- drawCalls: 0,
30
- triangles: 0,
31
- materials: 0,
32
- textures: 0,
33
- textureMemory: 0,
34
- bufferMemory: 0,
35
27
  gpuMemory: 0,
36
28
  };
37
29
  this.animationFrameId = null;
@@ -74,112 +66,112 @@ export class Engine {
74
66
  });
75
67
  const shaderModule = this.device.createShaderModule({
76
68
  label: "model shaders",
77
- code: /* wgsl */ `
78
- struct CameraUniforms {
79
- view: mat4x4f,
80
- projection: mat4x4f,
81
- viewPos: vec3f,
82
- _padding: f32,
83
- };
84
-
85
- struct Light {
86
- direction: vec3f,
87
- _padding1: f32,
88
- color: vec3f,
89
- intensity: f32,
90
- };
91
-
92
- struct LightUniforms {
93
- ambient: f32,
94
- lightCount: f32,
95
- _padding1: f32,
96
- _padding2: f32,
97
- lights: array<Light, 4>,
98
- };
99
-
100
- struct MaterialUniforms {
101
- alpha: f32,
102
- _padding1: f32,
103
- _padding2: f32,
104
- _padding3: f32,
105
- };
106
-
107
- struct VertexOutput {
108
- @builtin(position) position: vec4f,
109
- @location(0) normal: vec3f,
110
- @location(1) uv: vec2f,
111
- @location(2) worldPos: vec3f,
112
- };
113
-
114
- @group(0) @binding(0) var<uniform> camera: CameraUniforms;
115
- @group(0) @binding(1) var<uniform> light: LightUniforms;
116
- @group(0) @binding(2) var diffuseTexture: texture_2d<f32>;
117
- @group(0) @binding(3) var diffuseSampler: sampler;
118
- @group(0) @binding(4) var<storage, read> skinMats: array<mat4x4f>;
119
- @group(0) @binding(5) var toonTexture: texture_2d<f32>;
120
- @group(0) @binding(6) var toonSampler: sampler;
121
- @group(0) @binding(7) var<uniform> material: MaterialUniforms;
122
-
123
- @vertex fn vs(
124
- @location(0) position: vec3f,
125
- @location(1) normal: vec3f,
126
- @location(2) uv: vec2f,
127
- @location(3) joints0: vec4<u32>,
128
- @location(4) weights0: vec4<f32>
129
- ) -> VertexOutput {
130
- var output: VertexOutput;
131
- let pos4 = vec4f(position, 1.0);
132
-
133
- // Normalize weights to ensure they sum to 1.0 (handles floating-point precision issues)
134
- let weightSum = weights0.x + weights0.y + weights0.z + weights0.w;
135
- var normalizedWeights: vec4f;
136
- if (weightSum > 0.0001) {
137
- normalizedWeights = weights0 / weightSum;
138
- } else {
139
- normalizedWeights = vec4f(1.0, 0.0, 0.0, 0.0);
140
- }
141
-
142
- var skinnedPos = vec4f(0.0, 0.0, 0.0, 0.0);
143
- var skinnedNrm = vec3f(0.0, 0.0, 0.0);
144
- for (var i = 0u; i < 4u; i++) {
145
- let j = joints0[i];
146
- let w = normalizedWeights[i];
147
- let m = skinMats[j];
148
- skinnedPos += (m * pos4) * w;
149
- let r3 = mat3x3f(m[0].xyz, m[1].xyz, m[2].xyz);
150
- skinnedNrm += (r3 * normal) * w;
151
- }
152
- let worldPos = skinnedPos.xyz;
153
- output.position = camera.projection * camera.view * vec4f(worldPos, 1.0);
154
- output.normal = normalize(skinnedNrm);
155
- output.uv = uv;
156
- output.worldPos = worldPos;
157
- return output;
158
- }
159
-
160
- @fragment fn fs(input: VertexOutput) -> @location(0) vec4f {
161
- let n = normalize(input.normal);
162
- let albedo = textureSample(diffuseTexture, diffuseSampler, input.uv).rgb;
163
-
164
- var lightAccum = vec3f(light.ambient);
165
- let numLights = u32(light.lightCount);
166
- for (var i = 0u; i < numLights; i++) {
167
- let l = -light.lights[i].direction;
168
- let nDotL = max(dot(n, l), 0.0);
169
- let toonUV = vec2f(nDotL, 0.5);
170
- let toonFactor = textureSample(toonTexture, toonSampler, toonUV).rgb;
171
- let radiance = light.lights[i].color * light.lights[i].intensity;
172
- lightAccum += toonFactor * radiance * nDotL;
173
- }
174
-
175
- let color = albedo * lightAccum;
176
- let finalAlpha = material.alpha;
177
- if (finalAlpha < 0.001) {
178
- discard;
179
- }
180
-
181
- return vec4f(clamp(color, vec3f(0.0), vec3f(1.0)), finalAlpha);
182
- }
69
+ code: /* wgsl */ `
70
+ struct CameraUniforms {
71
+ view: mat4x4f,
72
+ projection: mat4x4f,
73
+ viewPos: vec3f,
74
+ _padding: f32,
75
+ };
76
+
77
+ struct Light {
78
+ direction: vec3f,
79
+ _padding1: f32,
80
+ color: vec3f,
81
+ intensity: f32,
82
+ };
83
+
84
+ struct LightUniforms {
85
+ ambient: f32,
86
+ lightCount: f32,
87
+ _padding1: f32,
88
+ _padding2: f32,
89
+ lights: array<Light, 4>,
90
+ };
91
+
92
+ struct MaterialUniforms {
93
+ alpha: f32,
94
+ _padding1: f32,
95
+ _padding2: f32,
96
+ _padding3: f32,
97
+ };
98
+
99
+ struct VertexOutput {
100
+ @builtin(position) position: vec4f,
101
+ @location(0) normal: vec3f,
102
+ @location(1) uv: vec2f,
103
+ @location(2) worldPos: vec3f,
104
+ };
105
+
106
+ @group(0) @binding(0) var<uniform> camera: CameraUniforms;
107
+ @group(0) @binding(1) var<uniform> light: LightUniforms;
108
+ @group(0) @binding(2) var diffuseTexture: texture_2d<f32>;
109
+ @group(0) @binding(3) var diffuseSampler: sampler;
110
+ @group(0) @binding(4) var<storage, read> skinMats: array<mat4x4f>;
111
+ @group(0) @binding(5) var toonTexture: texture_2d<f32>;
112
+ @group(0) @binding(6) var toonSampler: sampler;
113
+ @group(0) @binding(7) var<uniform> material: MaterialUniforms;
114
+
115
+ @vertex fn vs(
116
+ @location(0) position: vec3f,
117
+ @location(1) normal: vec3f,
118
+ @location(2) uv: vec2f,
119
+ @location(3) joints0: vec4<u32>,
120
+ @location(4) weights0: vec4<f32>
121
+ ) -> VertexOutput {
122
+ var output: VertexOutput;
123
+ let pos4 = vec4f(position, 1.0);
124
+
125
+ // Normalize weights to ensure they sum to 1.0 (handles floating-point precision issues)
126
+ let weightSum = weights0.x + weights0.y + weights0.z + weights0.w;
127
+ var normalizedWeights: vec4f;
128
+ if (weightSum > 0.0001) {
129
+ normalizedWeights = weights0 / weightSum;
130
+ } else {
131
+ normalizedWeights = vec4f(1.0, 0.0, 0.0, 0.0);
132
+ }
133
+
134
+ var skinnedPos = vec4f(0.0, 0.0, 0.0, 0.0);
135
+ var skinnedNrm = vec3f(0.0, 0.0, 0.0);
136
+ for (var i = 0u; i < 4u; i++) {
137
+ let j = joints0[i];
138
+ let w = normalizedWeights[i];
139
+ let m = skinMats[j];
140
+ skinnedPos += (m * pos4) * w;
141
+ let r3 = mat3x3f(m[0].xyz, m[1].xyz, m[2].xyz);
142
+ skinnedNrm += (r3 * normal) * w;
143
+ }
144
+ let worldPos = skinnedPos.xyz;
145
+ output.position = camera.projection * camera.view * vec4f(worldPos, 1.0);
146
+ output.normal = normalize(skinnedNrm);
147
+ output.uv = uv;
148
+ output.worldPos = worldPos;
149
+ return output;
150
+ }
151
+
152
+ @fragment fn fs(input: VertexOutput) -> @location(0) vec4f {
153
+ let n = normalize(input.normal);
154
+ let albedo = textureSample(diffuseTexture, diffuseSampler, input.uv).rgb;
155
+
156
+ var lightAccum = vec3f(light.ambient);
157
+ let numLights = u32(light.lightCount);
158
+ for (var i = 0u; i < numLights; i++) {
159
+ let l = -light.lights[i].direction;
160
+ let nDotL = max(dot(n, l), 0.0);
161
+ let toonUV = vec2f(nDotL, 0.5);
162
+ let toonFactor = textureSample(toonTexture, toonSampler, toonUV).rgb;
163
+ let radiance = light.lights[i].color * light.lights[i].intensity;
164
+ lightAccum += toonFactor * radiance * nDotL;
165
+ }
166
+
167
+ let color = albedo * lightAccum;
168
+ let finalAlpha = material.alpha;
169
+ if (finalAlpha < 0.001) {
170
+ discard;
171
+ }
172
+
173
+ return vec4f(clamp(color, vec3f(0.0), vec3f(1.0)), finalAlpha);
174
+ }
183
175
  `,
184
176
  });
185
177
  // Single pipeline for all materials with alpha blending
@@ -239,72 +231,72 @@ export class Engine {
239
231
  });
240
232
  const outlineShaderModule = this.device.createShaderModule({
241
233
  label: "outline shaders",
242
- code: /* wgsl */ `
243
- struct CameraUniforms {
244
- view: mat4x4f,
245
- projection: mat4x4f,
246
- viewPos: vec3f,
247
- _padding: f32,
248
- };
249
-
250
- struct MaterialUniforms {
251
- edgeColor: vec4f,
252
- edgeSize: f32,
253
- _padding1: f32,
254
- _padding2: f32,
255
- _padding3: f32,
256
- };
257
-
258
- @group(0) @binding(0) var<uniform> camera: CameraUniforms;
259
- @group(0) @binding(1) var<uniform> material: MaterialUniforms;
260
- @group(0) @binding(2) var<storage, read> skinMats: array<mat4x4f>;
261
-
262
- struct VertexOutput {
263
- @builtin(position) position: vec4f,
264
- };
265
-
266
- @vertex fn vs(
267
- @location(0) position: vec3f,
268
- @location(1) normal: vec3f,
269
- @location(2) uv: vec2f,
270
- @location(3) joints0: vec4<u32>,
271
- @location(4) weights0: vec4<f32>
272
- ) -> VertexOutput {
273
- var output: VertexOutput;
274
- let pos4 = vec4f(position, 1.0);
275
-
276
- // Normalize weights to ensure they sum to 1.0 (handles floating-point precision issues)
277
- let weightSum = weights0.x + weights0.y + weights0.z + weights0.w;
278
- var normalizedWeights: vec4f;
279
- if (weightSum > 0.0001) {
280
- normalizedWeights = weights0 / weightSum;
281
- } else {
282
- normalizedWeights = vec4f(1.0, 0.0, 0.0, 0.0);
283
- }
284
-
285
- var skinnedPos = vec4f(0.0, 0.0, 0.0, 0.0);
286
- var skinnedNrm = vec3f(0.0, 0.0, 0.0);
287
- for (var i = 0u; i < 4u; i++) {
288
- let j = joints0[i];
289
- let w = normalizedWeights[i];
290
- let m = skinMats[j];
291
- skinnedPos += (m * pos4) * w;
292
- let r3 = mat3x3f(m[0].xyz, m[1].xyz, m[2].xyz);
293
- skinnedNrm += (r3 * normal) * w;
294
- }
295
- let worldPos = skinnedPos.xyz;
296
- let worldNormal = normalize(skinnedNrm);
297
-
298
- // MMD invert hull: expand vertices outward along normals
299
- let scaleFactor = 0.01;
300
- let expandedPos = worldPos + worldNormal * material.edgeSize * scaleFactor;
301
- output.position = camera.projection * camera.view * vec4f(expandedPos, 1.0);
302
- return output;
303
- }
304
-
305
- @fragment fn fs() -> @location(0) vec4f {
306
- return material.edgeColor;
307
- }
234
+ code: /* wgsl */ `
235
+ struct CameraUniforms {
236
+ view: mat4x4f,
237
+ projection: mat4x4f,
238
+ viewPos: vec3f,
239
+ _padding: f32,
240
+ };
241
+
242
+ struct MaterialUniforms {
243
+ edgeColor: vec4f,
244
+ edgeSize: f32,
245
+ _padding1: f32,
246
+ _padding2: f32,
247
+ _padding3: f32,
248
+ };
249
+
250
+ @group(0) @binding(0) var<uniform> camera: CameraUniforms;
251
+ @group(0) @binding(1) var<uniform> material: MaterialUniforms;
252
+ @group(0) @binding(2) var<storage, read> skinMats: array<mat4x4f>;
253
+
254
+ struct VertexOutput {
255
+ @builtin(position) position: vec4f,
256
+ };
257
+
258
+ @vertex fn vs(
259
+ @location(0) position: vec3f,
260
+ @location(1) normal: vec3f,
261
+ @location(2) uv: vec2f,
262
+ @location(3) joints0: vec4<u32>,
263
+ @location(4) weights0: vec4<f32>
264
+ ) -> VertexOutput {
265
+ var output: VertexOutput;
266
+ let pos4 = vec4f(position, 1.0);
267
+
268
+ // Normalize weights to ensure they sum to 1.0 (handles floating-point precision issues)
269
+ let weightSum = weights0.x + weights0.y + weights0.z + weights0.w;
270
+ var normalizedWeights: vec4f;
271
+ if (weightSum > 0.0001) {
272
+ normalizedWeights = weights0 / weightSum;
273
+ } else {
274
+ normalizedWeights = vec4f(1.0, 0.0, 0.0, 0.0);
275
+ }
276
+
277
+ var skinnedPos = vec4f(0.0, 0.0, 0.0, 0.0);
278
+ var skinnedNrm = vec3f(0.0, 0.0, 0.0);
279
+ for (var i = 0u; i < 4u; i++) {
280
+ let j = joints0[i];
281
+ let w = normalizedWeights[i];
282
+ let m = skinMats[j];
283
+ skinnedPos += (m * pos4) * w;
284
+ let r3 = mat3x3f(m[0].xyz, m[1].xyz, m[2].xyz);
285
+ skinnedNrm += (r3 * normal) * w;
286
+ }
287
+ let worldPos = skinnedPos.xyz;
288
+ let worldNormal = normalize(skinnedNrm);
289
+
290
+ // MMD invert hull: expand vertices outward along normals
291
+ let scaleFactor = 0.01;
292
+ let expandedPos = worldPos + worldNormal * material.edgeSize * scaleFactor;
293
+ output.position = camera.projection * camera.view * vec4f(expandedPos, 1.0);
294
+ return output;
295
+ }
296
+
297
+ @fragment fn fs() -> @location(0) vec4f {
298
+ return material.edgeColor;
299
+ }
308
300
  `,
309
301
  });
310
302
  this.outlinePipeline = this.device.createRenderPipeline({
@@ -380,31 +372,31 @@ export class Engine {
380
372
  createSkinMatrixComputePipeline() {
381
373
  const computeShader = this.device.createShaderModule({
382
374
  label: "skin matrix compute",
383
- code: /* wgsl */ `
384
- struct BoneCountUniform {
385
- count: u32,
386
- _padding1: u32,
387
- _padding2: u32,
388
- _padding3: u32,
389
- _padding4: vec4<u32>,
390
- };
391
-
392
- @group(0) @binding(0) var<uniform> boneCount: BoneCountUniform;
393
- @group(0) @binding(1) var<storage, read> worldMatrices: array<mat4x4f>;
394
- @group(0) @binding(2) var<storage, read> inverseBindMatrices: array<mat4x4f>;
395
- @group(0) @binding(3) var<storage, read_write> skinMatrices: array<mat4x4f>;
396
-
397
- @compute @workgroup_size(64)
398
- fn main(@builtin(global_invocation_id) globalId: vec3<u32>) {
399
- let boneIndex = globalId.x;
400
- // Bounds check: we dispatch workgroups (64 threads each), so some threads may be out of range
401
- if (boneIndex >= boneCount.count) {
402
- return;
403
- }
404
- let worldMat = worldMatrices[boneIndex];
405
- let invBindMat = inverseBindMatrices[boneIndex];
406
- skinMatrices[boneIndex] = worldMat * invBindMat;
407
- }
375
+ code: /* wgsl */ `
376
+ struct BoneCountUniform {
377
+ count: u32,
378
+ _padding1: u32,
379
+ _padding2: u32,
380
+ _padding3: u32,
381
+ _padding4: vec4<u32>,
382
+ };
383
+
384
+ @group(0) @binding(0) var<uniform> boneCount: BoneCountUniform;
385
+ @group(0) @binding(1) var<storage, read> worldMatrices: array<mat4x4f>;
386
+ @group(0) @binding(2) var<storage, read> inverseBindMatrices: array<mat4x4f>;
387
+ @group(0) @binding(3) var<storage, read_write> skinMatrices: array<mat4x4f>;
388
+
389
+ @compute @workgroup_size(64)
390
+ fn main(@builtin(global_invocation_id) globalId: vec3<u32>) {
391
+ let boneIndex = globalId.x;
392
+ // Bounds check: we dispatch workgroups (64 threads each), so some threads may be out of range
393
+ if (boneIndex >= boneCount.count) {
394
+ return;
395
+ }
396
+ let worldMat = worldMatrices[boneIndex];
397
+ let invBindMat = inverseBindMatrices[boneIndex];
398
+ skinMatrices[boneIndex] = worldMat * invBindMat;
399
+ }
408
400
  `,
409
401
  });
410
402
  this.skinMatrixComputePipeline = this.device.createComputePipeline({
@@ -557,8 +549,8 @@ export class Engine {
557
549
  this.physics = new Physics(model.getRigidbodies(), model.getJoints());
558
550
  await this.setupModelBuffers(model);
559
551
  }
560
- rotateBones(bones, rotations, duration) {
561
- this.currentModel?.rotateBones(bones, rotations, duration);
552
+ rotateBones(bones, rotations, durationMs) {
553
+ this.currentModel?.rotateBones(bones, rotations, durationMs);
562
554
  }
563
555
  // Step 7: Create vertex, index, and joint buffers
564
556
  async setupModelBuffers(model) {
@@ -646,7 +638,11 @@ export class Engine {
646
638
  const texture = await loadTextureByIndex(toonTextureIndex);
647
639
  if (texture)
648
640
  return texture;
649
- // Default toon texture fallback
641
+ // Default toon texture fallback - cache it
642
+ const defaultToonPath = "__default_toon__";
643
+ const cached = this.textureCache.get(defaultToonPath);
644
+ if (cached)
645
+ return cached;
650
646
  const defaultToonData = new Uint8Array(256 * 2 * 4);
651
647
  for (let i = 0; i < 256; i++) {
652
648
  const factor = i / 255.0;
@@ -667,7 +663,8 @@ export class Engine {
667
663
  usage: GPUTextureUsage.TEXTURE_BINDING | GPUTextureUsage.COPY_DST,
668
664
  });
669
665
  this.device.queue.writeTexture({ texture: defaultToonTexture }, defaultToonData, { bytesPerRow: 256 * 4 }, [256, 2]);
670
- this.textureSizes.set("__default_toon__", { width: 256, height: 2 });
666
+ this.textureCache.set(defaultToonPath, defaultToonTexture);
667
+ this.textureSizes.set(defaultToonPath, { width: 256, height: 2 });
671
668
  return defaultToonTexture;
672
669
  };
673
670
  this.materialDraws = [];
@@ -752,8 +749,8 @@ export class Engine {
752
749
  runningFirstIndex += matCount;
753
750
  }
754
751
  }
755
- // Helper: Load texture from file path
756
- async createTextureFromPath(path) {
752
+ // Helper: Load texture from file path with optional max size limit
753
+ async createTextureFromPath(path, maxSize = 2048) {
757
754
  const cached = this.textureCache.get(path);
758
755
  if (cached) {
759
756
  return cached;
@@ -763,22 +760,34 @@ export class Engine {
763
760
  if (!response.ok) {
764
761
  throw new Error(`HTTP ${response.status}: ${response.statusText}`);
765
762
  }
766
- const imageBitmap = await createImageBitmap(await response.blob(), {
763
+ let imageBitmap = await createImageBitmap(await response.blob(), {
767
764
  premultiplyAlpha: "none",
768
765
  colorSpaceConversion: "none",
769
766
  });
767
+ // Downscale if texture is too large
768
+ let finalWidth = imageBitmap.width;
769
+ let finalHeight = imageBitmap.height;
770
+ if (finalWidth > maxSize || finalHeight > maxSize) {
771
+ const scale = Math.min(maxSize / finalWidth, maxSize / finalHeight);
772
+ finalWidth = Math.floor(finalWidth * scale);
773
+ finalHeight = Math.floor(finalHeight * scale);
774
+ // Create canvas to downscale
775
+ const canvas = new OffscreenCanvas(finalWidth, finalHeight);
776
+ const ctx = canvas.getContext("2d");
777
+ if (ctx) {
778
+ ctx.drawImage(imageBitmap, 0, 0, finalWidth, finalHeight);
779
+ imageBitmap = await createImageBitmap(canvas);
780
+ }
781
+ }
770
782
  const texture = this.device.createTexture({
771
783
  label: `texture: ${path}`,
772
- size: [imageBitmap.width, imageBitmap.height],
784
+ size: [finalWidth, finalHeight],
773
785
  format: "rgba8unorm",
774
786
  usage: GPUTextureUsage.TEXTURE_BINDING | GPUTextureUsage.COPY_DST | GPUTextureUsage.RENDER_ATTACHMENT,
775
787
  });
776
- this.device.queue.copyExternalImageToTexture({ source: imageBitmap }, { texture }, [
777
- imageBitmap.width,
778
- imageBitmap.height,
779
- ]);
788
+ this.device.queue.copyExternalImageToTexture({ source: imageBitmap }, { texture }, [finalWidth, finalHeight]);
780
789
  this.textureCache.set(path, texture);
781
- this.textureSizes.set(path, { width: imageBitmap.width, height: imageBitmap.height });
790
+ this.textureSizes.set(path, { width: finalWidth, height: finalHeight });
782
791
  return texture;
783
792
  }
784
793
  catch {
@@ -914,41 +923,14 @@ export class Engine {
914
923
  this.stats.fps = Math.round((this.framesSinceLastUpdate / elapsed) * 1000);
915
924
  this.framesSinceLastUpdate = 0;
916
925
  this.lastFpsUpdate = now;
917
- const perf = performance;
918
- if (perf.memory) {
919
- this.stats.memoryUsed = Math.round(perf.memory.usedJSHeapSize / 1024 / 1024);
920
- }
921
- }
922
- this.stats.vertices = this.vertexCount;
923
- this.stats.drawCalls = this.drawCallCount;
924
- // Calculate triangles from index buffer
925
- if (this.indexBuffer) {
926
- const indexCount = this.currentModel?.getIndices()?.length || 0;
927
- this.stats.triangles = Math.floor(indexCount / 3);
928
- }
929
- else {
930
- this.stats.triangles = Math.floor(this.vertexCount / 3);
931
926
  }
932
- // Material count
933
- this.stats.materials = this.materialDraws.length;
934
- // Texture stats
935
- this.stats.textures = this.textureCache.size;
927
+ // Calculate GPU memory: textures + buffers + render targets
936
928
  let textureMemoryBytes = 0;
937
929
  for (const [path, size] of this.textureSizes.entries()) {
938
930
  if (this.textureCache.has(path)) {
939
- // RGBA8 = 4 bytes per pixel
940
- textureMemoryBytes += size.width * size.height * 4;
931
+ textureMemoryBytes += size.width * size.height * 4; // RGBA8 = 4 bytes per pixel
941
932
  }
942
933
  }
943
- // Add render target textures (multisample + depth)
944
- if (this.multisampleTexture) {
945
- const width = this.canvas.width;
946
- const height = this.canvas.height;
947
- textureMemoryBytes += width * height * 4 * this.sampleCount; // multisample color
948
- textureMemoryBytes += width * height * 4; // depth (depth24plus = 4 bytes)
949
- }
950
- this.stats.textureMemory = Math.round((textureMemoryBytes / 1024 / 1024) * 100) / 100;
951
- // Buffer memory estimate
952
934
  let bufferMemoryBytes = 0;
953
935
  if (this.vertexBuffer) {
954
936
  const vertices = this.currentModel?.getVertices();
@@ -977,10 +959,15 @@ export class Engine {
977
959
  }
978
960
  bufferMemoryBytes += 40 * 4; // cameraUniformBuffer
979
961
  bufferMemoryBytes += 64 * 4; // lightUniformBuffer
980
- // Material uniform buffers (estimate: 4 bytes per material)
981
- bufferMemoryBytes += this.materialDraws.length * 4;
982
- this.stats.bufferMemory = Math.round((bufferMemoryBytes / 1024 / 1024) * 100) / 100;
983
- // Total GPU memory estimate
984
- this.stats.gpuMemory = Math.round((this.stats.textureMemory + this.stats.bufferMemory) * 100) / 100;
962
+ bufferMemoryBytes += this.materialDraws.length * 4; // Material uniform buffers
963
+ let renderTargetMemoryBytes = 0;
964
+ if (this.multisampleTexture) {
965
+ const width = this.canvas.width;
966
+ const height = this.canvas.height;
967
+ renderTargetMemoryBytes += width * height * 4 * this.sampleCount; // multisample color
968
+ renderTargetMemoryBytes += width * height * 4; // depth
969
+ }
970
+ const totalGPUMemoryBytes = textureMemoryBytes + bufferMemoryBytes + renderTargetMemoryBytes;
971
+ this.stats.gpuMemory = Math.round((totalGPUMemoryBytes / 1024 / 1024) * 100) / 100;
985
972
  }
986
973
  }
package/dist/math.d.ts CHANGED
@@ -1,3 +1,4 @@
1
+ export declare function easeInOut(t: number): number;
1
2
  export declare class Vec3 {
2
3
  x: number;
3
4
  y: number;
@@ -28,6 +29,7 @@ export declare class Quat {
28
29
  rotate(v: Vec3): Vec3;
29
30
  static fromTo(from: Vec3, to: Vec3): Quat;
30
31
  toArray(): [number, number, number, number];
32
+ static slerp(a: Quat, b: Quat, t: number): Quat;
31
33
  static fromEuler(rotX: number, rotY: number, rotZ: number): Quat;
32
34
  toEuler(): Vec3;
33
35
  }
@@ -1 +1 @@
1
- {"version":3,"file":"math.d.ts","sourceRoot":"","sources":["../src/math.ts"],"names":[],"mappings":"AAAA,qBAAa,IAAI;IACf,CAAC,EAAE,MAAM,CAAA;IACT,CAAC,EAAE,MAAM,CAAA;IACT,CAAC,EAAE,MAAM,CAAA;gBAEG,CAAC,EAAE,MAAM,EAAE,CAAC,EAAE,MAAM,EAAE,CAAC,EAAE,MAAM;IAM3C,GAAG,CAAC,KAAK,EAAE,IAAI,GAAG,IAAI;IAItB,QAAQ,CAAC,KAAK,EAAE,IAAI,GAAG,IAAI;IAI3B,MAAM,IAAI,MAAM;IAIhB,SAAS,IAAI,IAAI;IAMjB,KAAK,CAAC,KAAK,EAAE,IAAI,GAAG,IAAI;IAQxB,GAAG,CAAC,KAAK,EAAE,IAAI,GAAG,MAAM;IAIxB,KAAK,CAAC,MAAM,EAAE,MAAM,GAAG,IAAI;IAI3B,KAAK,IAAI,IAAI;CAGd;AAED,qBAAa,IAAI;IACf,CAAC,EAAE,MAAM,CAAA;IACT,CAAC,EAAE,MAAM,CAAA;IACT,CAAC,EAAE,MAAM,CAAA;IACT,CAAC,EAAE,MAAM,CAAA;gBAEG,CAAC,EAAE,MAAM,EAAE,CAAC,EAAE,MAAM,EAAE,CAAC,EAAE,MAAM,EAAE,CAAC,EAAE,MAAM;IAOtD,GAAG,CAAC,KAAK,EAAE,IAAI,GAAG,IAAI;IAItB,KAAK,IAAI,IAAI;IAIb,QAAQ,CAAC,KAAK,EAAE,IAAI,GAAG,IAAI;IAU3B,SAAS,IAAI,IAAI;IAKjB,MAAM,IAAI,MAAM;IAIhB,SAAS,IAAI,IAAI;IAOjB,SAAS,CAAC,CAAC,EAAE,IAAI,GAAG,IAAI;IAwBxB,MAAM,CAAC,CAAC,EAAE,IAAI,GAAG,IAAI;IAQrB,MAAM,CAAC,MAAM,CAAC,IAAI,EAAE,IAAI,EAAE,EAAE,EAAE,IAAI,GAAG,IAAI;IAgBzC,OAAO,IAAI,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,CAAC;IAK3C,MAAM,CAAC,SAAS,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,IAAI;IAiBhE,OAAO,IAAI,IAAI;CAuBhB;AAED,qBAAa,IAAI;IACf,MAAM,EAAE,YAAY,CAAA;gBAER,MAAM,EAAE,YAAY;IAIhC,MAAM,CAAC,QAAQ,IAAI,IAAI;IAMvB,MAAM,CAAC,WAAW,CAAC,GAAG,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,GAAG,IAAI;IA4BhF,MAAM,CAAC,MAAM,CAAC,GAAG,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,EAAE,EAAE,IAAI,GAAG,IAAI;IA4BtD,QAAQ,CAAC,KAAK,EAAE,IAAI,GAAG,IAAI;IAqB3B,MAAM,CAAC,cAAc,CACnB,CAAC,EAAE,YAAY,EACf,OAAO,EAAE,MAAM,EACf,CAAC,EAAE,YAAY,EACf,OAAO,EAAE,MAAM,EACf,GAAG,EAAE,YAAY,EACjB,SAAS,EAAE,MAAM,GAChB,IAAI;IAiBP,KAAK,IAAI,IAAI;IAIb,MAAM,CAAC,QAAQ,CAAC,CAAC,EAAE,MAAM,EAAE,CAAC,EAAE,MAAM,EAAE,CAAC,EAAE,MAAM,EAAE,CAAC,EAAE,MAAM,GAAG,IAAI;IAmCjE,MAAM,CAAC,oBAAoB,CAAC,QAAQ,EAAE,IAAI,EAAE,QAAQ,EAAE,IAAI,GAAG,IAAI;IASjE,WAAW,IAAI,IAAI;IAKnB,MAAM,IAAI,IAAI;IAKd,MAAM,CAAC,eAAe,CAAC,CAAC,EAAE,YAAY,EAAE,MAAM,EAAE,MAAM,GAAG,IAAI;IA6C7D,WAAW,IAAI,IAAI;IAqBnB,gBAAgB,CAAC,EAAE,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM,GAAG,IAAI;IAY1D,OAAO,IAAI,IAAI;CA8DhB"}
1
+ {"version":3,"file":"math.d.ts","sourceRoot":"","sources":["../src/math.ts"],"names":[],"mappings":"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"}
package/dist/math.js CHANGED
@@ -1,3 +1,7 @@
1
+ // Easing function: ease-in-out quadratic
2
+ export function easeInOut(t) {
3
+ return t < 0.5 ? 2 * t * t : 1 - Math.pow(-2 * t + 2, 2) / 2;
4
+ }
1
5
  export class Vec3 {
2
6
  constructor(x, y, z) {
3
7
  this.x = x;
@@ -101,6 +105,35 @@ export class Quat {
101
105
  toArray() {
102
106
  return [this.x, this.y, this.z, this.w];
103
107
  }
108
+ // Spherical linear interpolation between two quaternions
109
+ static slerp(a, b, t) {
110
+ let cos = a.x * b.x + a.y * b.y + a.z * b.z + a.w * b.w;
111
+ let bx = b.x, by = b.y, bz = b.z, bw = b.w;
112
+ // If dot product is negative, negate one quaternion to take shorter path
113
+ if (cos < 0) {
114
+ cos = -cos;
115
+ bx = -bx;
116
+ by = -by;
117
+ bz = -bz;
118
+ bw = -bw;
119
+ }
120
+ // If quaternions are very close, use linear interpolation
121
+ if (cos > 0.9995) {
122
+ const x = a.x + t * (bx - a.x);
123
+ const y = a.y + t * (by - a.y);
124
+ const z = a.z + t * (bz - a.z);
125
+ const w = a.w + t * (bw - a.w);
126
+ const invLen = 1 / Math.hypot(x, y, z, w);
127
+ return new Quat(x * invLen, y * invLen, z * invLen, w * invLen);
128
+ }
129
+ // Standard SLERP
130
+ const theta0 = Math.acos(cos);
131
+ const sinTheta0 = Math.sin(theta0);
132
+ const theta = theta0 * t;
133
+ const s0 = Math.sin(theta0 - theta) / sinTheta0;
134
+ const s1 = Math.sin(theta) / sinTheta0;
135
+ return new Quat(s0 * a.x + s1 * bx, s0 * a.y + s1 * by, s0 * a.z + s1 * bz, s0 * a.w + s1 * bw);
136
+ }
104
137
  // Convert Euler angles to quaternion (ZXY order, left-handed, PMX format)
105
138
  static fromEuler(rotX, rotY, rotZ) {
106
139
  const cx = Math.cos(rotX * 0.5);