react-three-game 0.0.14 → 0.0.16

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/README.md CHANGED
@@ -17,45 +17,26 @@ npm i react-three-game @react-three/fiber three
17
17
  Scenes are JSON prefabs. Components are registered modules. Hierarchy is declarative.
18
18
 
19
19
  ```jsx
20
- <PrefabRoot data={{
21
- root: {
22
- id: "cube",
23
- components: {
24
- transform: { type: "Transform", properties: { position: [0, 1, 0] } },
25
- geometry: { type: "Geometry", properties: { geometryType: "box" } },
26
- material: { type: "Material", properties: { color: "green" } },
27
- physics: { type: "Physics", properties: { type: "dynamic" } }
28
- }
29
- }
30
- }} />
31
- ```
32
-
33
- ## Tailwind CSS Support
34
-
35
- This library uses Tailwind CSS for styling its editor components. To ensure styles are correctly applied in your application, you need to configure Tailwind to scan the library's source files.
36
-
37
- ### Tailwind v4
38
-
39
- Add the library path to your CSS entry point using the `@source` directive:
40
-
41
- ```css
42
- @import "tailwindcss";
43
- @source "../../node_modules/react-three-game/dist/**/*.{js,ts,jsx,tsx}";
20
+ <PrefabRoot data={{
21
+ root: {
22
+ id: "cube",
23
+ components: {
24
+ transform: { type: "Transform", properties: { position: [0, 1, 0] } },
25
+ geometry: { type: "Geometry", properties: { geometryType: "box" } },
26
+ material: { type: "Material", properties: { color: "green" } },
27
+ physics: { type: "Physics", properties: { type: "dynamic" } }
28
+ }
29
+ }
30
+ }} />
44
31
  ```
45
32
 
46
- ### Tailwind v3
33
+ ## Styling
47
34
 
48
- Add the library path to your `tailwind.config.js`:
35
+ The prefab editor UI ships with **inline styles** (no Tailwind / CSS framework required). That means you can install and render it without any additional build-time CSS configuration.
49
36
 
50
- ```js
51
- module.exports = {
52
- content: [
53
- // ...
54
- "./node_modules/react-three-game/dist/**/*.{js,ts,jsx,tsx}",
55
- ],
56
- // ...
57
- }
58
- ```
37
+ If you want to fully restyle the editor, you can:
38
+ - Wrap `PrefabEditor` in your own layout and override positioning.
39
+ - Fork/compose the editor UI components (they’re plain React components).
59
40
 
60
41
  ## Quick Start
61
42
 
@@ -64,41 +45,44 @@ npm install react-three-game @react-three/fiber @react-three/rapier three
64
45
  ```
65
46
 
66
47
  ```jsx
48
+ import { Physics } from '@react-three/rapier';
67
49
  import { GameCanvas, PrefabRoot } from 'react-three-game';
68
50
 
