@godot-scene-web/scene-graph 0.1.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/LICENSE +21 -0
- package/dist/index.d.ts +188 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +635 -0
- package/dist/index.js.map +1 -0
- package/package.json +41 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Tomás Fox
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,188 @@
|
|
|
1
|
+
import { GodotNode, GodotRect, GodotResourceRefValue, GodotSceneState, GodotVariant } from "@godot-scene-web/core";
|
|
2
|
+
|
|
3
|
+
//#region src/public-types.d.ts
|
|
4
|
+
/**
|
|
5
|
+
* The rect-free fields shared by a structural {@link SceneGraphNode} (the
|
|
6
|
+
* browser-native producer output) and a fully laid-out {@link GodotSceneTreeNode}
|
|
7
|
+
* (the computed rect-engine output). Everything here is derived from the node's
|
|
8
|
+
* own properties and its place in the tree — no rect math. `deriveNodeVisuals`
|
|
9
|
+
* produces it; the rect engine layers `rect`/`renderedRect`/`cumulativeTransform`
|
|
10
|
+
* on top.
|
|
11
|
+
*/
|
|
12
|
+
interface GodotSceneNodeBase {
|
|
13
|
+
path: string;
|
|
14
|
+
name: string;
|
|
15
|
+
type: string;
|
|
16
|
+
parentPath: string | null;
|
|
17
|
+
children: string[];
|
|
18
|
+
source?: GodotNode;
|
|
19
|
+
visible: boolean;
|
|
20
|
+
zIndex: number;
|
|
21
|
+
drawOrder: number;
|
|
22
|
+
zAsRelative: boolean;
|
|
23
|
+
showBehindParent: boolean;
|
|
24
|
+
clipContents: boolean;
|
|
25
|
+
textAlign: "left" | "center" | "right" | "fill";
|
|
26
|
+
textVerticalAlign: "top" | "center" | "bottom" | "fill";
|
|
27
|
+
scale: {
|
|
28
|
+
x: number;
|
|
29
|
+
y: number;
|
|
30
|
+
};
|
|
31
|
+
pivotOffset: {
|
|
32
|
+
x: number;
|
|
33
|
+
y: number;
|
|
34
|
+
};
|
|
35
|
+
properties: Record<string, GodotVariant>;
|
|
36
|
+
resourceRefs: GodotResourceRefValue[];
|
|
37
|
+
textRuns?: GodotTextRunMetric[];
|
|
38
|
+
fontMetadata?: Record<string, unknown>;
|
|
39
|
+
}
|
|
40
|
+
/**
|
|
41
|
+
* A node in the browser-native {@link SceneGraph}: structure + the rect-free
|
|
42
|
+
* visual derivation, with NO computed rect. The browser HTML emitter consumes
|
|
43
|
+
* this directly (the CSS engine resolves geometry); the computed rect engine
|
|
44
|
+
* (`resolveGodotSceneTree`) layers `rect`/`renderedRect` on top to produce a
|
|
45
|
+
* {@link GodotSceneTreeNode}.
|
|
46
|
+
*/
|
|
47
|
+
type SceneGraphNode = GodotSceneNodeBase;
|
|
48
|
+
/**
|
|
49
|
+
* The structural + rect-free render model — the output of `deriveSceneGraph`
|
|
50
|
+
* (`@godot-scene-web/scene-graph`). The browser HTML emitter consumes it as-is;
|
|
51
|
+
* the computed rect engine (`@godot-scene-web/layout`) consumes it as the input
|
|
52
|
+
* to the rect cascade. Neither package depends on `scene-graph`; they exchange
|
|
53
|
+
* this shared type.
|
|
54
|
+
*/
|
|
55
|
+
interface SceneGraph {
|
|
56
|
+
nodes: SceneGraphNode[];
|
|
57
|
+
resourceStatuses: GodotResourceStatus[];
|
|
58
|
+
}
|
|
59
|
+
interface GodotTextRunMetric {
|
|
60
|
+
text: string;
|
|
61
|
+
style?: string;
|
|
62
|
+
fontSize?: number;
|
|
63
|
+
rect: GodotRect;
|
|
64
|
+
}
|
|
65
|
+
type GodotResourceStatusKind = "external-scene" | "resource" | "resource-path";
|
|
66
|
+
type GodotResourceLoadStatus = "ready" | "pending" | "error";
|
|
67
|
+
interface GodotResourceStatus {
|
|
68
|
+
kind: GodotResourceStatusKind;
|
|
69
|
+
status: GodotResourceLoadStatus;
|
|
70
|
+
nodePath?: string;
|
|
71
|
+
path?: string;
|
|
72
|
+
ref?: GodotResourceRefValue;
|
|
73
|
+
message?: string;
|
|
74
|
+
}
|
|
75
|
+
type GodotExternalSceneResolution = GodotSceneState | {
|
|
76
|
+
status: "ready";
|
|
77
|
+
scene: GodotSceneState;
|
|
78
|
+
path?: string;
|
|
79
|
+
} | {
|
|
80
|
+
status: "pending";
|
|
81
|
+
path?: string;
|
|
82
|
+
message?: string;
|
|
83
|
+
} | {
|
|
84
|
+
status: "error";
|
|
85
|
+
path?: string;
|
|
86
|
+
message: string;
|
|
87
|
+
};
|
|
88
|
+
interface GodotExternalSceneResolveContext {
|
|
89
|
+
ref: GodotResourceRefValue;
|
|
90
|
+
node: GodotNode;
|
|
91
|
+
nodePath: string;
|
|
92
|
+
props: Record<string, GodotVariant>;
|
|
93
|
+
}
|
|
94
|
+
/**
|
|
95
|
+
* The structural options consumed by `deriveSceneGraph`
|
|
96
|
+
* (`@godot-scene-web/scene-graph`): node inclusion, prop/type/instance overrides,
|
|
97
|
+
* repeated-node expansion, and external-scene mounting. The rect engine's
|
|
98
|
+
* `GodotLayoutOptions` and the host's render options extend this with their own
|
|
99
|
+
* rect-/resource-specific fields.
|
|
100
|
+
*/
|
|
101
|
+
interface SceneStructureOptions {
|
|
102
|
+
overrideNodeProps?: (node: GodotNode, path: string) => Record<string, GodotVariant> | undefined;
|
|
103
|
+
/**
|
|
104
|
+
* Override a node's reported Godot `type` (e.g. retype a `SpineSprite` to a
|
|
105
|
+
* `TextureRect`). Applied during indexing without mutating the source scene;
|
|
106
|
+
* return `undefined` to keep the authored type.
|
|
107
|
+
*/
|
|
108
|
+
overrideNodeType?: (node: GodotNode, path: string) => string | undefined;
|
|
109
|
+
/**
|
|
110
|
+
* Inject (or replace) the PackedScene `instance` ref on an existing node — a
|
|
111
|
+
* state-driven dynamic mount. Applied during indexing; resolved through the
|
|
112
|
+
* same `resolveExternalScene`/`mountExternalScene` path. Return `undefined` to
|
|
113
|
+
* keep the authored instance (if any).
|
|
114
|
+
*/
|
|
115
|
+
overrideNodeInstance?: (node: GodotNode, path: string) => GodotResourceRefValue | undefined;
|
|
116
|
+
includeNode?: (node: GodotNode, path: string, props: Record<string, GodotVariant>) => boolean;
|
|
117
|
+
mountExternalScene?: (ref: GodotResourceRefValue, node: GodotNode) => GodotSceneState | undefined;
|
|
118
|
+
resolveExternalScene?: (context: GodotExternalSceneResolveContext) => GodotExternalSceneResolution | undefined;
|
|
119
|
+
expandRepeatedNode?: (node: GodotNode, path: string) => GodotNode[] | undefined;
|
|
120
|
+
}
|
|
121
|
+
/**
|
|
122
|
+
* Structural input to {@link deriveNodeVisuals}: a node already flattened by the
|
|
123
|
+
* scene index, carrying its resolved props, scene-tree path, and source node.
|
|
124
|
+
*/
|
|
125
|
+
interface DerivedNodeInput {
|
|
126
|
+
node: GodotNode;
|
|
127
|
+
path: string;
|
|
128
|
+
parentPath: string | null;
|
|
129
|
+
children: string[];
|
|
130
|
+
props: Record<string, GodotVariant>;
|
|
131
|
+
order: number;
|
|
132
|
+
}
|
|
133
|
+
//#endregion
|
|
134
|
+
//#region src/derive.d.ts
|
|
135
|
+
interface SceneGraphNodeMemoEntry {
|
|
136
|
+
props: Record<string, GodotVariant>;
|
|
137
|
+
propsSig: string;
|
|
138
|
+
parentZIndex: number | undefined;
|
|
139
|
+
name: string;
|
|
140
|
+
type: string;
|
|
141
|
+
order: number;
|
|
142
|
+
childrenKey: string;
|
|
143
|
+
zAsRelative: boolean;
|
|
144
|
+
output: SceneGraphNode;
|
|
145
|
+
}
|
|
146
|
+
/**
|
|
147
|
+
* Cross-render per-node derivation cache (one per view, persisted across
|
|
148
|
+
* {@link deriveSceneGraph} calls). Lets an unchanged node reuse its prior
|
|
149
|
+
* {@link SceneGraphNode} object so its identity survives a re-derive — the
|
|
150
|
+
* prerequisite for the html/render memos downstream. Opaque to callers.
|
|
151
|
+
*/
|
|
152
|
+
type SceneGraphNodeMemo = Map<string, SceneGraphNodeMemoEntry>;
|
|
153
|
+
declare function createSceneGraphNodeMemo(): SceneGraphNodeMemo;
|
|
154
|
+
/**
|
|
155
|
+
* Produce the browser-native {@link SceneGraph} from a parsed scene: structurally
|
|
156
|
+
* index the nodes (flatten instances/mounts, apply overrides, expand anchor
|
|
157
|
+
* presets, resolve sibling/draw order), then derive each node's rect-free render
|
|
158
|
+
* fields. This is the single shared producer — the browser HTML emitter consumes
|
|
159
|
+
* the graph directly (CSS resolves geometry), and the computed rect engine
|
|
160
|
+
* (`resolveGodotSceneTree`) consumes it as the input to the rect cascade. Neither
|
|
161
|
+
* the `layout` nor `html` package depends on this one; they exchange the shared
|
|
162
|
+
* `SceneGraph` type (declared in `@godot-scene-web/core`).
|
|
163
|
+
*
|
|
164
|
+
* Pass a {@link SceneGraphNodeMemo} (one per view, reused across calls) to preserve
|
|
165
|
+
* the object identity of nodes whose derived render fields are unchanged across
|
|
166
|
+
* renders — so an overrides-only host change re-derives only the changed nodes.
|
|
167
|
+
*/
|
|
168
|
+
declare function deriveSceneGraph(scene: GodotSceneState, options?: SceneStructureOptions, nodeMemo?: SceneGraphNodeMemo): SceneGraph;
|
|
169
|
+
//#endregion
|
|
170
|
+
//#region src/provenance.d.ts
|
|
171
|
+
declare const SOURCE_SCENE_PATH_ATTRIBUTE = "metadata/godot_scene_web/source_scene_path";
|
|
172
|
+
declare const MOUNTED_INNER_SCENE_PATH_ATTRIBUTE = "metadata/godot_scene_web/mounted_inner_scene_path";
|
|
173
|
+
/** Stamp parser output before publishing it to graph derivation caches. */
|
|
174
|
+
declare function tagSceneNodes(scene: GodotSceneState, resourcePath: string): void;
|
|
175
|
+
//#endregion
|
|
176
|
+
//#region src/visuals.d.ts
|
|
177
|
+
/**
|
|
178
|
+
* Derive a node's rect-free render fields (scale, pivot, z-index, alignment,
|
|
179
|
+
* visibility, draw order, resource refs, …) from its resolved properties and its
|
|
180
|
+
* parent's z-index. Shared by the browser-native producer (`deriveSceneGraph`,
|
|
181
|
+
* which stops here) and the computed rect engine (`makeLayoutNode`, which adds
|
|
182
|
+
* `rect`/`renderedRect`/`cumulativeTransform`), so the two render modes can never
|
|
183
|
+
* drift on these values.
|
|
184
|
+
*/
|
|
185
|
+
declare function deriveNodeVisuals(indexed: DerivedNodeInput, parentZIndex: number | undefined): GodotSceneNodeBase;
|
|
186
|
+
//#endregion
|
|
187
|
+
export { type DerivedNodeInput, type GodotExternalSceneResolution, type GodotExternalSceneResolveContext, type GodotResourceLoadStatus, type GodotResourceStatus, type GodotResourceStatusKind, type GodotSceneNodeBase, type GodotTextRunMetric, MOUNTED_INNER_SCENE_PATH_ATTRIBUTE, SOURCE_SCENE_PATH_ATTRIBUTE, type SceneGraph, type SceneGraphNode, type SceneGraphNodeMemo, type SceneStructureOptions, createSceneGraphNodeMemo, deriveNodeVisuals, deriveSceneGraph, tagSceneNodes };
|
|
188
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","names":[],"sources":["../src/public-types.ts","../src/derive.ts","../src/provenance.ts","../src/visuals.ts"],"mappings":";;;;;AAgBA;;;;;;UAAiB,kBAAA;EACf,IAAA;EACA,IAAA;EACA,IAAA;EACA,UAAA;EACA,QAAA;EACA,MAAA,GAAS,SAAA;EACT,OAAA;EACA,MAAA;EACA,SAAA;EACA,WAAA;EACA,gBAAA;EACA,YAAA;EACA,SAAA;EACA,iBAAA;EACA,KAAA;IAAS,CAAA;IAAW,CAAA;EAAA;EACpB,WAAA;IAAe,CAAA;IAAW,CAAA;EAAA;EAC1B,UAAA,EAAY,MAAA,SAAe,YAAA;EAC3B,YAAA,EAAc,qBAAA;EACd,QAAA,GAAW,kBAAA;EACX,YAAA,GAAe,MAAA;AAAA;;;;;;;;KAUL,cAAA,GAAiB,kBAAkB;AAVxB;AAUvB;;;;AAA+C;AAS/C;AAnBuB,UAmBN,UAAA;EACf,KAAA,EAAO,cAAA;EACP,gBAAA,EAAkB,mBAAmB;AAAA;AAAA,UAGtB,kBAAA;EACf,IAAA;EACA,KAAA;EACA,QAAA;EACA,IAAA,EAAM,SAAS;AAAA;AAAA,KAGL,uBAAA;AAAA,KAKA,uBAAA;AAAA,UAEK,mBAAA;EACf,IAAA,EAAM,uBAAA;EACN,MAAA,EAAQ,uBAAA;EACR,QAAA;EACA,IAAA;EACA,GAAA,GAAM,qBAAA;EACN,OAAA;AAAA;AAAA,KAGU,4BAAA,GACR,eAAA;EACE,MAAA;EAAiB,KAAA,EAAO,eAAe;EAAE,IAAA;AAAA;EACzC,MAAA;EAAmB,IAAA;EAAe,OAAA;AAAA;EAClC,MAAA;EAAiB,IAAA;EAAe,OAAA;AAAA;AAAA,UAErB,gCAAA;EACf,GAAA,EAAK,qBAAA;EACL,IAAA,EAAM,SAAA;EACN,QAAA;EACA,KAAA,EAAO,MAAA,SAAe,YAAA;AAAA;;;;;;;;UAUP,qBAAA;EACf,iBAAA,IACE,IAAA,EAAM,SAAA,EACN,IAAA,aACG,MAAA,SAAe,YAAA;EA3Bb;AAGT;;;;EA8BE,gBAAA,IAAoB,IAAA,EAAM,SAAA,EAAW,IAAA;EA5BjC;;;;;;EAmCJ,oBAAA,IACE,IAAA,EAAM,SAAA,EACN,IAAA,aACG,qBAAA;EACL,WAAA,IACE,IAAA,EAAM,SAAA,EACN,IAAA,UACA,KAAA,EAAO,MAAA,SAAe,YAAA;EAExB,kBAAA,IACE,GAAA,EAAK,qBAAA,EACL,IAAA,EAAM,SAAA,KACH,eAAA;EACL,oBAAA,IACE,OAAA,EAAS,gCAAA,KACN,4BAAA;EACL,kBAAA,IACE,IAAA,EAAM,SAAA,EACN,IAAA,aACG,SAAA;AAAA;AAlDP;;;;AAAA,UAyDiB,gBAAA;EACf,IAAA,EAAM,SAAA;EACN,IAAA;EACA,UAAA;EACA,QAAA;EACA,KAAA,EAAO,MAAA,SAAe,YAAA;EACtB,KAAA;AAAA;;;UC9IQ,uBAAA;EACR,KAAA,EAAO,MAAA,SAAe,YAAA;EACtB,QAAA;EACA,YAAA;EACA,IAAA;EACA,IAAA;EACA,KAAA;EACA,WAAA;EACA,WAAA;EACA,MAAA,EAAQ,cAAA;AAAA;;;;;;;KASE,kBAAA,GAAqB,GAAG,SAAS,uBAAA;AAAA,iBAE7B,wBAAA,CAAA,GAA4B,kBAAkB;;;;;;;;;;;;;;;iBA2B9C,gBAAA,CACd,KAAA,EAAO,eAAA,EACP,OAAA,GAAS,qBAAA,EACT,QAAA,GAAW,kBAAA,GACV,UAAA;;;cC3DU,2BAAA;AAAA,cAEA,kCAAA;AFYb;AAAA,iBETgB,aAAA,CACd,KAAA,EAAO,eAAe,EACtB,YAAA;;;;;AFOF;;;;;;iBGGgB,iBAAA,CACd,OAAA,EAAS,gBAAA,EACT,YAAA,uBACC,kBAAkB"}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,635 @@
|
|
|
1
|
+
import { asBoolean, asNumber, asResourceRef, asString, asVector2, isGodotSceneState } from "@godot-scene-web/core";
|
|
2
|
+
//#region src/provenance.ts
|
|
3
|
+
const SOURCE_SCENE_PATH_ATTRIBUTE = "metadata/godot_scene_web/source_scene_path";
|
|
4
|
+
const MOUNTED_INNER_SCENE_PATH_ATTRIBUTE = "metadata/godot_scene_web/mounted_inner_scene_path";
|
|
5
|
+
/** Stamp parser output before publishing it to graph derivation caches. */
|
|
6
|
+
function tagSceneNodes(scene, resourcePath) {
|
|
7
|
+
for (const node of scene.nodes) {
|
|
8
|
+
const property = node.properties.find((entry) => entry.name === SOURCE_SCENE_PATH_ATTRIBUTE);
|
|
9
|
+
if (property) property.value = resourcePath;
|
|
10
|
+
else node.properties.push({
|
|
11
|
+
name: SOURCE_SCENE_PATH_ATTRIBUTE,
|
|
12
|
+
value: resourcePath
|
|
13
|
+
});
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
//#endregion
|
|
17
|
+
//#region src/scene-index.ts
|
|
18
|
+
const ANCHOR_PRESETS = {
|
|
19
|
+
0: [
|
|
20
|
+
0,
|
|
21
|
+
0,
|
|
22
|
+
0,
|
|
23
|
+
0
|
|
24
|
+
],
|
|
25
|
+
1: [
|
|
26
|
+
1,
|
|
27
|
+
0,
|
|
28
|
+
1,
|
|
29
|
+
0
|
|
30
|
+
],
|
|
31
|
+
2: [
|
|
32
|
+
0,
|
|
33
|
+
1,
|
|
34
|
+
0,
|
|
35
|
+
1
|
|
36
|
+
],
|
|
37
|
+
3: [
|
|
38
|
+
1,
|
|
39
|
+
1,
|
|
40
|
+
1,
|
|
41
|
+
1
|
|
42
|
+
],
|
|
43
|
+
4: [
|
|
44
|
+
0,
|
|
45
|
+
.5,
|
|
46
|
+
0,
|
|
47
|
+
.5
|
|
48
|
+
],
|
|
49
|
+
5: [
|
|
50
|
+
.5,
|
|
51
|
+
0,
|
|
52
|
+
.5,
|
|
53
|
+
0
|
|
54
|
+
],
|
|
55
|
+
6: [
|
|
56
|
+
1,
|
|
57
|
+
.5,
|
|
58
|
+
1,
|
|
59
|
+
.5
|
|
60
|
+
],
|
|
61
|
+
7: [
|
|
62
|
+
.5,
|
|
63
|
+
1,
|
|
64
|
+
.5,
|
|
65
|
+
1
|
|
66
|
+
],
|
|
67
|
+
8: [
|
|
68
|
+
.5,
|
|
69
|
+
.5,
|
|
70
|
+
.5,
|
|
71
|
+
.5
|
|
72
|
+
],
|
|
73
|
+
9: [
|
|
74
|
+
0,
|
|
75
|
+
0,
|
|
76
|
+
0,
|
|
77
|
+
1
|
|
78
|
+
],
|
|
79
|
+
10: [
|
|
80
|
+
0,
|
|
81
|
+
0,
|
|
82
|
+
1,
|
|
83
|
+
0
|
|
84
|
+
],
|
|
85
|
+
11: [
|
|
86
|
+
1,
|
|
87
|
+
0,
|
|
88
|
+
1,
|
|
89
|
+
1
|
|
90
|
+
],
|
|
91
|
+
12: [
|
|
92
|
+
0,
|
|
93
|
+
1,
|
|
94
|
+
1,
|
|
95
|
+
1
|
|
96
|
+
],
|
|
97
|
+
13: [
|
|
98
|
+
.5,
|
|
99
|
+
0,
|
|
100
|
+
.5,
|
|
101
|
+
1
|
|
102
|
+
],
|
|
103
|
+
14: [
|
|
104
|
+
0,
|
|
105
|
+
.5,
|
|
106
|
+
1,
|
|
107
|
+
.5
|
|
108
|
+
],
|
|
109
|
+
15: [
|
|
110
|
+
0,
|
|
111
|
+
0,
|
|
112
|
+
1,
|
|
113
|
+
1
|
|
114
|
+
]
|
|
115
|
+
};
|
|
116
|
+
const EXPLICIT_ANCHOR_KEYS = [
|
|
117
|
+
"anchor_left",
|
|
118
|
+
"anchor_top",
|
|
119
|
+
"anchor_right",
|
|
120
|
+
"anchor_bottom"
|
|
121
|
+
];
|
|
122
|
+
/**
|
|
123
|
+
* Expand `anchors_preset` into explicit `anchor_*` props when the scene did not
|
|
124
|
+
* already serialize them. Explicit anchors stay authoritative (a preset is only a
|
|
125
|
+
* convenience the editor resolves to anchors), so a node carrying both is left
|
|
126
|
+
* untouched.
|
|
127
|
+
*/
|
|
128
|
+
function applyAnchorPreset(props) {
|
|
129
|
+
const preset = asNumber(props.anchors_preset);
|
|
130
|
+
if (preset === void 0) return props;
|
|
131
|
+
const anchors = ANCHOR_PRESETS[preset];
|
|
132
|
+
if (!anchors || EXPLICIT_ANCHOR_KEYS.some((key) => props[key] !== void 0)) return props;
|
|
133
|
+
return {
|
|
134
|
+
...props,
|
|
135
|
+
anchor_left: anchors[0],
|
|
136
|
+
anchor_top: anchors[1],
|
|
137
|
+
anchor_right: anchors[2],
|
|
138
|
+
anchor_bottom: anchors[3]
|
|
139
|
+
};
|
|
140
|
+
}
|
|
141
|
+
const sceneNodesCache = /* @__PURE__ */ new WeakMap();
|
|
142
|
+
function sceneNodesFromState(state) {
|
|
143
|
+
const cached = sceneNodesCache.get(state);
|
|
144
|
+
if (cached) return cached;
|
|
145
|
+
const nodes = state.nodes.map((node) => {
|
|
146
|
+
const properties = Object.fromEntries(node.properties.map((property) => [property.name, property.value]));
|
|
147
|
+
const normalizedParent = node.parent?.startsWith("./") ? node.parent.slice(2) : node.parent;
|
|
148
|
+
const parent = node.index === 0 && (normalizedParent === "." || normalizedParent === "") ? void 0 : normalizedParent;
|
|
149
|
+
return {
|
|
150
|
+
name: node.name,
|
|
151
|
+
type: node.type,
|
|
152
|
+
parent,
|
|
153
|
+
instance: node.instance,
|
|
154
|
+
attributes: {
|
|
155
|
+
...node.owner !== void 0 ? { owner: node.owner } : {},
|
|
156
|
+
...node.instancePlaceholder !== void 0 ? { instance_placeholder: node.instancePlaceholder } : {},
|
|
157
|
+
...node.siblingIndex !== void 0 ? { index: node.siblingIndex } : {},
|
|
158
|
+
...node.groups.length > 0 ? { groups: node.groups } : {}
|
|
159
|
+
},
|
|
160
|
+
properties,
|
|
161
|
+
propertyEntries: node.properties.map((property) => ({ ...property }))
|
|
162
|
+
};
|
|
163
|
+
});
|
|
164
|
+
sceneNodesCache.set(state, nodes);
|
|
165
|
+
return nodes;
|
|
166
|
+
}
|
|
167
|
+
function indexSceneNodes(sceneNodes, options) {
|
|
168
|
+
const result = [];
|
|
169
|
+
const resourceStatuses = [];
|
|
170
|
+
let order = 0;
|
|
171
|
+
const nextOrder = () => order++;
|
|
172
|
+
const sourceInfos = sourceNodeInfos(sceneNodes, options);
|
|
173
|
+
const mountedScenes = /* @__PURE__ */ new Map();
|
|
174
|
+
const blockedMountRoots = /* @__PURE__ */ new Set();
|
|
175
|
+
for (const sourceNode of sceneNodes) {
|
|
176
|
+
const path = godotNodeScenePath(sourceNode);
|
|
177
|
+
const sourceInfo = sourceInfos.get(path);
|
|
178
|
+
const instance = effectiveNodeInstance(sourceNode, path, options);
|
|
179
|
+
if (!sourceInfo || sourceInfo.omitted || !instance) continue;
|
|
180
|
+
const resolved = resolveMountedScene(instance, sourceNode, path, sourceInfo.props, sourceInfo.effectivelyVisible, options, resourceStatuses);
|
|
181
|
+
if (resolved.blockedSubtree) blockedMountRoots.add(path);
|
|
182
|
+
if (resolved.scene?.nodes.length) mountedScenes.set(path, sceneNodesFromState(resolved.scene));
|
|
183
|
+
}
|
|
184
|
+
const mountedRootPaths = [...mountedScenes.keys()];
|
|
185
|
+
const overrideNodes = /* @__PURE__ */ new Map();
|
|
186
|
+
for (const sourceNode of sceneNodes) {
|
|
187
|
+
const path = godotNodeScenePath(sourceNode);
|
|
188
|
+
const sourceInfo = sourceInfos.get(path);
|
|
189
|
+
if (!sourceInfo || sourceInfo.omitted || isBlocked(path, blockedMountRoots)) continue;
|
|
190
|
+
if (mountedRootPaths.some((rootPath) => path.startsWith(`${rootPath}/`))) overrideNodes.set(path, sourceNode);
|
|
191
|
+
}
|
|
192
|
+
const consumedOverrides = /* @__PURE__ */ new Set();
|
|
193
|
+
for (const sourceNode of sceneNodes) {
|
|
194
|
+
const path = godotNodeScenePath(sourceNode);
|
|
195
|
+
const sourceInfo = sourceInfos.get(path);
|
|
196
|
+
if (!sourceInfo || sourceInfo.omitted || isBlocked(path, blockedMountRoots)) continue;
|
|
197
|
+
if (overrideNodes.has(path)) continue;
|
|
198
|
+
const mountedScene = mountedScenes.get(path);
|
|
199
|
+
if (mountedScene) {
|
|
200
|
+
addMountedSceneNodes(result, sourceNode, path, mountedScene, overrideNodes, consumedOverrides, options, resourceStatuses, nextOrder, new Set([resourceRefKey(sourceNode.instance)]), sourceInfo.effectivelyVisible);
|
|
201
|
+
continue;
|
|
202
|
+
}
|
|
203
|
+
addIndexedNode(result, sourceNode, options, nextOrder);
|
|
204
|
+
}
|
|
205
|
+
for (const [path, overrideNode] of overrideNodes) if (!consumedOverrides.has(path)) addIndexedNode(result, overrideNode, options, nextOrder);
|
|
206
|
+
const byPath = new Map(result.map((node) => [node.path, node]));
|
|
207
|
+
for (const node of result) if (node.parentPath && byPath.has(node.parentPath)) byPath.get(node.parentPath)?.children.push(node.path);
|
|
208
|
+
reorderChildrenByIndex(result, byPath);
|
|
209
|
+
reassignDrawOrder(result, byPath);
|
|
210
|
+
return {
|
|
211
|
+
nodes: result,
|
|
212
|
+
resourceStatuses
|
|
213
|
+
};
|
|
214
|
+
}
|
|
215
|
+
/**
|
|
216
|
+
* Godot appends children in scene order, then relocates any node carrying an
|
|
217
|
+
* explicit `index` to that position among its siblings (`SceneState::instantiate`
|
|
218
|
+
* -> `Node::move_child`). Moves are applied in scene order, each against the live
|
|
219
|
+
* sibling list, mirroring the engine. Nodes without `index` keep scene order.
|
|
220
|
+
*/
|
|
221
|
+
function reorderChildrenByIndex(result, byPath) {
|
|
222
|
+
for (const node of result) {
|
|
223
|
+
if (node.children.length < 2) continue;
|
|
224
|
+
const moves = node.children.map((childPath) => ({
|
|
225
|
+
path: childPath,
|
|
226
|
+
index: asNumber(byPath.get(childPath)?.node.attributes.index)
|
|
227
|
+
})).filter((move) => move.index !== void 0 && move.index >= 0);
|
|
228
|
+
for (const move of moves) {
|
|
229
|
+
const from = node.children.indexOf(move.path);
|
|
230
|
+
if (from < 0) continue;
|
|
231
|
+
node.children.splice(from, 1);
|
|
232
|
+
node.children.splice(Math.min(move.index, node.children.length), 0, move.path);
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
/**
|
|
237
|
+
* Re-derive `order` (draw order) from the resolved child lists via a pre-order
|
|
238
|
+
* walk so z-stacking and HTML child order follow the final sibling order. When no
|
|
239
|
+
* `index` reordering happened this reproduces the scene-order counter exactly,
|
|
240
|
+
* because a well-formed scene already lists nodes in pre-order.
|
|
241
|
+
*/
|
|
242
|
+
function reassignDrawOrder(result, byPath) {
|
|
243
|
+
let order = 0;
|
|
244
|
+
const visit = (node) => {
|
|
245
|
+
node.order = order++;
|
|
246
|
+
for (const childPath of node.children) {
|
|
247
|
+
const child = byPath.get(childPath);
|
|
248
|
+
if (child) visit(child);
|
|
249
|
+
}
|
|
250
|
+
};
|
|
251
|
+
for (const node of result) if (!node.parentPath || !byPath.has(node.parentPath)) visit(node);
|
|
252
|
+
}
|
|
253
|
+
function godotNodeScenePath(node) {
|
|
254
|
+
if (!node.parent) return ".";
|
|
255
|
+
return node.parent === "." ? node.name : `${node.parent}/${node.name}`;
|
|
256
|
+
}
|
|
257
|
+
function godotNodeParentScenePath(node) {
|
|
258
|
+
if (!node.parent) return null;
|
|
259
|
+
return node.parent;
|
|
260
|
+
}
|
|
261
|
+
function sourceNodeInfos(sceneNodes, options) {
|
|
262
|
+
const infos = /* @__PURE__ */ new Map();
|
|
263
|
+
for (const node of sceneNodes) {
|
|
264
|
+
const path = godotNodeScenePath(node);
|
|
265
|
+
const props = effectiveNodeProps(node, path, options);
|
|
266
|
+
const parentPath = godotNodeParentScenePath(node);
|
|
267
|
+
const parentInfo = parentPath ? infos.get(parentPath) : void 0;
|
|
268
|
+
const omitted = !(options.includeNode?.(node, path, props) ?? true) || Boolean(parentInfo?.omitted);
|
|
269
|
+
const effectivelyVisible = !omitted && (asBoolean(props.visible) ?? true) && (parentPath ? parentInfo?.effectivelyVisible ?? true : true);
|
|
270
|
+
infos.set(path, {
|
|
271
|
+
props,
|
|
272
|
+
omitted,
|
|
273
|
+
effectivelyVisible
|
|
274
|
+
});
|
|
275
|
+
}
|
|
276
|
+
return infos;
|
|
277
|
+
}
|
|
278
|
+
const basePropsCache = /* @__PURE__ */ new WeakMap();
|
|
279
|
+
const overrideMergeCache = /* @__PURE__ */ new WeakMap();
|
|
280
|
+
function effectiveNodeProps(node, path, options) {
|
|
281
|
+
const overrideProps = options.overrideNodeProps?.(node, path);
|
|
282
|
+
if (overrideProps !== void 0 && Object.keys(overrideProps).length > 0) {
|
|
283
|
+
let byOverride = overrideMergeCache.get(node.properties);
|
|
284
|
+
if (byOverride === void 0) {
|
|
285
|
+
byOverride = /* @__PURE__ */ new WeakMap();
|
|
286
|
+
overrideMergeCache.set(node.properties, byOverride);
|
|
287
|
+
}
|
|
288
|
+
const cachedMerge = byOverride.get(overrideProps);
|
|
289
|
+
if (cachedMerge !== void 0) return cachedMerge;
|
|
290
|
+
const merged = applyAnchorPreset({
|
|
291
|
+
...node.properties,
|
|
292
|
+
...overrideProps
|
|
293
|
+
});
|
|
294
|
+
byOverride.set(overrideProps, merged);
|
|
295
|
+
return merged;
|
|
296
|
+
}
|
|
297
|
+
const cached = basePropsCache.get(node.properties);
|
|
298
|
+
if (cached) return cached;
|
|
299
|
+
const props = applyAnchorPreset({ ...node.properties });
|
|
300
|
+
basePropsCache.set(node.properties, props);
|
|
301
|
+
return props;
|
|
302
|
+
}
|
|
303
|
+
function isBlocked(path, blockedRoots) {
|
|
304
|
+
for (const root of blockedRoots) if (path.startsWith(`${root}/`)) return true;
|
|
305
|
+
return false;
|
|
306
|
+
}
|
|
307
|
+
function resolveMountedScene(ref, node, nodePath, props, effectivelyVisible, options, resourceStatuses) {
|
|
308
|
+
if (options.resolveExternalScene) {
|
|
309
|
+
if (!effectivelyVisible) return {};
|
|
310
|
+
const resolution = options.resolveExternalScene({
|
|
311
|
+
ref,
|
|
312
|
+
node,
|
|
313
|
+
nodePath,
|
|
314
|
+
props
|
|
315
|
+
});
|
|
316
|
+
if (!resolution) return {};
|
|
317
|
+
if (isGodotSceneState(resolution)) return { scene: resolution };
|
|
318
|
+
if (resolution.status === "ready") return { scene: resolution.scene };
|
|
319
|
+
resourceStatuses.push({
|
|
320
|
+
kind: "external-scene",
|
|
321
|
+
status: resolution.status,
|
|
322
|
+
nodePath,
|
|
323
|
+
path: resolution.path,
|
|
324
|
+
ref,
|
|
325
|
+
message: resolution.message
|
|
326
|
+
});
|
|
327
|
+
return { blockedSubtree: true };
|
|
328
|
+
}
|
|
329
|
+
const scene = options.mountExternalScene?.(ref, node);
|
|
330
|
+
return scene ? { scene } : {};
|
|
331
|
+
}
|
|
332
|
+
function addIndexedNode(result, sourceNode, options, nextOrder) {
|
|
333
|
+
const path = godotNodeScenePath(sourceNode);
|
|
334
|
+
const props = effectiveNodeProps(sourceNode, path, options);
|
|
335
|
+
if (options.includeNode?.(sourceNode, path, props) === false) return;
|
|
336
|
+
result.push(makeIndexedNode(sourceNode, path, godotNodeParentScenePath(sourceNode), options, nextOrder(), props));
|
|
337
|
+
for (const repeated of options.expandRepeatedNode?.(sourceNode, path) ?? []) {
|
|
338
|
+
const repeatedPath = godotNodeScenePath(repeated);
|
|
339
|
+
const repeatedProps = effectiveNodeProps(repeated, repeatedPath, options);
|
|
340
|
+
if (options.includeNode?.(repeated, repeatedPath, repeatedProps) === false) continue;
|
|
341
|
+
result.push(makeIndexedNode(repeated, repeatedPath, godotNodeParentScenePath(repeated), options, nextOrder(), repeatedProps));
|
|
342
|
+
}
|
|
343
|
+
}
|
|
344
|
+
function addMountedSceneNodes(result, hostNode, hostPath, mountedNodes, overrideNodes, consumedOverrides, options, resourceStatuses, nextOrder, mountStack, hostEffectiveVisible) {
|
|
345
|
+
const [mountedRoot, ...mountedChildren] = mountedNodes;
|
|
346
|
+
if (!mountedRoot) {
|
|
347
|
+
addIndexedNode(result, hostNode, options, nextOrder);
|
|
348
|
+
return;
|
|
349
|
+
}
|
|
350
|
+
const hostIndexed = makeIndexedNode(mergeMountedNode(mountedRoot, hostNode, {
|
|
351
|
+
name: hostNode.name,
|
|
352
|
+
parent: hostNode.parent,
|
|
353
|
+
instance: hostNode.instance
|
|
354
|
+
}), hostPath, godotNodeParentScenePath(hostNode), options, nextOrder());
|
|
355
|
+
result.push(hostIndexed);
|
|
356
|
+
const mountedPaths = new Set([hostPath]);
|
|
357
|
+
const nestedMountedOverridePaths = /* @__PURE__ */ new Set();
|
|
358
|
+
const nestedBlockedMountRoots = /* @__PURE__ */ new Set();
|
|
359
|
+
const effectiveVisibleByPath = new Map([[hostPath, hostEffectiveVisible && (asBoolean(hostIndexed.props.visible) ?? true)]]);
|
|
360
|
+
for (const mountedChild of mountedChildren) {
|
|
361
|
+
const mountedChildPath = godotNodeScenePath(mountedChild);
|
|
362
|
+
const path = remapMountedPath(hostPath, godotNodeScenePath(mountedChild));
|
|
363
|
+
if (nestedMountedOverridePaths.has(path) || isBlocked(path, nestedBlockedMountRoots)) continue;
|
|
364
|
+
const parentPath = remapMountedParentPath(hostPath, godotNodeParentScenePath(mountedChild));
|
|
365
|
+
const override = overrideNodes.get(path);
|
|
366
|
+
if (override) consumedOverrides.add(path);
|
|
367
|
+
const node = override ? mergeMountedNode(mountedChild, override, {
|
|
368
|
+
name: path.split("/").at(-1) ?? mountedChild.name,
|
|
369
|
+
parent: parentPath ?? void 0
|
|
370
|
+
}) : remapMountedNode(mountedChild, path, parentPath);
|
|
371
|
+
const props = effectiveNodeProps(node, path, options);
|
|
372
|
+
if (options.includeNode?.(node, path, props) === false) {
|
|
373
|
+
nestedBlockedMountRoots.add(path);
|
|
374
|
+
continue;
|
|
375
|
+
}
|
|
376
|
+
const effectiveVisible = (parentPath === null ? true : effectiveVisibleByPath.get(parentPath) ?? true) && (asBoolean(props.visible) ?? true);
|
|
377
|
+
effectiveVisibleByPath.set(path, effectiveVisible);
|
|
378
|
+
const nestedInstance = effectiveNodeInstance(node, path, options);
|
|
379
|
+
const childMountKey = resourceRefKey(nestedInstance);
|
|
380
|
+
const nestedResolved = nestedInstance && childMountKey && !mountStack.has(childMountKey) ? resolveMountedScene(nestedInstance, node, path, props, effectiveVisible, options, resourceStatuses) : {};
|
|
381
|
+
if (nestedResolved.blockedSubtree) nestedBlockedMountRoots.add(path);
|
|
382
|
+
if (nestedResolved.scene?.nodes.length) {
|
|
383
|
+
const nestedOverrideNodes = new Map(overrideNodes);
|
|
384
|
+
for (const candidate of mountedChildren) {
|
|
385
|
+
const candidatePath = godotNodeScenePath(candidate);
|
|
386
|
+
if (!candidatePath.startsWith(`${mountedChildPath}/`)) continue;
|
|
387
|
+
const remappedCandidatePath = remapMountedPath(hostPath, candidatePath);
|
|
388
|
+
nestedOverrideNodes.set(remappedCandidatePath, candidate);
|
|
389
|
+
nestedMountedOverridePaths.add(remappedCandidatePath);
|
|
390
|
+
}
|
|
391
|
+
addMountedSceneNodes(result, node, path, sceneNodesFromState(nestedResolved.scene), nestedOverrideNodes, consumedOverrides, options, resourceStatuses, nextOrder, new Set([...mountStack, childMountKey]), effectiveVisible);
|
|
392
|
+
} else result.push(makeIndexedNode(node, path, parentPath, options, nextOrder(), props));
|
|
393
|
+
mountedPaths.add(path);
|
|
394
|
+
}
|
|
395
|
+
for (const [path, override] of overrideNodes) {
|
|
396
|
+
if (!path.startsWith(`${hostPath}/`) || mountedPaths.has(path) || isBlocked(path, nestedBlockedMountRoots)) continue;
|
|
397
|
+
consumedOverrides.add(path);
|
|
398
|
+
const parentPath = path.slice(0, path.lastIndexOf("/"));
|
|
399
|
+
const node = remapMountedNode(override, path, parentPath);
|
|
400
|
+
const props = effectiveNodeProps(node, path, options);
|
|
401
|
+
const nestedInstance = options.includeNode?.(node, path, props) !== false ? effectiveNodeInstance(node, path, options) : void 0;
|
|
402
|
+
const childMountKey = resourceRefKey(nestedInstance);
|
|
403
|
+
const effectiveVisible = (effectiveVisibleByPath.get(parentPath) ?? true) && (asBoolean(props.visible) ?? true);
|
|
404
|
+
const nestedResolved = nestedInstance && childMountKey && !mountStack.has(childMountKey) ? resolveMountedScene(nestedInstance, node, path, props, effectiveVisible, options, resourceStatuses) : {};
|
|
405
|
+
if (nestedResolved.scene?.nodes.length) {
|
|
406
|
+
effectiveVisibleByPath.set(path, effectiveVisible);
|
|
407
|
+
nestedBlockedMountRoots.add(path);
|
|
408
|
+
mountedPaths.add(path);
|
|
409
|
+
addMountedSceneNodes(result, node, path, sceneNodesFromState(nestedResolved.scene), new Map(overrideNodes), consumedOverrides, options, resourceStatuses, nextOrder, new Set([...mountStack, childMountKey]), effectiveVisible);
|
|
410
|
+
} else {
|
|
411
|
+
if (nestedResolved.blockedSubtree) nestedBlockedMountRoots.add(path);
|
|
412
|
+
addIndexedNode(result, node, options, nextOrder);
|
|
413
|
+
}
|
|
414
|
+
}
|
|
415
|
+
}
|
|
416
|
+
function effectiveNodeInstance(node, path, options) {
|
|
417
|
+
return options.overrideNodeInstance?.(node, path) ?? node.instance;
|
|
418
|
+
}
|
|
419
|
+
function makeIndexedNode(sourceNode, path, parentPath, options, order, precomputedProps) {
|
|
420
|
+
const props = precomputedProps ?? effectiveNodeProps(sourceNode, path, options);
|
|
421
|
+
const typeOverride = options.overrideNodeType?.(sourceNode, path);
|
|
422
|
+
return {
|
|
423
|
+
node: typeOverride !== void 0 && typeOverride !== sourceNode.type ? {
|
|
424
|
+
...sourceNode,
|
|
425
|
+
type: typeOverride
|
|
426
|
+
} : sourceNode,
|
|
427
|
+
path,
|
|
428
|
+
parentPath,
|
|
429
|
+
children: [],
|
|
430
|
+
props,
|
|
431
|
+
order
|
|
432
|
+
};
|
|
433
|
+
}
|
|
434
|
+
function mergeMountedNode(base, override, forced = {}) {
|
|
435
|
+
const merged = Object.keys(override.properties).length === 0 ? base.properties : {
|
|
436
|
+
...base.properties,
|
|
437
|
+
...override.properties
|
|
438
|
+
};
|
|
439
|
+
const baseScene = base.properties?.[SOURCE_SCENE_PATH_ATTRIBUTE];
|
|
440
|
+
const overrideScene = override.properties?.[SOURCE_SCENE_PATH_ATTRIBUTE];
|
|
441
|
+
const properties = typeof baseScene === "string" && baseScene !== overrideScene ? {
|
|
442
|
+
...merged,
|
|
443
|
+
[MOUNTED_INNER_SCENE_PATH_ATTRIBUTE]: baseScene
|
|
444
|
+
} : merged;
|
|
445
|
+
return {
|
|
446
|
+
name: forced.name ?? override.name,
|
|
447
|
+
type: override.type ?? base.type,
|
|
448
|
+
parent: forced.parent ?? override.parent,
|
|
449
|
+
instance: forced.instance ?? override.instance ?? base.instance,
|
|
450
|
+
attributes: {
|
|
451
|
+
...base.attributes,
|
|
452
|
+
...override.attributes
|
|
453
|
+
},
|
|
454
|
+
properties
|
|
455
|
+
};
|
|
456
|
+
}
|
|
457
|
+
function remapMountedNode(node, path, parentPath) {
|
|
458
|
+
return {
|
|
459
|
+
...node,
|
|
460
|
+
name: path.split("/").at(-1) ?? node.name,
|
|
461
|
+
parent: parentPath ?? void 0
|
|
462
|
+
};
|
|
463
|
+
}
|
|
464
|
+
function remapMountedPath(hostPath, mountedPath) {
|
|
465
|
+
return mountedPath === "." ? hostPath : `${hostPath}/${mountedPath}`;
|
|
466
|
+
}
|
|
467
|
+
function remapMountedParentPath(hostPath, parentPath) {
|
|
468
|
+
if (parentPath === null) return null;
|
|
469
|
+
return parentPath === "." ? hostPath : `${hostPath}/${parentPath}`;
|
|
470
|
+
}
|
|
471
|
+
function resourceRefKey(ref) {
|
|
472
|
+
return ref ? `${ref.type}:${ref.id ?? ref.path}` : "";
|
|
473
|
+
}
|
|
474
|
+
//#endregion
|
|
475
|
+
//#region src/visuals.ts
|
|
476
|
+
/**
|
|
477
|
+
* Derive a node's rect-free render fields (scale, pivot, z-index, alignment,
|
|
478
|
+
* visibility, draw order, resource refs, …) from its resolved properties and its
|
|
479
|
+
* parent's z-index. Shared by the browser-native producer (`deriveSceneGraph`,
|
|
480
|
+
* which stops here) and the computed rect engine (`makeLayoutNode`, which adds
|
|
481
|
+
* `rect`/`renderedRect`/`cumulativeTransform`), so the two render modes can never
|
|
482
|
+
* drift on these values.
|
|
483
|
+
*/
|
|
484
|
+
function deriveNodeVisuals(indexed, parentZIndex) {
|
|
485
|
+
const visuals = propsVisuals(indexed.props);
|
|
486
|
+
return {
|
|
487
|
+
path: indexed.path,
|
|
488
|
+
name: indexed.node.name,
|
|
489
|
+
type: indexed.node.type ?? "Node",
|
|
490
|
+
parentPath: indexed.parentPath,
|
|
491
|
+
children: [...indexed.children],
|
|
492
|
+
source: indexed.node,
|
|
493
|
+
visible: visuals.visible,
|
|
494
|
+
zIndex: visuals.zAsRelative ? (parentZIndex ?? 0) + visuals.ownZIndex : visuals.ownZIndex,
|
|
495
|
+
drawOrder: indexed.order,
|
|
496
|
+
zAsRelative: visuals.zAsRelative,
|
|
497
|
+
showBehindParent: visuals.showBehindParent,
|
|
498
|
+
clipContents: visuals.clipContents,
|
|
499
|
+
textAlign: visuals.textAlign,
|
|
500
|
+
textVerticalAlign: visuals.textVerticalAlign,
|
|
501
|
+
scale: visuals.scale,
|
|
502
|
+
pivotOffset: visuals.pivotOffset,
|
|
503
|
+
properties: indexed.props,
|
|
504
|
+
resourceRefs: visuals.resourceRefs
|
|
505
|
+
};
|
|
506
|
+
}
|
|
507
|
+
const propsVisualsCache = /* @__PURE__ */ new WeakMap();
|
|
508
|
+
function propsVisuals(props) {
|
|
509
|
+
const cached = propsVisualsCache.get(props);
|
|
510
|
+
if (cached) return cached;
|
|
511
|
+
const visuals = {
|
|
512
|
+
visible: asBoolean(props.visible) ?? true,
|
|
513
|
+
ownZIndex: asNumber(props.z_index) ?? 0,
|
|
514
|
+
zAsRelative: asBoolean(props.z_as_relative) ?? true,
|
|
515
|
+
showBehindParent: asBoolean(props.show_behind_parent) ?? false,
|
|
516
|
+
clipContents: (asBoolean(props.clip_contents) ?? false) || (asNumber(props.clip_children) ?? 0) > 0,
|
|
517
|
+
textAlign: deriveTextAlign(props.horizontal_alignment),
|
|
518
|
+
textVerticalAlign: deriveTextVerticalAlign(props.vertical_alignment),
|
|
519
|
+
scale: asVector2(props.scale) ?? {
|
|
520
|
+
x: asNumber(props.scale_x) ?? 1,
|
|
521
|
+
y: asNumber(props.scale_y) ?? 1
|
|
522
|
+
},
|
|
523
|
+
pivotOffset: asVector2(props.pivot_offset) ?? {
|
|
524
|
+
x: asNumber(props.pivot_offset_x) ?? 0,
|
|
525
|
+
y: asNumber(props.pivot_offset_y) ?? 0
|
|
526
|
+
},
|
|
527
|
+
resourceRefs: collectResourceRefs(props)
|
|
528
|
+
};
|
|
529
|
+
propsVisualsCache.set(props, visuals);
|
|
530
|
+
return visuals;
|
|
531
|
+
}
|
|
532
|
+
function deriveTextAlign(value) {
|
|
533
|
+
const number = asNumber(value);
|
|
534
|
+
if (number === 1) return "center";
|
|
535
|
+
if (number === 2) return "right";
|
|
536
|
+
if (number === 3) return "fill";
|
|
537
|
+
const string = asString(value)?.toLowerCase();
|
|
538
|
+
if (string === "center" || string === "horizontal_alignment_center") return "center";
|
|
539
|
+
if (string === "right" || string === "horizontal_alignment_right") return "right";
|
|
540
|
+
if (string === "fill" || string === "horizontal_alignment_fill") return "fill";
|
|
541
|
+
return "left";
|
|
542
|
+
}
|
|
543
|
+
function deriveTextVerticalAlign(value) {
|
|
544
|
+
const number = asNumber(value);
|
|
545
|
+
if (number === 1) return "center";
|
|
546
|
+
if (number === 2) return "bottom";
|
|
547
|
+
if (number === 3) return "fill";
|
|
548
|
+
const string = asString(value)?.toLowerCase();
|
|
549
|
+
if (string === "center" || string === "vertical_alignment_center") return "center";
|
|
550
|
+
if (string === "bottom" || string === "vertical_alignment_bottom") return "bottom";
|
|
551
|
+
if (string === "fill" || string === "vertical_alignment_fill") return "fill";
|
|
552
|
+
return "top";
|
|
553
|
+
}
|
|
554
|
+
function collectResourceRefs(props) {
|
|
555
|
+
return Object.values(props).flatMap(function collect(value) {
|
|
556
|
+
const ref = asResourceRef(value);
|
|
557
|
+
if (ref) return [ref];
|
|
558
|
+
if (Array.isArray(value)) return value.flatMap(collect);
|
|
559
|
+
if (value && typeof value === "object") return Object.values(value).flatMap(collect);
|
|
560
|
+
return [];
|
|
561
|
+
});
|
|
562
|
+
}
|
|
563
|
+
//#endregion
|
|
564
|
+
//#region src/derive.ts
|
|
565
|
+
function createSceneGraphNodeMemo() {
|
|
566
|
+
return /* @__PURE__ */ new Map();
|
|
567
|
+
}
|
|
568
|
+
function propsSignature(props) {
|
|
569
|
+
return JSON.stringify(props);
|
|
570
|
+
}
|
|
571
|
+
/**
|
|
572
|
+
* Produce the browser-native {@link SceneGraph} from a parsed scene: structurally
|
|
573
|
+
* index the nodes (flatten instances/mounts, apply overrides, expand anchor
|
|
574
|
+
* presets, resolve sibling/draw order), then derive each node's rect-free render
|
|
575
|
+
* fields. This is the single shared producer — the browser HTML emitter consumes
|
|
576
|
+
* the graph directly (CSS resolves geometry), and the computed rect engine
|
|
577
|
+
* (`resolveGodotSceneTree`) consumes it as the input to the rect cascade. Neither
|
|
578
|
+
* the `layout` nor `html` package depends on this one; they exchange the shared
|
|
579
|
+
* `SceneGraph` type (declared in `@godot-scene-web/core`).
|
|
580
|
+
*
|
|
581
|
+
* Pass a {@link SceneGraphNodeMemo} (one per view, reused across calls) to preserve
|
|
582
|
+
* the object identity of nodes whose derived render fields are unchanged across
|
|
583
|
+
* renders — so an overrides-only host change re-derives only the changed nodes.
|
|
584
|
+
*/
|
|
585
|
+
function deriveSceneGraph(scene, options = {}, nodeMemo) {
|
|
586
|
+
const indexed = indexSceneNodes(sceneNodesFromState(scene), options);
|
|
587
|
+
const byPath = new Map(indexed.nodes.map((node) => [node.path, node]));
|
|
588
|
+
const derivedByPath = /* @__PURE__ */ new Map();
|
|
589
|
+
const derive = (node) => {
|
|
590
|
+
const inCall = derivedByPath.get(node.path);
|
|
591
|
+
if (inCall) return inCall;
|
|
592
|
+
const parent = node.parentPath ? byPath.get(node.parentPath) : void 0;
|
|
593
|
+
const parentZIndex = parent ? derive(parent).zIndex : void 0;
|
|
594
|
+
const name = node.node.name;
|
|
595
|
+
const type = node.node.type ?? "Node";
|
|
596
|
+
const childrenKey = node.children.join("\0");
|
|
597
|
+
const prev = nodeMemo?.get(node.path);
|
|
598
|
+
const structureMatches = prev !== void 0 && prev.name === name && prev.type === type && prev.order === node.order && prev.childrenKey === childrenKey && (!prev.zAsRelative || prev.parentZIndex === parentZIndex);
|
|
599
|
+
if (structureMatches && prev.props === node.props) {
|
|
600
|
+
derivedByPath.set(node.path, prev.output);
|
|
601
|
+
return prev.output;
|
|
602
|
+
}
|
|
603
|
+
let sig;
|
|
604
|
+
if (structureMatches) {
|
|
605
|
+
sig = propsSignature(node.props);
|
|
606
|
+
if (sig === prev.propsSig) {
|
|
607
|
+
prev.props = node.props;
|
|
608
|
+
derivedByPath.set(node.path, prev.output);
|
|
609
|
+
return prev.output;
|
|
610
|
+
}
|
|
611
|
+
}
|
|
612
|
+
const derived = deriveNodeVisuals(node, parentZIndex);
|
|
613
|
+
nodeMemo?.set(node.path, {
|
|
614
|
+
props: node.props,
|
|
615
|
+
propsSig: sig ?? propsSignature(node.props),
|
|
616
|
+
parentZIndex,
|
|
617
|
+
name,
|
|
618
|
+
type,
|
|
619
|
+
order: node.order,
|
|
620
|
+
childrenKey,
|
|
621
|
+
zAsRelative: derived.zAsRelative,
|
|
622
|
+
output: derived
|
|
623
|
+
});
|
|
624
|
+
derivedByPath.set(node.path, derived);
|
|
625
|
+
return derived;
|
|
626
|
+
};
|
|
627
|
+
return {
|
|
628
|
+
nodes: indexed.nodes.map(derive),
|
|
629
|
+
resourceStatuses: indexed.resourceStatuses
|
|
630
|
+
};
|
|
631
|
+
}
|
|
632
|
+
//#endregion
|
|
633
|
+
export { MOUNTED_INNER_SCENE_PATH_ATTRIBUTE, SOURCE_SCENE_PATH_ATTRIBUTE, createSceneGraphNodeMemo, deriveNodeVisuals, deriveSceneGraph, tagSceneNodes };
|
|
634
|
+
|
|
635
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","names":[],"sources":["../src/provenance.ts","../src/scene-index.ts","../src/visuals.ts","../src/derive.ts"],"sourcesContent":["import type { GodotSceneState } from \"@godot-scene-web/core\";\n\nexport const SOURCE_SCENE_PATH_ATTRIBUTE =\n \"metadata/godot_scene_web/source_scene_path\";\nexport const MOUNTED_INNER_SCENE_PATH_ATTRIBUTE =\n \"metadata/godot_scene_web/mounted_inner_scene_path\";\n/** Stamp parser output before publishing it to graph derivation caches. */\nexport function tagSceneNodes(\n scene: GodotSceneState,\n resourcePath: string,\n): void {\n for (const node of scene.nodes) {\n const property = node.properties.find(\n (entry) => entry.name === SOURCE_SCENE_PATH_ATTRIBUTE,\n );\n if (property) property.value = resourcePath;\n else\n node.properties.push({\n name: SOURCE_SCENE_PATH_ATTRIBUTE,\n value: resourcePath,\n });\n }\n}\n","import {\n asBoolean,\n asNumber,\n type GodotNode,\n type GodotResourceRefValue,\n type GodotSceneState,\n type GodotVariant,\n isGodotSceneState,\n} from \"@godot-scene-web/core\";\nimport {\n MOUNTED_INNER_SCENE_PATH_ATTRIBUTE,\n SOURCE_SCENE_PATH_ATTRIBUTE,\n} from \"./provenance\";\nimport type {\n GodotResourceStatus,\n SceneStructureOptions,\n} from \"./public-types\";\nimport type { IndexedNode, IndexedScene } from \"./types\";\n\n// The scene a node was authored in (stamped per-scene on load by the project\n// resolver). A mounted node merged onto an instance placeholder takes the OUTER\n// scene's value (override wins); `MOUNTED_INNER_SCENE_PATH_ATTRIBUTE` records the\n// INNER (instanced) scene so the resolver can still resolve the node's own\n// base-authored ext-resource refs (whose ids are local to the inner scene).\n\n// Godot `LayoutPreset` (anchors_preset) -> [anchor_left, anchor_top, anchor_right,\n// anchor_bottom]. Real scenes set layout via the preset and only serialize explicit\n// `anchor_*` when they differ from it, so the layout engine must expand the preset\n// or every preset-anchored node falls back to anchors 0 (collapsing to 0 size or\n// the top-left corner). Mirrors `Control::set_anchors_preset`.\nconst ANCHOR_PRESETS: Record<number, [number, number, number, number]> = {\n 0: [0, 0, 0, 0], // TOP_LEFT\n 1: [1, 0, 1, 0], // TOP_RIGHT\n 2: [0, 1, 0, 1], // BOTTOM_LEFT\n 3: [1, 1, 1, 1], // BOTTOM_RIGHT\n 4: [0, 0.5, 0, 0.5], // CENTER_LEFT\n 5: [0.5, 0, 0.5, 0], // CENTER_TOP\n 6: [1, 0.5, 1, 0.5], // CENTER_RIGHT\n 7: [0.5, 1, 0.5, 1], // CENTER_BOTTOM\n 8: [0.5, 0.5, 0.5, 0.5], // CENTER\n 9: [0, 0, 0, 1], // LEFT_WIDE\n 10: [0, 0, 1, 0], // TOP_WIDE\n 11: [1, 0, 1, 1], // RIGHT_WIDE\n 12: [0, 1, 1, 1], // BOTTOM_WIDE\n 13: [0.5, 0, 0.5, 1], // VCENTER_WIDE\n 14: [0, 0.5, 1, 0.5], // HCENTER_WIDE\n 15: [0, 0, 1, 1], // FULL_RECT\n};\n\nconst EXPLICIT_ANCHOR_KEYS = [\n \"anchor_left\",\n \"anchor_top\",\n \"anchor_right\",\n \"anchor_bottom\",\n] as const;\n\n/**\n * Expand `anchors_preset` into explicit `anchor_*` props when the scene did not\n * already serialize them. Explicit anchors stay authoritative (a preset is only a\n * convenience the editor resolves to anchors), so a node carrying both is left\n * untouched.\n */\nfunction applyAnchorPreset(\n props: Record<string, GodotVariant>,\n): Record<string, GodotVariant> {\n const preset = asNumber(props.anchors_preset);\n if (preset === undefined) {\n return props;\n }\n const anchors = ANCHOR_PRESETS[preset];\n if (\n !anchors ||\n EXPLICIT_ANCHOR_KEYS.some((key) => props[key] !== undefined)\n ) {\n return props;\n }\n return {\n ...props,\n anchor_left: anchors[0],\n anchor_top: anchors[1],\n anchor_right: anchors[2],\n anchor_bottom: anchors[3],\n };\n}\n\n// Per-document memo of the state->GodotNode[] conversion. Derivation re-runs on\n// every resolver settle while the mounted tree grows, and this conversion (plus\n// everything keyed on the node `properties` records it allocates, see\n// `basePropsCache`) is a pure function of the document — so the first derive of a\n// document pays it and later derives reuse it, across every caller that holds the\n// same document object (the Vue view and each lazy-flatten consumer share the\n// fetch cache's identities). HARD CONTRACT this introduces: a `GodotSceneState`\n// must not be mutated after its first derive. Producers that annotate documents\n// (resource-path tagging, repeat splicing) must do so inside the loader before\n// handing the document out, or produce a NEW document object.\nconst sceneNodesCache = new WeakMap<GodotSceneState, GodotNode[]>();\n\nexport function sceneNodesFromState(state: GodotSceneState): GodotNode[] {\n const cached = sceneNodesCache.get(state);\n if (cached) {\n return cached;\n }\n const nodes = state.nodes.map((node): GodotNode => {\n const properties = Object.fromEntries(\n node.properties.map((property) => [property.name, property.value]),\n );\n // Live producers reading `GetNodePath(…, for_parent=true)` emit the NodePath\n // form with a leading `./` (`./Panel/Flow`), whereas `.tscn` text and gsw's\n // computed `path` use the bare relative form (`Panel/Flow`). Strip a single\n // leading `./` so both resolve to the same parent; a bare `.` (root) is left\n // untouched. Safe and shape-unambiguous: `./X` and `X` denote the same node,\n // and text never emits `./`, so this is a no-op there.\n const normalizedParent = node.parent?.startsWith(\"./\")\n ? node.parent.slice(2)\n : node.parent;\n const parent =\n node.index === 0 && (normalizedParent === \".\" || normalizedParent === \"\")\n ? undefined\n : normalizedParent;\n return {\n name: node.name,\n type: node.type,\n parent,\n instance: node.instance,\n attributes: {\n ...(node.owner !== undefined ? { owner: node.owner } : {}),\n ...(node.instancePlaceholder !== undefined\n ? { instance_placeholder: node.instancePlaceholder }\n : {}),\n ...(node.siblingIndex !== undefined\n ? { index: node.siblingIndex }\n : {}),\n ...(node.groups.length > 0 ? { groups: node.groups } : {}),\n },\n properties,\n propertyEntries: node.properties.map((property) => ({ ...property })),\n };\n });\n sceneNodesCache.set(state, nodes);\n return nodes;\n}\n\nexport function indexSceneNodes(\n sceneNodes: GodotNode[],\n options: SceneStructureOptions,\n): IndexedScene {\n const result: IndexedNode[] = [];\n const resourceStatuses: GodotResourceStatus[] = [];\n let order = 0;\n const nextOrder = () => order++;\n const sourceInfos = sourceNodeInfos(sceneNodes, options);\n const mountedScenes = new Map<string, GodotNode[]>();\n const blockedMountRoots = new Set<string>();\n for (const sourceNode of sceneNodes) {\n const path = godotNodeScenePath(sourceNode);\n const sourceInfo = sourceInfos.get(path);\n const instance = effectiveNodeInstance(sourceNode, path, options);\n if (!sourceInfo || sourceInfo.omitted || !instance) {\n continue;\n }\n const resolved = resolveMountedScene(\n instance,\n sourceNode,\n path,\n sourceInfo.props,\n sourceInfo.effectivelyVisible,\n options,\n resourceStatuses,\n );\n if (resolved.blockedSubtree) {\n blockedMountRoots.add(path);\n }\n if (resolved.scene?.nodes.length) {\n mountedScenes.set(path, sceneNodesFromState(resolved.scene));\n }\n }\n const mountedRootPaths = [...mountedScenes.keys()];\n const overrideNodes = new Map<string, GodotNode>();\n for (const sourceNode of sceneNodes) {\n const path = godotNodeScenePath(sourceNode);\n const sourceInfo = sourceInfos.get(path);\n if (\n !sourceInfo ||\n sourceInfo.omitted ||\n isBlocked(path, blockedMountRoots)\n ) {\n continue;\n }\n if (mountedRootPaths.some((rootPath) => path.startsWith(`${rootPath}/`))) {\n overrideNodes.set(path, sourceNode);\n }\n }\n const consumedOverrides = new Set<string>();\n\n for (const sourceNode of sceneNodes) {\n const path = godotNodeScenePath(sourceNode);\n const sourceInfo = sourceInfos.get(path);\n if (\n !sourceInfo ||\n sourceInfo.omitted ||\n isBlocked(path, blockedMountRoots)\n ) {\n continue;\n }\n // A node that overrides another mount's child is grafted by that enclosing\n // mount's `addMountedSceneNodes`, NOT here — even when it is ALSO a mount root\n // itself (an instance nested under an outer mount's added-child, e.g. a deck-view\n // sort button under the card-grid mount). Expanding it top-level too would\n // duplicate it against the enclosing mount's graft and collapse to a childless\n // leaf. So skip override children before the mount-root expansion below.\n if (overrideNodes.has(path)) {\n continue;\n }\n const mountedScene = mountedScenes.get(path);\n if (mountedScene) {\n addMountedSceneNodes(\n result,\n sourceNode,\n path,\n mountedScene,\n overrideNodes,\n consumedOverrides,\n options,\n resourceStatuses,\n nextOrder,\n new Set([resourceRefKey(sourceNode.instance)]),\n sourceInfo.effectivelyVisible,\n );\n continue;\n }\n addIndexedNode(result, sourceNode, options, nextOrder);\n }\n for (const [path, overrideNode] of overrideNodes) {\n if (!consumedOverrides.has(path)) {\n addIndexedNode(result, overrideNode, options, nextOrder);\n }\n }\n const byPath = new Map(result.map((node) => [node.path, node]));\n for (const node of result) {\n if (node.parentPath && byPath.has(node.parentPath)) {\n byPath.get(node.parentPath)?.children.push(node.path);\n }\n }\n reorderChildrenByIndex(result, byPath);\n reassignDrawOrder(result, byPath);\n return { nodes: result, resourceStatuses };\n}\n\n/**\n * Godot appends children in scene order, then relocates any node carrying an\n * explicit `index` to that position among its siblings (`SceneState::instantiate`\n * -> `Node::move_child`). Moves are applied in scene order, each against the live\n * sibling list, mirroring the engine. Nodes without `index` keep scene order.\n */\nfunction reorderChildrenByIndex(\n result: IndexedNode[],\n byPath: Map<string, IndexedNode>,\n): void {\n for (const node of result) {\n if (node.children.length < 2) {\n continue;\n }\n const moves = node.children\n .map((childPath) => ({\n path: childPath,\n index: asNumber(byPath.get(childPath)?.node.attributes.index),\n }))\n .filter(\n (move): move is { path: string; index: number } =>\n move.index !== undefined && move.index >= 0,\n );\n for (const move of moves) {\n const from = node.children.indexOf(move.path);\n if (from < 0) {\n continue;\n }\n node.children.splice(from, 1);\n node.children.splice(\n Math.min(move.index, node.children.length),\n 0,\n move.path,\n );\n }\n }\n}\n\n/**\n * Re-derive `order` (draw order) from the resolved child lists via a pre-order\n * walk so z-stacking and HTML child order follow the final sibling order. When no\n * `index` reordering happened this reproduces the scene-order counter exactly,\n * because a well-formed scene already lists nodes in pre-order.\n */\nfunction reassignDrawOrder(\n result: IndexedNode[],\n byPath: Map<string, IndexedNode>,\n): void {\n let order = 0;\n const visit = (node: IndexedNode): void => {\n node.order = order++;\n for (const childPath of node.children) {\n const child = byPath.get(childPath);\n if (child) {\n visit(child);\n }\n }\n };\n for (const node of result) {\n if (!node.parentPath || !byPath.has(node.parentPath)) {\n visit(node);\n }\n }\n}\n\nfunction godotNodeScenePath(node: GodotNode): string {\n if (!node.parent) {\n return \".\";\n }\n return node.parent === \".\" ? node.name : `${node.parent}/${node.name}`;\n}\n\nfunction godotNodeParentScenePath(node: GodotNode): string | null {\n if (!node.parent) {\n return null;\n }\n return node.parent;\n}\n\ninterface SourceNodeInfo {\n props: Record<string, GodotVariant>;\n omitted: boolean;\n effectivelyVisible: boolean;\n}\n\nfunction sourceNodeInfos(\n sceneNodes: GodotNode[],\n options: SceneStructureOptions,\n): Map<string, SourceNodeInfo> {\n const infos = new Map<string, SourceNodeInfo>();\n for (const node of sceneNodes) {\n const path = godotNodeScenePath(node);\n const props = effectiveNodeProps(node, path, options);\n const parentPath = godotNodeParentScenePath(node);\n const parentInfo = parentPath ? infos.get(parentPath) : undefined;\n const included = options.includeNode?.(node, path, props) ?? true;\n const omitted = !included || Boolean(parentInfo?.omitted);\n const effectivelyVisible =\n !omitted &&\n (asBoolean(props.visible) ?? true) &&\n (parentPath ? (parentInfo?.effectivelyVisible ?? true) : true);\n infos.set(path, { props, omitted, effectivelyVisible });\n }\n return infos;\n}\n\n// No-override effective props per `node.properties` identity. Keying on the\n// properties record (not the node) makes the cache hit through every node wrapper\n// that preserves the record — `remapMountedNode`, the `overrideNodeType` copy, and\n// override-free `mergeMountedNode` — so mounted subtrees reuse it too. The cached\n// value depends only on the record's contents plus `applyAnchorPreset` (pure), so\n// it is safe to share across options/callers. The override branch is keyed BOTH by the record\n// and by the override object (below), so it composes with this base cache.\nconst basePropsCache = new WeakMap<\n Record<string, GodotVariant>,\n Record<string, GodotVariant>\n>();\n\n// Override-branch merge cache, keyed by (node.properties, overrideProps). When the host keeps a\n// node's override object identity-stable across renders for unchanged values, this returns the SAME\n// merged object — so the derive node-memo's identity fast-path (`prev.props === node.props`) hits\n// and the node is reused (no propsSignature / re-derive / re-render). Safe for the SAME reason\n// basePropsCache is (the result is treated immutable downstream); option-safe because a different\n// resolver / different content yields a different `overrideProps` object → a different entry.\nconst overrideMergeCache = new WeakMap<\n Record<string, GodotVariant>,\n WeakMap<Record<string, GodotVariant>, Record<string, GodotVariant>>\n>();\n\nfunction effectiveNodeProps(\n node: GodotNode,\n path: string,\n options: SceneStructureOptions,\n): Record<string, GodotVariant> {\n const overrideProps = options.overrideNodeProps?.(node, path);\n if (overrideProps !== undefined && Object.keys(overrideProps).length > 0) {\n let byOverride = overrideMergeCache.get(node.properties);\n if (byOverride === undefined) {\n byOverride = new WeakMap();\n overrideMergeCache.set(node.properties, byOverride);\n }\n const cachedMerge = byOverride.get(overrideProps);\n if (cachedMerge !== undefined) return cachedMerge;\n const merged = applyAnchorPreset({ ...node.properties, ...overrideProps });\n byOverride.set(overrideProps, merged);\n return merged;\n }\n const cached = basePropsCache.get(node.properties);\n if (cached) {\n return cached;\n }\n const props = applyAnchorPreset({ ...node.properties });\n basePropsCache.set(node.properties, props);\n return props;\n}\n\nfunction isBlocked(path: string, blockedRoots: Set<string>): boolean {\n for (const root of blockedRoots) {\n if (path.startsWith(`${root}/`)) {\n return true;\n }\n }\n return false;\n}\n\nfunction resolveMountedScene(\n ref: GodotResourceRefValue,\n node: GodotNode,\n nodePath: string,\n props: Record<string, GodotVariant>,\n effectivelyVisible: boolean,\n options: SceneStructureOptions,\n resourceStatuses: GodotResourceStatus[],\n): { scene?: GodotSceneState; blockedSubtree?: boolean } {\n if (options.resolveExternalScene) {\n if (!effectivelyVisible) {\n return {};\n }\n const resolution = options.resolveExternalScene({\n ref,\n node,\n nodePath,\n props,\n });\n if (!resolution) {\n return {};\n }\n if (isGodotSceneState(resolution)) {\n return { scene: resolution };\n }\n if (resolution.status === \"ready\") {\n return { scene: resolution.scene };\n }\n resourceStatuses.push({\n kind: \"external-scene\",\n status: resolution.status,\n nodePath,\n path: resolution.path,\n ref,\n message: resolution.message,\n });\n return { blockedSubtree: true };\n }\n const scene = options.mountExternalScene?.(ref, node);\n return scene ? { scene } : {};\n}\n\nfunction addIndexedNode(\n result: IndexedNode[],\n sourceNode: GodotNode,\n options: SceneStructureOptions,\n nextOrder: () => number,\n): void {\n const path = godotNodeScenePath(sourceNode);\n const props = effectiveNodeProps(sourceNode, path, options);\n if (options.includeNode?.(sourceNode, path, props) === false) {\n return;\n }\n result.push(\n makeIndexedNode(\n sourceNode,\n path,\n godotNodeParentScenePath(sourceNode),\n options,\n nextOrder(),\n props,\n ),\n );\n for (const repeated of options.expandRepeatedNode?.(sourceNode, path) ?? []) {\n const repeatedPath = godotNodeScenePath(repeated);\n const repeatedProps = effectiveNodeProps(repeated, repeatedPath, options);\n if (\n options.includeNode?.(repeated, repeatedPath, repeatedProps) === false\n ) {\n continue;\n }\n result.push(\n makeIndexedNode(\n repeated,\n repeatedPath,\n godotNodeParentScenePath(repeated),\n options,\n nextOrder(),\n repeatedProps,\n ),\n );\n }\n}\n\nfunction addMountedSceneNodes(\n result: IndexedNode[],\n hostNode: GodotNode,\n hostPath: string,\n mountedNodes: GodotNode[],\n overrideNodes: Map<string, GodotNode>,\n consumedOverrides: Set<string>,\n options: SceneStructureOptions,\n resourceStatuses: GodotResourceStatus[],\n nextOrder: () => number,\n mountStack: Set<string>,\n hostEffectiveVisible: boolean,\n): void {\n const [mountedRoot, ...mountedChildren] = mountedNodes;\n if (!mountedRoot) {\n addIndexedNode(result, hostNode, options, nextOrder);\n return;\n }\n\n const hostMergedNode = mergeMountedNode(mountedRoot, hostNode, {\n name: hostNode.name,\n parent: hostNode.parent,\n instance: hostNode.instance,\n });\n const hostIndexed = makeIndexedNode(\n hostMergedNode,\n hostPath,\n godotNodeParentScenePath(hostNode),\n options,\n nextOrder(),\n );\n result.push(hostIndexed);\n\n const mountedPaths = new Set<string>([hostPath]);\n const nestedMountedOverridePaths = new Set<string>();\n const nestedBlockedMountRoots = new Set<string>();\n const effectiveVisibleByPath = new Map<string, boolean>([\n [\n hostPath,\n hostEffectiveVisible && (asBoolean(hostIndexed.props.visible) ?? true),\n ],\n ]);\n for (const mountedChild of mountedChildren) {\n const mountedChildPath = godotNodeScenePath(mountedChild);\n const path = remapMountedPath(hostPath, godotNodeScenePath(mountedChild));\n if (\n nestedMountedOverridePaths.has(path) ||\n isBlocked(path, nestedBlockedMountRoots)\n ) {\n continue;\n }\n const parentPath = remapMountedParentPath(\n hostPath,\n godotNodeParentScenePath(mountedChild),\n );\n const override = overrideNodes.get(path);\n if (override) {\n consumedOverrides.add(path);\n }\n // Force the host-remapped name/parent in BOTH branches. `override.parent` is the\n // override's raw scene-relative parent (e.g. a repeat mount's `CardGrid/ScrollContainer`),\n // which is NOT the assembled path in a nested mount — keeping it orphans the merged node\n // (its parent isn't in the index). `remapMountedNode` already does this for the no-override\n // branch; the merge must match so a mounted child WITH an override links under the same host.\n const node = override\n ? mergeMountedNode(mountedChild, override, {\n name: path.split(\"/\").at(-1) ?? mountedChild.name,\n parent: parentPath ?? undefined,\n })\n : remapMountedNode(mountedChild, path, parentPath);\n const props = effectiveNodeProps(node, path, options);\n if (options.includeNode?.(node, path, props) === false) {\n nestedBlockedMountRoots.add(path);\n continue;\n }\n const parentEffectiveVisible =\n parentPath === null\n ? true\n : (effectiveVisibleByPath.get(parentPath) ?? true);\n const effectiveVisible =\n parentEffectiveVisible && (asBoolean(props.visible) ?? true);\n effectiveVisibleByPath.set(path, effectiveVisible);\n const nestedInstance = effectiveNodeInstance(node, path, options);\n const childMountKey = resourceRefKey(nestedInstance);\n const nestedResolved =\n nestedInstance && childMountKey && !mountStack.has(childMountKey)\n ? resolveMountedScene(\n nestedInstance,\n node,\n path,\n props,\n effectiveVisible,\n options,\n resourceStatuses,\n )\n : {};\n if (nestedResolved.blockedSubtree) {\n nestedBlockedMountRoots.add(path);\n }\n if (nestedResolved.scene?.nodes.length) {\n const nestedOverrideNodes = new Map(overrideNodes);\n for (const candidate of mountedChildren) {\n const candidatePath = godotNodeScenePath(candidate);\n if (!candidatePath.startsWith(`${mountedChildPath}/`)) {\n continue;\n }\n const remappedCandidatePath = remapMountedPath(hostPath, candidatePath);\n nestedOverrideNodes.set(remappedCandidatePath, candidate);\n nestedMountedOverridePaths.add(remappedCandidatePath);\n }\n addMountedSceneNodes(\n result,\n node,\n path,\n sceneNodesFromState(nestedResolved.scene),\n nestedOverrideNodes,\n consumedOverrides,\n options,\n resourceStatuses,\n nextOrder,\n new Set([...mountStack, childMountKey]),\n effectiveVisible,\n );\n } else {\n result.push(\n makeIndexedNode(node, path, parentPath, options, nextOrder(), props),\n );\n }\n mountedPaths.add(path);\n }\n\n for (const [path, override] of overrideNodes) {\n if (\n !path.startsWith(`${hostPath}/`) ||\n mountedPaths.has(path) ||\n isBlocked(path, nestedBlockedMountRoots)\n ) {\n continue;\n }\n consumedOverrides.add(path);\n // `path` is the override's host-remapped key; the raw `override` node still\n // carries its source `parent`/`name` (e.g. a child ADDED under an instance:\n // `parent=\"Relic\"`). Grafting it raw makes `addIndexedNode` recompute the\n // un-remapped path (\"Relic/AmountLabel\"), which has no parent in a NESTED mount\n // and is dropped. Remap the node to `path` first (a no-op at the top level,\n // where the key already equals the raw path) so it links under the host.\n const parentPath = path.slice(0, path.lastIndexOf(\"/\"));\n const node = remapMountedNode(override, path, parentPath);\n const props = effectiveNodeProps(node, path, options);\n const included = options.includeNode?.(node, path, props) !== false;\n // The override may itself be an instance (a mount root nested under this host's\n // added-child subtree, e.g. a deck-view sort button under the card-grid mount).\n // Resolve + recurse so its internals expand, exactly like the mounted-children\n // loop above; otherwise graft it flat (the common added-child case is unchanged).\n const nestedInstance = included\n ? effectiveNodeInstance(node, path, options)\n : undefined;\n const childMountKey = resourceRefKey(nestedInstance);\n const parentEffectiveVisible =\n effectiveVisibleByPath.get(parentPath) ?? true;\n const effectiveVisible =\n parentEffectiveVisible && (asBoolean(props.visible) ?? true);\n const nestedResolved =\n nestedInstance && childMountKey && !mountStack.has(childMountKey)\n ? resolveMountedScene(\n nestedInstance,\n node,\n path,\n props,\n effectiveVisible,\n options,\n resourceStatuses,\n )\n : {};\n if (nestedResolved.scene?.nodes.length) {\n effectiveVisibleByPath.set(path, effectiveVisible);\n // Its descendants are grafted by the recursion; block them in this outer loop.\n nestedBlockedMountRoots.add(path);\n mountedPaths.add(path);\n addMountedSceneNodes(\n result,\n node,\n path,\n sceneNodesFromState(nestedResolved.scene),\n new Map(overrideNodes),\n consumedOverrides,\n options,\n resourceStatuses,\n nextOrder,\n new Set([...mountStack, childMountKey]),\n effectiveVisible,\n );\n } else {\n if (nestedResolved.blockedSubtree) {\n nestedBlockedMountRoots.add(path);\n }\n addIndexedNode(result, node, options, nextOrder);\n }\n }\n}\n\n// The node's effective PackedScene instance ref: a host-injected override (a\n// state-driven dynamic mount) wins over the authored `.tscn` instance.\nfunction effectiveNodeInstance(\n node: GodotNode,\n path: string,\n options: SceneStructureOptions,\n): GodotResourceRefValue | undefined {\n return options.overrideNodeInstance?.(node, path) ?? node.instance;\n}\n\nfunction makeIndexedNode(\n sourceNode: GodotNode,\n path: string,\n parentPath: string | null,\n options: SceneStructureOptions,\n order: number,\n precomputedProps?: Record<string, GodotVariant>,\n): IndexedNode {\n const props =\n precomputedProps ?? effectiveNodeProps(sourceNode, path, options);\n const typeOverride = options.overrideNodeType?.(sourceNode, path);\n const node =\n typeOverride !== undefined && typeOverride !== sourceNode.type\n ? { ...sourceNode, type: typeOverride }\n : sourceNode;\n return {\n node,\n path,\n parentPath,\n children: [],\n props,\n order,\n };\n}\n\nfunction mergeMountedNode(\n base: GodotNode,\n override: GodotNode,\n forced: Partial<GodotNode> = {},\n): GodotNode {\n // Reuse the base record when the instance site overrides nothing, so the\n // per-properties-identity caches keep hitting through the merge.\n const merged =\n Object.keys(override.properties).length === 0\n ? base.properties\n : { ...base.properties, ...override.properties };\n // `base` is the INNER (instanced) scene's node; `override` is the OUTER placeholder.\n // `source_scene_path` takes the outer value (override wins) so override-authored\n // refs (e.g. an outer-scene SubResource material) resolve against the outer scene.\n // But the node's OWN base-authored ext refs (e.g. a TextureRect's `texture`) use ext\n // ids LOCAL to the inner scene — record it so the resolver can fall back there.\n const baseScene = (base.properties as Record<string, GodotVariant>)?.[\n SOURCE_SCENE_PATH_ATTRIBUTE\n ];\n const overrideScene = (override.properties as Record<string, GodotVariant>)?.[\n SOURCE_SCENE_PATH_ATTRIBUTE\n ];\n const properties =\n typeof baseScene === \"string\" && baseScene !== overrideScene\n ? { ...merged, [MOUNTED_INNER_SCENE_PATH_ATTRIBUTE]: baseScene }\n : merged;\n return {\n name: forced.name ?? override.name,\n type: override.type ?? base.type,\n parent: forced.parent ?? override.parent,\n instance: forced.instance ?? override.instance ?? base.instance,\n attributes: { ...base.attributes, ...override.attributes },\n properties,\n };\n}\n\nfunction remapMountedNode(\n node: GodotNode,\n path: string,\n parentPath: string | null,\n): GodotNode {\n return {\n ...node,\n name: path.split(\"/\").at(-1) ?? node.name,\n parent: parentPath ?? undefined,\n };\n}\n\nfunction remapMountedPath(hostPath: string, mountedPath: string): string {\n return mountedPath === \".\" ? hostPath : `${hostPath}/${mountedPath}`;\n}\n\nfunction remapMountedParentPath(\n hostPath: string,\n parentPath: string | null,\n): string | null {\n if (parentPath === null) {\n return null;\n }\n return parentPath === \".\" ? hostPath : `${hostPath}/${parentPath}`;\n}\n\nfunction resourceRefKey(ref: GodotResourceRefValue | undefined): string {\n return ref ? `${ref.type}:${ref.id ?? ref.path}` : \"\";\n}\n","import {\n asBoolean,\n asNumber,\n asResourceRef,\n asString,\n asVector2,\n type GodotResourceRefValue,\n type GodotVariant,\n} from \"@godot-scene-web/core\";\nimport type { DerivedNodeInput, GodotSceneNodeBase } from \"./public-types\";\n\n/**\n * Derive a node's rect-free render fields (scale, pivot, z-index, alignment,\n * visibility, draw order, resource refs, …) from its resolved properties and its\n * parent's z-index. Shared by the browser-native producer (`deriveSceneGraph`,\n * which stops here) and the computed rect engine (`makeLayoutNode`, which adds\n * `rect`/`renderedRect`/`cumulativeTransform`), so the two render modes can never\n * drift on these values.\n */\nexport function deriveNodeVisuals(\n indexed: DerivedNodeInput,\n parentZIndex: number | undefined,\n): GodotSceneNodeBase {\n const visuals = propsVisuals(indexed.props);\n return {\n path: indexed.path,\n name: indexed.node.name,\n type: indexed.node.type ?? \"Node\",\n parentPath: indexed.parentPath,\n children: [...indexed.children],\n source: indexed.node,\n visible: visuals.visible,\n zIndex: visuals.zAsRelative\n ? (parentZIndex ?? 0) + visuals.ownZIndex\n : visuals.ownZIndex,\n drawOrder: indexed.order,\n zAsRelative: visuals.zAsRelative,\n showBehindParent: visuals.showBehindParent,\n clipContents: visuals.clipContents,\n textAlign: visuals.textAlign,\n textVerticalAlign: visuals.textVerticalAlign,\n scale: visuals.scale,\n pivotOffset: visuals.pivotOffset,\n properties: indexed.props,\n resourceRefs: visuals.resourceRefs,\n };\n}\n\n// The props-only slice of a node's visuals, memoized per effective-props record\n// identity. The scene index keeps those records identity-stable for unchanged\n// documents across derives, so re-derives (and the rect engine's repeated layout\n// passes) skip the per-property coercions and the recursive `collectResourceRefs`\n// walk. Context-dependent fields (path/name/type, children, draw order, the\n// parent-composed zIndex) must never enter this memo — `type` in particular is\n// option-scoped via `overrideNodeType`.\ninterface PropsVisuals {\n visible: boolean;\n ownZIndex: number;\n zAsRelative: boolean;\n showBehindParent: boolean;\n clipContents: boolean;\n textAlign: GodotSceneNodeBase[\"textAlign\"];\n textVerticalAlign: GodotSceneNodeBase[\"textVerticalAlign\"];\n scale: { x: number; y: number };\n pivotOffset: { x: number; y: number };\n resourceRefs: GodotResourceRefValue[];\n}\n\nconst propsVisualsCache = new WeakMap<\n Record<string, GodotVariant>,\n PropsVisuals\n>();\n\nfunction propsVisuals(props: Record<string, GodotVariant>): PropsVisuals {\n const cached = propsVisualsCache.get(props);\n if (cached) {\n return cached;\n }\n const visuals: PropsVisuals = {\n visible: asBoolean(props.visible) ?? true,\n ownZIndex: asNumber(props.z_index) ?? 0,\n zAsRelative: asBoolean(props.z_as_relative) ?? true,\n showBehindParent: asBoolean(props.show_behind_parent) ?? false,\n clipContents:\n (asBoolean(props.clip_contents) ?? false) ||\n (asNumber(props.clip_children) ?? 0) > 0,\n textAlign: deriveTextAlign(props.horizontal_alignment),\n textVerticalAlign: deriveTextVerticalAlign(props.vertical_alignment),\n scale: asVector2(props.scale) ?? {\n x: asNumber(props.scale_x) ?? 1,\n y: asNumber(props.scale_y) ?? 1,\n },\n pivotOffset: asVector2(props.pivot_offset) ?? {\n x: asNumber(props.pivot_offset_x) ?? 0,\n y: asNumber(props.pivot_offset_y) ?? 0,\n },\n resourceRefs: collectResourceRefs(props),\n };\n propsVisualsCache.set(props, visuals);\n return visuals;\n}\n\nfunction deriveTextAlign(\n value: GodotVariant | undefined,\n): GodotSceneNodeBase[\"textAlign\"] {\n const number = asNumber(value);\n if (number === 1) {\n return \"center\";\n }\n if (number === 2) {\n return \"right\";\n }\n if (number === 3) {\n return \"fill\";\n }\n const string = asString(value)?.toLowerCase();\n if (string === \"center\" || string === \"horizontal_alignment_center\") {\n return \"center\";\n }\n if (string === \"right\" || string === \"horizontal_alignment_right\") {\n return \"right\";\n }\n if (string === \"fill\" || string === \"horizontal_alignment_fill\") {\n return \"fill\";\n }\n return \"left\";\n}\n\nfunction deriveTextVerticalAlign(\n value: GodotVariant | undefined,\n): GodotSceneNodeBase[\"textVerticalAlign\"] {\n const number = asNumber(value);\n if (number === 1) {\n return \"center\";\n }\n if (number === 2) {\n return \"bottom\";\n }\n if (number === 3) {\n return \"fill\";\n }\n const string = asString(value)?.toLowerCase();\n if (string === \"center\" || string === \"vertical_alignment_center\") {\n return \"center\";\n }\n if (string === \"bottom\" || string === \"vertical_alignment_bottom\") {\n return \"bottom\";\n }\n if (string === \"fill\" || string === \"vertical_alignment_fill\") {\n return \"fill\";\n }\n return \"top\";\n}\n\nfunction collectResourceRefs(\n props: Record<string, GodotVariant>,\n): GodotResourceRefValue[] {\n return Object.values(props).flatMap(\n function collect(value): GodotResourceRefValue[] {\n const ref = asResourceRef(value);\n if (ref) {\n return [ref];\n }\n if (Array.isArray(value)) {\n return value.flatMap(collect);\n }\n if (value && typeof value === \"object\") {\n return Object.values(value).flatMap(collect);\n }\n return [];\n },\n );\n}\n","import type { GodotSceneState, GodotVariant } from \"@godot-scene-web/core\";\nimport type {\n SceneGraph,\n SceneGraphNode,\n SceneStructureOptions,\n} from \"./public-types\";\nimport { indexSceneNodes, sceneNodesFromState } from \"./scene-index\";\nimport type { IndexedNode } from \"./types\";\nimport { deriveNodeVisuals } from \"./visuals\";\n\ninterface SceneGraphNodeMemoEntry {\n props: Record<string, GodotVariant>;\n propsSig: string;\n parentZIndex: number | undefined;\n name: string;\n type: string;\n order: number;\n childrenKey: string;\n zAsRelative: boolean;\n output: SceneGraphNode;\n}\n\n/**\n * Cross-render per-node derivation cache (one per view, persisted across\n * {@link deriveSceneGraph} calls). Lets an unchanged node reuse its prior\n * {@link SceneGraphNode} object so its identity survives a re-derive — the\n * prerequisite for the html/render memos downstream. Opaque to callers.\n */\nexport type SceneGraphNodeMemo = Map<string, SceneGraphNodeMemoEntry>;\n\nexport function createSceneGraphNodeMemo(): SceneGraphNodeMemo {\n return new Map();\n}\n\n// Content signature of a node's resolved props. Used as the change signal when the\n// props RECORD identity is not stable across renders — which is the case for any node\n// carrying an override (the producer rebuilds `{...base, ...override}` fresh every\n// render even when the override value is unchanged). Deterministic: the same node's\n// props are assembled by the same code path, so key order is stable.\nfunction propsSignature(props: Record<string, GodotVariant>): string {\n return JSON.stringify(props);\n}\n\n/**\n * Produce the browser-native {@link SceneGraph} from a parsed scene: structurally\n * index the nodes (flatten instances/mounts, apply overrides, expand anchor\n * presets, resolve sibling/draw order), then derive each node's rect-free render\n * fields. This is the single shared producer — the browser HTML emitter consumes\n * the graph directly (CSS resolves geometry), and the computed rect engine\n * (`resolveGodotSceneTree`) consumes it as the input to the rect cascade. Neither\n * the `layout` nor `html` package depends on this one; they exchange the shared\n * `SceneGraph` type (declared in `@godot-scene-web/core`).\n *\n * Pass a {@link SceneGraphNodeMemo} (one per view, reused across calls) to preserve\n * the object identity of nodes whose derived render fields are unchanged across\n * renders — so an overrides-only host change re-derives only the changed nodes.\n */\nexport function deriveSceneGraph(\n scene: GodotSceneState,\n options: SceneStructureOptions = {},\n nodeMemo?: SceneGraphNodeMemo,\n): SceneGraph {\n const indexed = indexSceneNodes(sceneNodesFromState(scene), options);\n const byPath = new Map(indexed.nodes.map((node) => [node.path, node]));\n const derivedByPath = new Map<string, SceneGraphNode>();\n // z-index is parent-relative when `z_as_relative`, so a node's derived z-index\n // needs its parent's. Derive lazily + memoized up the parent chain (the tree is\n // acyclic), independent of array order.\n const derive = (node: IndexedNode): SceneGraphNode => {\n const inCall = derivedByPath.get(node.path);\n if (inCall) {\n return inCall;\n }\n const parent = node.parentPath ? byPath.get(node.parentPath) : undefined;\n const parentZIndex = parent ? derive(parent).zIndex : undefined;\n\n // Cross-render reuse: an unchanged node keeps its `SceneGraphNode` identity so the\n // downstream html/render memos can bail out. The output is a pure function of\n // (props, parentZIndex when z_as_relative, name, type, order, children). Guard on\n // the cheap value fields first; for props, fast-path on record identity (true for\n // un-overridden nodes when the doc is stable), else fall back to a content signature\n // (overridden nodes get a fresh-but-equal record every render).\n const name = node.node.name;\n const type = node.node.type ?? \"Node\";\n const childrenKey = node.children.join(\"\\0\");\n const prev = nodeMemo?.get(node.path);\n const structureMatches =\n prev !== undefined &&\n prev.name === name &&\n prev.type === type &&\n prev.order === node.order &&\n prev.childrenKey === childrenKey &&\n (!prev.zAsRelative || prev.parentZIndex === parentZIndex);\n if (structureMatches && prev.props === node.props) {\n derivedByPath.set(node.path, prev.output);\n return prev.output;\n }\n let sig: string | undefined;\n if (structureMatches) {\n sig = propsSignature(node.props);\n if (sig === prev.propsSig) {\n prev.props = node.props; // refresh → next render takes the identity fast-path\n derivedByPath.set(node.path, prev.output);\n return prev.output;\n }\n }\n\n const derived = deriveNodeVisuals(node, parentZIndex);\n nodeMemo?.set(node.path, {\n props: node.props,\n propsSig: sig ?? propsSignature(node.props),\n parentZIndex,\n name,\n type,\n order: node.order,\n childrenKey,\n zAsRelative: derived.zAsRelative,\n output: derived,\n });\n derivedByPath.set(node.path, derived);\n return derived;\n };\n return {\n nodes: indexed.nodes.map(derive),\n resourceStatuses: indexed.resourceStatuses,\n };\n}\n"],"mappings":"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"}
|
package/package.json
ADDED
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@godot-scene-web/scene-graph",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"license": "MIT",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"description": "Godot scene graph derivation utilities for godot-scene-web.",
|
|
7
|
+
"publishConfig": {
|
|
8
|
+
"access": "public",
|
|
9
|
+
"registry": "https://registry.npmjs.org/"
|
|
10
|
+
},
|
|
11
|
+
"repository": {
|
|
12
|
+
"type": "git",
|
|
13
|
+
"url": "git+https://github.com/tfoxy/godot-scene-web.git"
|
|
14
|
+
},
|
|
15
|
+
"homepage": "https://github.com/tfoxy/godot-scene-web#readme",
|
|
16
|
+
"bugs": {
|
|
17
|
+
"url": "https://github.com/tfoxy/godot-scene-web/issues"
|
|
18
|
+
},
|
|
19
|
+
"sideEffects": false,
|
|
20
|
+
"exports": {
|
|
21
|
+
".": {
|
|
22
|
+
"development": "./src/index.ts",
|
|
23
|
+
"types": "./dist/index.d.ts",
|
|
24
|
+
"import": "./dist/index.js"
|
|
25
|
+
}
|
|
26
|
+
},
|
|
27
|
+
"main": "./dist/index.js",
|
|
28
|
+
"types": "./dist/index.d.ts",
|
|
29
|
+
"files": [
|
|
30
|
+
"dist",
|
|
31
|
+
"LICENSE"
|
|
32
|
+
],
|
|
33
|
+
"dependencies": {
|
|
34
|
+
"@godot-scene-web/core": "0.1.0"
|
|
35
|
+
},
|
|
36
|
+
"scripts": {
|
|
37
|
+
"build": "tsdown",
|
|
38
|
+
"typecheck": "tsc -p tsconfig.json --noEmit",
|
|
39
|
+
"test": "vitest run --root ../.. --config ../../vitest.config.ts packages/scene-graph/test"
|
|
40
|
+
}
|
|
41
|
+
}
|