react-three-game 0.0.106 → 0.0.108

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.
Files changed (28) hide show
  1. package/README.md +7 -0
  2. package/dist/plugins/crashcat/CrashcatPhysicsComponent.js +74 -46
  3. package/dist/plugins/crashcat/CrashcatRagdoll.d.ts +58 -0
  4. package/dist/plugins/crashcat/CrashcatRagdoll.js +410 -0
  5. package/dist/plugins/crashcat/CrashcatRuntime.js +23 -32
  6. package/dist/plugins/crashcat/index.d.ts +1 -0
  7. package/dist/plugins/crashcat/index.js +1 -0
  8. package/dist/tools/assetviewer/page.js +4 -4
  9. package/dist/tools/prefabeditor/EditorTree.js +5 -2
  10. package/dist/tools/prefabeditor/EditorTreeMenus.d.ts +2 -1
  11. package/dist/tools/prefabeditor/EditorTreeMenus.js +17 -5
  12. package/dist/tools/prefabeditor/GameEvents.d.ts +1 -0
  13. package/dist/tools/prefabeditor/PrefabEditor.d.ts +2 -1
  14. package/dist/tools/prefabeditor/PrefabEditor.js +26 -13
  15. package/dist/tools/prefabeditor/PrefabRoot.d.ts +3 -1
  16. package/dist/tools/prefabeditor/PrefabRoot.js +61 -25
  17. package/dist/tools/prefabeditor/components/ComponentRegistry.d.ts +15 -0
  18. package/dist/tools/prefabeditor/components/PrefabRefComponent.d.ts +3 -0
  19. package/dist/tools/prefabeditor/components/PrefabRefComponent.js +72 -0
  20. package/dist/tools/prefabeditor/components/TextComponent.js +8 -5
  21. package/dist/tools/prefabeditor/components/index.js +2 -0
  22. package/dist/tools/prefabeditor/modelPrefab.d.ts +3 -0
  23. package/dist/tools/prefabeditor/modelPrefab.js +44 -11
  24. package/dist/tools/prefabeditor/prefab.d.ts +5 -4
  25. package/dist/tools/prefabeditor/prefab.js +47 -29
  26. package/dist/tools/prefabeditor/utils.d.ts +8 -1
  27. package/dist/tools/prefabeditor/utils.js +74 -22
  28. package/package.json +13 -13
@@ -1,4 +1,5 @@
1
1
  import type { FC } from "react";
2
+ import type { ThreeEvent } from "@react-three/fiber";
2
3
  import type { ComponentData, GameObject } from "../types";
