@woosh/meep-engine 2.36.0 → 2.36.1

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 (32) hide show
  1. package/core/geom/AABB2.d.ts +2 -0
  2. package/core/geom/Vector3.js +5 -1
  3. package/core/process/undo/ActionProcessor.js +31 -3
  4. package/core/process/undo/ActionProcessor.spec.js +1 -1
  5. package/engine/animation/curve/AnimationCurve.js +40 -1
  6. package/engine/animation/curve/Keyframe.js +19 -0
  7. package/engine/animation/curve/compression/animation_curve_to_float_array.js +35 -0
  8. package/engine/animation/curve/compression/downsample_float_array.js +50 -0
  9. package/engine/animation/curve/compression/downsample_float_array_curve_by_error.js +147 -0
  10. package/engine/animation/curve/compression/downsample_float_array_curve_by_error.spec.js +47 -0
  11. package/engine/animation/curve/compression/prototypeCurveCompression.js +154 -0
  12. package/engine/animation/curve/draw/build_curve_editor.js +316 -0
  13. package/engine/animation/curve/draw/build_plot_entity_from_array.js +47 -0
  14. package/engine/animation/curve/draw/build_tangent_editor.js +115 -0
  15. package/engine/animation/curve/draw/draw_grid.js +27 -0
  16. package/engine/animation/curve/draw/draw_label.js +17 -0
  17. package/engine/animation/curve/draw/plot_array.js +76 -0
  18. package/engine/animation/curve/draw/plot_data.js +49 -0
  19. package/engine/ecs/parent/EntityNode.d.ts +7 -3
  20. package/engine/ecs/parent/EntityNode.js +21 -0
  21. package/engine/ecs/parent/entity_node_compute_bounding_box.js +41 -0
  22. package/engine/graphics/ecs/mesh-v2/render/adapters/InstancedRendererAdapter.js +2 -0
  23. package/engine/graphics/geometry/instancing/InstancedMeshGroup.js +34 -3
  24. package/engine/graphics/micron/format/serialization/collection/geometry_collection_serialization.spec.js +24 -0
  25. package/engine/graphics/micron/plugin/shaded_geometry/MicronShadedGeometryRenderAdapter.js +10 -0
  26. package/engine/graphics/micron/prototypeVirtualGeometry.js +5 -3
  27. package/engine/graphics/micron/render/v1/MicronRenderContext.js +0 -6
  28. package/engine/graphics/render/forward_plus/LightManager.js +8 -6
  29. package/engine/graphics/render/forward_plus/data/channelCountToThreIntegerTextureType.js +3 -3
  30. package/engine/graphics/texture/channelCountToThreeTextureFormat.js +2 -2
  31. package/engine/ui/DraggableAspect.js +4 -1
  32. package/package.json +1 -1
