reze-engine 0.10.1 → 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 +41 -7
- package/dist/asset-reader.d.ts +16 -0
- package/dist/asset-reader.d.ts.map +1 -0
- package/dist/asset-reader.js +74 -0
- package/dist/engine.d.ts +9 -2
- package/dist/engine.d.ts.map +1 -1
- package/dist/engine.js +60 -20
- package/dist/folder-upload.d.ts +24 -0
- package/dist/folder-upload.d.ts.map +1 -0
- package/dist/folder-upload.js +50 -0
- package/dist/index.d.ts +2 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -1
- package/dist/model.d.ts +6 -1
- package/dist/model.d.ts.map +1 -1
- package/dist/model.js +34 -2
- package/dist/pmx-loader.d.ts +3 -0
- package/dist/pmx-loader.d.ts.map +1 -1
- package/dist/pmx-loader.js +9 -2
- package/package.json +1 -1
- package/src/asset-reader.ts +79 -0
- package/src/engine.ts +84 -20
- package/src/folder-upload.ts +59 -0
- package/src/index.ts +3 -3
- package/src/model.ts +34 -2
- package/src/pmx-loader.ts +11 -2
package/README.md
CHANGED
|
@@ -43,13 +43,14 @@ engine.runRenderLoop();
|
|
|
43
43
|
|
|
44
44
|
## API
|
|
45
45
|
|
|
46
|
-
One WebGPU **Engine** per page (singleton after `init()`). Load models via
|
|
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
47
|
|
|
48
48
|
### Engine
|
|
49
49
|
|
|
50
50
|
```javascript
|
|
51
51
|
engine.init()
|
|
52
52
|
engine.loadModel(name, path)
|
|
53
|
+
engine.loadModel(name, { files, pmxFile? }) // folder upload — see below
|
|
53
54
|
engine.getModel(name)
|
|
54
55
|
engine.getModelNames()
|
|
55
56
|
engine.removeModel(name)
|
|
@@ -75,6 +76,38 @@ engine.getStats()
|
|
|
75
76
|
engine.dispose()
|
|
76
77
|
```
|
|
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.
|
|
110
|
+
|
|
78
111
|
### Model
|
|
79
112
|
|
|
80
113
|
```javascript
|
|
@@ -108,12 +141,12 @@ model.getBoneWorldPosition(name)
|
|
|
108
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.
|
|
109
142
|
|
|
110
143
|
```javascript
|
|
111
|
-
const buffer = model.exportVmd("idle")
|
|
112
|
-
const blob = new Blob([buffer], { type: "application/octet-stream" })
|
|
113
|
-
const link = document.createElement("a")
|
|
114
|
-
link.href = URL.createObjectURL(blob)
|
|
115
|
-
link.download = "idle.vmd"
|
|
116
|
-
link.click()
|
|
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();
|
|
117
150
|
```
|
|
118
151
|
|
|
119
152
|
#### Playback
|
|
@@ -149,6 +182,7 @@ Call `model.play(name, options?)` to start or switch motion. `loop: true` makes
|
|
|
149
182
|
|
|
150
183
|
## Projects Using This Engine
|
|
151
184
|
|
|
185
|
+
- **[Reze Studio](https://reze.studio)** - Web-native MMD animation editor
|
|
152
186
|
- **[MiKaPo](https://mikapo.vercel.app)** — Real-time motion capture for MMD
|
|
153
187
|
- **[Popo](https://popo.love)** — LLM-generated MMD poses
|
|
154
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
|
-
|
|
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
|
|
188
|
+
private createTextureFromLogicalPath;
|
|
182
189
|
private renderGround;
|
|
183
190
|
private handleCanvasDoubleClick;
|
|
184
191
|
private handleCanvasTouch;
|
package/dist/engine.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"engine.d.ts","sourceRoot":"","sources":["../src/engine.ts"],"names":[],"mappings":"AACA,OAAO,EAAQ,IAAI,EAAE,MAAM,QAAQ,CAAA;AACnC,OAAO,EAAE,KAAK,EAAE,MAAM,SAAS,CAAA;AAE/B,OAAO,EAAW,KAAK,cAAc,EAAE,MAAM,WAAW,CAAA;
|
|
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,
|
|
969
|
-
|
|
970
|
-
|
|
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
|
|
984
|
-
|
|
985
|
-
|
|
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
|
|
1289
|
-
return this.
|
|
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
|
|
1375
|
-
const
|
|
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
|
|
1381
|
-
|
|
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: ${
|
|
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(
|
|
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
|
+
}
|
package/dist/index.d.ts
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
export { Engine, type EngineStats } from "./engine";
|
|
1
|
+
export { Engine, type EngineStats, type LoadModelFromFilesOptions } from "./engine";
|
|
2
|
+
export { parsePmxFolderInput, pmxFileAtRelativePath, type PmxFolderInputResult } from "./folder-upload";
|
|
2
3
|
export { Model } from "./model";
|
|
3
4
|
export { Vec3, Quat, Mat4 } from "./math";
|
|
4
5
|
export type { AnimationClip, AnimationPlayOptions, AnimationProgress, BoneKeyframe, MorphKeyframe, BoneInterpolation, ControlPoint, } from "./animation";
|
|
5
6
|
export { FPS } from "./animation";
|
|
6
7
|
export { Physics, type PhysicsOptions } from "./physics";
|
|
7
|
-
export { VMDWriter } from "./vmd-writer";
|
|
8
8
|
//# sourceMappingURL=index.d.ts.map
|
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAE,KAAK,WAAW,EAAE,MAAM,UAAU,CAAA;
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAE,KAAK,WAAW,EAAE,KAAK,yBAAyB,EAAE,MAAM,UAAU,CAAA;AACnF,OAAO,EAAE,mBAAmB,EAAE,qBAAqB,EAAE,KAAK,oBAAoB,EAAE,MAAM,iBAAiB,CAAA;AACvG,OAAO,EAAE,KAAK,EAAE,MAAM,SAAS,CAAA;AAC/B,OAAO,EAAE,IAAI,EAAE,IAAI,EAAE,IAAI,EAAE,MAAM,QAAQ,CAAA;AACzC,YAAY,EACV,aAAa,EACb,oBAAoB,EACpB,iBAAiB,EACjB,YAAY,EACZ,aAAa,EACb,iBAAiB,EACjB,YAAY,GACb,MAAM,aAAa,CAAA;AACpB,OAAO,EAAE,GAAG,EAAE,MAAM,aAAa,CAAA;AACjC,OAAO,EAAE,OAAO,EAAE,KAAK,cAAc,EAAE,MAAM,WAAW,CAAA"}
|
package/dist/index.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
export { Engine } from "./engine";
|
|
2
|
+
export { parsePmxFolderInput, pmxFileAtRelativePath } from "./folder-upload";
|
|
2
3
|
export { Model } from "./model";
|
|
3
4
|
export { Vec3, Quat, Mat4 } from "./math";
|
|
4
5
|
export { FPS } from "./animation";
|
|
5
6
|
export { Physics } from "./physics";
|
|
6
|
-
export { VMDWriter } from "./vmd-writer";
|
package/dist/model.d.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { Mat4, Quat, Vec3 } from "./math";
|
|
2
|
+
import { type AssetReader } from "./asset-reader";
|
|
2
3
|
import { Rigidbody, Joint } from "./physics";
|
|
3
4
|
import { AnimationClip, AnimationPlayOptions, AnimationProgress } from "./animation";
|
|
4
5
|
export interface Texture {
|
|
@@ -118,6 +119,10 @@ export declare class Model {
|
|
|
118
119
|
private boneTrackIndices;
|
|
119
120
|
private morphTrackIndices;
|
|
120
121
|
private lastAppliedClip;
|
|
122
|
+
private assetReader;
|
|
123
|
+
private assetBasePath;
|
|
124
|
+
/** Called by Engine when registering the model; enables loadVmd to resolve relative paths for folder uploads. */
|
|
125
|
+
setAssetContext(reader: AssetReader, basePath: string): void;
|
|
121
126
|
constructor(vertexData: Float32Array<ArrayBuffer>, indexData: Uint32Array<ArrayBuffer>, textures: Texture[], materials: Material[], skeleton: Skeleton, skinning: Skinning, morphing: Morphing, rigidbodies?: Rigidbody[], joints?: Joint[]);
|
|
122
127
|
private initializeRuntimeSkeleton;
|
|
123
128
|
private initializeIKRuntime;
|
|
@@ -145,7 +150,7 @@ export declare class Model {
|
|
|
145
150
|
setMorphWeight(name: string, weight: number, durationMs?: number): void;
|
|
146
151
|
private applyMorphs;
|
|
147
152
|
private buildClipFromVmdKeyFrames;
|
|
148
|
-
loadVmd(name: string,
|
|
153
|
+
loadVmd(name: string, urlOrRelative: string): Promise<void>;
|
|
149
154
|
loadClip(name: string, clip: AnimationClip): void;
|
|
150
155
|
resetAllBones(): void;
|
|
151
156
|
resetAllMorphs(): void;
|
package/dist/model.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"model.d.ts","sourceRoot":"","sources":["../src/model.ts"],"names":[],"mappings":"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
|
|
1
|
+
{"version":3,"file":"model.d.ts","sourceRoot":"","sources":["../src/model.ts"],"names":[],"mappings":"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"}
|
package/dist/model.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { Mat4, Quat, Vec3 } from "./math";
|
|
2
2
|
import { Engine } from "./engine";
|
|
3
|
+
import { joinAssetPath } from "./asset-reader";
|
|
3
4
|
import { IKSolverSystem } from "./ik-solver";
|
|
4
5
|
import { VMDLoader } from "./vmd-loader";
|
|
5
6
|
import { VMDWriter } from "./vmd-writer";
|
|
@@ -12,6 +13,11 @@ export class Model {
|
|
|
12
13
|
setName(value) {
|
|
13
14
|
this._name = value;
|
|
14
15
|
}
|
|
16
|
+
/** Called by Engine when registering the model; enables loadVmd to resolve relative paths for folder uploads. */
|
|
17
|
+
setAssetContext(reader, basePath) {
|
|
18
|
+
this.assetReader = reader;
|
|
19
|
+
this.assetBasePath = basePath;
|
|
20
|
+
}
|
|
15
21
|
constructor(vertexData, indexData, textures, materials, skeleton, skinning, morphing, rigidbodies = [], joints = []) {
|
|
16
22
|
this._name = "";
|
|
17
23
|
this.textures = [];
|
|
@@ -29,6 +35,8 @@ export class Model {
|
|
|
29
35
|
this.boneTrackIndices = new Map();
|
|
30
36
|
this.morphTrackIndices = new Map();
|
|
31
37
|
this.lastAppliedClip = null;
|
|
38
|
+
this.assetReader = null;
|
|
39
|
+
this.assetBasePath = "";
|
|
32
40
|
// Cached set to track which bones are being computed in current IK pass (to avoid infinite recursion)
|
|
33
41
|
this.ikComputedSet = new Set();
|
|
34
42
|
// Store base vertex data (original positions before morphing)
|
|
@@ -579,8 +587,32 @@ export class Model {
|
|
|
579
587
|
}
|
|
580
588
|
return { boneTracks, morphTracks, frameCount: maxFrame };
|
|
581
589
|
}
|
|
582
|
-
loadVmd(name,
|
|
583
|
-
|
|
590
|
+
loadVmd(name, urlOrRelative) {
|
|
591
|
+
const loadBuffer = () => {
|
|
592
|
+
const u = urlOrRelative.trim();
|
|
593
|
+
const useSiteFetch = u.startsWith("http://") ||
|
|
594
|
+
u.startsWith("https://") ||
|
|
595
|
+
u.startsWith("/") ||
|
|
596
|
+
u.startsWith("blob:") ||
|
|
597
|
+
u.startsWith("data:");
|
|
598
|
+
if (useSiteFetch) {
|
|
599
|
+
return fetch(u).then((r) => {
|
|
600
|
+
if (!r.ok)
|
|
601
|
+
throw new Error(`Failed to fetch VMD ${u}: ${r.status}`);
|
|
602
|
+
return r.arrayBuffer();
|
|
603
|
+
});
|
|
604
|
+
}
|
|
605
|
+
if (this.assetReader) {
|
|
606
|
+
return this.assetReader.readBinary(joinAssetPath(this.assetBasePath, u));
|
|
607
|
+
}
|
|
608
|
+
return fetch(u).then((r) => {
|
|
609
|
+
if (!r.ok)
|
|
610
|
+
throw new Error(`Failed to fetch VMD ${u}: ${r.status}`);
|
|
611
|
+
return r.arrayBuffer();
|
|
612
|
+
});
|
|
613
|
+
};
|
|
614
|
+
return loadBuffer().then((buf) => {
|
|
615
|
+
const vmdKeyFrames = VMDLoader.loadFromBuffer(buf);
|
|
584
616
|
const clip = this.buildClipFromVmdKeyFrames(vmdKeyFrames);
|
|
585
617
|
this.animationState.loadAnimation(name, clip);
|
|
586
618
|
});
|
package/dist/pmx-loader.d.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { Model } from "./model";
|
|
2
|
+
import { type AssetReader } from "./asset-reader";
|
|
2
3
|
export declare class PmxLoader {
|
|
3
4
|
private view;
|
|
4
5
|
private offset;
|
|
@@ -23,6 +24,8 @@ export declare class PmxLoader {
|
|
|
23
24
|
private joints;
|
|
24
25
|
private constructor();
|
|
25
26
|
static load(url: string): Promise<Model>;
|
|
27
|
+
static loadFromBuffer(buffer: ArrayBuffer): Model;
|
|
28
|
+
static loadFromReader(reader: AssetReader, pmxLogicalPath: string): Promise<Model>;
|
|
26
29
|
private parse;
|
|
27
30
|
private parseHeader;
|
|
28
31
|
private parseVertices;
|
package/dist/pmx-loader.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"pmx-loader.d.ts","sourceRoot":"","sources":["../src/pmx-loader.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,KAAK,EAWN,MAAM,SAAS,CAAA;
|
|
1
|
+
{"version":3,"file":"pmx-loader.d.ts","sourceRoot":"","sources":["../src/pmx-loader.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,KAAK,EAWN,MAAM,SAAS,CAAA;AAGhB,OAAO,EAA0B,KAAK,WAAW,EAAE,MAAM,gBAAgB,CAAA;AAEzE,qBAAa,SAAS;IACpB,OAAO,CAAC,IAAI,CAAU;IACtB,OAAO,CAAC,MAAM,CAAI;IAClB,OAAO,CAAC,OAAO,CAAc;IAC7B,OAAO,CAAC,QAAQ,CAAI;IACpB,OAAO,CAAC,mBAAmB,CAAI;IAC/B,OAAO,CAAC,eAAe,CAAI;IAC3B,OAAO,CAAC,gBAAgB,CAAI;IAC5B,OAAO,CAAC,iBAAiB,CAAI;IAC7B,OAAO,CAAC,aAAa,CAAI;IACzB,OAAO,CAAC,cAAc,CAAI;IAC1B,OAAO,CAAC,kBAAkB,CAAI;IAC9B,OAAO,CAAC,QAAQ,CAAgB;IAChC,OAAO,CAAC,SAAS,CAAiB;IAClC,OAAO,CAAC,KAAK,CAAa;IAC1B,OAAO,CAAC,mBAAmB,CAA4B;IACvD,OAAO,CAAC,OAAO,CAA2B;IAC1C,OAAO,CAAC,QAAQ,CAA0B;IAC1C,OAAO,CAAC,MAAM,CAAc;IAC5B,OAAO,CAAC,WAAW,CAAY;IAC/B,OAAO,CAAC,WAAW,CAAkB;IACrC,OAAO,CAAC,MAAM,CAAc;IAE5B,OAAO;WAIM,IAAI,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,KAAK,CAAC;IAI9C,MAAM,CAAC,cAAc,CAAC,MAAM,EAAE,WAAW,GAAG,KAAK;WAIpC,cAAc,CAAC,MAAM,EAAE,WAAW,EAAE,cAAc,EAAE,MAAM,GAAG,OAAO,CAAC,KAAK,CAAC;IAKxF,OAAO,CAAC,KAAK;IAiBb,OAAO,CAAC,WAAW;IA+CnB,OAAO,CAAC,aAAa;IA+FrB,OAAO,CAAC,YAAY;IAWpB,OAAO,CAAC,aAAa;IAkBrB,OAAO,CAAC,cAAc;IAoEtB,OAAO,CAAC,UAAU;IAsKlB,OAAO,CAAC,WAAW;IA+InB,OAAO,CAAC,iBAAiB;IAgDzB,OAAO,CAAC,gBAAgB;IAyFxB,OAAO,CAAC,WAAW;IAmGnB,OAAO,CAAC,kBAAkB;IAmC1B,OAAO,CAAC,OAAO;IAkLf,OAAO,CAAC,QAAQ;IAOhB,OAAO,CAAC,SAAS;IAUjB,OAAO,CAAC,cAAc;IAWtB,OAAO,CAAC,iBAAiB;IAczB,OAAO,CAAC,QAAQ;IAShB,OAAO,CAAC,UAAU;IASlB,OAAO,CAAC,SAAS;IAMjB,OAAO,CAAC,OAAO;IAmBf,OAAO,CAAC,QAAQ;CAIjB"}
|
package/dist/pmx-loader.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { Model, } from "./model";
|
|
2
2
|
import { Mat4, Vec3 } from "./math";
|
|
3
|
+
import { createFetchAssetReader } from "./asset-reader";
|
|
3
4
|
export class PmxLoader {
|
|
4
5
|
constructor(buffer) {
|
|
5
6
|
this.offset = 0;
|
|
@@ -24,8 +25,14 @@ export class PmxLoader {
|
|
|
24
25
|
this.view = new DataView(buffer);
|
|
25
26
|
}
|
|
26
27
|
static async load(url) {
|
|
27
|
-
|
|
28
|
-
|
|
28
|
+
return PmxLoader.loadFromReader(createFetchAssetReader(), url);
|
|
29
|
+
}
|
|
30
|
+
static loadFromBuffer(buffer) {
|
|
31
|
+
return new PmxLoader(buffer).parse();
|
|
32
|
+
}
|
|
33
|
+
static async loadFromReader(reader, pmxLogicalPath) {
|
|
34
|
+
const buffer = await reader.readBinary(pmxLogicalPath);
|
|
35
|
+
return PmxLoader.loadFromBuffer(buffer);
|
|
29
36
|
}
|
|
30
37
|
parse() {
|
|
31
38
|
this.parseHeader();
|
package/package.json
CHANGED
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
/** Unified binary I/O for PMX/VMD/textures: HTTP(s) or user folder (File map). */
|
|
2
|
+
|
|
3
|
+
export type AssetReader = {
|
|
4
|
+
readBinary(logicalPath: string): Promise<ArrayBuffer>
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
/** Normalize PMX-style paths: backslashes, trim, strip leading ./ */
|
|
8
|
+
export function normalizeAssetPath(p: string): string {
|
|
9
|
+
let s = p.replace(/\\/g, "/").trim()
|
|
10
|
+
if (s.startsWith("./")) s = s.slice(2)
|
|
11
|
+
return s
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
/** Join PMX directory prefix and texture-relative path (both may be ""). */
|
|
15
|
+
export function joinAssetPath(baseDir: string, relative: string): string {
|
|
16
|
+
const rel = normalizeAssetPath(relative)
|
|
17
|
+
if (!rel) return normalizeAssetPath(baseDir)
|
|
18
|
+
const base = baseDir.endsWith("/") ? baseDir.slice(0, -1) : baseDir
|
|
19
|
+
if (!base) return rel
|
|
20
|
+
return `${base}/${rel}`
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
/** Same rules as the original engine string split: supports absolute site paths like `/models/a/b.pmx`. */
|
|
24
|
+
export function deriveBasePathFromPmxPath(pmxPath: string): string {
|
|
25
|
+
const pathParts = pmxPath.replace(/\\/g, "/").split("/")
|
|
26
|
+
pathParts.pop()
|
|
27
|
+
return pathParts.join("/") + (pathParts.length > 0 ? "/" : "")
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export function createFetchAssetReader(): AssetReader {
|
|
31
|
+
return {
|
|
32
|
+
async readBinary(logicalPath: string) {
|
|
33
|
+
const r = await fetch(logicalPath)
|
|
34
|
+
if (!r.ok) throw new Error(`Failed to fetch ${logicalPath}: ${r.status} ${r.statusText}`)
|
|
35
|
+
return r.arrayBuffer()
|
|
36
|
+
},
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/** Keys must be normalized paths relative to the selected folder root (see fileListToMap). */
|
|
41
|
+
export function createFileMapAssetReader(files: Map<string, File>): AssetReader {
|
|
42
|
+
return {
|
|
43
|
+
async readBinary(logicalPath: string) {
|
|
44
|
+
const key = normalizeAssetPath(logicalPath)
|
|
45
|
+
let file = files.get(key)
|
|
46
|
+
if (!file) {
|
|
47
|
+
const lower = key.toLowerCase()
|
|
48
|
+
for (const [k, f] of files) {
|
|
49
|
+
if (k.toLowerCase() === lower) {
|
|
50
|
+
file = f
|
|
51
|
+
break
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
if (!file) throw new Error(`Missing file in folder: ${key}`)
|
|
56
|
+
return file.arrayBuffer()
|
|
57
|
+
},
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
export function fileListToMap(files: FileList | File[]): Map<string, File> {
|
|
62
|
+
const m = new Map<string, File>()
|
|
63
|
+
for (const f of Array.from(files)) {
|
|
64
|
+
const rel = (f as File & { webkitRelativePath?: string }).webkitRelativePath ?? f.name
|
|
65
|
+
m.set(normalizeAssetPath(rel), f)
|
|
66
|
+
}
|
|
67
|
+
return m
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
export function findFirstPmxFileInList(files: FileList | File[]): File | null {
|
|
71
|
+
const list = Array.from(files).filter((f) => f.name.toLowerCase().endsWith(".pmx"))
|
|
72
|
+
if (list.length === 0) return null
|
|
73
|
+
list.sort((a, b) => {
|
|
74
|
+
const pa = (a as File & { webkitRelativePath?: string }).webkitRelativePath ?? a.name
|
|
75
|
+
const pb = (b as File & { webkitRelativePath?: string }).webkitRelativePath ?? b.name
|
|
76
|
+
return pa.localeCompare(pb)
|
|
77
|
+
})
|
|
78
|
+
return list[0] ?? null
|
|
79
|
+
}
|
package/src/engine.ts
CHANGED
|
@@ -3,9 +3,25 @@ import { Mat4, Vec3 } from "./math"
|
|
|
3
3
|
import { Model } from "./model"
|
|
4
4
|
import { PmxLoader } from "./pmx-loader"
|
|
5
5
|
import { Physics, type PhysicsOptions } from "./physics"
|
|
6
|
+
import {
|
|
7
|
+
createFetchAssetReader,
|
|
8
|
+
createFileMapAssetReader,
|
|
9
|
+
deriveBasePathFromPmxPath,
|
|
10
|
+
fileListToMap,
|
|
11
|
+
findFirstPmxFileInList,
|
|
12
|
+
joinAssetPath,
|
|
13
|
+
normalizeAssetPath,
|
|
14
|
+
type AssetReader,
|
|
15
|
+
} from "./asset-reader"
|
|
6
16
|
|
|
7
17
|
export type RaycastCallback = (modelName: string, material: string | null, screenX: number, screenY: number) => void
|
|
8
18
|
|
|
19
|
+
/** Select a folder (webkitdirectory) and pass FileList or File[]; pmxFile picks which .pmx when several exist. */
|
|
20
|
+
export type LoadModelFromFilesOptions = {
|
|
21
|
+
files: FileList | File[]
|
|
22
|
+
pmxFile?: File
|
|
23
|
+
}
|
|
24
|
+
|
|
9
25
|
export type EngineOptions = {
|
|
10
26
|
ambientColor?: Vec3
|
|
11
27
|
directionalLightIntensity?: number
|
|
@@ -62,6 +78,9 @@ interface ModelInstance {
|
|
|
62
78
|
name: string
|
|
63
79
|
model: Model
|
|
64
80
|
basePath: string
|
|
81
|
+
assetReader: AssetReader
|
|
82
|
+
gpuBuffers: GPUBuffer[]
|
|
83
|
+
textureCacheKeys: string[]
|
|
65
84
|
vertexBuffer: GPUBuffer
|
|
66
85
|
indexBuffer: GPUBuffer
|
|
67
86
|
jointsBuffer: GPUBuffer
|
|
@@ -1151,30 +1170,62 @@ export class Engine {
|
|
|
1151
1170
|
|
|
1152
1171
|
async loadModel(path: string): Promise<Model>
|
|
1153
1172
|
async loadModel(name: string, path: string): Promise<Model>
|
|
1154
|
-
async loadModel(
|
|
1155
|
-
|
|
1156
|
-
|
|
1173
|
+
async loadModel(name: string, options: LoadModelFromFilesOptions): Promise<Model>
|
|
1174
|
+
async loadModel(
|
|
1175
|
+
nameOrPath: string,
|
|
1176
|
+
pathOrOptions?: string | LoadModelFromFilesOptions
|
|
1177
|
+
): Promise<Model> {
|
|
1178
|
+
if (pathOrOptions !== undefined && typeof pathOrOptions === "object" && "files" in pathOrOptions) {
|
|
1179
|
+
const name = nameOrPath
|
|
1180
|
+
const pmxFile = pathOrOptions.pmxFile ?? findFirstPmxFileInList(pathOrOptions.files)
|
|
1181
|
+
if (!pmxFile) throw new Error("No .pmx file found in the selected folder")
|
|
1182
|
+
const map = fileListToMap(pathOrOptions.files)
|
|
1183
|
+
const pmxKey = normalizeAssetPath(
|
|
1184
|
+
(pmxFile as File & { webkitRelativePath?: string }).webkitRelativePath ?? pmxFile.name
|
|
1185
|
+
)
|
|
1186
|
+
const reader = createFileMapAssetReader(map)
|
|
1187
|
+
const model = await PmxLoader.loadFromReader(reader, pmxKey)
|
|
1188
|
+
model.setName(name)
|
|
1189
|
+
await this.addModel(model, pmxKey, name, reader)
|
|
1190
|
+
return model
|
|
1191
|
+
}
|
|
1192
|
+
|
|
1193
|
+
const pmxPath = pathOrOptions === undefined ? nameOrPath : pathOrOptions
|
|
1194
|
+
const name = pathOrOptions === undefined ? "model_" + this._nextDefaultModelId++ : nameOrPath
|
|
1157
1195
|
const model = await PmxLoader.load(pmxPath)
|
|
1158
1196
|
model.setName(name)
|
|
1159
1197
|
await this.addModel(model, pmxPath, name)
|
|
1160
1198
|
return model
|
|
1161
1199
|
}
|
|
1162
1200
|
|
|
1163
|
-
async addModel(model: Model, pmxPath: string, name?: string): Promise<string> {
|
|
1201
|
+
async addModel(model: Model, pmxPath: string, name?: string, assetReader?: AssetReader): Promise<string> {
|
|
1164
1202
|
const requested = name ?? model.name
|
|
1165
1203
|
let key = requested
|
|
1166
1204
|
let n = 1
|
|
1167
1205
|
while (this.modelInstances.has(key)) {
|
|
1168
1206
|
key = `${requested}_${n++}`
|
|
1169
1207
|
}
|
|
1170
|
-
const
|
|
1171
|
-
|
|
1172
|
-
|
|
1173
|
-
await this.setupModelInstance(key, model, basePath)
|
|
1208
|
+
const reader = assetReader ?? createFetchAssetReader()
|
|
1209
|
+
const basePath = deriveBasePathFromPmxPath(pmxPath)
|
|
1210
|
+
model.setAssetContext(reader, basePath)
|
|
1211
|
+
await this.setupModelInstance(key, model, basePath, reader)
|
|
1174
1212
|
return key
|
|
1175
1213
|
}
|
|
1176
1214
|
|
|
1177
1215
|
removeModel(name: string): void {
|
|
1216
|
+
const inst = this.modelInstances.get(name)
|
|
1217
|
+
if (!inst) return
|
|
1218
|
+
inst.model.stopAnimation()
|
|
1219
|
+
for (const path of inst.textureCacheKeys) {
|
|
1220
|
+
const tex = this.textureCache.get(path)
|
|
1221
|
+
if (tex) {
|
|
1222
|
+
tex.destroy()
|
|
1223
|
+
this.textureCache.delete(path)
|
|
1224
|
+
}
|
|
1225
|
+
}
|
|
1226
|
+
for (const buf of inst.gpuBuffers) {
|
|
1227
|
+
buf.destroy()
|
|
1228
|
+
}
|
|
1178
1229
|
this.modelInstances.delete(name)
|
|
1179
1230
|
}
|
|
1180
1231
|
|
|
@@ -1262,7 +1313,7 @@ export class Engine {
|
|
|
1262
1313
|
inst.vertexBufferNeedsUpdate = false
|
|
1263
1314
|
}
|
|
1264
1315
|
|
|
1265
|
-
private async setupModelInstance(name: string, model: Model, basePath: string): Promise<void> {
|
|
1316
|
+
private async setupModelInstance(name: string, model: Model, basePath: string, assetReader: AssetReader): Promise<void> {
|
|
1266
1317
|
const vertices = model.getVertices()
|
|
1267
1318
|
const skinning = model.getSkinning()
|
|
1268
1319
|
const skeleton = model.getSkeleton()
|
|
@@ -1345,10 +1396,21 @@ export class Engine {
|
|
|
1345
1396
|
],
|
|
1346
1397
|
})
|
|
1347
1398
|
|
|
1399
|
+
const gpuBuffers: GPUBuffer[] = [
|
|
1400
|
+
vertexBuffer,
|
|
1401
|
+
indexBuffer,
|
|
1402
|
+
jointsBuffer,
|
|
1403
|
+
weightsBuffer,
|
|
1404
|
+
skinMatrixBuffer,
|
|
1405
|
+
]
|
|
1406
|
+
|
|
1348
1407
|
const inst: ModelInstance = {
|
|
1349
1408
|
name,
|
|
1350
1409
|
model,
|
|
1351
1410
|
basePath,
|
|
1411
|
+
assetReader,
|
|
1412
|
+
gpuBuffers,
|
|
1413
|
+
textureCacheKeys: [],
|
|
1352
1414
|
vertexBuffer,
|
|
1353
1415
|
indexBuffer,
|
|
1354
1416
|
jointsBuffer,
|
|
@@ -1510,8 +1572,8 @@ export class Engine {
|
|
|
1510
1572
|
|
|
1511
1573
|
const loadTextureByIndex = async (texIndex: number): Promise<GPUTexture | null> => {
|
|
1512
1574
|
if (texIndex < 0 || texIndex >= textures.length) return null
|
|
1513
|
-
const
|
|
1514
|
-
return this.
|
|
1575
|
+
const logicalPath = joinAssetPath(inst.basePath, normalizeAssetPath(textures[texIndex].path))
|
|
1576
|
+
return this.createTextureFromLogicalPath(inst, logicalPath)
|
|
1515
1577
|
}
|
|
1516
1578
|
|
|
1517
1579
|
let currentIndexOffset = 0
|
|
@@ -1535,6 +1597,7 @@ export class Engine {
|
|
|
1535
1597
|
mat.specular,
|
|
1536
1598
|
mat.shininess
|
|
1537
1599
|
)
|
|
1600
|
+
inst.gpuBuffers.push(materialUniformBuffer)
|
|
1538
1601
|
|
|
1539
1602
|
const textureView = diffuseTexture.createView()
|
|
1540
1603
|
const bindGroup = this.device.createBindGroup({
|
|
@@ -1555,6 +1618,7 @@ export class Engine {
|
|
|
1555
1618
|
mat.edgeSize, 0, 0, 0,
|
|
1556
1619
|
])
|
|
1557
1620
|
const outlineUniformBuffer = this.createUniformBuffer(`${prefix}outline: ${mat.name}`, materialUniformData)
|
|
1621
|
+
inst.gpuBuffers.push(outlineUniformBuffer)
|
|
1558
1622
|
const outlineBindGroup = this.device.createBindGroup({
|
|
1559
1623
|
label: `${prefix}outline: ${mat.name}`,
|
|
1560
1624
|
layout: this.outlinePerMaterialBindGroupLayout,
|
|
@@ -1569,6 +1633,7 @@ export class Engine {
|
|
|
1569
1633
|
if (this.onRaycast) {
|
|
1570
1634
|
const pickIdData = new Float32Array([modelId, materialId, 0, 0])
|
|
1571
1635
|
const pickIdBuffer = this.createUniformBuffer(`${prefix}pick: ${mat.name}`, pickIdData)
|
|
1636
|
+
inst.gpuBuffers.push(pickIdBuffer)
|
|
1572
1637
|
const pickBindGroup = this.device.createBindGroup({
|
|
1573
1638
|
label: `${prefix}pick: ${mat.name}`,
|
|
1574
1639
|
layout: this.pickPerMaterialBindGroupLayout,
|
|
@@ -1621,24 +1686,22 @@ export class Engine {
|
|
|
1621
1686
|
return !inst.hiddenMaterials.has(drawCall.materialName)
|
|
1622
1687
|
}
|
|
1623
1688
|
|
|
1624
|
-
private async
|
|
1625
|
-
const
|
|
1689
|
+
private async createTextureFromLogicalPath(inst: ModelInstance, logicalPath: string): Promise<GPUTexture | null> {
|
|
1690
|
+
const cacheKey = logicalPath
|
|
1691
|
+
const cached = this.textureCache.get(cacheKey)
|
|
1626
1692
|
if (cached) {
|
|
1627
1693
|
return cached
|
|
1628
1694
|
}
|
|
1629
1695
|
|
|
1630
1696
|
try {
|
|
1631
|
-
const
|
|
1632
|
-
|
|
1633
|
-
throw new Error(`HTTP ${response.status}: ${response.statusText}`)
|
|
1634
|
-
}
|
|
1635
|
-
const imageBitmap = await createImageBitmap(await response.blob(), {
|
|
1697
|
+
const buffer = await inst.assetReader.readBinary(logicalPath)
|
|
1698
|
+
const imageBitmap = await createImageBitmap(new Blob([buffer]), {
|
|
1636
1699
|
premultiplyAlpha: "none",
|
|
1637
1700
|
colorSpaceConversion: "none",
|
|
1638
1701
|
})
|
|
1639
1702
|
|
|
1640
1703
|
const texture = this.device.createTexture({
|
|
1641
|
-
label: `texture: ${
|
|
1704
|
+
label: `texture: ${cacheKey}`,
|
|
1642
1705
|
size: [imageBitmap.width, imageBitmap.height],
|
|
1643
1706
|
format: "rgba8unorm",
|
|
1644
1707
|
usage: GPUTextureUsage.TEXTURE_BINDING | GPUTextureUsage.COPY_DST | GPUTextureUsage.RENDER_ATTACHMENT,
|
|
@@ -1648,7 +1711,8 @@ export class Engine {
|
|
|
1648
1711
|
imageBitmap.height,
|
|
1649
1712
|
])
|
|
1650
1713
|
|
|
1651
|
-
this.textureCache.set(
|
|
1714
|
+
this.textureCache.set(cacheKey, texture)
|
|
1715
|
+
inst.textureCacheKeys.push(cacheKey)
|
|
1652
1716
|
return texture
|
|
1653
1717
|
} catch {
|
|
1654
1718
|
return null
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
import { normalizeAssetPath } from "./asset-reader"
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Call on `<input type="file" webkitdirectory>` `change` **before** `input.value = ""`.
|
|
5
|
+
* `FileList` is live — clearing the input empties it; this copies to a stable `File[]`.
|
|
6
|
+
*/
|
|
7
|
+
function prepareLocalFolderFiles(fileList: FileList | null | undefined): {
|
|
8
|
+
files: File[]
|
|
9
|
+
pmxRelativePaths: string[]
|
|
10
|
+
} {
|
|
11
|
+
const files = fileList?.length ? Array.from(fileList) : []
|
|
12
|
+
const pmxRelativePaths: string[] = []
|
|
13
|
+
for (const f of files) {
|
|
14
|
+
const wr = (f as File & { webkitRelativePath?: string }).webkitRelativePath
|
|
15
|
+
if (!wr || !wr.toLowerCase().endsWith(".pmx")) continue
|
|
16
|
+
pmxRelativePaths.push(normalizeAssetPath(wr))
|
|
17
|
+
}
|
|
18
|
+
pmxRelativePaths.sort((a, b) => a.localeCompare(b))
|
|
19
|
+
return { files, pmxRelativePaths }
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
function isDirectoryUpload(files: File[]): boolean {
|
|
23
|
+
return files.length > 0 && files.every((f) => !!(f as File & { webkitRelativePath?: string }).webkitRelativePath)
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/** After choosing a path from `multiple`, get the `File` for `loadModel(..., { files, pmxFile })`. */
|
|
27
|
+
export function pmxFileAtRelativePath(files: File[], relativePath: string): File | undefined {
|
|
28
|
+
const norm = normalizeAssetPath(relativePath)
|
|
29
|
+
for (const f of files) {
|
|
30
|
+
const wr = (f as File & { webkitRelativePath?: string }).webkitRelativePath
|
|
31
|
+
if (wr && normalizeAssetPath(wr) === norm) return f
|
|
32
|
+
}
|
|
33
|
+
return undefined
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/** Result of reading a folder input — switch on `status` in your UI. */
|
|
37
|
+
export type PmxFolderInputResult =
|
|
38
|
+
| { status: "empty" }
|
|
39
|
+
| { status: "not_directory" }
|
|
40
|
+
| { status: "no_pmx" }
|
|
41
|
+
| { status: "single"; files: File[]; pmxFile: File }
|
|
42
|
+
| { status: "multiple"; files: File[]; pmxRelativePaths: string[] }
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* One call from `onChange`: snapshots files, validates folder pick, resolves a single PMX or asks you to pick among several.
|
|
46
|
+
* Reset the input after: `e.target.value = ""`.
|
|
47
|
+
*/
|
|
48
|
+
export function parsePmxFolderInput(fileList: FileList | null | undefined): PmxFolderInputResult {
|
|
49
|
+
const { files, pmxRelativePaths } = prepareLocalFolderFiles(fileList)
|
|
50
|
+
if (files.length === 0) return { status: "empty" }
|
|
51
|
+
if (!isDirectoryUpload(files)) return { status: "not_directory" }
|
|
52
|
+
if (pmxRelativePaths.length === 0) return { status: "no_pmx" }
|
|
53
|
+
if (pmxRelativePaths.length === 1) {
|
|
54
|
+
const pmxFile = pmxFileAtRelativePath(files, pmxRelativePaths[0]!)
|
|
55
|
+
if (!pmxFile) return { status: "no_pmx" }
|
|
56
|
+
return { status: "single", files, pmxFile }
|
|
57
|
+
}
|
|
58
|
+
return { status: "multiple", files, pmxRelativePaths }
|
|
59
|
+
}
|
package/src/index.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
export { Engine, type EngineStats } from "./engine"
|
|
1
|
+
export { Engine, type EngineStats, type LoadModelFromFilesOptions } from "./engine"
|
|
2
|
+
export { parsePmxFolderInput, pmxFileAtRelativePath, type PmxFolderInputResult } from "./folder-upload"
|
|
2
3
|
export { Model } from "./model"
|
|
3
4
|
export { Vec3, Quat, Mat4 } from "./math"
|
|
4
5
|
export type {
|
|
@@ -11,5 +12,4 @@ export type {
|
|
|
11
12
|
ControlPoint,
|
|
12
13
|
} from "./animation"
|
|
13
14
|
export { FPS } from "./animation"
|
|
14
|
-
export { Physics, type PhysicsOptions } from "./physics"
|
|
15
|
-
export { VMDWriter } from "./vmd-writer"
|
|
15
|
+
export { Physics, type PhysicsOptions } from "./physics"
|
package/src/model.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { Mat4, Quat, Vec3 } from "./math"
|
|
2
2
|
import { Engine } from "./engine"
|
|
3
|
+
import { joinAssetPath, type AssetReader } from "./asset-reader"
|
|
3
4
|
import { Rigidbody, Joint } from "./physics"
|
|
4
5
|
import { IKSolverSystem } from "./ik-solver"
|
|
5
6
|
import { VMDLoader, type VMDKeyFrame } from "./vmd-loader"
|
|
@@ -206,6 +207,14 @@ export class Model {
|
|
|
206
207
|
private morphTrackIndices: Map<string, number> = new Map()
|
|
207
208
|
private lastAppliedClip: AnimationClip | null = null
|
|
208
209
|
|
|
210
|
+
private assetReader: AssetReader | null = null
|
|
211
|
+
private assetBasePath = ""
|
|
212
|
+
|
|
213
|
+
/** Called by Engine when registering the model; enables loadVmd to resolve relative paths for folder uploads. */
|
|
214
|
+
setAssetContext(reader: AssetReader, basePath: string): void {
|
|
215
|
+
this.assetReader = reader
|
|
216
|
+
this.assetBasePath = basePath
|
|
217
|
+
}
|
|
209
218
|
|
|
210
219
|
constructor(
|
|
211
220
|
vertexData: Float32Array<ArrayBuffer>,
|
|
@@ -857,8 +866,31 @@ export class Model {
|
|
|
857
866
|
return { boneTracks, morphTracks, frameCount: maxFrame }
|
|
858
867
|
}
|
|
859
868
|
|
|
860
|
-
loadVmd(name: string,
|
|
861
|
-
|
|
869
|
+
loadVmd(name: string, urlOrRelative: string): Promise<void> {
|
|
870
|
+
const loadBuffer = (): Promise<ArrayBuffer> => {
|
|
871
|
+
const u = urlOrRelative.trim()
|
|
872
|
+
const useSiteFetch =
|
|
873
|
+
u.startsWith("http://") ||
|
|
874
|
+
u.startsWith("https://") ||
|
|
875
|
+
u.startsWith("/") ||
|
|
876
|
+
u.startsWith("blob:") ||
|
|
877
|
+
u.startsWith("data:")
|
|
878
|
+
if (useSiteFetch) {
|
|
879
|
+
return fetch(u).then((r) => {
|
|
880
|
+
if (!r.ok) throw new Error(`Failed to fetch VMD ${u}: ${r.status}`)
|
|
881
|
+
return r.arrayBuffer()
|
|
882
|
+
})
|
|
883
|
+
}
|
|
884
|
+
if (this.assetReader) {
|
|
885
|
+
return this.assetReader.readBinary(joinAssetPath(this.assetBasePath, u))
|
|
886
|
+
}
|
|
887
|
+
return fetch(u).then((r) => {
|
|
888
|
+
if (!r.ok) throw new Error(`Failed to fetch VMD ${u}: ${r.status}`)
|
|
889
|
+
return r.arrayBuffer()
|
|
890
|
+
})
|
|
891
|
+
}
|
|
892
|
+
return loadBuffer().then((buf) => {
|
|
893
|
+
const vmdKeyFrames = VMDLoader.loadFromBuffer(buf)
|
|
862
894
|
const clip = this.buildClipFromVmdKeyFrames(vmdKeyFrames)
|
|
863
895
|
this.animationState.loadAnimation(name, clip)
|
|
864
896
|
})
|
package/src/pmx-loader.ts
CHANGED
|
@@ -13,6 +13,7 @@ import {
|
|
|
13
13
|
} from "./model"
|
|
14
14
|
import { Mat4, Vec3 } from "./math"
|
|
15
15
|
import { Rigidbody, Joint, RigidbodyShape, RigidbodyType } from "./physics"
|
|
16
|
+
import { createFetchAssetReader, type AssetReader } from "./asset-reader"
|
|
16
17
|
|
|
17
18
|
export class PmxLoader {
|
|
18
19
|
private view: DataView
|
|
@@ -42,8 +43,16 @@ export class PmxLoader {
|
|
|
42
43
|
}
|
|
43
44
|
|
|
44
45
|
static async load(url: string): Promise<Model> {
|
|
45
|
-
|
|
46
|
-
|
|
46
|
+
return PmxLoader.loadFromReader(createFetchAssetReader(), url)
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
static loadFromBuffer(buffer: ArrayBuffer): Model {
|
|
50
|
+
return new PmxLoader(buffer).parse()
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
static async loadFromReader(reader: AssetReader, pmxLogicalPath: string): Promise<Model> {
|
|
54
|
+
const buffer = await reader.readBinary(pmxLogicalPath)
|
|
55
|
+
return PmxLoader.loadFromBuffer(buffer)
|
|
47
56
|
}
|
|
48
57
|
|
|
49
58
|
private parse(): Model {
|