babylon.quarks-editor 0.17.7 → 0.17.8
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/core/timeline.d.ts +1 -1
- package/dist/core/timeline.js +3 -2
- package/dist/react/EffectEditor.d.ts +3 -0
- package/dist/react/EffectEditor.js +6 -1
- package/dist/react/EffectEditorHost.js +72 -11
- package/dist/react/ObjectModule.d.ts +6 -0
- package/dist/react/ObjectModule.js +28 -0
- package/dist/react/TimelinePanel.d.ts +4 -3
- package/dist/react/TimelinePanel.js +60 -10
- package/dist/react/TimelineTrackRow.d.ts +2 -2
- package/dist/react/TimelineTrackRow.js +16 -12
- package/dist/react/theme.js +44 -21
- package/package.json +68 -63
- package/src/core/binding.ts +105 -105
- package/src/core/collision.ts +40 -40
- package/src/core/groups.ts +29 -29
- package/src/core/loadEffect.ts +102 -102
- package/src/core/scrub.ts +28 -28
- package/src/core/shapes.ts +133 -133
- package/src/core/systems.ts +98 -98
- package/src/core/timeline.ts +95 -94
- package/src/core/values.ts +113 -113
- package/src/index.ts +32 -32
- package/src/react/CurveEditor.tsx +168 -168
- package/src/react/EffectEditor.tsx +125 -111
- package/src/react/EffectEditorHost.tsx +466 -377
- package/src/react/GradientEditor.tsx +127 -127
- package/src/react/ModuleSection.tsx +54 -54
- package/src/react/ObjectModule.tsx +47 -0
- package/src/react/PromptDialog.tsx +85 -85
- package/src/react/TimelinePanel.tsx +412 -354
- package/src/react/TimelineTrackRow.tsx +291 -267
- package/src/react/behaviorModules.tsx +658 -658
- package/src/react/fields.tsx +76 -76
- package/src/react/index.ts +43 -43
- package/src/react/modules.tsx +1034 -1034
- package/src/react/theme.ts +108 -85
package/package.json
CHANGED
|
@@ -1,63 +1,68 @@
|
|
|
1
|
-
{
|
|
2
|
-
"name": "babylon.quarks-editor",
|
|
3
|
-
"version": "0.17.
|
|
4
|
-
"description": "Shuriken-style effect editor for babylon.quarks: headless edit-model core + React module-stack UI",
|
|
5
|
-
"type": "module",
|
|
6
|
-
"main": "./dist/index.js",
|
|
7
|
-
"types": "./dist/index.d.ts",
|
|
8
|
-
"exports": {
|
|
9
|
-
".": {
|
|
10
|
-
"import": "./dist/index.js",
|
|
11
|
-
"types": "./dist/index.d.ts"
|
|
12
|
-
},
|
|
13
|
-
"./react": {
|
|
14
|
-
"import": "./dist/react/index.js",
|
|
15
|
-
"types": "./dist/react/index.d.ts"
|
|
16
|
-
}
|
|
17
|
-
},
|
|
18
|
-
"files": [
|
|
19
|
-
"dist",
|
|
20
|
-
"src",
|
|
21
|
-
"LICENSE",
|
|
22
|
-
"README.md",
|
|
23
|
-
"CHANGELOG.md"
|
|
24
|
-
],
|
|
25
|
-
"scripts": {
|
|
26
|
-
"clean": "rimraf dist",
|
|
27
|
-
"build": "npm run clean && tsc -p tsconfig.json"
|
|
28
|
-
},
|
|
29
|
-
"repository": {
|
|
30
|
-
"type": "git",
|
|
31
|
-
"url": "git+https://github.com/Soullnik/babylon.quarks-standalone.git"
|
|
32
|
-
},
|
|
33
|
-
"keywords": [
|
|
34
|
-
"babylonjs",
|
|
35
|
-
"
|
|
36
|
-
"
|
|
37
|
-
"
|
|
38
|
-
"
|
|
39
|
-
"
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
"
|
|
45
|
-
|
|
46
|
-
"
|
|
47
|
-
"
|
|
48
|
-
|
|
49
|
-
"
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
"
|
|
56
|
-
"
|
|
57
|
-
"react": "
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
"
|
|
61
|
-
"@types/react
|
|
62
|
-
|
|
63
|
-
|
|
1
|
+
{
|
|
2
|
+
"name": "babylon.quarks-editor",
|
|
3
|
+
"version": "0.17.8",
|
|
4
|
+
"description": "Shuriken-style effect editor for babylon.quarks: headless edit-model core + React module-stack UI",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "./dist/index.js",
|
|
7
|
+
"types": "./dist/index.d.ts",
|
|
8
|
+
"exports": {
|
|
9
|
+
".": {
|
|
10
|
+
"import": "./dist/index.js",
|
|
11
|
+
"types": "./dist/index.d.ts"
|
|
12
|
+
},
|
|
13
|
+
"./react": {
|
|
14
|
+
"import": "./dist/react/index.js",
|
|
15
|
+
"types": "./dist/react/index.d.ts"
|
|
16
|
+
}
|
|
17
|
+
},
|
|
18
|
+
"files": [
|
|
19
|
+
"dist",
|
|
20
|
+
"src",
|
|
21
|
+
"LICENSE",
|
|
22
|
+
"README.md",
|
|
23
|
+
"CHANGELOG.md"
|
|
24
|
+
],
|
|
25
|
+
"scripts": {
|
|
26
|
+
"clean": "rimraf dist",
|
|
27
|
+
"build": "npm run clean && tsc -p tsconfig.json"
|
|
28
|
+
},
|
|
29
|
+
"repository": {
|
|
30
|
+
"type": "git",
|
|
31
|
+
"url": "git+https://github.com/Soullnik/babylon.quarks-standalone.git"
|
|
32
|
+
},
|
|
33
|
+
"keywords": [
|
|
34
|
+
"babylonjs",
|
|
35
|
+
"babylon.js",
|
|
36
|
+
"particles",
|
|
37
|
+
"particle-system",
|
|
38
|
+
"particle-editor",
|
|
39
|
+
"vfx",
|
|
40
|
+
"vfx-editor",
|
|
41
|
+
"shuriken",
|
|
42
|
+
"editor",
|
|
43
|
+
"react",
|
|
44
|
+
"quarks"
|
|
45
|
+
],
|
|
46
|
+
"author": "soullnik <soullnik96@gmail.com>",
|
|
47
|
+
"license": "MIT",
|
|
48
|
+
"bugs": {
|
|
49
|
+
"url": "https://github.com/Soullnik/babylon.quarks-standalone/issues"
|
|
50
|
+
},
|
|
51
|
+
"homepage": "https://github.com/Soullnik/babylon.quarks-standalone#readme",
|
|
52
|
+
"peerDependencies": {
|
|
53
|
+
"@babylonjs/core": ">=9.0.0",
|
|
54
|
+
"@babylonjs/materials": ">=9.0.0",
|
|
55
|
+
"babylon.quarks": ">=0.17.0",
|
|
56
|
+
"react": ">=18",
|
|
57
|
+
"react-dom": ">=18"
|
|
58
|
+
},
|
|
59
|
+
"devDependencies": {
|
|
60
|
+
"@babylonjs/materials": "9.6.2",
|
|
61
|
+
"@types/react": "^19.0.0",
|
|
62
|
+
"react": "^19.0.0",
|
|
63
|
+
"rimraf": "^6.0.0",
|
|
64
|
+
"typescript": "^5.9.3",
|
|
65
|
+
"react-dom": "^19.0.0",
|
|
66
|
+
"@types/react-dom": "^19.0.0"
|
|
67
|
+
}
|
|
68
|
+
}
|
package/src/core/binding.ts
CHANGED
|
@@ -1,105 +1,105 @@
|
|
|
1
|
-
import type {ParticleSystem} from 'babylon.quarks';
|
|
2
|
-
import type {TransformNode} from '@babylonjs/core/Meshes/transformNode';
|
|
3
|
-
import {buildEffectTree, serializeEffectTree} from './effectTree';
|
|
4
|
-
import type {EffectTreeNode} from './effectTree';
|
|
5
|
-
|
|
6
|
-
export type EditorListener = () => void;
|
|
7
|
-
|
|
8
|
-
/**
|
|
9
|
-
* Headless edit-model over a live ParticleSystem: mutate through apply() so every
|
|
10
|
-
* change notifies subscribers (React UI, save indicators, hosts like BabylonJS Editor).
|
|
11
|
-
*/
|
|
12
|
-
export class EffectBinding {
|
|
13
|
-
/** Not `readonly` — `promoteToMain` reassigns this when the current main system is removed. */
|
|
14
|
-
system: ParticleSystem;
|
|
15
|
-
/** Systems spawned by sub-emitter behaviors; serialized as children of the root emitter. */
|
|
16
|
-
readonly subSystems: ParticleSystem[] = [];
|
|
17
|
-
/** Loaded scene root of the whole effect (Group tree); defaults to the system's emitter. */
|
|
18
|
-
readonly root: TransformNode;
|
|
19
|
-
/** Invoked before any mutation — used by EffectHistory to capture undo snapshots. */
|
|
20
|
-
onBeforeChange?: () => void;
|
|
21
|
-
private listeners = new Set<EditorListener>();
|
|
22
|
-
private revision = 0;
|
|
23
|
-
|
|
24
|
-
constructor(system: ParticleSystem, subSystems: ParticleSystem[] = [], root?: TransformNode) {
|
|
25
|
-
this.system = system;
|
|
26
|
-
this.subSystems.push(...subSystems);
|
|
27
|
-
this.root = root ?? system.emitter;
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
/** Editor hierarchy tree (groups + emitters) rooted at this effect's scene root. */
|
|
31
|
-
getTree(): EffectTreeNode {
|
|
32
|
-
return buildEffectTree(this.root);
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
subscribe(listener: EditorListener): () => void {
|
|
36
|
-
this.listeners.add(listener);
|
|
37
|
-
return () => this.listeners.delete(listener);
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
/** Monotonic change counter, usable as a React external-store snapshot. */
|
|
41
|
-
getRevision(): number {
|
|
42
|
-
return this.revision;
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
apply(mutate: (system: ParticleSystem) => void): void {
|
|
46
|
-
this.onBeforeChange?.();
|
|
47
|
-
mutate(this.system);
|
|
48
|
-
this.notify();
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
addSubSystem(sub: ParticleSystem): void {
|
|
52
|
-
this.subSystems.push(sub);
|
|
53
|
-
this.notify();
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
removeSubSystem(sub: ParticleSystem): void {
|
|
57
|
-
const index = this.subSystems.indexOf(sub);
|
|
58
|
-
if (index >= 0) {
|
|
59
|
-
this.subSystems.splice(index, 1);
|
|
60
|
-
}
|
|
61
|
-
this.notify();
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
/**
|
|
65
|
-
* Swaps which system is "main" — used when the current `system` is about to be removed
|
|
66
|
-
* but other top-level systems remain. Being "main" vs. a sub-system is otherwise
|
|
67
|
-
* meaningless to the effect itself (both play/restart/export the same way); it only
|
|
68
|
-
* exists so there's always a designated fallback system to select.
|
|
69
|
-
*/
|
|
70
|
-
promoteToMain(next: ParticleSystem): void {
|
|
71
|
-
const index = this.subSystems.indexOf(next);
|
|
72
|
-
if (index < 0) {
|
|
73
|
-
return;
|
|
74
|
-
}
|
|
75
|
-
this.subSystems.splice(index, 1, this.system);
|
|
76
|
-
this.system = next;
|
|
77
|
-
this.notify();
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
private notify(): void {
|
|
81
|
-
this.revision++;
|
|
82
|
-
for (const listener of this.listeners) {
|
|
83
|
-
listener();
|
|
84
|
-
}
|
|
85
|
-
}
|
|
86
|
-
|
|
87
|
-
restart(): void {
|
|
88
|
-
// Replay every independent system; sub-emitter targets (onlyUsedByOther) are only
|
|
89
|
-
// reset — their parents re-drive them, so playing them directly would double-emit.
|
|
90
|
-
for (const system of [this.system, ...this.subSystems]) {
|
|
91
|
-
system.restart();
|
|
92
|
-
if (!system.onlyUsedByOther) {
|
|
93
|
-
system.play();
|
|
94
|
-
}
|
|
95
|
-
}
|
|
96
|
-
}
|
|
97
|
-
|
|
98
|
-
/**
|
|
99
|
-
* Serializes the effect into the Quarks JSON envelope understood by QuarksLoader,
|
|
100
|
-
* preserving the loaded group/emitter hierarchy.
|
|
101
|
-
*/
|
|
102
|
-
exportJSON(name = 'effect'): string {
|
|
103
|
-
return serializeEffectTree(this.root, name);
|
|
104
|
-
}
|
|
105
|
-
}
|
|
1
|
+
import type {ParticleSystem} from 'babylon.quarks';
|
|
2
|
+
import type {TransformNode} from '@babylonjs/core/Meshes/transformNode';
|
|
3
|
+
import {buildEffectTree, serializeEffectTree} from './effectTree';
|
|
4
|
+
import type {EffectTreeNode} from './effectTree';
|
|
5
|
+
|
|
6
|
+
export type EditorListener = () => void;
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Headless edit-model over a live ParticleSystem: mutate through apply() so every
|
|
10
|
+
* change notifies subscribers (React UI, save indicators, hosts like BabylonJS Editor).
|
|
11
|
+
*/
|
|
12
|
+
export class EffectBinding {
|
|
13
|
+
/** Not `readonly` — `promoteToMain` reassigns this when the current main system is removed. */
|
|
14
|
+
system: ParticleSystem;
|
|
15
|
+
/** Systems spawned by sub-emitter behaviors; serialized as children of the root emitter. */
|
|
16
|
+
readonly subSystems: ParticleSystem[] = [];
|
|
17
|
+
/** Loaded scene root of the whole effect (Group tree); defaults to the system's emitter. */
|
|
18
|
+
readonly root: TransformNode;
|
|
19
|
+
/** Invoked before any mutation — used by EffectHistory to capture undo snapshots. */
|
|
20
|
+
onBeforeChange?: () => void;
|
|
21
|
+
private listeners = new Set<EditorListener>();
|
|
22
|
+
private revision = 0;
|
|
23
|
+
|
|
24
|
+
constructor(system: ParticleSystem, subSystems: ParticleSystem[] = [], root?: TransformNode) {
|
|
25
|
+
this.system = system;
|
|
26
|
+
this.subSystems.push(...subSystems);
|
|
27
|
+
this.root = root ?? system.emitter;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/** Editor hierarchy tree (groups + emitters) rooted at this effect's scene root. */
|
|
31
|
+
getTree(): EffectTreeNode {
|
|
32
|
+
return buildEffectTree(this.root);
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
subscribe(listener: EditorListener): () => void {
|
|
36
|
+
this.listeners.add(listener);
|
|
37
|
+
return () => this.listeners.delete(listener);
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/** Monotonic change counter, usable as a React external-store snapshot. */
|
|
41
|
+
getRevision(): number {
|
|
42
|
+
return this.revision;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
apply(mutate: (system: ParticleSystem) => void): void {
|
|
46
|
+
this.onBeforeChange?.();
|
|
47
|
+
mutate(this.system);
|
|
48
|
+
this.notify();
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
addSubSystem(sub: ParticleSystem): void {
|
|
52
|
+
this.subSystems.push(sub);
|
|
53
|
+
this.notify();
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
removeSubSystem(sub: ParticleSystem): void {
|
|
57
|
+
const index = this.subSystems.indexOf(sub);
|
|
58
|
+
if (index >= 0) {
|
|
59
|
+
this.subSystems.splice(index, 1);
|
|
60
|
+
}
|
|
61
|
+
this.notify();
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* Swaps which system is "main" — used when the current `system` is about to be removed
|
|
66
|
+
* but other top-level systems remain. Being "main" vs. a sub-system is otherwise
|
|
67
|
+
* meaningless to the effect itself (both play/restart/export the same way); it only
|
|
68
|
+
* exists so there's always a designated fallback system to select.
|
|
69
|
+
*/
|
|
70
|
+
promoteToMain(next: ParticleSystem): void {
|
|
71
|
+
const index = this.subSystems.indexOf(next);
|
|
72
|
+
if (index < 0) {
|
|
73
|
+
return;
|
|
74
|
+
}
|
|
75
|
+
this.subSystems.splice(index, 1, this.system);
|
|
76
|
+
this.system = next;
|
|
77
|
+
this.notify();
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
private notify(): void {
|
|
81
|
+
this.revision++;
|
|
82
|
+
for (const listener of this.listeners) {
|
|
83
|
+
listener();
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
restart(): void {
|
|
88
|
+
// Replay every independent system; sub-emitter targets (onlyUsedByOther) are only
|
|
89
|
+
// reset — their parents re-drive them, so playing them directly would double-emit.
|
|
90
|
+
for (const system of [this.system, ...this.subSystems]) {
|
|
91
|
+
system.restart();
|
|
92
|
+
if (!system.onlyUsedByOther) {
|
|
93
|
+
system.play();
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
/**
|
|
99
|
+
* Serializes the effect into the Quarks JSON envelope understood by QuarksLoader,
|
|
100
|
+
* preserving the loaded group/emitter hierarchy.
|
|
101
|
+
*/
|
|
102
|
+
exportJSON(name = 'effect'): string {
|
|
103
|
+
return serializeEffectTree(this.root, name);
|
|
104
|
+
}
|
|
105
|
+
}
|
package/src/core/collision.ts
CHANGED
|
@@ -1,40 +1,40 @@
|
|
|
1
|
-
import {Vector3, getPhysicsResolver, setPhysicsResolver} from 'babylon.quarks';
|
|
2
|
-
|
|
3
|
-
/**
|
|
4
|
-
* A horizontal ground plane collider. quarks.core's ApplyCollision behavior delegates to a
|
|
5
|
-
* host-provided `PhysicsResolver` (a global singleton) and ships no collider primitive of its
|
|
6
|
-
* own, so the editor supplies this — the common "particles bounce off the floor" case.
|
|
7
|
-
*
|
|
8
|
-
* `resolve` returns true and fills the surface normal when the point is at or below the plane,
|
|
9
|
-
* which is exactly what ApplyCollision.update reflects the velocity against.
|
|
10
|
-
*/
|
|
11
|
-
export class GroundPlaneResolver {
|
|
12
|
-
constructor(public y = 0) {}
|
|
13
|
-
|
|
14
|
-
resolve(position: Vector3, normal: Vector3): boolean {
|
|
15
|
-
if (position.y <= this.y) {
|
|
16
|
-
normal.set(0, 1, 0);
|
|
17
|
-
return true;
|
|
18
|
-
}
|
|
19
|
-
return false;
|
|
20
|
-
}
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
/**
|
|
24
|
-
* Registers (once) a shared ground-plane resolver so ApplyCollision behaviors — whether added
|
|
25
|
-
* in the inspector or deserialized from imported JSON — have something to collide against.
|
|
26
|
-
* Idempotent: an already-registered resolver is left untouched and returned.
|
|
27
|
-
*/
|
|
28
|
-
export function ensureGroundResolver(): GroundPlaneResolver {
|
|
29
|
-
const existing = getPhysicsResolver();
|
|
30
|
-
if (existing instanceof GroundPlaneResolver) {
|
|
31
|
-
return existing;
|
|
32
|
-
}
|
|
33
|
-
if (existing) {
|
|
34
|
-
// A host registered its own resolver; don't clobber it, but we can't tweak its floor.
|
|
35
|
-
return existing as GroundPlaneResolver;
|
|
36
|
-
}
|
|
37
|
-
const resolver = new GroundPlaneResolver(0);
|
|
38
|
-
setPhysicsResolver(resolver);
|
|
39
|
-
return resolver;
|
|
40
|
-
}
|
|
1
|
+
import {Vector3, getPhysicsResolver, setPhysicsResolver} from 'babylon.quarks';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* A horizontal ground plane collider. quarks.core's ApplyCollision behavior delegates to a
|
|
5
|
+
* host-provided `PhysicsResolver` (a global singleton) and ships no collider primitive of its
|
|
6
|
+
* own, so the editor supplies this — the common "particles bounce off the floor" case.
|
|
7
|
+
*
|
|
8
|
+
* `resolve` returns true and fills the surface normal when the point is at or below the plane,
|
|
9
|
+
* which is exactly what ApplyCollision.update reflects the velocity against.
|
|
10
|
+
*/
|
|
11
|
+
export class GroundPlaneResolver {
|
|
12
|
+
constructor(public y = 0) {}
|
|
13
|
+
|
|
14
|
+
resolve(position: Vector3, normal: Vector3): boolean {
|
|
15
|
+
if (position.y <= this.y) {
|
|
16
|
+
normal.set(0, 1, 0);
|
|
17
|
+
return true;
|
|
18
|
+
}
|
|
19
|
+
return false;
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Registers (once) a shared ground-plane resolver so ApplyCollision behaviors — whether added
|
|
25
|
+
* in the inspector or deserialized from imported JSON — have something to collide against.
|
|
26
|
+
* Idempotent: an already-registered resolver is left untouched and returned.
|
|
27
|
+
*/
|
|
28
|
+
export function ensureGroundResolver(): GroundPlaneResolver {
|
|
29
|
+
const existing = getPhysicsResolver();
|
|
30
|
+
if (existing instanceof GroundPlaneResolver) {
|
|
31
|
+
return existing;
|
|
32
|
+
}
|
|
33
|
+
if (existing) {
|
|
34
|
+
// A host registered its own resolver; don't clobber it, but we can't tweak its floor.
|
|
35
|
+
return existing as GroundPlaneResolver;
|
|
36
|
+
}
|
|
37
|
+
const resolver = new GroundPlaneResolver(0);
|
|
38
|
+
setPhysicsResolver(resolver);
|
|
39
|
+
return resolver;
|
|
40
|
+
}
|
package/src/core/groups.ts
CHANGED
|
@@ -1,29 +1,29 @@
|
|
|
1
|
-
import {TransformNode} from '@babylonjs/core/Meshes/transformNode';
|
|
2
|
-
|
|
3
|
-
/**
|
|
4
|
-
* Groups the given nodes under a new organizational TransformNode. Each node's world
|
|
5
|
-
* transform is preserved via `setParent` — grouping never moves anything visually, it only
|
|
6
|
-
* changes the hierarchy so the group can be repositioned/organized as one unit afterwards.
|
|
7
|
-
*/
|
|
8
|
-
export function groupNodes(nodes: TransformNode[], name = 'Group'): TransformNode {
|
|
9
|
-
const scene = nodes[0].getScene();
|
|
10
|
-
const group = new TransformNode(name, scene);
|
|
11
|
-
group.setParent(nodes[0].parent);
|
|
12
|
-
for (const node of nodes) {
|
|
13
|
-
node.setParent(group);
|
|
14
|
-
}
|
|
15
|
-
return group;
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
/**
|
|
19
|
-
* Dissolves a group, reparenting its children onto the group's own parent and disposing the
|
|
20
|
-
* (now childless) group node. World transform of each child is preserved.
|
|
21
|
-
*/
|
|
22
|
-
export function ungroupNode(group: TransformNode): void {
|
|
23
|
-
const parent = group.parent;
|
|
24
|
-
const children = group.getChildren().filter((c): c is TransformNode => c instanceof TransformNode);
|
|
25
|
-
for (const child of children) {
|
|
26
|
-
child.setParent(parent);
|
|
27
|
-
}
|
|
28
|
-
group.dispose(true, false);
|
|
29
|
-
}
|
|
1
|
+
import {TransformNode} from '@babylonjs/core/Meshes/transformNode';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Groups the given nodes under a new organizational TransformNode. Each node's world
|
|
5
|
+
* transform is preserved via `setParent` — grouping never moves anything visually, it only
|
|
6
|
+
* changes the hierarchy so the group can be repositioned/organized as one unit afterwards.
|
|
7
|
+
*/
|
|
8
|
+
export function groupNodes(nodes: TransformNode[], name = 'Group'): TransformNode {
|
|
9
|
+
const scene = nodes[0].getScene();
|
|
10
|
+
const group = new TransformNode(name, scene);
|
|
11
|
+
group.setParent(nodes[0].parent);
|
|
12
|
+
for (const node of nodes) {
|
|
13
|
+
node.setParent(group);
|
|
14
|
+
}
|
|
15
|
+
return group;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Dissolves a group, reparenting its children onto the group's own parent and disposing the
|
|
20
|
+
* (now childless) group node. World transform of each child is preserved.
|
|
21
|
+
*/
|
|
22
|
+
export function ungroupNode(group: TransformNode): void {
|
|
23
|
+
const parent = group.parent;
|
|
24
|
+
const children = group.getChildren().filter((c): c is TransformNode => c instanceof TransformNode);
|
|
25
|
+
for (const child of children) {
|
|
26
|
+
child.setParent(parent);
|
|
27
|
+
}
|
|
28
|
+
group.dispose(true, false);
|
|
29
|
+
}
|