@ludra/three 0.94.0 → 0.96.0
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/index.d.ts +3 -2
- package/dist/index.js +22 -9
- package/package.json +2 -2
- package/src/index.ts +22 -9
package/dist/index.d.ts
CHANGED
|
@@ -54,8 +54,9 @@ export declare class ThreeRenderer implements Renderer3D {
|
|
|
54
54
|
/** Addons render hooks are still loading. */
|
|
55
55
|
private readonly pending;
|
|
56
56
|
private readonly def;
|
|
57
|
-
|
|
58
|
-
|
|
57
|
+
/** Loads go through this, so a .gltf's own relative files (its .bin, its textures) are found wherever assets live. */
|
|
58
|
+
private readonly manager;
|
|
59
|
+
constructor(def: SceneDef, width: number, height: number, assetUrl: (path: string) => string);
|
|
59
60
|
/** Loads every texture and model the scene (and its prefabs) names, so the first frame (and screenshots) are complete. */
|
|
60
61
|
load(): Promise<void>;
|
|
61
62
|
/** Loads a glTF file once. Draco and meshopt compressed files work too. */
|
package/dist/index.js
CHANGED
|
@@ -22,9 +22,15 @@ const TONE_MAPPING = {
|
|
|
22
22
|
};
|
|
23
23
|
/** Collider colors in the debug view, as in 2D: static, dynamic, kinematic, sensor, player. */
|
|
24
24
|
const DEBUG_COLORS = { static: 0x7fdbff, dynamic: 0xffdc00, kinematic: 0xff851b, sensor: 0x2ecc40, player: 0xff4136 };
|
|
25
|
+
/**
|
|
26
|
+
* Loaders see project paths under this made-up scheme, which the loading manager turns into real
|
|
27
|
+
* URLs. three resolves a .gltf's relative files against it like any other base.
|
|
28
|
+
*/
|
|
29
|
+
const PROJECT = "ludra:/";
|
|
25
30
|
/** Creates the 3D renderer for a scene. Pass it to startGame as `loadRenderer3D`. */
|
|
26
31
|
export const createRenderer3D = async (scene, options) => {
|
|
27
|
-
const
|
|
32
|
+
const base = options.assetBase;
|
|
33
|
+
const renderer = new ThreeRenderer(scene, options.width, options.height, options.assetUrl ?? ((path) => base + path));
|
|
28
34
|
await renderer.load();
|
|
29
35
|
return renderer;
|
|
30
36
|
};
|
|
@@ -67,10 +73,12 @@ export class ThreeRenderer {
|
|
|
67
73
|
/** Addons render hooks are still loading. */
|
|
68
74
|
pending = new Set();
|
|
69
75
|
def;
|
|
70
|
-
|
|
71
|
-
|
|
76
|
+
/** Loads go through this, so a .gltf's own relative files (its .bin, its textures) are found wherever assets live. */
|
|
77
|
+
manager;
|
|
78
|
+
constructor(def, width, height, assetUrl) {
|
|
72
79
|
this.def = def;
|
|
73
|
-
this.
|
|
80
|
+
this.manager = new THREE.LoadingManager();
|
|
81
|
+
this.manager.setURLModifier((url) => (url.startsWith(PROJECT) ? assetUrl(url.slice(PROJECT.length)) : url));
|
|
74
82
|
this.canvas = document.createElement("canvas");
|
|
75
83
|
this.canvas.width = width;
|
|
76
84
|
this.canvas.height = height;
|
|
@@ -90,7 +98,7 @@ export class ThreeRenderer {
|
|
|
90
98
|
}
|
|
91
99
|
/** Loads every texture and model the scene (and its prefabs) names, so the first frame (and screenshots) are complete. */
|
|
92
100
|
async load() {
|
|
93
|
-
const loader = new THREE.TextureLoader();
|
|
101
|
+
const loader = new THREE.TextureLoader(this.manager);
|
|
94
102
|
const meshes = [...this.def.entities, ...Object.values(this.def.prefabs ?? {})].map((e) => e.components?.mesh);
|
|
95
103
|
const named = (field) => new Set(meshes.map((m) => m?.[field]).filter((p) => typeof p === "string" && p !== ""));
|
|
96
104
|
const paths = named("texture");
|
|
@@ -99,7 +107,7 @@ export class ThreeRenderer {
|
|
|
99
107
|
...models,
|
|
100
108
|
...[...paths].map(async (path) => {
|
|
101
109
|
try {
|
|
102
|
-
const texture = await loader.loadAsync(
|
|
110
|
+
const texture = await loader.loadAsync(PROJECT + path);
|
|
103
111
|
texture.colorSpace = THREE.SRGBColorSpace;
|
|
104
112
|
texture.wrapS = texture.wrapT = THREE.RepeatWrapping;
|
|
105
113
|
texture.anisotropy = 4;
|
|
@@ -117,14 +125,19 @@ export class ThreeRenderer {
|
|
|
117
125
|
if (load)
|
|
118
126
|
return load;
|
|
119
127
|
if (!this.gltfLoader) {
|
|
120
|
-
this.gltfLoader = new GLTFLoader();
|
|
128
|
+
this.gltfLoader = new GLTFLoader(this.manager);
|
|
121
129
|
const draco = new DRACOLoader();
|
|
122
130
|
// The decoder sits in three's addons, which the import map serves.
|
|
123
|
-
|
|
131
|
+
try {
|
|
132
|
+
draco.setDecoderPath(new URL("../libs/draco/gltf/", import.meta.resolve("three/addons/loaders/DRACOLoader.js")).href);
|
|
133
|
+
}
|
|
134
|
+
catch {
|
|
135
|
+
// A single-file build has no folder to load the decoder from: Draco models don't load there.
|
|
136
|
+
}
|
|
124
137
|
this.gltfLoader.setDRACOLoader(draco);
|
|
125
138
|
this.gltfLoader.setMeshoptDecoder(MeshoptDecoder);
|
|
126
139
|
}
|
|
127
|
-
load = this.track(this.gltfLoader.loadAsync(
|
|
140
|
+
load = this.track(this.gltfLoader.loadAsync(PROJECT + path).then((gltf) => void this.gltfs.set(path, gltf), (err) => {
|
|
128
141
|
console.warn(`[ludra] could not load model "${path}" (${err?.message ?? err}), drawing its shape instead.`);
|
|
129
142
|
this.gltfs.set(path, null);
|
|
130
143
|
}));
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ludra/three",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.96.0",
|
|
4
4
|
"description": "The 3D renderer for Ludra: Three.js meshes, PBR materials, lights, shadows, cameras and environments for 3D scenes.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"repository": {
|
|
@@ -31,7 +31,7 @@
|
|
|
31
31
|
"src"
|
|
32
32
|
],
|
|
33
33
|
"dependencies": {
|
|
34
|
-
"@ludra/core": "0.
|
|
34
|
+
"@ludra/core": "0.96.0",
|
|
35
35
|
"three": "0.186.1"
|
|
36
36
|
},
|
|
37
37
|
"devDependencies": {
|
package/src/index.ts
CHANGED
|
@@ -44,9 +44,16 @@ const TONE_MAPPING = {
|
|
|
44
44
|
/** Collider colors in the debug view, as in 2D: static, dynamic, kinematic, sensor, player. */
|
|
45
45
|
const DEBUG_COLORS = { static: 0x7fdbff, dynamic: 0xffdc00, kinematic: 0xff851b, sensor: 0x2ecc40, player: 0xff4136 } as const;
|
|
46
46
|
|
|
47
|
+
/**
|
|
48
|
+
* Loaders see project paths under this made-up scheme, which the loading manager turns into real
|
|
49
|
+
* URLs. three resolves a .gltf's relative files against it like any other base.
|
|
50
|
+
*/
|
|
51
|
+
const PROJECT = "ludra:/";
|
|
52
|
+
|
|
47
53
|
/** Creates the 3D renderer for a scene. Pass it to startGame as `loadRenderer3D`. */
|
|
48
54
|
export const createRenderer3D: Renderer3DFactory = async (scene, options) => {
|
|
49
|
-
const
|
|
55
|
+
const base = options.assetBase;
|
|
56
|
+
const renderer = new ThreeRenderer(scene, options.width, options.height, options.assetUrl ?? ((path) => base + path));
|
|
50
57
|
await renderer.load();
|
|
51
58
|
return renderer;
|
|
52
59
|
};
|
|
@@ -115,11 +122,13 @@ export class ThreeRenderer implements Renderer3D {
|
|
|
115
122
|
private readonly pending = new Set<Promise<unknown>>();
|
|
116
123
|
|
|
117
124
|
private readonly def: SceneDef;
|
|
118
|
-
|
|
125
|
+
/** Loads go through this, so a .gltf's own relative files (its .bin, its textures) are found wherever assets live. */
|
|
126
|
+
private readonly manager: THREE.LoadingManager;
|
|
119
127
|
|
|
120
|
-
constructor(def: SceneDef, width: number, height: number,
|
|
128
|
+
constructor(def: SceneDef, width: number, height: number, assetUrl: (path: string) => string) {
|
|
121
129
|
this.def = def;
|
|
122
|
-
this.
|
|
130
|
+
this.manager = new THREE.LoadingManager();
|
|
131
|
+
this.manager.setURLModifier((url) => (url.startsWith(PROJECT) ? assetUrl(url.slice(PROJECT.length)) : url));
|
|
123
132
|
this.canvas = document.createElement("canvas");
|
|
124
133
|
this.canvas.width = width;
|
|
125
134
|
this.canvas.height = height;
|
|
@@ -140,7 +149,7 @@ export class ThreeRenderer implements Renderer3D {
|
|
|
140
149
|
|
|
141
150
|
/** Loads every texture and model the scene (and its prefabs) names, so the first frame (and screenshots) are complete. */
|
|
142
151
|
async load(): Promise<void> {
|
|
143
|
-
const loader = new THREE.TextureLoader();
|
|
152
|
+
const loader = new THREE.TextureLoader(this.manager);
|
|
144
153
|
const meshes = [...this.def.entities, ...Object.values(this.def.prefabs ?? {})].map((e) => e.components?.mesh as Record<string, unknown> | undefined);
|
|
145
154
|
const named = (field: string) => new Set(meshes.map((m) => m?.[field]).filter((p): p is string => typeof p === "string" && p !== ""));
|
|
146
155
|
const paths = named("texture");
|
|
@@ -149,7 +158,7 @@ export class ThreeRenderer implements Renderer3D {
|
|
|
149
158
|
...models,
|
|
150
159
|
...[...paths].map(async (path) => {
|
|
151
160
|
try {
|
|
152
|
-
const texture = await loader.loadAsync(
|
|
161
|
+
const texture = await loader.loadAsync(PROJECT + path);
|
|
153
162
|
texture.colorSpace = THREE.SRGBColorSpace;
|
|
154
163
|
texture.wrapS = texture.wrapT = THREE.RepeatWrapping;
|
|
155
164
|
texture.anisotropy = 4;
|
|
@@ -166,15 +175,19 @@ export class ThreeRenderer implements Renderer3D {
|
|
|
166
175
|
let load = this.gltfLoads.get(path);
|
|
167
176
|
if (load) return load;
|
|
168
177
|
if (!this.gltfLoader) {
|
|
169
|
-
this.gltfLoader = new GLTFLoader();
|
|
178
|
+
this.gltfLoader = new GLTFLoader(this.manager);
|
|
170
179
|
const draco = new DRACOLoader();
|
|
171
180
|
// The decoder sits in three's addons, which the import map serves.
|
|
172
|
-
|
|
181
|
+
try {
|
|
182
|
+
draco.setDecoderPath(new URL("../libs/draco/gltf/", import.meta.resolve("three/addons/loaders/DRACOLoader.js")).href);
|
|
183
|
+
} catch {
|
|
184
|
+
// A single-file build has no folder to load the decoder from: Draco models don't load there.
|
|
185
|
+
}
|
|
173
186
|
this.gltfLoader.setDRACOLoader(draco);
|
|
174
187
|
this.gltfLoader.setMeshoptDecoder(MeshoptDecoder);
|
|
175
188
|
}
|
|
176
189
|
load = this.track(
|
|
177
|
-
this.gltfLoader.loadAsync(
|
|
190
|
+
this.gltfLoader.loadAsync(PROJECT + path).then(
|
|
178
191
|
(gltf) => void this.gltfs.set(path, gltf),
|
|
179
192
|
(err: unknown) => {
|
|
180
193
|
console.warn(`[ludra] could not load model "${path}" (${(err as Error)?.message ?? err}), drawing its shape instead.`);
|