reze-engine 0.10.0 → 0.10.2

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/README.md CHANGED
@@ -2,6 +2,8 @@
2
2
 
3
3
  A minimal-dependency WebGPU engine for real-time MMD/PMX rendering. Only external dependency is Ammo.js for physics.
4
4
 
5
+ ![screenshot](./screenshot.png)
6
+
5
7
  ## Install
6
8
 
7
9
  ```bash
@@ -14,80 +16,146 @@ npm install reze-engine
14
16
  - VMD animation with IK solver and Bullet physics
15
17
  - Orbit camera with bone-follow mode
16
18
  - GPU picking (double-click/tap)
17
- - Ground plane with PCF shadow mapping, grid lines, and frosted texture
19
+ - Ground plane with PCF shadow mapping
18
20
  - Multi-model support
19
21
 
20
- ## Quick Start
22
+ ## Usage
21
23
 
22
24
  ```javascript
23
- import { Engine, Vec3 } from "reze-engine"
25
+ import { Engine, Vec3 } from "reze-engine";
24
26
 
25
27
  const engine = new Engine(canvas, {
26
28
  ambientColor: new Vec3(0.88, 0.92, 0.99),
27
29
  cameraDistance: 31.5, // MMD units (1 unit = 8 cm)
28
- })
29
- await engine.init()
30
-
31
- const model = await engine.loadModel("hero", "/models/hero/hero.pmx")
32
- await model.loadAnimation("idle", "/animations/idle.vmd")
33
- model.show("idle")
34
- model.play()
35
-
36
- engine.setCameraFollow(model, "センター", new Vec3(0, 3.5, 0))
37
- engine.addGround({ width: 160, height: 160 })
38
- engine.runRenderLoop()
30
+ cameraTarget: new Vec3(0, 11.5, 0),
31
+ });
32
+ await engine.init();
33
+
34
+ const model = await engine.loadModel("hero", "/models/hero/hero.pmx");
35
+ await model.loadVmd("idle", "/animations/idle.vmd");
36
+ model.show("idle");
37
+ model.play();
38
+
39
+ engine.setCameraFollow(model, "センター", new Vec3(0, 3.5, 0));
40
+ engine.addGround({ width: 160, height: 160 });
41
+ engine.runRenderLoop();
39
42
  ```
40
43
 
41
44
  ## API
42
45
 
46
+ One WebGPU **Engine** per page (singleton after `init()`). Load models via URL **or** from a user-selected folder (see [Local folder uploads](#local-folder-uploads-browser)).
47
+
43
48
  ### Engine
44
49
 
45
- | Method | Description |
46
- |--------|-------------|
47
- | `new Engine(canvas, options?)` | Create engine with optional config |
48
- | `engine.init()` | Initialize WebGPU device and context |
49
- | `engine.loadModel(path)` | Load PMX model (auto-named) |
50
- | `engine.loadModel(name, path)` | Load PMX model with name |
51
- | `engine.getModel(name)` | Get model by name |
52
- | `engine.getModelNames()` | List all model names |
53
- | `engine.removeModel(name)` | Remove model |
54
- | `engine.setMaterialVisible(model, mat, visible)` | Show/hide material |
55
- | `engine.toggleMaterialVisible(model, mat)` | Toggle material visibility |
56
- | `engine.setIKEnabled(enabled)` | Enable/disable IK globally |
57
- | `engine.setPhysicsEnabled(enabled)` | Enable/disable physics globally |
58
- | `engine.setCameraFollow(model, bone?, offset?)` | Orbit center tracks a bone |
59
- | `engine.setCameraTarget(vec3)` | Static camera target |
60
- | `engine.setCameraDistance(d)` | Set orbit radius |
61
- | `engine.setCameraAlpha(a)` | Set horizontal orbit angle |
62
- | `engine.setCameraBeta(b)` | Set vertical orbit angle |
63
- | `engine.addGround(options?)` | Add ground plane with shadows |
64
- | `engine.runRenderLoop(callback?)` | Start render loop |
65
- | `engine.stopRenderLoop()` | Stop render loop |
66
- | `engine.getStats()` | Returns `{ fps, frameTime }` |
67
- | `engine.dispose()` | Clean up all resources |
50
+ ```javascript
51
+ engine.init()
52
+ engine.loadModel(name, path)
53
+ engine.loadModel(name, { files, pmxFile? }) // folder upload — see below
54
+ engine.getModel(name)
55
+ engine.getModelNames()
56
+ engine.removeModel(name)
57
+
58
+ engine.setMaterialVisible(name, material, visible)
59
+ engine.toggleMaterialVisible(name, material)
60
+ engine.isMaterialVisible(name, material)
61
+
62
+ engine.setIKEnabled(enabled)
63
+ engine.setPhysicsEnabled(enabled)
64
+
65
+ engine.setCameraFollow(model, bone?, offset?)
66
+ engine.setCameraFollow(null)
67
+ engine.setCameraTarget(vec3)
68
+ engine.setCameraDistance(d)
69
+ engine.setCameraAlpha(a)
70
+ engine.setCameraBeta(b)
71
+
72
+ engine.addGround(options?)
73
+ engine.runRenderLoop(callback?)
74
+ engine.stopRenderLoop()
75
+ engine.getStats()
76
+ engine.dispose()
77
+ ```
78
+
79
+ ### Local folder uploads (browser)
80
+
81
+ Use a hidden `<input type="file" webkitdirectory multiple>` (or drag/drop) and pass the resulting `FileList` or `File[]` into the engine. Textures resolve relative to the chosen PMX file inside that tree.
82
+
83
+ **Important:** read `input.files` into a normal array **before** setting `input.value = ""`. The browser’s `FileList` is *live* — clearing the input empties it.
84
+
85
+ 1. **`parsePmxFolderInput(fileList)`** — returns a tagged result (`empty` | `not_directory` | `no_pmx` | `single` | `multiple`). For `single`, you already have `files` and `pmxFile`. For `multiple`, show a picker (dropdown) of `pmxRelativePaths`, then resolve with **`pmxFileAtRelativePath(files, path)`**.
86
+ 2. **`engine.loadModel(name, { files, pmxFile })`** — `pmxFile` selects which `.pmx` when the folder contains several.
87
+
88
+ ```javascript
89
+ import { Engine, parsePmxFolderInput, pmxFileAtRelativePath } from "reze-engine";
90
+
91
+ // In <input onChange>:
92
+ const picked = parsePmxFolderInput(e.target.files);
93
+ e.target.value = "";
94
+
95
+ if (picked.status === "single") {
96
+ const model = await engine.loadModel("myModel", {
97
+ files: picked.files,
98
+ pmxFile: picked.pmxFile,
99
+ });
100
+ }
101
+
102
+ if (picked.status === "multiple") {
103
+ // Let the user choose `chosenPath` from picked.pmxRelativePaths, then:
104
+ const pmxFile = pmxFileAtRelativePath(picked.files, chosenPath);
105
+ const model = await engine.loadModel("myModel", { files: picked.files, pmxFile });
106
+ }
107
+ ```
108
+
109
+ VMD and other assets still load by URL when the path starts with `/` or `http(s):`; relative paths are resolved against the PMX directory inside the upload.
68
110
 
69
111
  ### Model
70
112
 
71
- | Method | Description |
72
- |--------|-------------|
73
- | `model.loadAnimation(name, url)` | Load VMD animation |
74
- | `model.loadAnimation(name, clip)` | Load/replace animation clip directly |
75
- | `model.show(name)` | Set pose at time 0 (resets bones and morphs first) |
76
- | `model.play(name?)` | Play animation (queued if busy; named play resets bones/morphs first) |
77
- | `model.play(name, { priority?, loop? })` | Priority-aware play; `loop` wraps at end (`0` default/lowest priority) |
78
- | `model.pause()` | Pause playback |
79
- | `model.stop()` | Stop playback |
80
- | `model.seek(time)` | Seek to time |
81
- | `model.getAnimationProgress()` | `{ current, duration, percentage, animationName, looping, playing, paused }` — `current`/`duration` are seconds |
82
- | `model.getAnimationClip(name)` | Get loaded clip by name |
83
- | `model.rotateBones(rotations, ms?)` | Tween bone rotations |
84
- | `model.moveBones(translations, ms?)` | Tween bone translations |
85
- | `model.setMorphWeight(name, weight, ms?)` | Tween morph weight |
86
- | `model.resetAllBones()` | Reset to bind pose |
87
- | `model.resetAllMorphs()` | Reset all morph weights |
88
- | `model.getBoneWorldPosition(name)` | World position of bone |
89
-
90
- `AnimationClip` is frame-based: `frameCount` is the last keyframe frame index, keyframes store `frame`. Engine playback uses fixed 30 FPS. Looping is controlled via `play(name, { loop: true })`, not on the clip.
113
+ ```javascript
114
+ await model.loadVmd(name, url)
115
+ model.loadClip(name, clip)
116
+ model.show(name)
117
+ model.play(name)
118
+ model.play(name, { priority: 8 }) // higher number = higher priority (0 default/lowest)
119
+ model.play(name, { loop: true }) // repeat until stop/pause or another play
120
+ model.pause()
121
+ model.stop()
122
+ model.seek(time)
123
+ model.getAnimationProgress()
124
+ model.getClip(name)
125
+ model.exportVmd(name) // returns ArrayBuffer
126
+
127
+ model.rotateBones({ 首: quat, 頭: quat }, ms?)
128
+ model.moveBones({ センター: vec3 }, ms?)
129
+ model.setMorphWeight(name, weight, ms?)
130
+ model.resetAllBones()
131
+ model.resetAllMorphs()
132
+ model.getBoneWorldPosition(name)
133
+ ```
134
+
135
+ #### Animation data
136
+
137
+ `AnimationClip` holds keyframes only: bone/morph tracks keyed by `frame`, and `frameCount` (last keyframe index). Time advances at fixed `FPS` (see package export `FPS`, default 30).
138
+
139
+ #### VMD Export
140
+
141
+ `model.exportVmd(name)` serialises a loaded clip back to the VMD binary format and returns an `ArrayBuffer`. Bone and morph names are Shift-JIS encoded for compatibility with standard MMD tools.
142
+
143
+ ```javascript
144
+ const buffer = model.exportVmd("idle");
145
+ const blob = new Blob([buffer], { type: "application/octet-stream" });
146
+ const link = document.createElement("a");
147
+ link.href = URL.createObjectURL(blob);
148
+ link.download = "idle.vmd";
149
+ link.click();
150
+ ```
151
+
152
+ #### Playback
153
+
154
+ Call `model.play(name, options?)` to start or switch motion. `loop: true` makes the playhead wrap at the end of the clip until you stop, pause, or call `play` with something else. `priority` chooses which request wins when several clips compete.
155
+
156
+ #### Progress
157
+
158
+ `getAnimationProgress()` reports `current` and `duration` in seconds, plus `playing`, `paused`, `looping`, and related fields.
91
159
 
92
160
  ### Engine Options
93
161
 
@@ -112,27 +180,9 @@ engine.runRenderLoop()
112
180
 
113
181
  `constraintSolverKeywords` — joints whose name contains any keyword use the Bullet 2.75 constraint solver; all others keep the stable Ammo 2.82+ default. See [babylon-mmd: Fix Constraint Behavior](https://noname0310.github.io/babylon-mmd/docs/reference/runtime/apply-physics-to-mmd-models/#fix-constraint-behavior) for details.
114
182
 
115
- ### Ground Options
116
-
117
- ```javascript
118
- engine.addGround({
119
- width: 100, // ground plane width
120
- height: 100, // ground plane depth
121
- diffuseColor: Vec3, // base color (default: 0.8, 0.1, 1.0)
122
- fadeStart: 5.0, // distance where edge fade begins
123
- fadeEnd: 60.0, // distance where ground fully fades out
124
- shadowMapSize: 4096, // shadow map resolution
125
- shadowStrength: 1.0, // shadow darkness
126
- gridSpacing: 5.0, // world-space distance between grid lines
127
- gridLineWidth: 0.012, // thickness of grid lines
128
- gridLineOpacity: 0.4, // grid line visibility (0–1)
129
- gridLineColor: Vec3, // grid line color (default: 0.8, 0.8, 0.8)
130
- noiseStrength: 0.08, // frosted/matte micro-texture intensity
131
- })
132
- ```
133
-
134
183
  ## Projects Using This Engine
135
184
 
185
+ - **[Reze Studio](https://reze.studio)** - Web-native MMD animation editor
136
186
  - **[MiKaPo](https://mikapo.vercel.app)** — Real-time motion capture for MMD
137
187
  - **[Popo](https://popo.love)** — LLM-generated MMD poses
138
188
  - **[MPL](https://mmd-mpl.vercel.app)** — Motion programming language for MMD
@@ -0,0 +1,16 @@
1
+ /** Unified binary I/O for PMX/VMD/textures: HTTP(s) or user folder (File map). */
2
+ export type AssetReader = {
3
+ readBinary(logicalPath: string): Promise<ArrayBuffer>;
4
+ };
5
+ /** Normalize PMX-style paths: backslashes, trim, strip leading ./ */
6
+ export declare function normalizeAssetPath(p: string): string;
7
+ /** Join PMX directory prefix and texture-relative path (both may be ""). */
8
+ export declare function joinAssetPath(baseDir: string, relative: string): string;
9
+ /** Same rules as the original engine string split: supports absolute site paths like `/models/a/b.pmx`. */
10
+ export declare function deriveBasePathFromPmxPath(pmxPath: string): string;
11
+ export declare function createFetchAssetReader(): AssetReader;
12
+ /** Keys must be normalized paths relative to the selected folder root (see fileListToMap). */
13
+ export declare function createFileMapAssetReader(files: Map<string, File>): AssetReader;
14
+ export declare function fileListToMap(files: FileList | File[]): Map<string, File>;
15
+ export declare function findFirstPmxFileInList(files: FileList | File[]): File | null;
16
+ //# sourceMappingURL=asset-reader.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"asset-reader.d.ts","sourceRoot":"","sources":["../src/asset-reader.ts"],"names":[],"mappings":"AAAA,kFAAkF;AAElF,MAAM,MAAM,WAAW,GAAG;IACxB,UAAU,CAAC,WAAW,EAAE,MAAM,GAAG,OAAO,CAAC,WAAW,CAAC,CAAA;CACtD,CAAA;AAED,qEAAqE;AACrE,wBAAgB,kBAAkB,CAAC,CAAC,EAAE,MAAM,GAAG,MAAM,CAIpD;AAED,4EAA4E;AAC5E,wBAAgB,aAAa,CAAC,OAAO,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,MAAM,CAMvE;AAED,2GAA2G;AAC3G,wBAAgB,yBAAyB,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,CAIjE;AAED,wBAAgB,sBAAsB,IAAI,WAAW,CAQpD;AAED,8FAA8F;AAC9F,wBAAgB,wBAAwB,CAAC,KAAK,EAAE,GAAG,CAAC,MAAM,EAAE,IAAI,CAAC,GAAG,WAAW,CAkB9E;AAED,wBAAgB,aAAa,CAAC,KAAK,EAAE,QAAQ,GAAG,IAAI,EAAE,GAAG,GAAG,CAAC,MAAM,EAAE,IAAI,CAAC,CAOzE;AAED,wBAAgB,sBAAsB,CAAC,KAAK,EAAE,QAAQ,GAAG,IAAI,EAAE,GAAG,IAAI,GAAG,IAAI,CAS5E"}
@@ -0,0 +1,74 @@
1
+ /** Unified binary I/O for PMX/VMD/textures: HTTP(s) or user folder (File map). */
2
+ /** Normalize PMX-style paths: backslashes, trim, strip leading ./ */
3
+ export function normalizeAssetPath(p) {
4
+ let s = p.replace(/\\/g, "/").trim();
5
+ if (s.startsWith("./"))
6
+ s = s.slice(2);
7
+ return s;
8
+ }
9
+ /** Join PMX directory prefix and texture-relative path (both may be ""). */
10
+ export function joinAssetPath(baseDir, relative) {
11
+ const rel = normalizeAssetPath(relative);
12
+ if (!rel)
13
+ return normalizeAssetPath(baseDir);
14
+ const base = baseDir.endsWith("/") ? baseDir.slice(0, -1) : baseDir;
15
+ if (!base)
16
+ return rel;
17
+ return `${base}/${rel}`;
18
+ }
19
+ /** Same rules as the original engine string split: supports absolute site paths like `/models/a/b.pmx`. */
20
+ export function deriveBasePathFromPmxPath(pmxPath) {
21
+ const pathParts = pmxPath.replace(/\\/g, "/").split("/");
22
+ pathParts.pop();
23
+ return pathParts.join("/") + (pathParts.length > 0 ? "/" : "");
24
+ }
25
+ export function createFetchAssetReader() {
26
+ return {
27
+ async readBinary(logicalPath) {
28
+ const r = await fetch(logicalPath);
29
+ if (!r.ok)
30
+ throw new Error(`Failed to fetch ${logicalPath}: ${r.status} ${r.statusText}`);
31
+ return r.arrayBuffer();
32
+ },
33
+ };
34
+ }
35
+ /** Keys must be normalized paths relative to the selected folder root (see fileListToMap). */
36
+ export function createFileMapAssetReader(files) {
37
+ return {
38
+ async readBinary(logicalPath) {
39
+ const key = normalizeAssetPath(logicalPath);
40
+ let file = files.get(key);
41
+ if (!file) {
42
+ const lower = key.toLowerCase();
43
+ for (const [k, f] of files) {
44
+ if (k.toLowerCase() === lower) {
45
+ file = f;
46
+ break;
47
+ }
48
+ }
49
+ }
50
+ if (!file)
51
+ throw new Error(`Missing file in folder: ${key}`);
52
+ return file.arrayBuffer();
53
+ },
54
+ };
55
+ }
56
+ export function fileListToMap(files) {
57
+ const m = new Map();
58
+ for (const f of Array.from(files)) {
59
+ const rel = f.webkitRelativePath ?? f.name;
60
+ m.set(normalizeAssetPath(rel), f);
61
+ }
62
+ return m;
63
+ }
64
+ export function findFirstPmxFileInList(files) {
65
+ const list = Array.from(files).filter((f) => f.name.toLowerCase().endsWith(".pmx"));
66
+ if (list.length === 0)
67
+ return null;
68
+ list.sort((a, b) => {
69
+ const pa = a.webkitRelativePath ?? a.name;
70
+ const pb = b.webkitRelativePath ?? b.name;
71
+ return pa.localeCompare(pb);
72
+ });
73
+ return list[0] ?? null;
74
+ }
package/dist/engine.d.ts CHANGED
@@ -1,7 +1,13 @@
1
1
  import { Vec3 } from "./math";
2
2
  import { Model } from "./model";
3
3
  import { type PhysicsOptions } from "./physics";
4
+ import { type AssetReader } from "./asset-reader";
4
5
  export type RaycastCallback = (modelName: string, material: string | null, screenX: number, screenY: number) => void;
6
+ /** Select a folder (webkitdirectory) and pass FileList or File[]; pmxFile picks which .pmx when several exist. */
7
+ export type LoadModelFromFilesOptions = {
8
+ files: FileList | File[];
9
+ pmxFile?: File;
10
+ };
5
11
  export type EngineOptions = {
6
12
  ambientColor?: Vec3;
7
13
  directionalLightIntensity?: number;
@@ -154,7 +160,8 @@ export declare class Engine {
154
160
  dispose(): void;
155
161
  loadModel(path: string): Promise<Model>;
156
162
  loadModel(name: string, path: string): Promise<Model>;
157
- addModel(model: Model, pmxPath: string, name?: string): Promise<string>;
163
+ loadModel(name: string, options: LoadModelFromFilesOptions): Promise<Model>;
164
+ addModel(model: Model, pmxPath: string, name?: string, assetReader?: AssetReader): Promise<string>;
158
165
  removeModel(name: string): void;
159
166
  getModelNames(): string[];
160
167
  getModel(name: string): Model | null;
@@ -178,7 +185,7 @@ export declare class Engine {
178
185
  private createMaterialUniformBuffer;
179
186
  private createUniformBuffer;
180
187
  private shouldRenderDrawCall;
181
- private createTextureFromPath;
188
+ private createTextureFromLogicalPath;
182
189
  private renderGround;
183
190
  private handleCanvasDoubleClick;
184
191
  private handleCanvasTouch;
@@ -1 +1 @@
1
- {"version":3,"file":"engine.d.ts","sourceRoot":"","sources":["../src/engine.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"engine.d.ts","sourceRoot":"","sources":["../src/engine.ts"],"names":[],"mappings":"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"}
package/dist/engine.js CHANGED
@@ -2,6 +2,7 @@ import { Camera } from "./camera";
2
2
  import { Mat4, Vec3 } from "./math";
3
3
  import { PmxLoader } from "./pmx-loader";
4
4
  import { Physics } from "./physics";
5
+ import { createFetchAssetReader, createFileMapAssetReader, deriveBasePathFromPmxPath, fileListToMap, findFirstPmxFileInList, joinAssetPath, normalizeAssetPath, } from "./asset-reader";
5
6
  export const DEFAULT_ENGINE_OPTIONS = {
6
7
  ambientColor: new Vec3(0.88, 0.88, 0.88),
7
8
  directionalLightIntensity: 0.24,
@@ -965,28 +966,55 @@ export class Engine {
965
966
  this.resizeObserver = null;
966
967
  }
967
968
  }
968
- async loadModel(nameOrPath, path) {
969
- const pmxPath = path === undefined ? nameOrPath : path;
970
- const name = path === undefined ? "model_" + (this._nextDefaultModelId++) : nameOrPath;
969
+ async loadModel(nameOrPath, pathOrOptions) {
970
+ if (pathOrOptions !== undefined && typeof pathOrOptions === "object" && "files" in pathOrOptions) {
971
+ const name = nameOrPath;
972
+ const pmxFile = pathOrOptions.pmxFile ?? findFirstPmxFileInList(pathOrOptions.files);
973
+ if (!pmxFile)
974
+ throw new Error("No .pmx file found in the selected folder");
975
+ const map = fileListToMap(pathOrOptions.files);
976
+ const pmxKey = normalizeAssetPath(pmxFile.webkitRelativePath ?? pmxFile.name);
977
+ const reader = createFileMapAssetReader(map);
978
+ const model = await PmxLoader.loadFromReader(reader, pmxKey);
979
+ model.setName(name);
980
+ await this.addModel(model, pmxKey, name, reader);
981
+ return model;
982
+ }
983
+ const pmxPath = pathOrOptions === undefined ? nameOrPath : pathOrOptions;
984
+ const name = pathOrOptions === undefined ? "model_" + this._nextDefaultModelId++ : nameOrPath;
971
985
  const model = await PmxLoader.load(pmxPath);
972
986
  model.setName(name);
973
987
  await this.addModel(model, pmxPath, name);
974
988
  return model;
975
989
  }
976
- async addModel(model, pmxPath, name) {
990
+ async addModel(model, pmxPath, name, assetReader) {
977
991
  const requested = name ?? model.name;
978
992
  let key = requested;
979
993
  let n = 1;
980
994
  while (this.modelInstances.has(key)) {
981
995
  key = `${requested}_${n++}`;
982
996
  }
983
- const pathParts = pmxPath.split("/");
984
- pathParts.pop();
985
- const basePath = pathParts.join("/") + "/";
986
- await this.setupModelInstance(key, model, basePath);
997
+ const reader = assetReader ?? createFetchAssetReader();
998
+ const basePath = deriveBasePathFromPmxPath(pmxPath);
999
+ model.setAssetContext(reader, basePath);
1000
+ await this.setupModelInstance(key, model, basePath, reader);
987
1001
  return key;
988
1002
  }
989
1003
  removeModel(name) {
1004
+ const inst = this.modelInstances.get(name);
1005
+ if (!inst)
1006
+ return;
1007
+ inst.model.stopAnimation();
1008
+ for (const path of inst.textureCacheKeys) {
1009
+ const tex = this.textureCache.get(path);
1010
+ if (tex) {
1011
+ tex.destroy();
1012
+ this.textureCache.delete(path);
1013
+ }
1014
+ }
1015
+ for (const buf of inst.gpuBuffers) {
1016
+ buf.destroy();
1017
+ }
990
1018
  this.modelInstances.delete(name);
991
1019
  }
992
1020
  getModelNames() {
@@ -1068,7 +1096,7 @@ export class Engine {
1068
1096
  this.device.queue.writeBuffer(inst.vertexBuffer, 0, vertices);
1069
1097
  inst.vertexBufferNeedsUpdate = false;
1070
1098
  }
1071
- async setupModelInstance(name, model, basePath) {
1099
+ async setupModelInstance(name, model, basePath, assetReader) {
1072
1100
  const vertices = model.getVertices();
1073
1101
  const skinning = model.getSkinning();
1074
1102
  const skeleton = model.getSkeleton();
@@ -1130,10 +1158,20 @@ export class Engine {
1130
1158
  { binding: 0, resource: { buffer: skinMatrixBuffer } },
1131
1159
  ],
1132
1160
  });
1161
+ const gpuBuffers = [
1162
+ vertexBuffer,
1163
+ indexBuffer,
1164
+ jointsBuffer,
1165
+ weightsBuffer,
1166
+ skinMatrixBuffer,
1167
+ ];
1133
1168
  const inst = {
1134
1169
  name,
1135
1170
  model,
1136
1171
  basePath,
1172
+ assetReader,
1173
+ gpuBuffers,
1174
+ textureCacheKeys: [],
1137
1175
  vertexBuffer,
1138
1176
  indexBuffer,
1139
1177
  jointsBuffer,
@@ -1285,8 +1323,8 @@ export class Engine {
1285
1323
  const loadTextureByIndex = async (texIndex) => {
1286
1324
  if (texIndex < 0 || texIndex >= textures.length)
1287
1325
  return null;
1288
- const path = inst.basePath + textures[texIndex].path;
1289
- return this.createTextureFromPath(path);
1326
+ const logicalPath = joinAssetPath(inst.basePath, normalizeAssetPath(textures[texIndex].path));
1327
+ return this.createTextureFromLogicalPath(inst, logicalPath);
1290
1328
  };
1291
1329
  let currentIndexOffset = 0;
1292
1330
  let materialId = 0;
@@ -1301,6 +1339,7 @@ export class Engine {
1301
1339
  const materialAlpha = mat.diffuse[3];
1302
1340
  const isTransparent = materialAlpha < 1.0 - 0.001;
1303
1341
  const materialUniformBuffer = this.createMaterialUniformBuffer(prefix + mat.name, materialAlpha, [mat.diffuse[0], mat.diffuse[1], mat.diffuse[2]], mat.ambient, mat.specular, mat.shininess);
1342
+ inst.gpuBuffers.push(materialUniformBuffer);
1304
1343
  const textureView = diffuseTexture.createView();
1305
1344
  const bindGroup = this.device.createBindGroup({
1306
1345
  label: `${prefix}material: ${mat.name}`,
@@ -1318,6 +1357,7 @@ export class Engine {
1318
1357
  mat.edgeSize, 0, 0, 0,
1319
1358
  ]);
1320
1359
  const outlineUniformBuffer = this.createUniformBuffer(`${prefix}outline: ${mat.name}`, materialUniformData);
1360
+ inst.gpuBuffers.push(outlineUniformBuffer);
1321
1361
  const outlineBindGroup = this.device.createBindGroup({
1322
1362
  label: `${prefix}outline: ${mat.name}`,
1323
1363
  layout: this.outlinePerMaterialBindGroupLayout,
@@ -1331,6 +1371,7 @@ export class Engine {
1331
1371
  if (this.onRaycast) {
1332
1372
  const pickIdData = new Float32Array([modelId, materialId, 0, 0]);
1333
1373
  const pickIdBuffer = this.createUniformBuffer(`${prefix}pick: ${mat.name}`, pickIdData);
1374
+ inst.gpuBuffers.push(pickIdBuffer);
1334
1375
  const pickBindGroup = this.device.createBindGroup({
1335
1376
  label: `${prefix}pick: ${mat.name}`,
1336
1377
  layout: this.pickPerMaterialBindGroupLayout,
@@ -1371,22 +1412,20 @@ export class Engine {
1371
1412
  shouldRenderDrawCall(inst, drawCall) {
1372
1413
  return !inst.hiddenMaterials.has(drawCall.materialName);
1373
1414
  }
1374
- async createTextureFromPath(path) {
1375
- const cached = this.textureCache.get(path);
1415
+ async createTextureFromLogicalPath(inst, logicalPath) {
1416
+ const cacheKey = logicalPath;
1417
+ const cached = this.textureCache.get(cacheKey);
1376
1418
  if (cached) {
1377
1419
  return cached;
1378
1420
  }
1379
1421
  try {
1380
- const response = await fetch(path);
1381
- if (!response.ok) {
1382
- throw new Error(`HTTP ${response.status}: ${response.statusText}`);
1383
- }
1384
- const imageBitmap = await createImageBitmap(await response.blob(), {
1422
+ const buffer = await inst.assetReader.readBinary(logicalPath);
1423
+ const imageBitmap = await createImageBitmap(new Blob([buffer]), {
1385
1424
  premultiplyAlpha: "none",
1386
1425
  colorSpaceConversion: "none",
1387
1426
  });
1388
1427
  const texture = this.device.createTexture({
1389
- label: `texture: ${path}`,
1428
+ label: `texture: ${cacheKey}`,
1390
1429
  size: [imageBitmap.width, imageBitmap.height],
1391
1430
  format: "rgba8unorm",
1392
1431
  usage: GPUTextureUsage.TEXTURE_BINDING | GPUTextureUsage.COPY_DST | GPUTextureUsage.RENDER_ATTACHMENT,
@@ -1395,7 +1434,8 @@ export class Engine {
1395
1434
  imageBitmap.width,
1396
1435
  imageBitmap.height,
1397
1436
  ]);
1398
- this.textureCache.set(path, texture);
1437
+ this.textureCache.set(cacheKey, texture);
1438
+ inst.textureCacheKeys.push(cacheKey);
1399
1439
  return texture;
1400
1440
  }
1401
1441
  catch {
@@ -0,0 +1,24 @@
1
+ /** After choosing a path from `multiple`, get the `File` for `loadModel(..., { files, pmxFile })`. */
2
+ export declare function pmxFileAtRelativePath(files: File[], relativePath: string): File | undefined;
3
+ /** Result of reading a folder input — switch on `status` in your UI. */
4
+ export type PmxFolderInputResult = {
5
+ status: "empty";
6
+ } | {
7
+ status: "not_directory";
8
+ } | {
9
+ status: "no_pmx";
10
+ } | {
11
+ status: "single";
12
+ files: File[];
13
+ pmxFile: File;
14
+ } | {
15
+ status: "multiple";
16
+ files: File[];
17
+ pmxRelativePaths: string[];
18
+ };
19
+ /**
20
+ * One call from `onChange`: snapshots files, validates folder pick, resolves a single PMX or asks you to pick among several.
21
+ * Reset the input after: `e.target.value = ""`.
22
+ */
23
+ export declare function parsePmxFolderInput(fileList: FileList | null | undefined): PmxFolderInputResult;
24
+ //# sourceMappingURL=folder-upload.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"folder-upload.d.ts","sourceRoot":"","sources":["../src/folder-upload.ts"],"names":[],"mappings":"AAyBA,sGAAsG;AACtG,wBAAgB,qBAAqB,CAAC,KAAK,EAAE,IAAI,EAAE,EAAE,YAAY,EAAE,MAAM,GAAG,IAAI,GAAG,SAAS,CAO3F;AAED,wEAAwE;AACxE,MAAM,MAAM,oBAAoB,GAC5B;IAAE,MAAM,EAAE,OAAO,CAAA;CAAE,GACnB;IAAE,MAAM,EAAE,eAAe,CAAA;CAAE,GAC3B;IAAE,MAAM,EAAE,QAAQ,CAAA;CAAE,GACpB;IAAE,MAAM,EAAE,QAAQ,CAAC;IAAC,KAAK,EAAE,IAAI,EAAE,CAAC;IAAC,OAAO,EAAE,IAAI,CAAA;CAAE,GAClD;IAAE,MAAM,EAAE,UAAU,CAAC;IAAC,KAAK,EAAE,IAAI,EAAE,CAAC;IAAC,gBAAgB,EAAE,MAAM,EAAE,CAAA;CAAE,CAAA;AAErE;;;GAGG;AACH,wBAAgB,mBAAmB,CAAC,QAAQ,EAAE,QAAQ,GAAG,IAAI,GAAG,SAAS,GAAG,oBAAoB,CAW/F"}
@@ -0,0 +1,50 @@
1
+ import { normalizeAssetPath } from "./asset-reader";
2
+ /**
3
+ * Call on `<input type="file" webkitdirectory>` `change` **before** `input.value = ""`.
4
+ * `FileList` is live — clearing the input empties it; this copies to a stable `File[]`.
5
+ */
6
+ function prepareLocalFolderFiles(fileList) {
7
+ const files = fileList?.length ? Array.from(fileList) : [];
8
+ const pmxRelativePaths = [];
9
+ for (const f of files) {
10
+ const wr = f.webkitRelativePath;
11
+ if (!wr || !wr.toLowerCase().endsWith(".pmx"))
12
+ continue;
13
+ pmxRelativePaths.push(normalizeAssetPath(wr));
14
+ }
15
+ pmxRelativePaths.sort((a, b) => a.localeCompare(b));
16
+ return { files, pmxRelativePaths };
17
+ }
18
+ function isDirectoryUpload(files) {
19
+ return files.length > 0 && files.every((f) => !!f.webkitRelativePath);
20
+ }
21
+ /** After choosing a path from `multiple`, get the `File` for `loadModel(..., { files, pmxFile })`. */
22
+ export function pmxFileAtRelativePath(files, relativePath) {
23
+ const norm = normalizeAssetPath(relativePath);
24
+ for (const f of files) {
25
+ const wr = f.webkitRelativePath;
26
+ if (wr && normalizeAssetPath(wr) === norm)
27
+ return f;
28
+ }
29
+ return undefined;
30
+ }
31
+ /**
32
+ * One call from `onChange`: snapshots files, validates folder pick, resolves a single PMX or asks you to pick among several.
33
+ * Reset the input after: `e.target.value = ""`.
34
+ */
35
+ export function parsePmxFolderInput(fileList) {
36
+ const { files, pmxRelativePaths } = prepareLocalFolderFiles(fileList);
37
+ if (files.length === 0)
38
+ return { status: "empty" };
39
+ if (!isDirectoryUpload(files))
40
+ return { status: "not_directory" };
41
+ if (pmxRelativePaths.length === 0)
42
+ return { status: "no_pmx" };
43
+ if (pmxRelativePaths.length === 1) {
44
+ const pmxFile = pmxFileAtRelativePath(files, pmxRelativePaths[0]);
45
+ if (!pmxFile)
46
+ return { status: "no_pmx" };
47
+ return { status: "single", files, pmxFile };
48
+ }
49
+ return { status: "multiple", files, pmxRelativePaths };
50
+ }