69
51
  export default function App() {
70
52
  return (
71
53
  <GameCanvas>
72
- <ambientLight intensity={0.5} />
73
- <PrefabRoot data={{
74
- id: "scene",
75
- root: {
76
- id: "root",
77
- components: {
78
- transform: { type: "Transform", properties: { position: [0, 0, 0] } }
79
- },
80
- children: [
81
- {
82
- id: "floor",
83
- components: {
84
- transform: { type: "Transform", properties: { position: [0, -1, 0] } },
85
- geometry: { type: "Geometry", properties: { geometryType: "box", args: [10, 0.5, 10] } },
86
- material: { type: "Material", properties: { color: "#2d5f2e" } },
87
- physics: { type: "Physics", properties: { type: "fixed" } }
88
- }
89
- },
90
- {
91
- id: "player",
92
- components: {
93
- transform: { type: "Transform", properties: { position: [0, 2, 0] } },
94
- geometry: { type: "Geometry", properties: { geometryType: "sphere" } },
95
- material: { type: "Material", properties: { color: "#ff6b6b" } },
96
- physics: { type: "Physics", properties: { type: "dynamic" } }
54
+ <Physics>
55
+ <ambientLight intensity={0.8} />
56
+ <PrefabRoot
57
+ data={{
58
+ id: "scene",
59
+ name: "scene",
60
+ root: {
61
+ id: "root",
62
+ children: [
63
+ {
64
+ id: "ground",
65
+ components: {
66
+ transform: { type: "Transform", properties: { position: [0, 0, 0], rotation: [-1.57, 0, 0] } },
67
+ geometry: { type: "Geometry", properties: { geometryType: "plane", args: [50, 50] } },
68
+ material: { type: "Material", properties: { color: "green" } },
69
+ physics: { type: "Physics", properties: { type: "fixed" } }
70
+ }
71
+ },
72
+ {
73
+ id: "player",
74
+ components: {
75
+ transform: { type: "Transform", properties: { position: [0, 2, 0] } },
76
+ geometry: { type: "Geometry", properties: { geometryType: "sphere" } },
77
+ material: { type: "Material", properties: { color: "#ff6b6b" } },
78
+ physics: { type: "Physics", properties: { type: "dynamic" } }
79
+ }
80
+ }
81
+ ]
97
82
  }
98
- }
99
- ]
100
- }
101
- }} />
83
+ }}
84
+ />
85
+ </Physics>
102
86
  </GameCanvas>
103
87
  );
104
88
  }
@@ -109,8 +93,8 @@ export default function App() {
109
93
  ```typescript
110
94
  interface GameObject {
111
95
  id: string;
112
- enabled?: boolean;
113
- visible?: boolean;
96
+ disabled?: boolean;
97
+ hidden?: boolean;
114
98
  components: {
115
99
  transform?: TransformComponent;
116
100
  geometry?: GeometryComponent;
@@ -0,0 +1,35 @@
1
+ import type { GameObject } from "../tools/prefabeditor/types";
2
+ export type Vec3 = [number, number, number];
3
+ export interface GroundOptions {
4
+ /** GameObject id. Defaults to "ground". */
5
+ id?: string;
6
+ /** Plane size. Defaults to 50. */
7
+ size?: number;
8
+ /** Transform overrides. */
9
+ position?: Vec3;
10
+ rotation?: Vec3;
11
+ scale?: Vec3;
12
+ /** Material overrides. */
13
+ color?: string;
14
+ texture?: string;
15
+ /** When true, set repeat wrapping. Defaults to true if texture is provided. */
16
+ repeat?: boolean;
17
+ /** Texture repeat counts when repeat=true. Defaults to [25,25]. */
18
+ repeatCount?: [number, number];
19
+ /** Physics body type. Defaults to "fixed". */
20
+ physicsType?: "fixed" | "dynamic" | "kinematic";
21
+ /** Set true to hide the node. */
22
+ hidden?: boolean;
23
+ /** Set true to disable the node. */
24
+ disabled?: boolean;
25
+ }
26
+ /**
27
+ * Create a ready-to-use plane ground GameObject.
28
+ *
29
+ * Designed to reduce prefab boilerplate:
30
+ * - Transform (rotated to lie flat)
31
+ * - Geometry (plane)
32
+ * - Material (optional texture + repeat)
33
+ * - Physics (fixed by default)
34
+ */
35
+ export declare function ground(options?: GroundOptions): GameObject;
@@ -0,0 +1,44 @@
1
+ /**
2
+ * Create a ready-to-use plane ground GameObject.
3
+ *
4
+ * Designed to reduce prefab boilerplate:
5
+ * - Transform (rotated to lie flat)
6
+ * - Geometry (plane)
7
+ * - Material (optional texture + repeat)
8
+ * - Physics (fixed by default)
9
+ */
10
+ export function ground(options = {}) {
11
+ const { id = "ground", size = 50, position = [0, 0, 0], rotation = [-Math.PI / 2, 0, 0], scale = [1, 1, 1], color = "white", texture, repeat = texture ? true : false, repeatCount = [25, 25], physicsType = "fixed", hidden = false, disabled = false, } = options;
12
+ return {
13
+ id,
14
+ disabled,
15
+ hidden,
16
+ components: {
17
+ transform: {
18
+ type: "Transform",
19
+ properties: {
20
+ position,
21
+ rotation,
22
+ scale,
23
+ },
24
+ },
25
+ geometry: {
26
+ type: "Geometry",
27
+ properties: {
28
+ geometryType: "plane",
29
+ args: [size, size],
30
+ },
31
+ },
32
+ material: {
33
+ type: "Material",
34
+ properties: Object.assign(Object.assign({ color }, (texture ? { texture } : {})), (repeat ? { repeat: true, repeatCount } : {})),
35
+ },
36
+ physics: {
37
+ type: "Physics",
38
+ properties: {
39
+ type: physicsType,
40
+ },
41
+ },
42
+ },
43
+ };
44
+ }
package/dist/index.d.ts CHANGED
@@ -3,4 +3,5 @@ export { default as PrefabEditor } from './tools/prefabeditor/PrefabEditor';
3
3
  export { default as PrefabRoot } from './tools/prefabeditor/PrefabRoot';
4
4
  export { DragDropLoader } from './tools/dragdrop/DragDropLoader';
5
5
  export { TextureListViewer, ModelListViewer, SoundListViewer, SharedCanvas, } from './tools/assetviewer/page';
6
+ export * from './helpers';
6
7
  export type { Prefab, GameObject } from './tools/prefabeditor/types';
package/dist/index.js CHANGED
@@ -4,3 +4,5 @@ export { default as PrefabEditor } from './tools/prefabeditor/PrefabEditor';
4
4
  export { default as PrefabRoot } from './tools/prefabeditor/PrefabRoot';
5
5
  export { DragDropLoader } from './tools/dragdrop/DragDropLoader';
6
6
  export { TextureListViewer, ModelListViewer, SoundListViewer, SharedCanvas, } from './tools/assetviewer/page';
7
+ // Helpers
8
+ export * from './helpers';
@@ -1,3 +1,4 @@
1
+ "use client";
1
2
  var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
2
3
  function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
3
4
  return new (P || (P = Promise))(function (resolve, reject) {
@@ -18,7 +19,7 @@ var __rest = (this && this.__rest) || function (s, e) {
18
19
  }
19
20
  return t;
20
21
  };
21
- import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-runtime";
22
+ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
22
23
  import { Canvas, extend } from "@react-three/fiber";
23
24
  import { WebGPURenderer, MeshBasicNodeMaterial, MeshStandardNodeMaterial, SpriteNodeMaterial, PCFShadowMap } from "three/webgpu";
24
25
  import { Suspense, useState } from "react";
@@ -33,16 +34,15 @@ extend({
33
34
  export default function GameCanvas(_a) {
34
35
  var { loader = false, children } = _a, props = __rest(_a, ["loader", "children"]);
35
36
  const [frameloop, setFrameloop] = useState("never");
36
- const [loading, setLoading] = useState(true);
37
- return _jsxs(_Fragment, { children: [_jsx(Canvas, { shadows: { type: PCFShadowMap, }, frameloop: frameloop, gl: (_a) => __awaiter(this, [_a], void 0, function* ({ canvas }) {
38
- const renderer = new WebGPURenderer(Object.assign({ canvas: canvas,
39
- // @ts-expect-error futuristic
40
- shadowMap: true, antialias: true }, props));
41
- yield renderer.init().then(() => {
42
- setFrameloop("always");
43
- });
44
- return renderer;
45
- }), camera: {
46
- position: [0, 1, 5],
47
- }, children: _jsx(Suspense, { children: children }) }), loader ? _jsx(Loader, {}) : null] });
37
+ return _jsx(_Fragment, { children: _jsxs(Canvas, { style: { touchAction: 'none' }, shadows: { type: PCFShadowMap, }, frameloop: frameloop, gl: (_a) => __awaiter(this, [_a], void 0, function* ({ canvas }) {
38
+ const renderer = new WebGPURenderer(Object.assign({ canvas: canvas,
39
+ // @ts-expect-error futuristic
40
+ shadowMap: true, antialias: true }, props));
41
+ yield renderer.init().then(() => {
42
+ setFrameloop("always");
43
+ });
44
+ return renderer;
45
+ }), camera: {
46
+ position: [0, 1, 5],
47
+ }, children: [_jsx(Suspense, { children: children }), loader ? _jsx(Loader, {}) : null] }) });
48
48
  }
@@ -1,3 +1,4 @@
1
+ "use client";
1
2
  import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-runtime";
2
3
  // DragDropLoader.tsx
3
4
  import { useEffect } from "react";
@@ -6,6 +6,94 @@ export default function EditorTree({ prefabData, setPrefabData, selectedId, setS
6
6
  const [draggedId, setDraggedId] = useState(null);
7
7
  const [collapsedIds, setCollapsedIds] = useState(new Set());
8
8
  const [isTreeCollapsed, setIsTreeCollapsed] = useState(false);
9
+ const styles = {
10
+ panel: {
11
+ background: "rgba(0,0,0,0.55)",
12
+ color: "rgba(255,255,255,0.9)",
13
+ border: "1px solid rgba(255,255,255,0.12)",
14
+ borderRadius: 6,
15
+ overflow: "hidden",
16
+ maxHeight: "85vh",
17
+ display: "flex",
18
+ flexDirection: "column",
19
+ backdropFilter: "blur(6px)",
20
+ WebkitBackdropFilter: "blur(6px)",
21
+ fontFamily: "ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, 'Liberation Mono', 'Courier New', monospace",
22
+ fontSize: 11,
23
+ lineHeight: 1.2,
24
+ userSelect: "none",
25
+ WebkitUserSelect: "none",
26
+ },
27
+ panelHeader: {
28
+ padding: "4px 6px",
29
+ borderBottom: "1px solid rgba(255,255,255,0.10)",
30
+ display: "flex",
31
+ gap: 8,
32
+ alignItems: "center",
33
+ justifyContent: "space-between",
34
+ cursor: "pointer",
35
+ background: "rgba(255,255,255,0.05)",
36
+ textTransform: "uppercase",
37
+ letterSpacing: "0.08em",
38
+ fontSize: 10,
39
+ color: "rgba(255,255,255,0.7)",
40
+ },
41
+ scroll: {
42
+ overflowY: "auto",
43
+ },
44
+ row: {
45
+ display: "flex",
46
+ alignItems: "center",
47
+ padding: "2px 6px",
48
+ borderBottom: "1px solid rgba(255,255,255,0.07)",
49
+ cursor: "pointer",
50
+ whiteSpace: "nowrap",
51
+ },
52
+ rowSelected: {
53
+ background: "rgba(255,255,255,0.10)",
54
+ },
55
+ chevron: {
56
+ width: 12,
57
+ textAlign: "center",
58
+ opacity: 0.55,
59
+ fontSize: 10,
60
+ marginRight: 4,
61
+ cursor: "pointer",
62
+ },
63
+ idText: {
64
+ fontSize: 11,
65
+ overflow: "hidden",
66
+ textOverflow: "ellipsis",
67
+ },
68
+ contextMenu: {
69
+ position: "fixed",
70
+ zIndex: 50,
71
+ minWidth: 120,
72
+ background: "rgba(0,0,0,0.82)",
73
+ border: "1px solid rgba(255,255,255,0.16)",
74
+ borderRadius: 6,
75
+ overflow: "hidden",
76
+ boxShadow: "0 12px 32px rgba(0,0,0,0.45)",
77
+ backdropFilter: "blur(6px)",
78
+ WebkitBackdropFilter: "blur(6px)",
79
+ },
80
+ menuItem: {
81
+ width: "100%",
82
+ textAlign: "left",
83
+ padding: "6px 8px",
84
+ background: "transparent",
85
+ border: "none",
86
+ color: "rgba(255,255,255,0.9)",
87
+ font: "inherit",
88
+ cursor: "pointer",
89
+ },
90
+ menuItemDanger: {
91
+ color: "rgba(255,120,120,0.95)",
92
+ },
93
+ menuDivider: {
94
+ borderTop: "1px solid rgba(255,255,255,0.10)",
95
+ }
96
+ };
9
97
  if (!prefabData || !setPrefabData)
10
98
  return null;
11
99
  const handleContextMenu = (e, nodeId) => {
@@ -30,8 +118,6 @@ export default function EditorTree({ prefabData, setPrefabData, selectedId, setS
30
118
  var _a;
31
119
  const newNode = {
32
120
  id: crypto.randomUUID(),
33
- enabled: true,
34
- visible: true,
35
121
  components: {
36
122
  transform: {
37
123
  type: "Transform",
@@ -134,9 +220,35 @@ export default function EditorTree({ prefabData, setPrefabData, selectedId, setS
134
220
  const isSelected = node.id === selectedId;
135
221
  const isCollapsed = collapsedIds.has(node.id);
136
222
  const hasChildren = node.children && node.children.length > 0;
137
- return (_jsxs("div", { className: "select-none", children: [_jsxs("div", { className: `flex items-center py-0.5 px-1 cursor-pointer border-b border-cyan-500/10 ${isSelected ? 'bg-cyan-500/30 hover:bg-cyan-500/40 border-cyan-400/30' : 'hover:bg-cyan-500/10'}`, style: { paddingLeft: `${depth * 8 + 4}px` }, onClick: (e) => { e.stopPropagation(); setSelectedId(node.id); }, onContextMenu: (e) => handleContextMenu(e, node.id), draggable: node.id !== prefabData.root.id, onDragStart: (e) => handleDragStart(e, node.id), onDragOver: (e) => handleDragOver(e, node.id), onDrop: (e) => handleDrop(e, node.id), children: [_jsx("span", { className: `mr-0.5 w-3 text-center text-cyan-400/50 hover:text-cyan-400 cursor-pointer text-[8px] ${hasChildren ? '' : 'invisible'}`, onClick: (e) => hasChildren && toggleCollapse(e, node.id), children: isCollapsed ? '▶' : '▼' }), _jsx("span", { className: "text-[10px] truncate font-mono text-cyan-300", children: node.id })] }), !isCollapsed && node.children && (_jsx("div", { children: node.children.map(child => renderNode(child, depth + 1)) }))] }, node.id));
223
+ return (_jsxs("div", { children: [_jsxs("div", { style: Object.assign(Object.assign(Object.assign({}, styles.row), (isSelected ? styles.rowSelected : null)), { paddingLeft: `${depth * 10 + 6}px` }), onClick: (e) => { e.stopPropagation(); setSelectedId(node.id); }, onContextMenu: (e) => handleContextMenu(e, node.id), draggable: node.id !== prefabData.root.id, onDragStart: (e) => handleDragStart(e, node.id), onDragOver: (e) => handleDragOver(e, node.id), onDrop: (e) => handleDrop(e, node.id), onPointerEnter: (e) => {
224
+ if (!isSelected)
225
+ e.currentTarget.style.background = "rgba(255,255,255,0.06)";
226
+ }, onPointerLeave: (e) => {
227
+ if (!isSelected)
228
+ e.currentTarget.style.background = "transparent";
229
+ }, children: [_jsx("span", { style: Object.assign(Object.assign({}, styles.chevron), { visibility: hasChildren ? 'visible' : 'hidden' }), onClick: (e) => hasChildren && toggleCollapse(e, node.id), onPointerEnter: (e) => {
230
+ e.currentTarget.style.opacity = "0.9";
231
+ }, onPointerLeave: (e) => {
232
+ e.currentTarget.style.opacity = "0.55";
233
+ }, children: isCollapsed ? '▶' : '▼' }), _jsx("span", { style: styles.idText, children: node.id })] }), !isCollapsed && node.children && (_jsx("div", { children: node.children.map(child => renderNode(child, depth + 1)) }))] }, node.id));
138
234
  };
139
- return (_jsxs(_Fragment, { children: [_jsxs("div", { className: "bg-black/70 backdrop-blur-sm text-white border border-cyan-500/30 max-h-[85vh] overflow-y-auto flex flex-col", style: { width: isTreeCollapsed ? 'auto' : '14rem' }, onClick: closeContextMenu, children: [_jsxs("div", { className: "px-1.5 py-1 font-mono text-[10px] bg-cyan-500/10 border-b border-cyan-500/30 sticky top-0 uppercase tracking-wider text-cyan-400/80 cursor-pointer hover:bg-cyan-500/20 flex items-center justify-between", onClick: (e) => { e.stopPropagation(); setIsTreeCollapsed(!isTreeCollapsed); }, children: [_jsx("span", { children: "Prefab Graph" }), _jsx("span", { className: "text-[8px]", children: isTreeCollapsed ? '▶' : '◀' })] }), !isTreeCollapsed && (_jsx("div", { className: "flex-1 py-0.5", children: renderNode(prefabData.root) }))] }), contextMenu && (_jsxs("div", { className: "fixed bg-black/90 backdrop-blur-sm border border-cyan-500/40 z-50 min-w-[100px]", style: { top: contextMenu.y, left: contextMenu.x }, onClick: (e) => e.stopPropagation(), onPointerLeave: closeContextMenu, children: [_jsx("button", { className: "w-full text-left px-2 py-1 hover:bg-cyan-500/20 text-[10px] text-cyan-300 font-mono border-b border-cyan-500/20", onClick: () => handleAddChild(contextMenu.nodeId), children: "Add Child" }), contextMenu.nodeId !== prefabData.root.id && (_jsxs(_Fragment, { children: [_jsx("button", { className: "w-full text-left px-2 py-1 hover:bg-cyan-500/20 text-[10px] text-cyan-300 font-mono border-b border-cyan-500/20", onClick: () => handleDuplicate(contextMenu.nodeId), children: "Duplicate" }), _jsx("button", { className: "w-full text-left px-2 py-1 hover:bg-red-500/20 text-[10px] text-red-400 font-mono", onClick: () => handleDelete(contextMenu.nodeId), children: "Delete" })] }))] }))] }));
235
+ return (_jsxs(_Fragment, { children: [_jsxs("div", { style: Object.assign(Object.assign({}, styles.panel), { width: isTreeCollapsed ? 'auto' : '14rem' }), onClick: closeContextMenu, children: [_jsxs("div", { style: styles.panelHeader, onClick: (e) => { e.stopPropagation(); setIsTreeCollapsed(!isTreeCollapsed); }, onPointerEnter: (e) => {
236
+ e.currentTarget.style.background = "rgba(255,255,255,0.08)";
237
+ }, onPointerLeave: (e) => {
238
+ e.currentTarget.style.background = "rgba(255,255,255,0.05)";
239
+ }, children: [_jsx("span", { children: "Prefab Graph" }), _jsx("span", { style: { fontSize: 10, opacity: 0.8 }, children: isTreeCollapsed ? '▶' : '◀' })] }), !isTreeCollapsed && (_jsx("div", { style: Object.assign(Object.assign({}, styles.scroll), { padding: 2 }), children: renderNode(prefabData.root) }))] }), contextMenu && (_jsxs("div", { style: Object.assign(Object.assign({}, styles.contextMenu), { top: contextMenu.y, left: contextMenu.x }), onClick: (e) => e.stopPropagation(), onPointerLeave: closeContextMenu, children: [_jsx("button", { style: Object.assign(Object.assign({}, styles.menuItem), styles.menuDivider), onClick: () => handleAddChild(contextMenu.nodeId), onPointerEnter: (e) => {
240
+ e.currentTarget.style.background = "rgba(255,255,255,0.08)";
241
+ }, onPointerLeave: (e) => {
242
+ e.currentTarget.style.background = "transparent";
243
+ }, children: "Add Child" }), contextMenu.nodeId !== prefabData.root.id && (_jsxs(_Fragment, { children: [_jsx("button", { style: Object.assign(Object.assign({}, styles.menuItem), styles.menuDivider), onClick: () => handleDuplicate(contextMenu.nodeId), onPointerEnter: (e) => {
244
+ e.currentTarget.style.background = "rgba(255,255,255,0.08)";
245
+ }, onPointerLeave: (e) => {
246
+ e.currentTarget.style.background = "transparent";
247
+ }, children: "Duplicate" }), _jsx("button", { style: Object.assign(Object.assign({}, styles.menuItem), styles.menuItemDanger), onClick: () => handleDelete(contextMenu.nodeId), onPointerEnter: (e) => {
248
+ e.currentTarget.style.background = "rgba(255,255,255,0.08)";
249
+ }, onPointerLeave: (e) => {
250
+ e.currentTarget.style.background = "transparent";
251
+ }, children: "Delete" })] }))] }))] }));
140
252
  }
141
253
  // --- Helpers ---
142
254
  function findNode(root, id) {