3
4
  export type AssetRef = {
4
5
  type: "model" | "texture" | "sound";
@@ -12,13 +13,27 @@ export interface ComponentViewProps<P = Record<string, unknown>> {
12
13
  properties: P;
13
14
  /** Children to render for components that wrap the current subtree. */
14
15
  children?: React.ReactNode;
16
+ /** Whether this node is currently rendered in editor mode. */
17
+ editMode?: boolean;
18
+ /** Node-level pointer/click handlers for custom components that render their own pickable objects. */
19
+ nodeInteractionHandlers?: NodeInteractionHandlers;
15
20
  /** Current node local position for wrapper components. */
16
21
  position?: [number, number, number];
17
22
  /** Current node local rotation in radians for wrapper components. */
18
23
  rotation?: [number, number, number];
19
24
  /** Current node local scale for wrapper components. */
20
25
  scale?: [number, number, number];
26
+ /** Current node world position. Components that create world-space resources should prefer this. */
27
+ worldPosition?: [number, number, number];
28
+ /** Public asset URL prefix, such as a Next.js basePath. */
29
+ basePath?: string;
21
30
  }
31
+ export type NodeInteractionHandlers = {
32
+ onClick?: (event: ThreeEvent<PointerEvent>) => void;
33
+ onPointerDown?: (event: ThreeEvent<PointerEvent>) => void;
34
+ onPointerMove?: (event: ThreeEvent<PointerEvent>) => void;
35
+ onPointerUp?: (event: ThreeEvent<PointerEvent>) => void;
36
+ };
22
37
  export interface Component {
23
38
  name: string;
24
39
  /** Set when this component occupies a single slot on a node. Use a string to share a slot across component types. */
@@ -0,0 +1,3 @@
1
+ import type { Component } from './ComponentRegistry';
2
+ declare const PrefabRefComponent: Component;
3
+ export default PrefabRefComponent;
@@ -0,0 +1,72 @@
1
+ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
2
+ function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
3
+ return new (P || (P = Promise))(function (resolve, reject) {
4
+ function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
5
+ function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
6
+ function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
7
+ step((generator = generator.apply(thisArg, _arguments || [])).next());
8
+ });
9
+ };
10
+ import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-runtime";
11
+ import { useEffect, useState } from 'react';
12
+ import PrefabRoot from '../PrefabRoot';
13
+ import { useEditorRef } from '../PrefabEditor';
14
+ import { withBasePath } from '../utils';
15
+ import { base, colors } from '../styles';
16
+ import { FieldGroup, Label } from './Input';
17
+ function PrefabRefView({ properties, children, basePath = '' }) {
18
+ var _a;
19
+ const [loadedPrefab, setLoadedPrefab] = useState(null);
20
+ const url = (_a = properties.url) !== null && _a !== void 0 ? _a : '';
21
+ const resolvedUrl = url ? withBasePath(basePath, url) : '';
22
+ useEffect(() => {
23
+ if (!resolvedUrl)
24
+ return;
25
+ let cancelled = false;
26
+ fetch(resolvedUrl)
27
+ .then(r => r.json())
28
+ .then(data => { if (!cancelled)
29
+ setLoadedPrefab(data); })
30
+ .catch(err => console.warn('[PrefabRef] Failed to load:', resolvedUrl, err));
31
+ return () => { cancelled = true; };
32
+ }, [resolvedUrl]);
33
+ return (_jsxs(_Fragment, { children: [loadedPrefab && _jsx(PrefabRoot, { data: loadedPrefab, editMode: false, basePath: basePath }), children] }));
34
+ }
35
+ function PrefabRefEditor({ node, component, onUpdate, basePath = '', }) {
36
+ var _a, _b;
37
+ const url = (_b = (_a = component.properties) === null || _a === void 0 ? void 0 : _a.url) !== null && _b !== void 0 ? _b : '';
38
+ const [manifest, setManifest] = useState([]);
39
+ const [unpacking, setUnpacking] = useState(false);
40
+ const editor = useEditorRef();
41
+ useEffect(() => {
42
+ fetch(withBasePath(basePath, '/prefabs/manifest.json'))
43
+ .then(r => r.json())
44
+ .then(data => setManifest(data))
45
+ .catch(() => setManifest([]));
46
+ }, []);
47
+ const handleUnpack = () => __awaiter(this, void 0, void 0, function* () {
48
+ if (!node || !url)
49
+ return;
50
+ setUnpacking(true);
51
+ try {
52
+ const prefab = yield fetch(withBasePath(basePath, url)).then(r => r.json());
53
+ editor.replaceNode(node.id, prefab.root);
54
+ }
55
+ catch (err) {
56
+ console.error('[PrefabRef] Unpack failed:', err);
57
+ }
58
+ finally {
59
+ setUnpacking(false);
60
+ }
61
+ });
62
+ return (_jsxs(FieldGroup, { children: [_jsxs("div", { children: [_jsx(Label, { children: "Prefab URL" }), _jsx("input", { type: "text", style: Object.assign(Object.assign({}, base.input), { width: '100%', boxSizing: 'border-box', fontFamily: 'monospace' }), value: url, onChange: e => onUpdate({ url: e.target.value }), placeholder: "/prefabs/my-prefab.json" }), manifest.length > 0 && (_jsxs("select", { style: Object.assign(Object.assign({}, base.input), { width: '100%', marginTop: 4, background: colors.bgInput, boxSizing: 'border-box' }), value: url, onChange: e => onUpdate({ url: e.target.value }), children: [_jsx("option", { value: "", children: "\u2014 pick from manifest \u2014" }), manifest.map(entry => (_jsx("option", { value: entry, children: entry.replace(/^.*\//, '') }, entry)))] }))] }), _jsx("button", { type: "button", style: Object.assign(Object.assign({}, base.btn), { width: '100%', opacity: unpacking || !url ? 0.5 : 1 }), disabled: unpacking || !url, onClick: handleUnpack, children: unpacking ? 'Unpacking…' : 'Unpack' })] }));
63
+ }
64
+ const PrefabRefComponent = {
65
+ name: 'PrefabRef',
66
+ Editor: PrefabRefEditor,
67
+ View: PrefabRefView,
68
+ defaultProperties: {
69
+ url: '',
70
+ },
71
+ };
72
+ export default PrefabRefComponent;
@@ -1,9 +1,8 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
2
  import { ColorField, FieldGroup, NumberField, SelectField, StringField } from "./Input";
3
3
  import { Text } from 'three-text/three/react';
4
- import { useRef, useState, useCallback } from 'react';
5
- // Initialize HarfBuzz path for font shaping
6
- Text.setHarfBuzzPath('/fonts/hb.wasm');
4
+ import { useRef, useState, useCallback, useEffect } from 'react';
5
+ import { withBasePath } from "../utils";
7
6
  function TextComponentEditor({ component, onUpdate, }) {
8
7
  return (_jsxs(FieldGroup, { children: [_jsx(StringField, { name: "text", label: "Text", values: component.properties, onChange: onUpdate, placeholder: "Enter text..." }), _jsx(ColorField, { name: "color", label: "Color", values: component.properties, onChange: onUpdate }), _jsx(StringField, { name: "font", label: "Font", values: component.properties, onChange: onUpdate, placeholder: "/fonts/NotoSans-Regular.ttf" }), _jsx(NumberField, { name: "size", label: "Size", values: component.properties, onChange: onUpdate, min: 0.01, step: 0.1 }), _jsx(NumberField, { name: "depth", label: "Depth", values: component.properties, onChange: onUpdate, min: 0, step: 0.1 }), _jsx(NumberField, { name: "width", label: "Width", values: component.properties, onChange: onUpdate, min: 0, step: 0.5 }), _jsx(SelectField, { name: "align", label: "Align", values: component.properties, onChange: onUpdate, options: [
9
8
  { value: 'left', label: 'Left' },
@@ -11,11 +10,15 @@ function TextComponentEditor({ component, onUpdate, }) {
11
10
  { value: 'right', label: 'Right' },
12
11
  ] })] }));
13
12
  }
14
- function TextComponentView({ properties, children }) {
13
+ function TextComponentView({ properties, children, basePath = "" }) {
15
14
  const { text = '', font, size, depth, width, align, color } = properties;
16
15
  const textContent = String(text || '');
16
+ const resolvedFont = font ? withBasePath(basePath, font) : font;
17
17
  const meshRef = useRef(null);
18
18
  const [offset, setOffset] = useState([0, 0, 0]);
19
+ useEffect(() => {
20
+ Text.setHarfBuzzPath(withBasePath(basePath, '/fonts/hb.wasm'));
21
+ }, [basePath]);
19
22
  const handleLoad = useCallback((_geometry, info) => {
20
23
  if (info === null || info === void 0 ? void 0 : info.planeBounds) {
21
24
  const bounds = info.planeBounds;
@@ -37,7 +40,7 @@ function TextComponentView({ properties, children }) {
37
40
  }, [align]);
38
41
  if (!textContent)
39
42
  return null;
40
- return (_jsxs("group", { position: offset, children: [_jsx(Text, { ref: meshRef, font: font, size: size, depth: depth, layout: { align, width }, color: color, onLoad: handleLoad, children: textContent }), children] }));
43
+ return (_jsxs("group", { position: offset, children: [_jsx(Text, { ref: meshRef, font: resolvedFont, size: size, depth: depth, layout: { align, width }, color: color, onLoad: handleLoad, children: textContent }), children] }));
41
44
  }
42
45
  const TextComponent = {
43
46
  name: 'Text',
@@ -1,5 +1,6 @@
1
1
  // biome-ignore assist/source/organizeImports: <in order of display in the editor>
2
2
  import TransformComponent from "./TransformComponent";
3
+ import PrefabRefComponent from "./PrefabRefComponent";
3
4
  import GeometryComponent from "./GeometryComponent";
4
5
  import BufferGeometryComponent from "./BufferGeometryComponent";
5
6
  import ModelComponent from "./ModelComponent";
@@ -35,4 +36,5 @@ export const builtinComponents = [
35
36
  CameraComponent,
36
37
  SoundComponent,
37
38
  DataComponent,
39
+ PrefabRefComponent,
38
40
  ];
@@ -6,9 +6,12 @@ export interface DecomposeModelOptions {
6
6
  idPrefix?: string;
7
7
  /** Include invisible Three objects in the generated prefab tree. */
8
8
  includeInvisible?: boolean;
9
+ /** Create CrashcatPhysics components from Blender-style mesh name suffixes. */
10
+ inferCollisionMeshes?: boolean;
9
11
  /** Return a serializable texture ref for embedded or externally loaded textures. */
10
12
  getTexturePath?: (texture: Texture, usage: 'map' | 'normalMap') => string | null | undefined;
11
13
  }
14
+ export declare function hasCollisionMeshConventions(object: Object3D, enabled?: boolean): boolean;
12
15
  /**
13
16
  * Converts a live Three object hierarchy into prefab JSON nodes made from
14
17
  * Transform, BufferGeometry, and Material components.
@@ -52,6 +52,27 @@ function serializeGeometry(geometry) {
52
52
  computeVertexNormals: normals.length === 0,
53
53
  };
54
54
  }
55
+ function getCollisionMeshConvention(name, enabled) {
56
+ if (!enabled)
57
+ return null;
58
+ const match = name.match(/^(.*)_(colonly|col)(?:\.\d+)?$/i);
59
+ if (!match)
60
+ return null;
61
+ const [, baseName, suffix] = match;
62
+ return {
63
+ displayName: baseName || name,
64
+ renderMesh: suffix.toLowerCase() === 'col',
65
+ };
66
+ }
67
+ export function hasCollisionMeshConventions(object, enabled = true) {
68
+ let hasConvention = false;
69
+ object.traverse(child => {
70
+ if (hasConvention)
71
+ return;
72
+ hasConvention = child instanceof Mesh && getCollisionMeshConvention(child.name, enabled) != null;
73
+ });
74
+ return hasConvention;
75
+ }
55
76
  function getSideName(side) {
56
77
  if (side === BackSide)
57
78
  return 'BackSide';
@@ -138,17 +159,19 @@ function createTransformComponent(object) {
138
159
  },
139
160
  };
140
161
  }
141
- function createNode(object, idPrefix, children = [], components = {}) {
162
+ function createNode(object, idPrefix, children = [], components = {}, options = {}) {
163
+ var _a, _b;
142
164
  return {
143
165
  id: createId(idPrefix),
144
- name: object.name || object.type,
145
- hidden: object.visible === false ? true : undefined,
166
+ name: ((_a = options.name) !== null && _a !== void 0 ? _a : object.name) || object.type,
167
+ hidden: (_b = options.hidden) !== null && _b !== void 0 ? _b : (object.visible === false ? true : undefined),
146
168
  components: Object.assign({ transform: createTransformComponent(object) }, components),
147
169
  children,
148
170
  };
149
171
  }
150
172
  function decomposeObject(object, options) {
151
- if (!options.includeInvisible && !object.visible)
173
+ const collisionMesh = getCollisionMeshConvention(object.name, options.inferCollisionMeshes);
174
+ if (!options.includeInvisible && !object.visible && !collisionMesh)
152
175
  return null;
153
176
  const childNodes = object.children
154
177
  .map(child => decomposeObject(child, options))
@@ -161,20 +184,30 @@ function decomposeObject(object, options) {
161
184
  result[part.key] = serializeMaterial(part.material, part.attach, options);
162
185
  return result;
163
186
  }, {});
164
- return createNode(object, options.idPrefix, childNodes, Object.assign({ geometry: {
187
+ return createNode(object, options.idPrefix, childNodes, Object.assign(Object.assign({ geometry: {
165
188
  type: 'BufferGeometry',
166
- properties: Object.assign(Object.assign({}, serializeGeometry(object.geometry)), { visible: object.visible, castShadow: object.castShadow, receiveShadow: object.receiveShadow }),
167
- } }, materialComponents));
189
+ properties: Object.assign(Object.assign({}, serializeGeometry(object.geometry)), { visible: collisionMesh ? collisionMesh.renderMesh : object.visible, castShadow: object.castShadow, receiveShadow: object.receiveShadow }),
190
+ } }, materialComponents), (collisionMesh ? {
191
+ crashcatPhysics: {
192
+ type: 'CrashcatPhysics',
193
+ properties: {
194
+ type: 'fixed',
195
+ colliders: 'trimesh',
196
+ sensor: false,
197
+ },
198
+ },
199
+ } : null)), collisionMesh ? { name: collisionMesh.displayName, hidden: false } : undefined);
168
200
  }
169
201
  /**
170
202
  * Converts a live Three object hierarchy into prefab JSON nodes made from
171
203
  * Transform, BufferGeometry, and Material components.
172
204
  */
173
205
  export function decomposeModelToPrefabNodes(object, options = {}) {
174
- var _a, _b, _c, _d, _e;
175
- return (_d = decomposeObject(object, {
206
+ var _a, _b, _c, _d, _e, _f;
207
+ return (_e = decomposeObject(object, {
176
208
  idPrefix: (_a = options.idPrefix) !== null && _a !== void 0 ? _a : 'model',
177
209
  includeInvisible: (_b = options.includeInvisible) !== null && _b !== void 0 ? _b : false,
178
- getTexturePath: (_c = options.getTexturePath) !== null && _c !== void 0 ? _c : (() => undefined),
179
- })) !== null && _d !== void 0 ? _d : createNode(object, (_e = options.idPrefix) !== null && _e !== void 0 ? _e : 'model');
210
+ inferCollisionMeshes: (_c = options.inferCollisionMeshes) !== null && _c !== void 0 ? _c : true,
211
+ getTexturePath: (_d = options.getTexturePath) !== null && _d !== void 0 ? _d : (() => undefined),
212
+ })) !== null && _e !== void 0 ? _e : createNode(object, (_f = options.idPrefix) !== null && _f !== void 0 ? _f : 'model');
180
213
  }
@@ -1,5 +1,5 @@
1
- import type { ComponentData, GameObject, Prefab } from './types';
2
- export type PrefabNodeRecord = Omit<GameObject, 'children'>;
1
+ import type { ComponentData, GameObject, Prefab } from "./types";
2
+ export type PrefabNodeRecord = Omit<GameObject, "children">;
3
3
  export type PrefabAssetRefCounts = Record<string, number>;
4
4
  export interface PrefabState {
5
5
  prefabId?: string;
@@ -24,12 +24,13 @@ export declare function createEmptyNode(name?: string): GameObject;
24
24
  export declare function createEmptyPrefab(): Prefab;
25
25
  export declare function createModelNode(filename: string, name?: string): GameObject;
26
26
  export declare function createImageNode(texturePath: string, name?: string): GameObject;
27
+ export declare function createPackedPrefabNode(url: string): GameObject;
27
28
  export declare function normalizePrefab(prefab: Prefab): PrefabState;
28
29
  export declare function createPrefabPatch(state: PrefabState, patch: Partial<PrefabState>, nextAssetRefCounts?: PrefabAssetRefCounts): Partial<PrefabState>;
29
- export declare function denormalizePrefab(state: Pick<PrefabState, 'prefabId' | 'prefabName' | 'rootId' | 'nodesById' | 'childIdsById'>): Prefab;
30
+ export declare function denormalizePrefab(state: Pick<PrefabState, "prefabId" | "prefabName" | "rootId" | "nodesById" | "childIdsById">): Prefab;
30
31
  export declare function collectSubtreeIds(id: string, childIdsById: Record<string, string[]>): string[];
31
32
  export declare function insertSubtree(node: GameObject, parentId: string | null, nodesById: Record<string, PrefabNodeRecord>, childIdsById: Record<string, string[]>, parentIdById: Record<string, string | null>): void;
32
- export declare function cloneSubtree(id: string, parentId: string | null, source: Pick<PrefabState, 'nodesById' | 'childIdsById'>, nodesById: Record<string, PrefabNodeRecord>, childIdsById: Record<string, string[]>, parentIdById: Record<string, string | null>): string | null;
33
+ export declare function cloneSubtree(id: string, parentId: string | null, source: Pick<PrefabState, "nodesById" | "childIdsById">, nodesById: Record<string, PrefabNodeRecord>, childIdsById: Record<string, string[]>, parentIdById: Record<string, string | null>): string | null;
33
34
  export declare function isDescendant(id: string, potentialAncestorId: string, parentIdById: Record<string, string | null>): boolean;
34
35
  export declare function updateAssetRefsForNodeChange(assetRefCounts: PrefabAssetRefCounts, currentNode: PrefabNodeRecord, nextNode: PrefabNodeRecord): PrefabAssetRefCounts;
35
36
  export declare function collectSubtreeAssetRefs(node: GameObject): string[];
@@ -9,12 +9,12 @@ var __rest = (this && this.__rest) || function (s, e) {
9
9
  }
10
10
  return t;
11
11
  };
12
- import { getComponentAssetRefs, getComponentDef } from './components/ComponentRegistry';
12
+ import { getComponentAssetRefs, getComponentDef, } from "./components/ComponentRegistry";
13
13
  function clonePrefabValue(value) {
14
14
  if (Array.isArray(value)) {
15
- return value.map(item => clonePrefabValue(item));
15
+ return value.map((item) => clonePrefabValue(item));
16
16
  }
17
- if (value && typeof value === 'object') {
17
+ if (value && typeof value === "object") {
18
18
  const clone = {};
19
19
  Object.entries(value).forEach(([key, entry]) => {
20
20
  clone[key] = clonePrefabValue(entry);
@@ -25,7 +25,7 @@ function clonePrefabValue(value) {
25
25
  }
26
26
  function createComponentMap(components) {
27
27
  const componentMap = {
28
- transform: createComponentData('Transform'),
28
+ transform: createComponentData("Transform"),
29
29
  };
30
30
  Object.entries(components).forEach(([key, component]) => {
31
31
  componentMap[key] = createComponentData(component.type, component.properties);
@@ -33,10 +33,10 @@ function createComponentMap(components) {
33
33
  return componentMap;
34
34
  }
35
35
  function getNodeNameFromPath(path, name) {
36
- return name !== null && name !== void 0 ? name : path.replace(/^.*[\/]/, '').replace(/\.[^.]+$/, '');
36
+ return name !== null && name !== void 0 ? name : path.replace(/^.*[\/]/, "").replace(/\.[^.]+$/, "");
37
37
  }
38
38
  function getAssetManifestKey(assetRefCounts) {
39
- return Object.keys(assetRefCounts).sort().join('|');
39
+ return Object.keys(assetRefCounts).sort().join("|");
40
40
  }
41
41
  function sameStringArrays(left, right) {
42
42
  if (left.length !== right.length)
@@ -46,7 +46,7 @@ function sameStringArrays(left, right) {
46
46
  function getAssetRefs(node) {
47
47
  var _a;
48
48
  const refs = [];
49
- Object.values((_a = node === null || node === void 0 ? void 0 : node.components) !== null && _a !== void 0 ? _a : {}).forEach(component => {
49
+ Object.values((_a = node === null || node === void 0 ? void 0 : node.components) !== null && _a !== void 0 ? _a : {}).forEach((component) => {
50
50
  var _a;
51
51
  if (!(component === null || component === void 0 ? void 0 : component.type))
52
52
  return;
@@ -57,13 +57,13 @@ function getAssetRefs(node) {
57
57
  return refs.sort();
58
58
  }
59
59
  function addAssetRefs(assetRefCounts, refs) {
60
- refs.forEach(ref => {
60
+ refs.forEach((ref) => {
61
61
  var _a;
62
62
  assetRefCounts[ref] = ((_a = assetRefCounts[ref]) !== null && _a !== void 0 ? _a : 0) + 1;
63
63
  });
64
64
  }
65
65
  function removeAssetRefs(assetRefCounts, refs) {
66
- refs.forEach(ref => {
66
+ refs.forEach((ref) => {
67
67
  var _a;
68
68
  const nextCount = ((_a = assetRefCounts[ref]) !== null && _a !== void 0 ? _a : 0) - 1;
69
69
  if (nextCount > 0) {
@@ -75,13 +75,15 @@ function removeAssetRefs(assetRefCounts, refs) {
75
75
  }
76
76
  function createAssetRefCounts(nodesById) {
77
77
  const assetRefCounts = {};
78
- Object.values(nodesById).forEach(node => addAssetRefs(assetRefCounts, getAssetRefs(node)));
78
+ Object.values(nodesById).forEach((node) => {
79
+ addAssetRefs(assetRefCounts, getAssetRefs(node));
80
+ });
79
81
  return assetRefCounts;
80
82
  }
81
83
  function denormalizeNode(id, nodesById, childIdsById) {
82
84
  var _a;
83
85
  const node = nodesById[id];
84
- return Object.assign(Object.assign({}, node), { children: ((_a = childIdsById[id]) !== null && _a !== void 0 ? _a : []).map(childId => denormalizeNode(childId, nodesById, childIdsById)) });
86
+ return Object.assign(Object.assign({}, node), { children: ((_a = childIdsById[id]) !== null && _a !== void 0 ? _a : []).map((childId) => denormalizeNode(childId, nodesById, childIdsById)) });
85
87
  }
86
88
  export function createDefaultComponentProperties(type) {
87
89
  var _a, _b;
@@ -90,32 +92,34 @@ export function createDefaultComponentProperties(type) {
90
92
  export function createComponentData(type, properties) {
91
93
  return {
92
94
  type,
93
- properties: properties ? clonePrefabValue(properties) : createDefaultComponentProperties(type),
95
+ properties: properties
96
+ ? clonePrefabValue(properties)
97
+ : createDefaultComponentProperties(type),
94
98
  };
95
99
  }
96
100
  export function createNode(name, components = {}, options) {
97
101
  var _a;
98
102
  return Object.assign({ id: (_a = options === null || options === void 0 ? void 0 : options.id) !== null && _a !== void 0 ? _a : crypto.randomUUID(), name, components: createComponentMap(components) }, ((options === null || options === void 0 ? void 0 : options.children) ? { children: options.children } : null));
99
103
  }
100
- export function createEmptyNode(name = 'New Node') {
104
+ export function createEmptyNode(name = "New Node") {
101
105
  return createNode(name);
102
106
  }
103
107
  export function createEmptyPrefab() {
104
108
  return {
105
109
  id: crypto.randomUUID(),
106
- name: 'New Prefab',
107
- root: createNode('Root', {}, { id: crypto.randomUUID(), children: [] }),
110
+ name: "New Prefab",
111
+ root: createNode("Root", {}, { id: crypto.randomUUID(), children: [] }),
108
112
  };
109
113
  }
110
114
  export function createModelNode(filename, name) {
111
115
  return createNode(getNodeNameFromPath(filename, name), {
112
116
  model: {
113
- type: 'Model',
117
+ type: "Model",
114
118
  properties: {
115
119
  filename,
116
120
  instanced: false,
117
121
  repeat: false,
118
- repeatAxes: [{ axis: 'x', count: 1, offset: 1 }],
122
+ repeatAxes: [{ axis: "x", count: 1, offset: 1 }],
119
123
  },
120
124
  },
121
125
  });
@@ -123,12 +127,20 @@ export function createModelNode(filename, name) {
123
127
  export function createImageNode(texturePath, name) {
124
128
  return createNode(getNodeNameFromPath(texturePath, name), {
125
129
  geometry: {
126
- type: 'Geometry',
127
- properties: { geometryType: 'plane', args: [1, 1] },
130
+ type: "Geometry",
131
+ properties: { geometryType: "plane", args: [1, 1] },
128
132
  },
129
133
  material: {
130
- type: 'Material',
131
- properties: { color: '#ffffff', texture: texturePath },
134
+ type: "Material",
135
+ properties: { color: "#ffffff", texture: texturePath },
136
+ },
137
+ });
138
+ }
139
+ export function createPackedPrefabNode(url) {
140
+ return createNode("Packed Prefab", {
141
+ prefabref: {
142
+ type: "PrefabRef",
143
+ properties: { url },
132
144
  },
133
145
  });
134
146
  }
@@ -151,10 +163,12 @@ export function normalizePrefab(prefab) {
151
163
  }
152
164
  export function createPrefabPatch(state, patch, nextAssetRefCounts = state.assetRefCounts) {
153
165
  const assetRefsChanged = nextAssetRefCounts !== state.assetRefCounts;
154
- return Object.assign(Object.assign({}, patch), (assetRefsChanged ? {
155
- assetRefCounts: nextAssetRefCounts,
156
- assetManifestKey: getAssetManifestKey(nextAssetRefCounts),
157
- } : null));
166
+ return Object.assign(Object.assign({}, patch), (assetRefsChanged
167
+ ? {
168
+ assetRefCounts: nextAssetRefCounts,
169
+ assetManifestKey: getAssetManifestKey(nextAssetRefCounts),
170
+ }
171
+ : null));
158
172
  }
159
173
  export function denormalizePrefab(state) {
160
174
  return {
@@ -175,9 +189,11 @@ export function insertSubtree(node, parentId, nodesById, childIdsById, parentIdB
175
189
  var _a;
176
190
  const { children } = node, nodeRecord = __rest(node, ["children"]);
177
191
  nodesById[node.id] = nodeRecord;
178
- childIdsById[node.id] = (_a = children === null || children === void 0 ? void 0 : children.map(child => child.id)) !== null && _a !== void 0 ? _a : [];
192
+ childIdsById[node.id] = (_a = children === null || children === void 0 ? void 0 : children.map((child) => child.id)) !== null && _a !== void 0 ? _a : [];
179
193
  parentIdById[node.id] = parentId;
180
- children === null || children === void 0 ? void 0 : children.forEach(child => insertSubtree(child, node.id, nodesById, childIdsById, parentIdById));
194
+ children === null || children === void 0 ? void 0 : children.forEach((child) => {
195
+ insertSubtree(child, node.id, nodesById, childIdsById, parentIdById);
196
+ });
181
197
  }
182
198
  export function cloneSubtree(id, parentId, source, nodesById, childIdsById, parentIdById) {
183
199
  var _a, _b;
@@ -189,7 +205,7 @@ export function cloneSubtree(id, parentId, source, nodesById, childIdsById, pare
189
205
  nodesById[clonedId] = clonedNode;
190
206
  parentIdById[clonedId] = parentId;
191
207
  const clonedChildIds = ((_b = source.childIdsById[id]) !== null && _b !== void 0 ? _b : [])
192
- .map(childId => cloneSubtree(childId, clonedId, source, nodesById, childIdsById, parentIdById))
208
+ .map((childId) => cloneSubtree(childId, clonedId, source, nodesById, childIdsById, parentIdById))
193
209
  .filter((childId) => Boolean(childId));
194
210
  childIdsById[clonedId] = clonedChildIds;
195
211
  return clonedId;
@@ -217,7 +233,9 @@ export function updateAssetRefsForNodeChange(assetRefCounts, currentNode, nextNo
217
233
  export function collectSubtreeAssetRefs(node) {
218
234
  var _a;
219
235
  const refs = getAssetRefs(node);
220
- (_a = node.children) === null || _a === void 0 ? void 0 : _a.forEach(child => refs.push(...collectSubtreeAssetRefs(child)));
236
+ (_a = node.children) === null || _a === void 0 ? void 0 : _a.forEach((child) => {
237
+ refs.push(...collectSubtreeAssetRefs(child));
238
+ });
221
239
  return refs;
222
240
  }
223
241
  export function collectAssetRefsForIds(ids, nodesById) {
@@ -1,5 +1,7 @@
1
1
  import { GameObject, Prefab } from "./types";
2
- import { Matrix4, Object3D, Vector3 } from 'three';
2
+ import { Matrix4, Object3D, Vector3 } from "three";
3
+ export declare function isExternalPath(path: string): boolean;
4
+ export declare function withBasePath(basePath: string | undefined, path: string): string;
3
5
  export interface ExportGLBOptions {
4
6
  filename?: string;
5
7
  }
@@ -7,6 +9,11 @@ export interface ExportGLBOptions {
7
9
  export declare function saveJson(data: Prefab, filename: string): Promise<void>;
8
10
  /** Load scene JSON from a file */
9
11
  export declare function loadJson(): Promise<Prefab | undefined>;
12
+ /** Load scene JSON from a file, also returning the original filename */
13
+ export declare function loadJsonFile(): Promise<{
14
+ prefab: Prefab;
15
+ filename: string;
16
+ } | undefined>;
10
17
  /**
11
18
  * Export a Three.js scene or object to GLB binary data
12
19
  */