@3driseai/3drise-core 1.0.8 → 1.0.10
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/utils/galleriesUtils.js +1 -1
- package/dist/utils/index.d.ts +1 -0
- package/dist/utils/index.d.ts.map +1 -1
- package/dist/utils/index.js +1 -0
- package/dist/utils/materialApplicationUtils.d.ts +1 -0
- package/dist/utils/materialApplicationUtils.d.ts.map +1 -1
- package/dist/utils/materialApplicationUtils.js +2 -1
- package/dist/utils/modelCache.d.ts +78 -0
- package/dist/utils/modelCache.d.ts.map +1 -0
- package/dist/utils/modelCache.js +164 -0
- package/dist/utils/utils.d.ts +8 -0
- package/dist/utils/utils.d.ts.map +1 -1
- package/dist/utils/utils.js +17 -3
- package/package.json +1 -1
|
@@ -90,7 +90,7 @@ export const updateCurrentLayoutSettings = (currentSettings, newSettings) => {
|
|
|
90
90
|
? currentSettings.imageUrls
|
|
91
91
|
: createNumberOfDefaultImages(galleryState.numImages ?? 20), galleryState);
|
|
92
92
|
const layoutSettings = getLayoutSettings(currentSettings.layout, galleryState);
|
|
93
|
-
return { frames, layoutSettings };
|
|
93
|
+
return { frames, layoutSettings: galleryState };
|
|
94
94
|
};
|
|
95
95
|
// =============================================================================
|
|
96
96
|
// DEFAULT STATE
|
package/dist/utils/index.d.ts
CHANGED
|
@@ -8,6 +8,7 @@ export * from './GalleryLayouts';
|
|
|
8
8
|
export * from './gridUtils';
|
|
9
9
|
export * from './idUtils';
|
|
10
10
|
export * from './loadingManager';
|
|
11
|
+
export * from './modelCache';
|
|
11
12
|
export * from './materialApplicationUtils';
|
|
12
13
|
export * from './materialUtils';
|
|
13
14
|
export * from './meshUtils';
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/utils/index.ts"],"names":[],"mappings":"AAAA,cAAc,aAAa,CAAC;AAC5B,cAAc,eAAe,CAAC;AAC9B,cAAc,mBAAmB,CAAC;AAClC,cAAc,sBAAsB,CAAC;AACrC,cAAc,kBAAkB,CAAC;AACjC,cAAc,kBAAkB,CAAC;AACjC,cAAc,kBAAkB,CAAC;AACjC,cAAc,aAAa,CAAC;AAC5B,cAAc,WAAW,CAAC;AAC1B,cAAc,kBAAkB,CAAC;AACjC,cAAc,4BAA4B,CAAC;AAC3C,cAAc,iBAAiB,CAAC;AAChC,cAAc,aAAa,CAAC;AAC5B,cAAc,aAAa,CAAC;AAC5B,cAAc,yBAAyB,CAAC;AACxC,cAAc,YAAY,CAAC;AAC3B,cAAc,SAAS,CAAC;AACxB,cAAc,aAAa,CAAC;AAC5B,cAAc,gBAAgB,CAAC;AAC/B,cAAc,mBAAmB,CAAC;AAClC,cAAc,0BAA0B,CAAC;AACzC,cAAc,yBAAyB,CAAC;AACxC,cAAc,yBAAyB,CAAC;AACxC,cAAc,yBAAyB,CAAC;AACxC,cAAc,2BAA2B,CAAC;AAC1C,cAAc,kBAAkB,CAAC;AACjC,cAAc,4BAA4B,CAAC;AAC3C,cAAc,2BAA2B,CAAC;AAC1C,cAAc,wBAAwB,CAAC;AACvC,cAAc,wBAAwB,CAAC;AACvC,cAAc,cAAc,CAAC;AAC7B,cAAc,uBAAuB,CAAC;AACtC,cAAc,oBAAoB,CAAC;AACnC,cAAc,oBAAoB,CAAC"}
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/utils/index.ts"],"names":[],"mappings":"AAAA,cAAc,aAAa,CAAC;AAC5B,cAAc,eAAe,CAAC;AAC9B,cAAc,mBAAmB,CAAC;AAClC,cAAc,sBAAsB,CAAC;AACrC,cAAc,kBAAkB,CAAC;AACjC,cAAc,kBAAkB,CAAC;AACjC,cAAc,kBAAkB,CAAC;AACjC,cAAc,aAAa,CAAC;AAC5B,cAAc,WAAW,CAAC;AAC1B,cAAc,kBAAkB,CAAC;AACjC,cAAc,cAAc,CAAC;AAC7B,cAAc,4BAA4B,CAAC;AAC3C,cAAc,iBAAiB,CAAC;AAChC,cAAc,aAAa,CAAC;AAC5B,cAAc,aAAa,CAAC;AAC5B,cAAc,yBAAyB,CAAC;AACxC,cAAc,YAAY,CAAC;AAC3B,cAAc,SAAS,CAAC;AACxB,cAAc,aAAa,CAAC;AAC5B,cAAc,gBAAgB,CAAC;AAC/B,cAAc,mBAAmB,CAAC;AAClC,cAAc,0BAA0B,CAAC;AACzC,cAAc,yBAAyB,CAAC;AACxC,cAAc,yBAAyB,CAAC;AACxC,cAAc,yBAAyB,CAAC;AACxC,cAAc,2BAA2B,CAAC;AAC1C,cAAc,kBAAkB,CAAC;AACjC,cAAc,4BAA4B,CAAC;AAC3C,cAAc,2BAA2B,CAAC;AAC1C,cAAc,wBAAwB,CAAC;AACvC,cAAc,wBAAwB,CAAC;AACvC,cAAc,cAAc,CAAC;AAC7B,cAAc,uBAAuB,CAAC;AACtC,cAAc,oBAAoB,CAAC;AACnC,cAAc,oBAAoB,CAAC"}
|
package/dist/utils/index.js
CHANGED
|
@@ -8,6 +8,7 @@ export * from './GalleryLayouts.js';
|
|
|
8
8
|
export * from './gridUtils.js';
|
|
9
9
|
export * from './idUtils.js';
|
|
10
10
|
export * from './loadingManager.js';
|
|
11
|
+
export * from './modelCache.js';
|
|
11
12
|
export * from './materialApplicationUtils.js';
|
|
12
13
|
export * from './materialUtils.js';
|
|
13
14
|
export * from './meshUtils.js';
|
|
@@ -3,6 +3,7 @@ import { MaterialInstance } from '../services/MaterialRegistry';
|
|
|
3
3
|
export declare const MATERIAL_INSTANCE_KEY = "_3driseMaterialInstance";
|
|
4
4
|
export declare const EDGES_GROUP_KEY = "__isEdgesGroup";
|
|
5
5
|
export declare const GEREATIVE_EFFECTS_KEY = "__isGenerativeEffectsGroup";
|
|
6
|
+
export declare const IMAGE_KEY = "__isImage";
|
|
6
7
|
/**
|
|
7
8
|
* Set by a renderer that builds and owns its object's material itself — today the
|
|
8
9
|
* points renderers, while shader effects are active. Two writers on one
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"materialApplicationUtils.d.ts","sourceRoot":"","sources":["../../src/utils/materialApplicationUtils.ts"],"names":[],"mappings":"AAWA,OAAO,KAAK,EAAE,QAAQ,EAAE,QAAQ,EAAE,MAAM,OAAO,CAAC;AAChD,OAA4B,EAAE,gBAAgB,EAAE,MAAM,8BAA8B,CAAC;AAKrF,eAAO,MAAM,qBAAqB,4BAA4B,CAAC;AAE/D,eAAO,MAAM,eAAe,mBAAmB,CAAC;AAChD,eAAO,MAAM,qBAAqB,+BAA+B,CAAC;AAClE;;;;;;GAMG;AACH,eAAO,MAAM,uBAAuB,sBAAsB,CAAC;AAC3D;;;;;GAKG;AACH,wBAAgB,+BAA+B,CAAC,GAAG,EAAE,QAAQ,EAAE,MAAM,EAAE,OAAO,EAAE,QAAQ,EAAE,CAAC,IAAI,EAAE,QAAQ,KAAK,IAAI,GAAG,IAAI,CAOxH;AAKD;;GAEG;AACH,wBAAgB,sBAAsB,CAAC,OAAO,EAAE,MAAM,EAAE,gBAAgB,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,GAAG,gBAAgB,CAI/G;AACD;;;GAGG;AACH,wBAAsB,eAAe,CAAC,OAAO,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,GAAG,OAAO,CAAC,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,CAAC,CAQlH;AACD,wBAAgB,mBAAmB,CAAC,QAAQ,EAAE,QAAQ,EAAE,GAAG,EAAE,MAAM,EAAE,KAAK,EAAE,GAAG,GAAG,IAAI,CAsCrF;AACD;;;GAGG;AACH,wBAAgB,kBAAkB,CAAC,QAAQ,EAAE,QAAQ,EAAE,GAAG,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,IAAI,CAE1F;AACD;;GAEG;AACH,wBAAgB,eAAe,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAEpD;AACD;;GAEG;AACH,wBAAgB,iBAAiB,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAGtD;AACD;;GAEG;AACH,wBAAgB,iBAAiB,CAAC,GAAG,EAAE,MAAM,EAAE,KAAK,EAAE,GAAG,GAAG,OAAO,CAElE;AACD;;GAEG;AACH,wBAAgB,iBAAiB,CAAC,GAAG,EAAE,MAAM,EAAE,KAAK,EAAE,GAAG,GAAG,OAAO,CAElE;AACD;;GAEG;AACH,eAAO,MAAM,eAAe,EAAE,GAAG,CAAC,MAAM,CAGtC,CAAC;AACH;;GAEG;AACH,wBAAgB,kBAAkB,CAAC,QAAQ,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,GAAG;IAC3D,OAAO,EAAE,KAAK,CAAC;QACX,GAAG,EAAE,MAAM,CAAC;QACZ,KAAK,EAAE,MAAM,CAAC;KACjB,CAAC,CAAC;IACH,MAAM,EAAE,KAAK,CAAC;QACV,GAAG,EAAE,MAAM,CAAC;QACZ,KAAK,EAAE,MAAM,CAAC;KACjB,CAAC,CAAC;IACH,QAAQ,EAAE,KAAK,CAAC;QACZ,GAAG,EAAE,MAAM,CAAC;QACZ,KAAK,EAAE,GAAG,CAAC;KACd,CAAC,CAAC;IACH,QAAQ,EAAE,KAAK,CAAC;QACZ,GAAG,EAAE,MAAM,CAAC;QACZ,KAAK,EAAE,OAAO,CAAC;KAClB,CAAC,CAAC;IACH,KAAK,EAAE,KAAK,CAAC;QACT,GAAG,EAAE,MAAM,CAAC;QACZ,KAAK,EAAE,GAAG,CAAC;KACd,CAAC,CAAC;CACN,CA6BJ"}
|
|
1
|
+
{"version":3,"file":"materialApplicationUtils.d.ts","sourceRoot":"","sources":["../../src/utils/materialApplicationUtils.ts"],"names":[],"mappings":"AAWA,OAAO,KAAK,EAAE,QAAQ,EAAE,QAAQ,EAAE,MAAM,OAAO,CAAC;AAChD,OAA4B,EAAE,gBAAgB,EAAE,MAAM,8BAA8B,CAAC;AAKrF,eAAO,MAAM,qBAAqB,4BAA4B,CAAC;AAE/D,eAAO,MAAM,eAAe,mBAAmB,CAAC;AAChD,eAAO,MAAM,qBAAqB,+BAA+B,CAAC;AAClE,eAAO,MAAM,SAAS,cAAc,CAAC;AACrC;;;;;;GAMG;AACH,eAAO,MAAM,uBAAuB,sBAAsB,CAAC;AAC3D;;;;;GAKG;AACH,wBAAgB,+BAA+B,CAAC,GAAG,EAAE,QAAQ,EAAE,MAAM,EAAE,OAAO,EAAE,QAAQ,EAAE,CAAC,IAAI,EAAE,QAAQ,KAAK,IAAI,GAAG,IAAI,CAOxH;AAKD;;GAEG;AACH,wBAAgB,sBAAsB,CAAC,OAAO,EAAE,MAAM,EAAE,gBAAgB,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,GAAG,gBAAgB,CAI/G;AACD;;;GAGG;AACH,wBAAsB,eAAe,CAAC,OAAO,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,GAAG,OAAO,CAAC,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,CAAC,CAQlH;AACD,wBAAgB,mBAAmB,CAAC,QAAQ,EAAE,QAAQ,EAAE,GAAG,EAAE,MAAM,EAAE,KAAK,EAAE,GAAG,GAAG,IAAI,CAsCrF;AACD;;;GAGG;AACH,wBAAgB,kBAAkB,CAAC,QAAQ,EAAE,QAAQ,EAAE,GAAG,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,IAAI,CAE1F;AACD;;GAEG;AACH,wBAAgB,eAAe,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAEpD;AACD;;GAEG;AACH,wBAAgB,iBAAiB,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAGtD;AACD;;GAEG;AACH,wBAAgB,iBAAiB,CAAC,GAAG,EAAE,MAAM,EAAE,KAAK,EAAE,GAAG,GAAG,OAAO,CAElE;AACD;;GAEG;AACH,wBAAgB,iBAAiB,CAAC,GAAG,EAAE,MAAM,EAAE,KAAK,EAAE,GAAG,GAAG,OAAO,CAElE;AACD;;GAEG;AACH,eAAO,MAAM,eAAe,EAAE,GAAG,CAAC,MAAM,CAGtC,CAAC;AACH;;GAEG;AACH,wBAAgB,kBAAkB,CAAC,QAAQ,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,GAAG;IAC3D,OAAO,EAAE,KAAK,CAAC;QACX,GAAG,EAAE,MAAM,CAAC;QACZ,KAAK,EAAE,MAAM,CAAC;KACjB,CAAC,CAAC;IACH,MAAM,EAAE,KAAK,CAAC;QACV,GAAG,EAAE,MAAM,CAAC;QACZ,KAAK,EAAE,MAAM,CAAC;KACjB,CAAC,CAAC;IACH,QAAQ,EAAE,KAAK,CAAC;QACZ,GAAG,EAAE,MAAM,CAAC;QACZ,KAAK,EAAE,GAAG,CAAC;KACd,CAAC,CAAC;IACH,QAAQ,EAAE,KAAK,CAAC;QACZ,GAAG,EAAE,MAAM,CAAC;QACZ,KAAK,EAAE,OAAO,CAAC;KAClB,CAAC,CAAC;IACH,KAAK,EAAE,KAAK,CAAC;QACT,GAAG,EAAE,MAAM,CAAC;QACZ,KAAK,EAAE,GAAG,CAAC;KACd,CAAC,CAAC;CACN,CA6BJ"}
|
|
@@ -18,6 +18,7 @@ export const MATERIAL_INSTANCE_KEY = '_3driseMaterialInstance';
|
|
|
18
18
|
// Key placed on edges group userData to prevent material bleed-through
|
|
19
19
|
export const EDGES_GROUP_KEY = '__isEdgesGroup';
|
|
20
20
|
export const GEREATIVE_EFFECTS_KEY = '__isGenerativeEffectsGroup';
|
|
21
|
+
export const IMAGE_KEY = '__isImage';
|
|
21
22
|
/**
|
|
22
23
|
* Set by a renderer that builds and owns its object's material itself — today the
|
|
23
24
|
* points renderers, while shader effects are active. Two writers on one
|
|
@@ -33,7 +34,7 @@ export const SELF_OWNED_MATERIAL_KEY = '__ownsItsMaterial';
|
|
|
33
34
|
* apply its own material via useMaterialApplication).
|
|
34
35
|
*/
|
|
35
36
|
export function traverseSkippingEdgesAndEffects(obj, isRoot, callback) {
|
|
36
|
-
if (!isRoot && (obj.userData[EDGES_GROUP_KEY] || obj.userData[GEREATIVE_EFFECTS_KEY] || obj.userData[SELF_OWNED_MATERIAL_KEY]))
|
|
37
|
+
if (!isRoot && (obj.userData[EDGES_GROUP_KEY] || obj.userData[GEREATIVE_EFFECTS_KEY] || obj.userData[SELF_OWNED_MATERIAL_KEY] || obj.userData[IMAGE_KEY]))
|
|
37
38
|
return;
|
|
38
39
|
callback(obj);
|
|
39
40
|
for (const child of obj.children) {
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
import type { AnimationClip, Object3D } from 'three';
|
|
2
|
+
/**
|
|
3
|
+
* URL-keyed model cache.
|
|
4
|
+
*
|
|
5
|
+
* One fetch per URL for the life of the page, shared by every consumer: the project
|
|
6
|
+
* loaders, the object API, and any model leaf that mounts without a preload pass. The
|
|
7
|
+
* cached value is the *master* — it is never mounted into a scene. Consumers take a clone.
|
|
8
|
+
*
|
|
9
|
+
* Loading goes through the `gltfLoader` singleton in `loadingManager.ts`, so the Draco
|
|
10
|
+
* decoder configuration and the LoadingManager progress events stay in one place.
|
|
11
|
+
*/
|
|
12
|
+
export interface ModelAsset {
|
|
13
|
+
/** The master scene. Never mount this — clone it. */
|
|
14
|
+
scene: Object3D;
|
|
15
|
+
/** Clips shipped with the GLTF, to bind against a clone's root. */
|
|
16
|
+
animations: AnimationClip[];
|
|
17
|
+
}
|
|
18
|
+
export interface ModelCacheEntry {
|
|
19
|
+
url: string;
|
|
20
|
+
promise: Promise<ModelAsset>;
|
|
21
|
+
/** Set once the load resolves. */
|
|
22
|
+
asset?: ModelAsset;
|
|
23
|
+
/** Set once the load rejects. */
|
|
24
|
+
error?: unknown;
|
|
25
|
+
}
|
|
26
|
+
export interface ModelInstance {
|
|
27
|
+
object: Object3D;
|
|
28
|
+
animations: AnimationClip[];
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* Start (or join) the load of a model URL. Safe to call from anywhere, any number of
|
|
32
|
+
* times — concurrent callers share one request and one master.
|
|
33
|
+
*/
|
|
34
|
+
export declare const loadModelAsset: (url: string) => ModelCacheEntry;
|
|
35
|
+
/**
|
|
36
|
+
* Suspense-flavoured read: returns the asset if it is already here, otherwise throws the
|
|
37
|
+
* promise (React suspends) or the error (the nearest error boundary catches it).
|
|
38
|
+
*
|
|
39
|
+
* Call this from render, never from an effect.
|
|
40
|
+
*/
|
|
41
|
+
export declare const readModelAsset: (url: string) => ModelAsset;
|
|
42
|
+
/** The resolved asset if the URL is already cached, otherwise null. Never suspends. */
|
|
43
|
+
export declare const peekModelAsset: (url: string) => ModelAsset | null;
|
|
44
|
+
/**
|
|
45
|
+
* Warm the cache. Never rejects — a model that fails to load is reported by the leaf that
|
|
46
|
+
* needs it, not by the preload.
|
|
47
|
+
*/
|
|
48
|
+
export declare const preloadModels: (urls: string[]) => Promise<void>;
|
|
49
|
+
/**
|
|
50
|
+
* Clone a master for mounting.
|
|
51
|
+
*
|
|
52
|
+
* `SkeletonUtils.clone` rather than `Object3D.clone` so skinned meshes and their bones are
|
|
53
|
+
* rebound to the copy. Geometry stays shared with the master — cheap, and never disposed by
|
|
54
|
+
* an instance. Materials are cloned per instance because `useMaterialApplication` mutates
|
|
55
|
+
* them per scene object; sharing them would make one object's colour change repaint every
|
|
56
|
+
* instance of the same URL.
|
|
57
|
+
*/
|
|
58
|
+
export declare const cloneModelAsset: (asset: ModelAsset) => ModelInstance;
|
|
59
|
+
/**
|
|
60
|
+
* Release what a single mounted instance owns.
|
|
61
|
+
*
|
|
62
|
+
* Only the per-instance material clones. Geometry and textures are reached from the cache
|
|
63
|
+
* master and are shared by every other instance of that URL — disposing them here is how
|
|
64
|
+
* you make every other copy of the model turn black.
|
|
65
|
+
*/
|
|
66
|
+
export declare const disposeModelInstance: (object: Object3D | null | undefined) => void;
|
|
67
|
+
/** Claim a mounted instance. Cancels a disposal another component scheduled for it. */
|
|
68
|
+
export declare const retainModelInstance: (object: Object3D | null | undefined) => void;
|
|
69
|
+
/** Give up a mounted instance. Frees it a tick later if nothing else claims it first. */
|
|
70
|
+
export declare const releaseModelInstance: (object: Object3D | null | undefined) => void;
|
|
71
|
+
/** Drop cache entries. For tests and debugging — masters are not disposed. */
|
|
72
|
+
export declare const clearModelCache: (url?: string) => void;
|
|
73
|
+
/** Diagnostics: what the cache is holding right now. */
|
|
74
|
+
export declare const modelCacheStats: () => {
|
|
75
|
+
url: string;
|
|
76
|
+
state: "loading" | "ready" | "error";
|
|
77
|
+
}[];
|
|
78
|
+
//# sourceMappingURL=modelCache.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"modelCache.d.ts","sourceRoot":"","sources":["../../src/utils/modelCache.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,aAAa,EAAY,QAAQ,EAAE,MAAM,OAAO,CAAC;AAG/D;;;;;;;;;GASG;AAEH,MAAM,WAAW,UAAU;IACvB,qDAAqD;IACrD,KAAK,EAAE,QAAQ,CAAC;IAChB,mEAAmE;IACnE,UAAU,EAAE,aAAa,EAAE,CAAC;CAC/B;AAED,MAAM,WAAW,eAAe;IAC5B,GAAG,EAAE,MAAM,CAAC;IACZ,OAAO,EAAE,OAAO,CAAC,UAAU,CAAC,CAAC;IAC7B,kCAAkC;IAClC,KAAK,CAAC,EAAE,UAAU,CAAC;IACnB,iCAAiC;IACjC,KAAK,CAAC,EAAE,OAAO,CAAC;CACnB;AAED,MAAM,WAAW,aAAa;IAC1B,MAAM,EAAE,QAAQ,CAAC;IACjB,UAAU,EAAE,aAAa,EAAE,CAAC;CAC/B;AAOD;;;GAGG;AACH,eAAO,MAAM,cAAc,GAAI,KAAK,MAAM,KAAG,eAyB5C,CAAC;AAEF;;;;;GAKG;AACH,eAAO,MAAM,cAAc,GAAI,KAAK,MAAM,KAAG,UAK5C,CAAC;AAEF,uFAAuF;AACvF,eAAO,MAAM,cAAc,GAAI,KAAK,MAAM,KAAG,UAAU,GAAG,IACvB,CAAC;AAEpC;;;GAGG;AACH,eAAO,MAAM,aAAa,GAAU,MAAM,MAAM,EAAE,KAAG,OAAO,CAAC,IAAI,CAKhE,CAAC;AAEF;;;;;;;;GAQG;AACH,eAAO,MAAM,eAAe,GAAI,OAAO,UAAU,KAAG,aAoBnD,CAAC;AAEF;;;;;;GAMG;AACH,eAAO,MAAM,oBAAoB,GAAI,QAAQ,QAAQ,GAAG,IAAI,GAAG,SAAS,KAAG,IAU1E,CAAC;AAkBF,uFAAuF;AACvF,eAAO,MAAM,mBAAmB,GAAI,QAAQ,QAAQ,GAAG,IAAI,GAAG,SAAS,KAAG,IAQzE,CAAC;AAEF,yFAAyF;AACzF,eAAO,MAAM,oBAAoB,GAAI,QAAQ,QAAQ,GAAG,IAAI,GAAG,SAAS,KAAG,IAgB1E,CAAC;AAEF,8EAA8E;AAC9E,eAAO,MAAM,eAAe,GAAI,MAAM,MAAM,KAAG,IAG9C,CAAC;AAEF,wDAAwD;AACxD,eAAO,MAAM,eAAe,QAAO;IAAE,GAAG,EAAE,MAAM,CAAC;IAAC,KAAK,EAAE,SAAS,GAAG,OAAO,GAAG,OAAO,CAAA;CAAE,EAIjF,CAAC"}
|
|
@@ -0,0 +1,164 @@
|
|
|
1
|
+
import { clone as cloneSkeleton } from 'three/addons/utils/SkeletonUtils.js';
|
|
2
|
+
import { gltfLoader } from './loadingManager.js';
|
|
3
|
+
/** Marks a material this instance owns and may dispose. */
|
|
4
|
+
const OWNED_MATERIAL = '__3driseOwnedMaterial';
|
|
5
|
+
const entries = new Map();
|
|
6
|
+
/**
|
|
7
|
+
* Start (or join) the load of a model URL. Safe to call from anywhere, any number of
|
|
8
|
+
* times — concurrent callers share one request and one master.
|
|
9
|
+
*/
|
|
10
|
+
export const loadModelAsset = (url) => {
|
|
11
|
+
const existing = entries.get(url);
|
|
12
|
+
if (existing)
|
|
13
|
+
return existing;
|
|
14
|
+
const entry = {
|
|
15
|
+
url,
|
|
16
|
+
promise: gltfLoader
|
|
17
|
+
.loadAsync(url)
|
|
18
|
+
.then((gltf) => {
|
|
19
|
+
const asset = {
|
|
20
|
+
scene: gltf.scene,
|
|
21
|
+
animations: gltf.animations ?? [],
|
|
22
|
+
};
|
|
23
|
+
entry.asset = asset;
|
|
24
|
+
return asset;
|
|
25
|
+
})
|
|
26
|
+
.catch((error) => {
|
|
27
|
+
console.error(`[modelCache] Failed to load model: ${url}`, error);
|
|
28
|
+
entry.error = error;
|
|
29
|
+
throw error;
|
|
30
|
+
}),
|
|
31
|
+
};
|
|
32
|
+
entries.set(url, entry);
|
|
33
|
+
return entry;
|
|
34
|
+
};
|
|
35
|
+
/**
|
|
36
|
+
* Suspense-flavoured read: returns the asset if it is already here, otherwise throws the
|
|
37
|
+
* promise (React suspends) or the error (the nearest error boundary catches it).
|
|
38
|
+
*
|
|
39
|
+
* Call this from render, never from an effect.
|
|
40
|
+
*/
|
|
41
|
+
export const readModelAsset = (url) => {
|
|
42
|
+
const entry = loadModelAsset(url);
|
|
43
|
+
if (entry.error)
|
|
44
|
+
throw entry.error;
|
|
45
|
+
if (!entry.asset)
|
|
46
|
+
throw entry.promise;
|
|
47
|
+
return entry.asset;
|
|
48
|
+
};
|
|
49
|
+
/** The resolved asset if the URL is already cached, otherwise null. Never suspends. */
|
|
50
|
+
export const peekModelAsset = (url) => entries.get(url)?.asset ?? null;
|
|
51
|
+
/**
|
|
52
|
+
* Warm the cache. Never rejects — a model that fails to load is reported by the leaf that
|
|
53
|
+
* needs it, not by the preload.
|
|
54
|
+
*/
|
|
55
|
+
export const preloadModels = async (urls) => {
|
|
56
|
+
const unique = [...new Set(urls.filter(Boolean))];
|
|
57
|
+
await Promise.all(unique.map(url => loadModelAsset(url).promise.catch(() => undefined)));
|
|
58
|
+
};
|
|
59
|
+
/**
|
|
60
|
+
* Clone a master for mounting.
|
|
61
|
+
*
|
|
62
|
+
* `SkeletonUtils.clone` rather than `Object3D.clone` so skinned meshes and their bones are
|
|
63
|
+
* rebound to the copy. Geometry stays shared with the master — cheap, and never disposed by
|
|
64
|
+
* an instance. Materials are cloned per instance because `useMaterialApplication` mutates
|
|
65
|
+
* them per scene object; sharing them would make one object's colour change repaint every
|
|
66
|
+
* instance of the same URL.
|
|
67
|
+
*/
|
|
68
|
+
export const cloneModelAsset = (asset) => {
|
|
69
|
+
const object = cloneSkeleton(asset.scene);
|
|
70
|
+
object.traverse(child => {
|
|
71
|
+
const material = child.material;
|
|
72
|
+
if (!material)
|
|
73
|
+
return;
|
|
74
|
+
if (Array.isArray(material)) {
|
|
75
|
+
child.material = material.map(m => {
|
|
76
|
+
const copy = m.clone();
|
|
77
|
+
copy.userData[OWNED_MATERIAL] = true;
|
|
78
|
+
return copy;
|
|
79
|
+
});
|
|
80
|
+
}
|
|
81
|
+
else {
|
|
82
|
+
const copy = material.clone();
|
|
83
|
+
copy.userData[OWNED_MATERIAL] = true;
|
|
84
|
+
child.material = copy;
|
|
85
|
+
}
|
|
86
|
+
});
|
|
87
|
+
return { object, animations: asset.animations };
|
|
88
|
+
};
|
|
89
|
+
/**
|
|
90
|
+
* Release what a single mounted instance owns.
|
|
91
|
+
*
|
|
92
|
+
* Only the per-instance material clones. Geometry and textures are reached from the cache
|
|
93
|
+
* master and are shared by every other instance of that URL — disposing them here is how
|
|
94
|
+
* you make every other copy of the model turn black.
|
|
95
|
+
*/
|
|
96
|
+
export const disposeModelInstance = (object) => {
|
|
97
|
+
if (!object)
|
|
98
|
+
return;
|
|
99
|
+
object.traverse(child => {
|
|
100
|
+
const material = child.material;
|
|
101
|
+
if (!material)
|
|
102
|
+
return;
|
|
103
|
+
const list = Array.isArray(material) ? material : [material];
|
|
104
|
+
list.forEach(m => {
|
|
105
|
+
if (m?.userData?.[OWNED_MATERIAL])
|
|
106
|
+
m.dispose();
|
|
107
|
+
});
|
|
108
|
+
});
|
|
109
|
+
};
|
|
110
|
+
/**
|
|
111
|
+
* Mount/unmount bookkeeping for cloned instances.
|
|
112
|
+
*
|
|
113
|
+
* An instance can be mounted by one component at a time, but the *component* mounting it
|
|
114
|
+
* changes more often than you would think: React StrictMode double-mounts, a re-parent in
|
|
115
|
+
* the object tree remounts, and adopting a saved object's real id in place of its temp id
|
|
116
|
+
* changes the React key, which unmounts one leaf and mounts another around the same
|
|
117
|
+
* Object3D in a single commit.
|
|
118
|
+
*
|
|
119
|
+
* Disposing on the unmount half of any of those turns a model that is still on screen
|
|
120
|
+
* black. So disposal is refcounted and deferred by a tick: whoever mounts next cancels it,
|
|
121
|
+
* and only a release that is never followed by a retain actually frees anything.
|
|
122
|
+
*/
|
|
123
|
+
const instanceRefCounts = new Map();
|
|
124
|
+
const pendingDisposals = new Map();
|
|
125
|
+
/** Claim a mounted instance. Cancels a disposal another component scheduled for it. */
|
|
126
|
+
export const retainModelInstance = (object) => {
|
|
127
|
+
if (!object)
|
|
128
|
+
return;
|
|
129
|
+
const pending = pendingDisposals.get(object);
|
|
130
|
+
if (pending !== undefined) {
|
|
131
|
+
clearTimeout(pending);
|
|
132
|
+
pendingDisposals.delete(object);
|
|
133
|
+
}
|
|
134
|
+
instanceRefCounts.set(object, (instanceRefCounts.get(object) ?? 0) + 1);
|
|
135
|
+
};
|
|
136
|
+
/** Give up a mounted instance. Frees it a tick later if nothing else claims it first. */
|
|
137
|
+
export const releaseModelInstance = (object) => {
|
|
138
|
+
if (!object)
|
|
139
|
+
return;
|
|
140
|
+
const next = (instanceRefCounts.get(object) ?? 1) - 1;
|
|
141
|
+
if (next > 0) {
|
|
142
|
+
instanceRefCounts.set(object, next);
|
|
143
|
+
return;
|
|
144
|
+
}
|
|
145
|
+
instanceRefCounts.delete(object);
|
|
146
|
+
if (pendingDisposals.has(object))
|
|
147
|
+
return;
|
|
148
|
+
pendingDisposals.set(object, setTimeout(() => {
|
|
149
|
+
pendingDisposals.delete(object);
|
|
150
|
+
disposeModelInstance(object);
|
|
151
|
+
}, 0));
|
|
152
|
+
};
|
|
153
|
+
/** Drop cache entries. For tests and debugging — masters are not disposed. */
|
|
154
|
+
export const clearModelCache = (url) => {
|
|
155
|
+
if (url)
|
|
156
|
+
entries.delete(url);
|
|
157
|
+
else
|
|
158
|
+
entries.clear();
|
|
159
|
+
};
|
|
160
|
+
/** Diagnostics: what the cache is holding right now. */
|
|
161
|
+
export const modelCacheStats = () => [...entries.values()].map(entry => ({
|
|
162
|
+
url: entry.url,
|
|
163
|
+
state: entry.error ? 'error' : entry.asset ? 'ready' : 'loading',
|
|
164
|
+
}));
|
package/dist/utils/utils.d.ts
CHANGED
|
@@ -8,6 +8,14 @@ export declare const createModelFromUrl: (model: ModelData) => Promise<CreatedRe
|
|
|
8
8
|
export declare const buildQueryString: (params: Record<string, any>) => string;
|
|
9
9
|
export declare const loadModels: (models: ModelData[]) => Promise<CreatedRef[]>;
|
|
10
10
|
export declare const dontAnimate: (key: string) => boolean;
|
|
11
|
+
/**
|
|
12
|
+
* Preload a project's models and register one instance per scene object id.
|
|
13
|
+
*
|
|
14
|
+
* This is an optimisation, not a precondition: a model leaf that mounts without this
|
|
15
|
+
* having run loads itself through the same cache under Suspense (see
|
|
16
|
+
* `useModelObject` in the viewer package). Keep calling it so a scene reveals whole
|
|
17
|
+
* instead of popping in model by model.
|
|
18
|
+
*/
|
|
11
19
|
export declare const loadAssets: (models: ModelData[]) => Promise<CreatedRef[]>;
|
|
12
20
|
export declare const createUniformsText: (uniforms: any[]) => string;
|
|
13
21
|
/**
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"utils.d.ts","sourceRoot":"","sources":["../../src/utils/utils.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,OAAO,CAAC;AAGtC,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,eAAe,CAAC;AAC/C,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,kBAAkB,CAAC;AACnD,OAAO,EAAE,WAAW,EAAE,MAAM,UAAU,CAAC;AAEvC,eAAO,MAAM,wBAAwB,GAAI,QAAQ,QAAQ,KAAG,WAY3D,CAAC;AACF,eAAO,MAAM,kBAAkB,GAAI,OAAO,QAAQ,EAAE,UAAS,MAAU,KAAG,MAWzE,CAAC;AAEF,eAAO,MAAM,kBAAkB,GAAU,OAAO,SAAS,KAAG,OAAO,CAAC,UAAU,GAAG,IAAI,
|
|
1
|
+
{"version":3,"file":"utils.d.ts","sourceRoot":"","sources":["../../src/utils/utils.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,OAAO,CAAC;AAGtC,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,eAAe,CAAC;AAC/C,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,kBAAkB,CAAC;AACnD,OAAO,EAAE,WAAW,EAAE,MAAM,UAAU,CAAC;AAEvC,eAAO,MAAM,wBAAwB,GAAI,QAAQ,QAAQ,KAAG,WAY3D,CAAC;AACF,eAAO,MAAM,kBAAkB,GAAI,OAAO,QAAQ,EAAE,UAAS,MAAU,KAAG,MAWzE,CAAC;AAEF,eAAO,MAAM,kBAAkB,GAAU,OAAO,SAAS,KAAG,OAAO,CAAC,UAAU,GAAG,IAAI,CAgCpF,CAAC;AACF,eAAO,MAAM,gBAAgB,GAAI,QAAQ,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,KAAG,MAQ9D,CAAC;AACF,eAAO,MAAM,UAAU,GAAU,QAAQ,SAAS,EAAE,KAAG,OAAO,CAAC,UAAU,EAAE,CA0C1E,CAAC;AACF,eAAO,MAAM,WAAW,GAAI,KAAK,MAAM,KAAG,OAGzC,CAAC;AACF;;;;;;;GAOG;AACH,eAAO,MAAM,UAAU,GAAU,QAAQ,SAAS,EAAE,KAAG,OAAO,CAAC,UAAU,EAAE,CAyB1E,CAAC;AACF,eAAO,MAAM,kBAAkB,GAAI,UAAU,GAAG,EAAE,KAAG,MAQpD,CAAC;AACF;;;GAGG;AACH,eAAO,MAAM,cAAc,GAAI,UAAU,GAAG,EAAE,EAAE,cAAc,MAAM,EAAE,gBAAgB,MAAM,KAAG;IACvF,MAAM,EAAE,MAAM,CAAC;IACf,QAAQ,EAAE,MAAM,CAAC;CAOxB,CAAC"}
|
package/dist/utils/utils.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { Box3, Vector3 } from "three";
|
|
2
|
-
import {
|
|
2
|
+
import { cloneModelAsset, loadModelAsset, preloadModels } from "./modelCache.js";
|
|
3
3
|
import ObjectManager from "../services/ObjectManager.js";
|
|
4
4
|
export const createDefaultEffectState = (object) => {
|
|
5
5
|
const material = object.material;
|
|
@@ -30,8 +30,10 @@ export const calculateAutoScale = (model, maxSize = 5) => {
|
|
|
30
30
|
export const createModelFromUrl = async (model) => {
|
|
31
31
|
try {
|
|
32
32
|
console.log(`Loading model from model: `, model);
|
|
33
|
-
|
|
34
|
-
//
|
|
33
|
+
// One fetch per URL for the life of the page; this is a per-instance clone of the
|
|
34
|
+
// cached master, so geometry is shared and materials are this object's own.
|
|
35
|
+
const asset = await loadModelAsset(model.url).promise;
|
|
36
|
+
const { object: loadedModel } = cloneModelAsset(asset);
|
|
35
37
|
if (loadedModel) {
|
|
36
38
|
loadedModel.name = model.name || `model_${Date.now()}`;
|
|
37
39
|
loadedModel.scale.set(...(model.scale || [1, 1, 1]));
|
|
@@ -114,6 +116,14 @@ export const dontAnimate = (key) => {
|
|
|
114
116
|
return key.toLowerCase().includes('texture') ||
|
|
115
117
|
key.toLowerCase().includes('noise');
|
|
116
118
|
};
|
|
119
|
+
/**
|
|
120
|
+
* Preload a project's models and register one instance per scene object id.
|
|
121
|
+
*
|
|
122
|
+
* This is an optimisation, not a precondition: a model leaf that mounts without this
|
|
123
|
+
* having run loads itself through the same cache under Suspense (see
|
|
124
|
+
* `useModelObject` in the viewer package). Keep calling it so a scene reveals whole
|
|
125
|
+
* instead of popping in model by model.
|
|
126
|
+
*/
|
|
117
127
|
export const loadAssets = async (models) => {
|
|
118
128
|
console.log(`[loadAssets] Starting to load ${models.length} models`);
|
|
119
129
|
if (models.length === 0) {
|
|
@@ -132,6 +142,10 @@ export const loadAssets = async (models) => {
|
|
|
132
142
|
return true;
|
|
133
143
|
});
|
|
134
144
|
console.log(`[loadAssets] Loading ${cleanedModels.length} valid models (filtered ${models.length - cleanedModels.length})`);
|
|
145
|
+
// Warm the cache for every URL in parallel first — loadModels then walks the list
|
|
146
|
+
// sequentially, but each step is a clone of an already-resolved master rather than a
|
|
147
|
+
// fresh download. Repeated URLs cost one fetch, not one per scene object.
|
|
148
|
+
await preloadModels(cleanedModels.map(model => model.url));
|
|
135
149
|
const createdObjects = await loadModels(cleanedModels);
|
|
136
150
|
console.log(`[loadAssets] Completed. Loaded ${createdObjects.length}/${cleanedModels.length} models`);
|
|
137
151
|
return createdObjects;
|