@@ -0,0 +1,316 @@
1
+ import Vector2 from "../../../../core/geom/Vector2.d.ts";
2
+ import EmptyView from "../../../../view/elements/EmptyView.js";
3
+ import { CanvasView } from "../../../../view/elements/CanvasView.js";
4
+ import AABB2 from "../../../../core/geom/AABB2.d.ts";
5
+ import Signal from "../../../../core/events/signal/Signal.d.ts";
6
+ import ObservedValue from "../../../../core/model/ObservedValue.js";
7
+ import { animation_curve_to_float_array } from "../compression/animation_curve_to_float_array.js";
8
+ import { plot_data } from "./plot_data.js";
9
+ import { DraggableAspect } from "../../../ui/DraggableAspect.js";
10
+ import { min2 } from "../../../../core/math/min2.js";
11
+ import { max2 } from "../../../../core/math/max2.js";
12
+
13
+ /**
14
+ *
15
+ * @param {AABB2} out
16
+ * @param {AnimationCurve} curve
17
+ */
18
+ function compute_curve_aabb(out, curve) {
19
+ let x0 = Number.POSITIVE_INFINITY;
20
+ let x1 = Number.NEGATIVE_INFINITY;
21
+ let y0 = Number.POSITIVE_INFINITY;
22
+ let y1 = Number.NEGATIVE_INFINITY;
23
+
24
+ for (let i = 0; i < curve.keys.length; i++) {
25
+ const keyframe = curve.keys[i];
26
+
27
+ x0 = min2(x0, keyframe.time)
28
+ x1 = max2(x1, keyframe.time)
29
+
30
+ y0 = min2(y0, keyframe.value);
31
+ y1 = max2(y1, keyframe.value);
32
+ }
33
+
34
+ out.set(x0, y0, x1, y1);
35
+ }
36
+
37
+ /**
38
+ *
39
+ * @param {Vector2} size
40
+ * @param {AABB2} frame
41
+ * @param {Vector2} margin
42
+ * @param {number} x
43
+ * @param {number} y
44
+ * @return {Vector2}
45
+ */
46
+ function position_curve_to_canvas(size, frame, margin, x, y) {
47
+ const width = size.x;
48
+ const height = size.y;
49
+
50
+ const curve_width = frame.getWidth();
51
+ const curve_width_multiplier = curve_width > 0 ? 1 / curve_width : 0;
52
+ const u = (x - frame.x0) * curve_width_multiplier;
53
+
54
+ const curve_height = frame.getHeight();
55
+ const curve_height_multiplier = curve_height > 0 ? 1 / curve_height : 0;
56
+ const v = 1 - (y - frame.y0) * curve_height_multiplier;
57
+
58
+ return new Vector2(margin.x + (u * (width - margin.x * 2)), margin.y + v * (height - margin.y * 2));
59
+ }
60
+
61
+ /**
62
+ *
63
+ * @param {Vector2} size
64
+ * @param {AABB2} frame
65
+ * @param {Vector2} margin
66
+ * @param {number} x
67
+ * @param {number} y
68
+ * @return {Vector2}
69
+ */
70
+ function position_canvas_to_curve(size, frame, margin, x, y) {
71
+
72
+ const width = size.x;
73
+ const height = size.y;
74
+
75
+ const u = (x - margin.x) / (width - margin.x * 2);
76
+ const v = 1 - (y - margin.y) / (height - margin.y * 2);
77
+
78
+ return new Vector2(
79
+ u * frame.getWidth() + frame.x0,
80
+ v * frame.getHeight() + frame.y0
81
+ );
82
+ }
83
+
84
+ /**
85
+ *
86
+ * @param {AnimationCurve} curve
87
+ * @param {Vector2} [size]
88
+ * @param {Vector2} [margin]
89
+ * @returns {View}
90
+ */
91
+ export function build_curve_editor({
92
+ curve,
93
+ size = new Vector2(600, 200),
94
+ margin = new Vector2(8, 36)
95
+ }) {
96
+
97
+ const vContainer = new EmptyView();
98
+
99
+ const graph = new CanvasView();
100
+ graph.size.copy(size);
101
+
102
+ const frame = new AABB2();
103
+
104
+ const frame_updated = new Signal();
105
+
106
+ /**
107
+ *
108
+ * @type {ObservedValue<Keyframe>}
109
+ */
110
+ const active_keyframe = new ObservedValue(null);
111
+
112
+
113
+ function handle_curve_update() {
114
+ compute_curve_aabb(frame, curve);
115
+
116
+ frame_updated.send0();
117
+
118
+ update_graph();
119
+ }
120
+
121
+
122
+ function update_graph() {
123
+ const width = graph.size.x;
124
+ const data = new Float32Array(width);
125
+
126
+ animation_curve_to_float_array(data, curve, 0, data.length);
127
+
128
+ plot_data({
129
+ data,
130
+ ctx: graph.context2d,
131
+ width: width,
132
+ height: graph.size.y,
133
+ margin: margin,
134
+ range_y: [frame.y0, frame.y1]
135
+ });
136
+ }
137
+
138
+ const keyframe_views = new Map();
139
+
140
+ function add_keyframe(keyframe) {
141
+ const vContainerMarker = new EmptyView({
142
+ css: {
143
+ position: "absolute",
144
+ top: 0,
145
+ left: 0,
146
+ pointerEvents: "none"
147
+ }
148
+ });
149
+
150
+ const marker_size = 8;
151
+ const marker = new EmptyView({
152
+ css: {
153
+ width: `${marker_size}px`,
154
+ height: `${marker_size}px`,
155
+ background: "#00ff00",
156
+ border: "none",
157
+ position: "absolute",
158
+ top: `-${marker_size / 2}px`,
159
+ left: `-${marker_size / 2}px`,
160
+ pointerEvents: "auto",
161
+ borderRadius: `${marker_size}px`
162
+ }
163
+ });
164
+
165
+ // add extra element for mouse event capture to make it easier to click on the marker
166
+ const marker_hit_pad_size = 24;
167
+ marker.addChild(new EmptyView({
168
+ css: {
169
+ opacity: 0,
170
+ width: `${marker_hit_pad_size}px`,
171
+ height: `${marker_hit_pad_size}px`,
172
+ borderRadius: `${marker_hit_pad_size}px`,
173
+ // background: 'rgba(255,0,0,0.2)',
174
+ position: "absolute",
175
+ top: `-${(marker_hit_pad_size - marker_size) / 2}px`,
176
+ left: `-${(marker_hit_pad_size - marker_size) / 2}px`
177
+ }
178
+ }))
179
+
180
+ function updatePosition() {
181
+ const canvas_coordinates = position_curve_to_canvas(
182
+ graph.size,
183
+ frame,
184
+ margin,
185
+ keyframe.time,
186
+ keyframe.value
187
+ );
188
+ vContainerMarker.position.copy(canvas_coordinates);
189
+ }
190
+
191
+ function updateActiveState() {
192
+ if (active_keyframe.get() === keyframe) {
193
+ marker.css({
194
+ background: '#FFFFFF'
195
+ });
196
+ } else {
197
+ marker.css({
198
+ background: '#00ff00'
199
+ });
200
+ }
201
+ }
202
+
203
+ updatePosition();
204
+
205
+ const marker_el = marker.el;
206
+
207
+ const previous = new Vector2();
208
+ const draggable = new DraggableAspect({
209
+ el: marker_el, drag(position) {
210
+ const delta = new Vector2();
211
+ delta.subVectors(position, previous);
212
+
213
+ const keyframe_coord = position_canvas_to_curve(
214
+ graph.size,
215
+ frame,
216
+ margin,
217
+ position.x,
218
+ position.y
219
+ );
220
+
221
+ console.log(keyframe_coord);
222
+
223
+ keyframe.time = keyframe_coord.x;
224
+ keyframe.value = keyframe_coord.y;
225
+
226
+ let keyframe_index = curve.keys.indexOf(keyframe);
227
+
228
+ if (keyframe_index < (curve.keys.length - 1) && keyframe.time > curve.keys[keyframe_index + 1].time) {
229
+ const next_index = keyframe_index + 1;
230
+ const next = curve.keys[next_index];
231
+
232
+ curve.keys[next_index] = keyframe;
233
+ curve.keys[keyframe_index] = next;
234
+
235
+ keyframe_index = next_index;
236
+ } else if (keyframe_index > 0 && keyframe.time < curve.keys[keyframe_index - 1].time) {
237
+ const prev_index = keyframe_index - 1;
238
+ const prev = curve.keys[prev_index];
239
+
240
+ curve.keys[prev_index] = keyframe;
241
+ curve.keys[keyframe_index] = prev;
242
+
243
+ keyframe_index = prev_index;
244
+ }
245
+
246
+ curve.smoothTangents(keyframe_index, 1);
247
+
248
+ if (keyframe_index > 0) {
249
+ curve.smoothTangents(keyframe_index - 1, 1);
250
+ }
251
+ if (keyframe_index < curve.keys.length - 1) {
252
+ curve.smoothTangents(keyframe_index + 1, 1);
253
+ }
254
+
255
+ updatePosition();
256
+ update_graph();
257
+
258
+ previous.copy(position);
259
+ },
260
+ dragStart(position) {
261
+ previous.copy(position);
262
+
263
+ active_keyframe.set(keyframe);
264
+ },
265
+ dragEnd() {
266
+ handle_curve_update();
267
+ }
268
+ });
269
+
270
+ draggable.getPointer().on.tap.add(() => {
271
+ active_keyframe.set(keyframe);
272
+ });
273
+
274
+ marker.on.linked.add(draggable.start, draggable);
275
+ marker.on.unlinked.add(draggable.stop, draggable);
276
+
277
+ marker.bindSignal(frame_updated, updatePosition);
278
+ marker.bindSignal(active_keyframe.onChanged, updateActiveState);
279
+
280
+ // const vTangentEditor = build_tangent_editor({
281
+ // keyframe: keyframe,
282
+ // size: graph.size,
283
+ // ctx: graph.context2d,
284
+ // frame,
285
+ // margin
286
+ // });
287
+ // vContainerMarker.addChild(vTangentEditor);
288
+
289
+ vContainerMarker.addChild(marker);
290
+
291
+ keyframe_views.set(keyframe, vContainerMarker);
292
+
293
+ vContainer.addChild(vContainerMarker);
294
+ }
295
+
296
+ function remove_keyframe(keyframe) {
297
+ const marker_view = keyframe_views.get(keyframe);
298
+
299
+ vContainer.removeChild(marker_view);
300
+ }
301
+
302
+ graph.on.linked.add(() => {
303
+ curve.keys.forEach(add_keyframe);
304
+ });
305
+
306
+ graph.on.unlinked.add(() => {
307
+ curve.keys.forEach(remove_keyframe);
308
+ });
309
+
310
+
311
+ graph.on.linked.add(handle_curve_update);
312
+
313
+ vContainer.addChild(graph);
314
+
315
+ return vContainer;
316
+ }
@@ -0,0 +1,47 @@
1
+ import { CanvasView } from "../../../../view/elements/CanvasView.js";
2
+ import { plot_data } from "./plot_data.js";
3
+ import { draw_label } from "./draw_label.js";
4
+ import EntityBuilder from "../../../ecs/EntityBuilder.js";
5
+ import ViewportPosition from "../../../ecs/gui/position/ViewportPosition.js";
6
+ import GUIElement from "../../../ecs/gui/GUIElement.js";
7
+ import Vector2 from "../../../../core/geom/Vector2.js";
8
+
9
+ /**
10
+ *
11
+ * @param {number[]} data
12
+ * @param {number} x
13
+ * @param {number} y
14
+ * @param {number} [width]
15
+ * @param {number} [height]
16
+ * @param {Vector2} [margin]
17
+ * @param {string} [label]
18
+ * @return {EntityBuilder}
19
+ */
20
+ export function build_plot_entity_from_array({
21
+ data,
22
+ x, y,
23
+ width = 600,
24
+ height = 200,
25
+ margin = new Vector2(10,10),
26
+ label = ''
27
+ }) {
28
+
29
+ const canvasView = new CanvasView();
30
+ canvasView.size.set(width, height);
31
+ const ctx = canvasView.context2d;
32
+
33
+ plot_data({ ctx, data, width, height, margin });
34
+
35
+ const text = `${data.length}`;
36
+ draw_label(ctx, text, margin.x, height - (20));
37
+
38
+ if (typeof label === "string" && label.length > 0) {
39
+ draw_label(ctx, label, 0, 0)
40
+ }
41
+
42
+ return new EntityBuilder()
43
+ .add(ViewportPosition.fromJSON({
44
+ offset: { x, y }
45
+ }))
46
+ .add(GUIElement.fromView(canvasView));
47
+ }
@@ -0,0 +1,115 @@
1
+ import EmptyView from "../../../../view/elements/EmptyView.js";
2
+
3
+ /**
4
+ *
5
+ * @param {CanvasRenderingContext2D} ctx
6
+ * @param {Keyframe} keyframe
7
+ * @param {Vector2} size
8
+ * @param {AABB2} frame
9
+ * @param {Vector2} margin
10
+ */
11
+ export function build_tangent_editor({
12
+ ctx,
13
+ keyframe,
14
+ size,
15
+ frame,
16
+ margin
17
+ }) {
18
+
19
+ const scale = 36;
20
+
21
+
22
+ function build_handle() {
23
+
24
+ const marker_size = 4;
25
+ const marker = new EmptyView({
26
+ css: {
27
+ width: `${marker_size}px`,
28
+ height: `${marker_size}px`,
29
+ background: "#00ff00",
30
+ border: "none",
31
+ position: "absolute",
32
+ top: `-${marker_size / 2}px`,
33
+ left: `-${marker_size / 2}px`,
34
+ pointerEvents: "auto"
35
+ }
36
+ });
37
+
38
+ // add extra element for mouse event capture to make it easier to click on the marker
39
+ const marker_hit_pad_size = 24;
40
+ marker.addChild(new EmptyView({
41
+ css: {
42
+ opacity: 0,
43
+ width: `${marker_hit_pad_size}px`,
44
+ height: `${marker_hit_pad_size}px`,
45
+ borderRadius: `${marker_hit_pad_size}px`,
46
+ // background: 'rgba(255,0,0,0.2)',
47
+ position: "absolute",
48
+ top: `-${(marker_hit_pad_size - marker_size) / 2}px`,
49
+ left: `-${(marker_hit_pad_size - marker_size) / 2}px`
50
+ }
51
+ }))
52
+
53
+ return marker;
54
+ }
55
+
56
+ function build_tangent() {
57
+ const view = new EmptyView({
58
+ css: {
59
+ position: "absolute",
60
+ left: "0",
61
+ top: "0",
62
+ background: 'red'
63
+ }
64
+ });
65
+ view.position.set(-0.5, -0.5);
66
+ view.transformOrigin.set(0, 0.5);
67
+
68
+ return view;
69
+ }
70
+
71
+
72
+ const handle_in = build_handle();
73
+ const handle_out = build_handle();
74
+
75
+ const tangent_in = build_tangent();
76
+
77
+ const tangent_out = build_tangent();
78
+
79
+
80
+ function update_positions() {
81
+ tangent_in.size.set(scale, 1);
82
+ const angle_in = Math.PI - Math.atan2(keyframe.inTangent, 1);
83
+ tangent_in.rotation.set(angle_in);
84
+
85
+ tangent_out.size.set(scale, 1);
86
+ const angle_out = -Math.atan2(keyframe.outTangent, 1);
87
+ tangent_out.rotation.set(angle_out);
88
+
89
+ handle_in.position.set(
90
+ Math.cos(angle_in) * scale, Math.sin(angle_in) * scale
91
+ );
92
+
93
+ handle_out.position.set(
94
+ Math.cos(angle_out) * scale, Math.sin(angle_out) * scale
95
+ );
96
+ }
97
+
98
+ const vContainer = new EmptyView({
99
+ css: {
100
+ position: 'absolute',
101
+ left: 0,
102
+ top: 0
103
+ }
104
+ });
105
+
106
+ vContainer.addChild(handle_in);
107
+ vContainer.addChild(handle_out);
108
+
109
+ vContainer.addChild(tangent_in);
110
+ vContainer.addChild(tangent_out);
111
+
112
+ vContainer.on.linked.add(update_positions);
113
+
114
+ return vContainer;
115
+ }
@@ -0,0 +1,27 @@
1
+ /**
2
+ *
3
+ * @param {CanvasRenderingContext2D} ctx
4
+ * @param {number} width
5
+ * @param {number} height
6
+ * @param {string} color
7
+ * @param {number} spacing
8
+ * @param {number} [offset_x]
9
+ * @param {number} [offset_y]
10
+ */
11
+ export function draw_grid({ ctx, width, height, color = 'red', spacing = 10, offset_x = 0, offset_y = 0 }) {
12
+ ctx.fillStyle = 'none';
13
+ ctx.lineWidth = 1;
14
+ ctx.strokeStyle = color;
15
+
16
+
17
+ ctx.beginPath();
18
+ for (let x = offset_x; x < width; x += spacing) {
19
+ ctx.moveTo(x, 0);
20
+ ctx.lineTo(x, height);
21
+ }
22
+ for (let y = offset_y; y < width; y += spacing) {
23
+ ctx.moveTo(0, y);
24
+ ctx.lineTo(width, y);
25
+ }
26
+ ctx.stroke();
27
+ }
@@ -0,0 +1,17 @@
1
+ /**
2
+ *
3
+ * @param {CanvasRenderingContext2D} ctx
4
+ * @param {string} text
5
+ * @param {number} x
6
+ * @param {number} y
7
+ */
8
+ export function draw_label(ctx, text, x, y) {
9
+
10
+ ctx.fillStyle = 'white';
11
+ ctx.font = '10px Tahoma';
12
+
13
+ ctx.strokeStyle = `rgba(0, 0, 0, 0.5)`;
14
+ ctx.lineWidth = 2;
15
+ ctx.strokeText(text, x, y);
16
+ ctx.fillText(text, x, y);
17
+ }
@@ -0,0 +1,76 @@
1
+ import { min2 } from "../../../../core/math/min2.js";
2
+ import { max2 } from "../../../../core/math/max2.js";
3
+
4
+ /**
5
+ *
6
+ * @param {number[]} data
7
+ * @return {number[]}
8
+ */
9
+ export function array_compute_min_max(data) {
10
+ const point_count = data.length;
11
+
12
+ let min = Number.POSITIVE_INFINITY;
13
+ let max = Number.NEGATIVE_INFINITY;
14
+ for (let i = 0; i < point_count; i++) {
15
+ min = min2(data[i], min);
16
+ max = max2(data[i], max);
17
+ }
18
+
19
+ return [min, max];
20
+ }
21
+
22
+ /**
23
+ *
24
+ * @param {CanvasRenderingContext2D} ctx
25
+ * @param {number} width
26
+ * @param {number} height
27
+ * @param {number} x_offset
28
+ * @param {number} y_offset
29
+ * @param {number[]} data
30
+ * @param {number[]} data_range_y
31
+ */
32
+ export function plot_array(
33
+ ctx,
34
+ width,
35
+ height,
36
+ x_offset,
37
+ y_offset,
38
+ data,
39
+ data_range_y
40
+ ) {
41
+ const point_count = data.length;
42
+
43
+ if (data_range_y === undefined) {
44
+ data_range_y = array_compute_min_max(data)
45
+ }
46
+
47
+ const [min, max] = data_range_y;
48
+
49
+ const value_span = max - min;
50
+ const normalization_multiplier = value_span !== 0 ? 1 / value_span : 0;
51
+
52
+ ctx.beginPath();
53
+ if (point_count === 1) {
54
+ ctx.moveTo(x_offset, height + y_offset);
55
+ ctx.lineTo(x_offset + width, height + y_offset);
56
+ } else {
57
+
58
+ for (let i = 0; i < point_count; i++) {
59
+ const value = data[i];
60
+ const value_normalized = (value - min) * normalization_multiplier;
61
+
62
+ const v = 1 - value_normalized;
63
+ const u = i / (point_count - 1);
64
+
65
+ const x = u * width + x_offset;
66
+ const y = v * height + y_offset;
67
+
68
+ if (i === 0) {
69
+ ctx.moveTo(x, y);
70
+ } else {
71
+ ctx.lineTo(x, y);
72
+ }
73
+ }
74
+ }
75
+ ctx.stroke();
76
+ }
@@ -0,0 +1,49 @@
1
+ import { draw_grid } from "./draw_grid.js";
2
+ import { plot_array } from "./plot_array.js";
3
+ import Vector2 from "../../../../core/geom/Vector2.js";
4
+
5
+ /**
6
+ *
7
+ * @param {CanvasRenderingContext2D} ctx
8
+ * @param {number[]|Float32Array} data
9
+ * @param {Vector2} [margin]
10
+ * @param {number} width
11
+ * @param {number} height
12
+ * @param {number[]} [range_y]
13
+ */
14
+ export function plot_data({
15
+ ctx,
16
+ data,
17
+ margin = Vector2.zero,
18
+ width,
19
+ height,
20
+ range_y
21
+ }) {
22
+
23
+ ctx.fillStyle = '#222222';
24
+ ctx.fillRect(0, 0, width, height);
25
+
26
+ draw_grid({
27
+ ctx,
28
+ width,
29
+ height,
30
+ color: '#262626',
31
+ spacing: 32
32
+ });
33
+
34
+ draw_grid({
35
+ ctx,
36
+ width,
37
+ height,
38
+ color: '#303030',
39
+ spacing: 32,
40
+ offset_x: 16,
41
+ offset_y: 16
42
+ });
43
+
44
+ ctx.fillStyle = 'none';
45
+ ctx.strokeStyle = '#00ff00';
46
+ ctx.lineWidth = 1;
47
+ plot_array(ctx, width - margin.x * 2, height - margin.y * 2, margin.x, margin.y, data, range_y);
48
+
49
+ }
@@ -5,15 +5,19 @@ import {Transform} from "../transform/Transform";
5
5
  export class EntityNode {
6
6
  constructor(entity?: EntityBuilder)
7
7
 
8
- parent: EntityNode | null
9
- entity: EntityBuilder
8
+ readonly parent: EntityNode | null
9
+ readonly entity: EntityBuilder
10
10
 
11
- transform: Transform
11
+ readonly transform: Transform
12
+
13
+ readonly children: Readonly<EntityNode[]>
12
14
 
13
15
  addChild(node: EntityNode): boolean
14
16
 
15
17
  removeChild(node: EntityNode): boolean
16
18
 
19
+ traverseChildren(visitor: (EntityNode) => void, thisArg?: any): void
20
+
17
21
  build(ecd: EntityComponentDataset): void
18
22
 
19
23
  destroy(): void
@@ -41,6 +41,23 @@ export class EntityNode {
41
41
  this.__transform = new Transform();
42
42
  }
43
43
 
44
+ /**
45
+ *
46
+ * @param {(EntityNode) => *} visitor
47
+ * @param {*} [context]
48
+ */
49
+ traverseChildren(visitor, context) {
50
+ this.__children.forEach(visitor, context);
51
+ }
52
+
53
+ /**
54
+ *
55
+ * @return {Readonly<EntityNode[]>}
56
+ */
57
+ get children() {
58
+ return this.__children;
59
+ }
60
+
44
61
  /**
45
62
  *
46
63
  * @private
@@ -162,6 +179,10 @@ export class EntityNode {
162
179
  }
163
180
  }
164
181
 
182
+ /**
183
+ *
184
+ * @return {EntityBuilder}
185
+ */
165
186
  get entity() {
166
187
  return this.__entity;
167
188
  }