@lukekaalim/act-three 3.10.0 → 5.2.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/components/groups.js +37 -0
- package/components/lights.js +1 -0
- package/components.js +65 -26
- package/entry.js +7 -0
- package/hooks/matrix.js +28 -0
- package/hooks.js +177 -0
- package/objects.js +50 -0
- package/package.json +5 -4
- package/props.js +78 -0
- package/reconciler.js +27 -0
- package/renderer.js +32 -0
- package/main.js +0 -25
- package/node.js +0 -88
- package/render.js +0 -163
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
// @flow strict
|
|
2
|
+
/*:: import type { Component, Ref } from '@lukekaalim/act'; */
|
|
3
|
+
/*:: import type { Euler, Group, Object3D } from "three"; */
|
|
4
|
+
/*:: import type { GroupProps, RefProp } from '../components'; */
|
|
5
|
+
|
|
6
|
+
import { h, useEffect, useRef, useState } from '@lukekaalim/act';
|
|
7
|
+
import { Matrix4, Vector3 } from "three";
|
|
8
|
+
import { group } from '../components.js';
|
|
9
|
+
|
|
10
|
+
/*::
|
|
11
|
+
export type LookAtGroupProps = {
|
|
12
|
+
...$Diff<GroupProps, { rotation?: Euler, ref?: RefProp<Group> }>,
|
|
13
|
+
target: Vector3,
|
|
14
|
+
deps?: mixed[],
|
|
15
|
+
};
|
|
16
|
+
*/
|
|
17
|
+
|
|
18
|
+
export const LookAtGroup/*: Component<LookAtGroupProps>*/ = ({
|
|
19
|
+
target,
|
|
20
|
+
children,
|
|
21
|
+
position = new Vector3(0, 0, 0),
|
|
22
|
+
deps = [],
|
|
23
|
+
...groupProps
|
|
24
|
+
}) => {
|
|
25
|
+
const groupRef = useRef(null);
|
|
26
|
+
|
|
27
|
+
useEffect(() => {
|
|
28
|
+
const { current: groupNode } = groupRef;
|
|
29
|
+
if (!groupNode)
|
|
30
|
+
return;
|
|
31
|
+
|
|
32
|
+
groupNode.setRotationFromMatrix(new Matrix4().lookAt(position, target, new Vector3(0, 1, 0)));
|
|
33
|
+
groupNode.updateMatrix();
|
|
34
|
+
}, [target.x, target.y, target.z, position.x, position.y, position.z, ...deps]);
|
|
35
|
+
|
|
36
|
+
return h(group, { ...groupProps, position, ref: groupRef }, children);
|
|
37
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
// @flow strict
|
package/components.js
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
// @flow strict
|
|
2
|
-
/*:: import type { Component
|
|
2
|
+
/*:: import type { Component } from '@lukekaalim/act'; */
|
|
3
3
|
/*:: import type {
|
|
4
4
|
Vector3,
|
|
5
|
+
Vector2,
|
|
5
6
|
Object3D,
|
|
6
7
|
Quaternion,
|
|
7
8
|
BufferGeometry,
|
|
@@ -11,13 +12,19 @@
|
|
|
11
12
|
Group,
|
|
12
13
|
Camera,
|
|
13
14
|
PerspectiveCamera,
|
|
14
|
-
Euler
|
|
15
|
+
Euler,
|
|
16
|
+
Scene,
|
|
17
|
+
CubeTexture,
|
|
18
|
+
Fog,
|
|
19
|
+
WebGLRenderer,
|
|
15
20
|
} from "three"; */
|
|
16
21
|
/*:: import * as Three from 'three'; */
|
|
17
22
|
|
|
18
23
|
/*::
|
|
24
|
+
export type RefProp<T> = ((reference: T) => mixed) | { current: T | any }
|
|
25
|
+
|
|
19
26
|
export type Object3DProps<T> = {
|
|
20
|
-
ref?:
|
|
27
|
+
ref?: RefProp<?T>,
|
|
21
28
|
|
|
22
29
|
name?: string,
|
|
23
30
|
position?: Vector3,
|
|
@@ -26,14 +33,29 @@ export type Object3DProps<T> = {
|
|
|
26
33
|
visible?: boolean,
|
|
27
34
|
scale?: Vector3,
|
|
28
35
|
};
|
|
36
|
+
|
|
37
|
+
export type SceneProps = {
|
|
38
|
+
...Object3DProps<Scene>,
|
|
39
|
+
autoUpdate?: ?boolean,
|
|
40
|
+
background?: ?(Color | Texture | CubeTexture),
|
|
41
|
+
environment?: ?Texture,
|
|
42
|
+
fog?: ?Fog,
|
|
43
|
+
overrideMaterial?: ?Material
|
|
44
|
+
};
|
|
45
|
+
|
|
29
46
|
export type MeshProps = {
|
|
30
47
|
...Object3DProps<Three.Mesh>,
|
|
31
48
|
geometry?: BufferGeometry,
|
|
32
49
|
material?: Material,
|
|
33
50
|
};
|
|
34
|
-
export type
|
|
51
|
+
export type SpriteProps = {
|
|
52
|
+
...Object3DProps<Three.Sprite>,
|
|
53
|
+
material: Material;
|
|
54
|
+
center: Vector2;
|
|
55
|
+
};
|
|
56
|
+
export type InstancedMeshProps = {
|
|
35
57
|
...MeshProps,
|
|
36
|
-
...Object3DProps<Three.
|
|
58
|
+
...Object3DProps<Three.InstancedMesh>,
|
|
37
59
|
};
|
|
38
60
|
export type PointLightsProps = {
|
|
39
61
|
...Object3DProps<Three.PointLight>,
|
|
@@ -42,22 +64,23 @@ export type PointLightsProps = {
|
|
|
42
64
|
intensity?: number,
|
|
43
65
|
power?: number,
|
|
44
66
|
};
|
|
67
|
+
export type DirectionalLightProps = {
|
|
68
|
+
...Object3DProps<Three.DirectionalLight>,
|
|
69
|
+
distance?: number,
|
|
70
|
+
decay?: number,
|
|
71
|
+
intensity?: number,
|
|
72
|
+
power?: number,
|
|
73
|
+
};
|
|
74
|
+
export type AmbientLightProps = {
|
|
75
|
+
...Object3DProps<Three.AmbientLight>,
|
|
76
|
+
intensity?: number,
|
|
77
|
+
power?: number,
|
|
78
|
+
};
|
|
45
79
|
export type PointsProps = {
|
|
46
80
|
...Object3DProps<Three.Points>,
|
|
47
81
|
geometry?: BufferGeometry,
|
|
48
82
|
material?: Material,
|
|
49
83
|
};
|
|
50
|
-
type Ref<T> = ((reference: T) => mixed) | { current: T | any };
|
|
51
|
-
export type ThreeProps = {
|
|
52
|
-
ref?: Ref<{ scene: Three.Scene, camera: PerspectiveCamera, renderer: Three.WebGLRenderer }>,
|
|
53
|
-
width: number,
|
|
54
|
-
height: number,
|
|
55
|
-
setStyle?: boolean,
|
|
56
|
-
background?: Color | null | Texture,
|
|
57
|
-
alpha?: boolean,
|
|
58
|
-
camera?: Camera,
|
|
59
|
-
onRender?: (timestamp: number) => void,
|
|
60
|
-
};
|
|
61
84
|
export type GroupProps = {
|
|
62
85
|
...Object3DProps<Three.Group>,
|
|
63
86
|
group?: ?Three.Object3D,
|
|
@@ -70,15 +93,31 @@ export type PerspectiveCameraProps = {
|
|
|
70
93
|
fov?: number,
|
|
71
94
|
zoom?: number,
|
|
72
95
|
}
|
|
96
|
+
export type OrthographicCameraProps = {
|
|
97
|
+
...Object3DProps<Three.OrthographicCamera>,
|
|
98
|
+
aspect?: number,
|
|
99
|
+
near?: number,
|
|
100
|
+
far?: number,
|
|
101
|
+
zoom?: number,
|
|
102
|
+
}
|
|
73
103
|
*/
|
|
74
104
|
|
|
75
|
-
export const
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
export const
|
|
105
|
+
export const scene = ('scene'/*: Component<SceneProps>*/);
|
|
106
|
+
export const group = ('group'/*: Component<GroupProps>*/);
|
|
107
|
+
|
|
108
|
+
export const perspectiveCamera = ('perspectiveCamera'/*: Component<PerspectiveCameraProps>*/);
|
|
109
|
+
export const orthographicCamera = ('orthographicCamera'/*: Component<OrthographicCameraProps>*/);
|
|
110
|
+
|
|
111
|
+
export const mesh = ('mesh'/*: Component<MeshProps>*/);
|
|
112
|
+
export const sprite = ('sprite'/*: Component<SpriteProps>*/);
|
|
113
|
+
export const points = ('points'/*: Component<PointsProps>*/);
|
|
114
|
+
export const instancedMesh = ('instancedMesh'/*: Component<InstancedMeshProps>*/);
|
|
115
|
+
|
|
116
|
+
export const pointLight = ('pointLight'/*: Component<PointLightsProps>*/);
|
|
117
|
+
export const ambientLight = ('ambientLight'/*: Component<AmbientLightProps>*/);
|
|
118
|
+
export const hemisphereLight = ('hemisphereLight'/*: Component<PointLightsProps>*/);
|
|
119
|
+
export const directionalLight = ('directionalLight'/*: Component<DirectionalLightProps>*/);
|
|
120
|
+
|
|
121
|
+
|
|
122
|
+
export * from './components/groups.js';
|
|
123
|
+
export * from './components/lights.js';
|
package/entry.js
ADDED
package/hooks/matrix.js
ADDED
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
// @flow strict
|
|
2
|
+
/*:: import type { Component, Ref } from '@lukekaalim/act'; */
|
|
3
|
+
/*:: import type { Euler, Group, Object3D } from "three"; */
|
|
4
|
+
/*:: import type { GroupProps, RefProp } from '../components'; */
|
|
5
|
+
|
|
6
|
+
import { h, useEffect, useRef, useState } from '@lukekaalim/act';
|
|
7
|
+
import { Matrix4, Vector3 } from "three";
|
|
8
|
+
import { group } from '../components.js';
|
|
9
|
+
|
|
10
|
+
export const defaultUp/*: Vector3*/ = new Vector3(0, 1, 0);
|
|
11
|
+
|
|
12
|
+
export const useLookAt = /*:: <T: Object3D>*/(
|
|
13
|
+
objectRef/*: Ref<?T>*/,
|
|
14
|
+
target/*: Vector3*/,
|
|
15
|
+
deps/*: mixed[]*/ = [],
|
|
16
|
+
up/*: Vector3*/ = defaultUp,
|
|
17
|
+
) => {
|
|
18
|
+
|
|
19
|
+
useEffect(() => {
|
|
20
|
+
const { current: object } = objectRef;
|
|
21
|
+
if (!object)
|
|
22
|
+
return;
|
|
23
|
+
|
|
24
|
+
object.setRotationFromMatrix(new Matrix4().lookAt(object.position, target, up));
|
|
25
|
+
object.updateMatrix();
|
|
26
|
+
|
|
27
|
+
}, [target.x, target.y, target.z, ...deps]);
|
|
28
|
+
}
|
package/hooks.js
ADDED
|
@@ -0,0 +1,177 @@
|
|
|
1
|
+
// @flow strict
|
|
2
|
+
/*:: import type { Ref } from '@lukekaalim/act'; */
|
|
3
|
+
/*:: import type { Camera, Scene, Texture } from "three"; */
|
|
4
|
+
import { TextureLoader, Vector2, WebGLRenderer } from "three";
|
|
5
|
+
|
|
6
|
+
import { useEffect, useState } from "@lukekaalim/act";
|
|
7
|
+
import { useRef } from "@lukekaalim/act";
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
/*::
|
|
11
|
+
export type WebGLOptions = {
|
|
12
|
+
antialias?: boolean,
|
|
13
|
+
alpha?: boolean,
|
|
14
|
+
|
|
15
|
+
shadowMap?: {
|
|
16
|
+
enabled?: boolean,
|
|
17
|
+
autoUpdate?: boolean,
|
|
18
|
+
type?: number,
|
|
19
|
+
}
|
|
20
|
+
};
|
|
21
|
+
*/
|
|
22
|
+
|
|
23
|
+
export const useWebGLRenderer = (
|
|
24
|
+
canvasRef/*: Ref<?HTMLCanvasElement>*/,
|
|
25
|
+
options/*: WebGLOptions*/ = {},
|
|
26
|
+
deps/*: mixed[]*/ = []
|
|
27
|
+
)/*: ?WebGLRenderer*/ => {
|
|
28
|
+
const [renderer, setRenderer] = useState/*:: <?WebGLRenderer>*/(null);
|
|
29
|
+
useEffect(() => {
|
|
30
|
+
const { current: canvas } = canvasRef;
|
|
31
|
+
if (!canvas)
|
|
32
|
+
return;
|
|
33
|
+
|
|
34
|
+
const renderer = new WebGLRenderer({ ...options, canvas });
|
|
35
|
+
renderer.shadowMap.enabled = options.shadowMap?.enabled || false;
|
|
36
|
+
setRenderer(renderer);
|
|
37
|
+
|
|
38
|
+
return () => {
|
|
39
|
+
setRenderer(null);
|
|
40
|
+
renderer.dispose();
|
|
41
|
+
}
|
|
42
|
+
}, deps);
|
|
43
|
+
|
|
44
|
+
return renderer;
|
|
45
|
+
};
|
|
46
|
+
|
|
47
|
+
// Resize the canvas's resolution whenever it changes size
|
|
48
|
+
// useful for when the canvas is some percentage of the screen
|
|
49
|
+
// like: width: 100%
|
|
50
|
+
export const useResizingRenderer = (
|
|
51
|
+
canvasRef/*: Ref<?HTMLCanvasElement>*/,
|
|
52
|
+
renderer/*: ?WebGLRenderer*/
|
|
53
|
+
)/*: ?Vector2*/ => {
|
|
54
|
+
const [size, setSize] = useState(null)
|
|
55
|
+
useEffect(() => {
|
|
56
|
+
const { current: canvas } = canvasRef;
|
|
57
|
+
if (!canvas || !renderer)
|
|
58
|
+
return null;
|
|
59
|
+
|
|
60
|
+
const observer = new ResizeObserver((entries) => {
|
|
61
|
+
for (const entry of entries.filter(e => e.target === canvas)) {
|
|
62
|
+
const width = entry.contentBoxSize[0].inlineSize;
|
|
63
|
+
const height = entry.contentBoxSize[0].blockSize;
|
|
64
|
+
const size = new Vector2(width, height);
|
|
65
|
+
setSize(size);
|
|
66
|
+
renderer.setSize(width, height, false);
|
|
67
|
+
}
|
|
68
|
+
});
|
|
69
|
+
observer.observe(canvas, { box: 'content-box' });
|
|
70
|
+
|
|
71
|
+
return () => {
|
|
72
|
+
observer.unobserve(canvas);
|
|
73
|
+
setSize(null);
|
|
74
|
+
}
|
|
75
|
+
}, [renderer]);
|
|
76
|
+
|
|
77
|
+
return size;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
export const useAnimationFrame = (
|
|
81
|
+
onAnimate/*: ?(now: DOMHighResTimeStamp, delta: number) => mixed*/ = null,
|
|
82
|
+
deps/*: mixed[]*/ = []
|
|
83
|
+
) => {
|
|
84
|
+
useEffect(() => {
|
|
85
|
+
if (!onAnimate)
|
|
86
|
+
return;
|
|
87
|
+
|
|
88
|
+
let lastFrame = performance.now();
|
|
89
|
+
const animate = (now) => {
|
|
90
|
+
const delta = now - lastFrame;
|
|
91
|
+
lastFrame = now;
|
|
92
|
+
|
|
93
|
+
onAnimate(now, delta);
|
|
94
|
+
id = requestAnimationFrame(animate);
|
|
95
|
+
};
|
|
96
|
+
let id = requestAnimationFrame(animate);
|
|
97
|
+
|
|
98
|
+
return () => {
|
|
99
|
+
if (id)
|
|
100
|
+
cancelAnimationFrame(id);
|
|
101
|
+
}
|
|
102
|
+
}, deps);
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
export const useRenderLoop = (
|
|
106
|
+
renderer/*: ?WebGLRenderer*/,
|
|
107
|
+
camera/*: Ref<?Camera>*/,
|
|
108
|
+
scene/*: Ref<?Scene>*/,
|
|
109
|
+
onAnimate/*: (now: DOMHighResTimeStamp, delta: number) => mixed*/ = (_, __) => {},
|
|
110
|
+
deps/*: mixed[]*/ = []
|
|
111
|
+
) => {
|
|
112
|
+
const renderFunction = renderer && ((now, delta) => {
|
|
113
|
+
onAnimate(now, delta);
|
|
114
|
+
if (camera.current && scene.current)
|
|
115
|
+
renderer.render(scene.current, camera.current);
|
|
116
|
+
});
|
|
117
|
+
|
|
118
|
+
useEffect(() => {
|
|
119
|
+
if (renderFunction)
|
|
120
|
+
renderFunction(performance.now(), 0);
|
|
121
|
+
}, [renderer, ...deps])
|
|
122
|
+
|
|
123
|
+
useAnimationFrame(renderFunction, [renderer, ...deps])
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
/*::
|
|
127
|
+
export interface Disposable {
|
|
128
|
+
dispose(): void;
|
|
129
|
+
}
|
|
130
|
+
*/
|
|
131
|
+
|
|
132
|
+
export const useDisposable = /*:: <T: Disposable>*/(
|
|
133
|
+
create/*: () => T*/,
|
|
134
|
+
deps/*: mixed[]*/ = []
|
|
135
|
+
)/*: T*/ => {
|
|
136
|
+
const [resource, setResource] = useState/*:: <T>*/(() => create());
|
|
137
|
+
const disposedRef = useRef(false);
|
|
138
|
+
|
|
139
|
+
useEffect(() => {
|
|
140
|
+
const { current: disposed } = disposedRef
|
|
141
|
+
|
|
142
|
+
if (disposed) {
|
|
143
|
+
setResource(create());
|
|
144
|
+
disposedRef.current = false;
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
return () => {
|
|
148
|
+
resource.dispose();
|
|
149
|
+
disposedRef.current = true;
|
|
150
|
+
}
|
|
151
|
+
}, deps);
|
|
152
|
+
return resource;
|
|
153
|
+
};
|
|
154
|
+
|
|
155
|
+
export const useTexture = (url/*: string*/)/*: Texture*/ => {
|
|
156
|
+
const [texture, setTexture] = useState(null)
|
|
157
|
+
useEffect(() => {
|
|
158
|
+
const loader = new TextureLoader();
|
|
159
|
+
|
|
160
|
+
const texture = loader.load(url);
|
|
161
|
+
setTexture(texture);
|
|
162
|
+
|
|
163
|
+
console.log(loader);
|
|
164
|
+
|
|
165
|
+
return () => {
|
|
166
|
+
texture.dispose();
|
|
167
|
+
}
|
|
168
|
+
}, []);
|
|
169
|
+
|
|
170
|
+
if (!texture)
|
|
171
|
+
throw { type: 'loading' };
|
|
172
|
+
|
|
173
|
+
return texture;
|
|
174
|
+
};
|
|
175
|
+
|
|
176
|
+
|
|
177
|
+
export * from './hooks/matrix.js';
|
package/objects.js
ADDED
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
// @flow strict
|
|
2
|
+
/*:: import type { Object3D } from "three"; */
|
|
3
|
+
/*:: import type { PropDiff } from '@lukekaalim/act-reconciler'; */
|
|
4
|
+
import {
|
|
5
|
+
Group,
|
|
6
|
+
Mesh,
|
|
7
|
+
PerspectiveCamera,
|
|
8
|
+
PointLight,
|
|
9
|
+
Points,
|
|
10
|
+
Scene,
|
|
11
|
+
InstancedMesh,
|
|
12
|
+
OrthographicCamera,
|
|
13
|
+
DirectionalLight,
|
|
14
|
+
HemisphereLight,
|
|
15
|
+
AmbientLight,
|
|
16
|
+
} from "three";
|
|
17
|
+
|
|
18
|
+
const threeObjectClasses/*: { [string]: Class<Object3D> }*/ = Object.fromEntries(Object.entries({
|
|
19
|
+
// grouping
|
|
20
|
+
Scene,
|
|
21
|
+
Group,
|
|
22
|
+
|
|
23
|
+
// lights
|
|
24
|
+
DirectionalLight,
|
|
25
|
+
HemisphereLight,
|
|
26
|
+
PointLight,
|
|
27
|
+
AmbientLight,
|
|
28
|
+
|
|
29
|
+
// camera
|
|
30
|
+
PerspectiveCamera,
|
|
31
|
+
OrthographicCamera,
|
|
32
|
+
|
|
33
|
+
// lines
|
|
34
|
+
Points,
|
|
35
|
+
|
|
36
|
+
// mesh
|
|
37
|
+
Mesh,
|
|
38
|
+
InstancedMesh,
|
|
39
|
+
}).map(([key, value]) => [key.toLowerCase(), (value/*: any*/)]));
|
|
40
|
+
|
|
41
|
+
export const createObject = (type/*: string*/)/*: null | Object3D*/ => {
|
|
42
|
+
const objectClass = threeObjectClasses[type.toLowerCase()];
|
|
43
|
+
|
|
44
|
+
if (!objectClass) {
|
|
45
|
+
console.warn(`Unknown object type "${type}"`)
|
|
46
|
+
return null;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
return new objectClass();
|
|
50
|
+
};
|
package/package.json
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@lukekaalim/act-three",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "5.2.0",
|
|
4
4
|
"description": "Render threejs content using act",
|
|
5
|
-
"main": "
|
|
5
|
+
"main": "entry.js",
|
|
6
6
|
"type": "module",
|
|
7
7
|
"scripts": {
|
|
8
8
|
"test": "cd ../../; npm test;",
|
|
@@ -16,7 +16,8 @@
|
|
|
16
16
|
},
|
|
17
17
|
"dependencies": {
|
|
18
18
|
"@lukekaalim/act": "^2.2.0",
|
|
19
|
-
"@lukekaalim/act-
|
|
20
|
-
"@lukekaalim/act-
|
|
19
|
+
"@lukekaalim/act-renderer-core": "^3.1.1",
|
|
20
|
+
"@lukekaalim/act-reconciler": "^3.0.0",
|
|
21
|
+
"@lukekaalim/act-web": "^1.2.0"
|
|
21
22
|
}
|
|
22
23
|
}
|
package/props.js
ADDED
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
// @flow strict
|
|
2
|
+
/*:: import type { Object3D, WebGLRenderer } from "three"; */
|
|
3
|
+
/*:: import type { Props, PropDiff, CommitDiff } from '@lukekaalim/act-reconciler'; */
|
|
4
|
+
|
|
5
|
+
import { calculatePropsDiff } from "@lukekaalim/act-reconciler";
|
|
6
|
+
import { setHTMLProp } from "@lukekaalim/act-web";
|
|
7
|
+
import { DirectionalLight, Group, Light, OrthographicCamera } from "three";
|
|
8
|
+
import { setRef } from "@lukekaalim/act-renderer-core";
|
|
9
|
+
|
|
10
|
+
export const setObjectProp = ({ key, prev, next }/*: PropDiff*/, object/*: Object3D*/)/*: void*/ => {
|
|
11
|
+
switch (key) {
|
|
12
|
+
case 'position':
|
|
13
|
+
return object.position.copy((next/*: any*/));
|
|
14
|
+
case 'quaternion':
|
|
15
|
+
return void object.quaternion.copy((next/*: any*/));
|
|
16
|
+
case 'rotation':
|
|
17
|
+
return object.rotation.copy((next/*: any*/));
|
|
18
|
+
case 'scale':
|
|
19
|
+
return object.scale.copy((next/*: any*/));
|
|
20
|
+
case 'matrix':
|
|
21
|
+
return void object.matrix.copy((next/*: any*/));
|
|
22
|
+
default:
|
|
23
|
+
(object/*: any*/)[key] = next; break;
|
|
24
|
+
}
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
export const setGroupProps = (diff/*: CommitDiff*/, group/*: Group*/) => {
|
|
28
|
+
const diffs = calculatePropsDiff(diff.prev.element.props, diff.next.element.props);
|
|
29
|
+
for (const [key, diff] of diffs) {
|
|
30
|
+
switch (key) {
|
|
31
|
+
case 'group':
|
|
32
|
+
if (diff.prev && diff.prev !== diff.next)
|
|
33
|
+
group.remove((diff.prev/*: any*/));
|
|
34
|
+
if (diff.next)
|
|
35
|
+
group.add((diff.next/*: any*/));
|
|
36
|
+
break;
|
|
37
|
+
default:
|
|
38
|
+
setObjectProp(diff, group); break;
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
};
|
|
42
|
+
|
|
43
|
+
export const setDirectionalLightProps = (diff/*: CommitDiff*/, light/*: DirectionalLight*/) => {
|
|
44
|
+
const diffs = calculatePropsDiff(diff.prev.element.props, diff.next.element.props);
|
|
45
|
+
for (const [key, diff] of diffs) {
|
|
46
|
+
switch (key) {
|
|
47
|
+
case 'shadow': {
|
|
48
|
+
const diffs = calculatePropsDiff((diff.prev/*: any*/) || {}, (diff.next/*: any*/) || {});
|
|
49
|
+
for (const [key, diff] of diffs)
|
|
50
|
+
(light/*: any*/).shadow[key] = diff.next;
|
|
51
|
+
break;
|
|
52
|
+
}
|
|
53
|
+
default:
|
|
54
|
+
setObjectProp(diff, light); break;
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
export const setObjectProps = (diff/*: CommitDiff*/, object/*: ?Object3D*/)/*: void*/ => {
|
|
60
|
+
setRef(object, diff);
|
|
61
|
+
if (diff.next.pruned || !object)
|
|
62
|
+
return;
|
|
63
|
+
if (object instanceof Group)
|
|
64
|
+
return setGroupProps(diff, object);
|
|
65
|
+
if (object instanceof DirectionalLight)
|
|
66
|
+
return setDirectionalLightProps(diff, object);
|
|
67
|
+
if (object instanceof OrthographicCamera) {
|
|
68
|
+
const diffs = calculatePropsDiff(diff.prev.element.props, diff.next.element.props);
|
|
69
|
+
for (const [key, diff] of diffs)
|
|
70
|
+
setObjectProp(diff, object);
|
|
71
|
+
object.updateProjectionMatrix();
|
|
72
|
+
}
|
|
73
|
+
else {
|
|
74
|
+
const diffs = calculatePropsDiff(diff.prev.element.props, diff.next.element.props);
|
|
75
|
+
for (const [key, diff] of diffs)
|
|
76
|
+
setObjectProp(diff, object);
|
|
77
|
+
}
|
|
78
|
+
};
|
package/reconciler.js
ADDED
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
// @flow strict
|
|
2
|
+
/*:: import type { Element } from '@lukekaalim/act'; */
|
|
3
|
+
/*:: import type { Renderer } from '@lukekaalim/act-renderer-core'; */
|
|
4
|
+
|
|
5
|
+
import { createTree } from "@lukekaalim/act-reconciler";
|
|
6
|
+
import { createWebRenderer, setNodeChildren } from "@lukekaalim/act-web";
|
|
7
|
+
|
|
8
|
+
import { createSceneRenderer } from "./renderer";
|
|
9
|
+
|
|
10
|
+
export const render = (element/*: Element*/, node/*: HTMLElement*/) => {
|
|
11
|
+
|
|
12
|
+
const scene = createSceneRenderer();
|
|
13
|
+
const web = createWebRenderer({
|
|
14
|
+
'scene': scene,
|
|
15
|
+
});
|
|
16
|
+
const onDiff = diff => {
|
|
17
|
+
const children = web.render(diff);
|
|
18
|
+
setNodeChildren(diff, node, children)
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
const options = {
|
|
22
|
+
onDiff,
|
|
23
|
+
scheduleWork: (c) => requestAnimationFrame(() => void c()),
|
|
24
|
+
cancelWork: (t) => cancelAnimationFrame(t),
|
|
25
|
+
};
|
|
26
|
+
const tree = createTree(element, options);
|
|
27
|
+
};
|
package/renderer.js
ADDED
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
// @flow strict
|
|
2
|
+
/*:: import type { Renderer } from '@lukekaalim/act-renderer-core'; */
|
|
3
|
+
/*:: import type { CommitDiff, PropDiff, CommitID } from '@lukekaalim/act-reconciler'; */
|
|
4
|
+
/*:: import type { Object3D } from 'three'; */
|
|
5
|
+
import { createManagedRenderer, createNullRenderer } from '@lukekaalim/act-renderer-core';
|
|
6
|
+
import { createObject } from "./objects";
|
|
7
|
+
import { setObjectProps } from "./props";
|
|
8
|
+
|
|
9
|
+
export const createObjectRenderer = ()/*: Renderer<Object3D>*/ => {
|
|
10
|
+
return createManagedRenderer({
|
|
11
|
+
remove(object) {
|
|
12
|
+
object.removeFromParent();
|
|
13
|
+
},
|
|
14
|
+
update(object, diff, children) {
|
|
15
|
+
if (typeof diff.next.element.type === 'function')
|
|
16
|
+
return;
|
|
17
|
+
setObjectProps(diff, object);
|
|
18
|
+
if (children.length > 0)
|
|
19
|
+
object.add(...children.map(c => c.node));
|
|
20
|
+
},
|
|
21
|
+
add(diff) {
|
|
22
|
+
if (typeof diff.next.element.type === 'function')
|
|
23
|
+
return null;
|
|
24
|
+
return createObject(diff.next.element.type);
|
|
25
|
+
}
|
|
26
|
+
});
|
|
27
|
+
};
|
|
28
|
+
|
|
29
|
+
export const createSceneRenderer = /*:: <T>*/()/*: Renderer<T>*/ => {
|
|
30
|
+
const object = createObjectRenderer();
|
|
31
|
+
return createNullRenderer(object);
|
|
32
|
+
}
|
package/main.js
DELETED
|
@@ -1,25 +0,0 @@
|
|
|
1
|
-
// @flow strict
|
|
2
|
-
/*:: import type { Element } from '@lukekaalim/act'; */
|
|
3
|
-
/*:: import type { ThreeRenderer } from "./render.js"; */
|
|
4
|
-
|
|
5
|
-
import { createTree } from "@lukekaalim/act-reconciler";
|
|
6
|
-
import { createWebRenderService } from "@lukekaalim/act-web";
|
|
7
|
-
import { attachNodes } from "@lukekaalim/act-web/node.js";
|
|
8
|
-
import { createThreeRenderer } from "./render.js";
|
|
9
|
-
|
|
10
|
-
export const render = (element/*: Element*/, node/*: HTMLElement*/, renderer/*: ThreeRenderer*/ = createThreeRenderer()) => {
|
|
11
|
-
const { renderRoot } = renderer;
|
|
12
|
-
const web = createWebRenderService(new Map([['three', { render: renderRoot }]]));
|
|
13
|
-
|
|
14
|
-
const onDiff = diff => attachNodes(node, web.render(diff));
|
|
15
|
-
|
|
16
|
-
const options = {
|
|
17
|
-
onDiff,
|
|
18
|
-
scheduleWork: (c) => requestAnimationFrame(() => void c()),
|
|
19
|
-
};
|
|
20
|
-
const tree = createTree(element, options);
|
|
21
|
-
};
|
|
22
|
-
|
|
23
|
-
export * from './render.js';
|
|
24
|
-
export * from './node.js';
|
|
25
|
-
export * from './components.js';
|
package/node.js
DELETED
|
@@ -1,88 +0,0 @@
|
|
|
1
|
-
// @flow strict
|
|
2
|
-
/*:: import type { Object3D } from "three"; */
|
|
3
|
-
/*:: import type { PropDiff } from '@lukekaalim/act-reconciler'; */
|
|
4
|
-
import { Group, Mesh, PerspectiveCamera, PointLight, Points } from "three";
|
|
5
|
-
|
|
6
|
-
/*::
|
|
7
|
-
export type NodeInstance = {
|
|
8
|
-
object: Object3D,
|
|
9
|
-
update: (props: PropDiff[]) => void,
|
|
10
|
-
dispose: () => void,
|
|
11
|
-
};
|
|
12
|
-
|
|
13
|
-
export type NodeDefinition = {
|
|
14
|
-
type: string,
|
|
15
|
-
create: () => NodeInstance,
|
|
16
|
-
};
|
|
17
|
-
*/
|
|
18
|
-
|
|
19
|
-
const updateObjectProps = (object, props) => {
|
|
20
|
-
for (const { key, next } of props)
|
|
21
|
-
switch (key) {
|
|
22
|
-
case 'position':
|
|
23
|
-
object.position.copy((next/*: any*/)); break;
|
|
24
|
-
case 'quaternion':
|
|
25
|
-
object.quaternion.copy((next/*: any*/)); break;
|
|
26
|
-
case 'rotation':
|
|
27
|
-
object.rotation.copy((next/*: any*/)); break;
|
|
28
|
-
case 'scale':
|
|
29
|
-
object.scale.copy((next/*: any*/)); break;
|
|
30
|
-
default:
|
|
31
|
-
(object/*: any*/)[key] = next; break;
|
|
32
|
-
}
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
const createNodeDefinitionFromConstructor = ([type, constructor])/*: NodeDefinition*/ => {
|
|
36
|
-
const create = () => {
|
|
37
|
-
const object = constructor();
|
|
38
|
-
const update = (props) => {
|
|
39
|
-
updateObjectProps(object, props);
|
|
40
|
-
};
|
|
41
|
-
const dispose = () => {
|
|
42
|
-
return;
|
|
43
|
-
};
|
|
44
|
-
const instance = {
|
|
45
|
-
object,
|
|
46
|
-
update,
|
|
47
|
-
dispose,
|
|
48
|
-
};
|
|
49
|
-
return instance;
|
|
50
|
-
};
|
|
51
|
-
return {
|
|
52
|
-
type,
|
|
53
|
-
create,
|
|
54
|
-
}
|
|
55
|
-
};
|
|
56
|
-
|
|
57
|
-
const groupDefinition = {
|
|
58
|
-
type: 'group',
|
|
59
|
-
create: () => {
|
|
60
|
-
const object = new Group();
|
|
61
|
-
const update = (props) => {
|
|
62
|
-
updateObjectProps(object, props.filter(p => p.key !== 'group'));
|
|
63
|
-
for (const { key, next, prev } of props)
|
|
64
|
-
switch (key) {
|
|
65
|
-
case 'group':
|
|
66
|
-
prev && object.remove((prev/*: any*/));
|
|
67
|
-
next && object.add((next/*: any*/));
|
|
68
|
-
}
|
|
69
|
-
};
|
|
70
|
-
const dispose = () => {
|
|
71
|
-
|
|
72
|
-
};
|
|
73
|
-
return { object, update, dispose };
|
|
74
|
-
},
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
const constructorNodes/*: NodeDefinition[]*/ = [
|
|
78
|
-
['mesh', () => new Mesh()],
|
|
79
|
-
['pointLight', () => new PointLight()],
|
|
80
|
-
['points', () => new Points()],
|
|
81
|
-
['perspectiveCamera', () => new PerspectiveCamera()],
|
|
82
|
-
].map(createNodeDefinitionFromConstructor);
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
export const threeNodes/*: NodeDefinition[]*/ = [
|
|
86
|
-
...constructorNodes,
|
|
87
|
-
groupDefinition,
|
|
88
|
-
];
|
package/render.js
DELETED
|
@@ -1,163 +0,0 @@
|
|
|
1
|
-
// @flow strict
|
|
2
|
-
/*:: import type { CommitDiff, PropDiff, CommitID } from '@lukekaalim/act-reconciler'; */
|
|
3
|
-
/*:: import type { Object3D } from 'three'; */
|
|
4
|
-
/*:: import type { NodeDefinition } from './node.js'; */
|
|
5
|
-
import { PerspectiveCamera, Scene, WebGLRenderer } from 'three';
|
|
6
|
-
import { threeNodes } from './node.js';
|
|
7
|
-
import { calculatePropsDiff } from '@lukekaalim/act-reconciler'
|
|
8
|
-
|
|
9
|
-
/*::
|
|
10
|
-
export type ThreeRenderer = {
|
|
11
|
-
renderRoot: CommitDiff => Node[],
|
|
12
|
-
};
|
|
13
|
-
|
|
14
|
-
export type Frame = {
|
|
15
|
-
id: ?AnimationFrameID
|
|
16
|
-
}
|
|
17
|
-
*/
|
|
18
|
-
|
|
19
|
-
export const createThreeRenderer = (nodeDefs/*: NodeDefinition[]*/ = threeNodes)/*: ThreeRenderer*/ => {
|
|
20
|
-
const nodeDefsByType = new Map(nodeDefs.map(def => [def.type, def]));
|
|
21
|
-
|
|
22
|
-
const roots = new Map/*:: <CommitID, [WebGLRenderer, Scene, PerspectiveCamera, Frame]>*/();
|
|
23
|
-
const objects = new Map();
|
|
24
|
-
|
|
25
|
-
const createRoot = (diff) => {
|
|
26
|
-
const { width, height, setStyle = false, background = null, alpha = false, onRender, sceneRef } = (diff.next.element.props/*: any*/);
|
|
27
|
-
|
|
28
|
-
const renderer = new WebGLRenderer({ alpha });
|
|
29
|
-
renderer.setSize(width, height, setStyle);
|
|
30
|
-
const scene = new Scene();
|
|
31
|
-
scene.background = background;
|
|
32
|
-
const camera = new PerspectiveCamera( 75, width / height, 0.1, 1000 );
|
|
33
|
-
camera.position.z = 5;
|
|
34
|
-
|
|
35
|
-
const frame/*: Frame*/ = { id: null };
|
|
36
|
-
|
|
37
|
-
const animate = (timestamp) => {
|
|
38
|
-
onRender && onRender(timestamp);
|
|
39
|
-
renderer.render( scene, camera );
|
|
40
|
-
frame.id = requestAnimationFrame( animate );
|
|
41
|
-
};
|
|
42
|
-
frame.id = animate(performance.now());
|
|
43
|
-
|
|
44
|
-
roots.set(diff.next.id, [renderer, scene, camera, frame]);
|
|
45
|
-
return [renderer, scene, camera, frame];
|
|
46
|
-
};
|
|
47
|
-
const setRootProps = (diff, [renderer, scene, camera, frame]) => {
|
|
48
|
-
const props = calculatePropsDiff(diff.prev.element.props, diff.next.element.props);
|
|
49
|
-
const { width, height, setStyle = false, background = null, onRender } = (diff.next.element.props/*: any*/);
|
|
50
|
-
|
|
51
|
-
if (props.has('width') || props.has('height') || props.has('setStyle')) {
|
|
52
|
-
renderer.setSize(width, height, setStyle);
|
|
53
|
-
camera.aspect = width / height;
|
|
54
|
-
camera.updateProjectionMatrix();
|
|
55
|
-
}
|
|
56
|
-
if (props.has('background'))
|
|
57
|
-
scene.background = background;
|
|
58
|
-
if (props.has('onRender')) {
|
|
59
|
-
frame.id && cancelAnimationFrame(frame.id);
|
|
60
|
-
const animate = (timestamp) => {
|
|
61
|
-
onRender && onRender(timestamp);
|
|
62
|
-
renderer.render( scene, camera );
|
|
63
|
-
frame.id = requestAnimationFrame( animate );
|
|
64
|
-
};
|
|
65
|
-
frame.id = animate(performance.now());
|
|
66
|
-
}
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
const removeRenderer = (diff, [renderer, scene, camera, frame]) => {
|
|
70
|
-
roots.delete(diff.next.id);
|
|
71
|
-
renderer.dispose();
|
|
72
|
-
frame.id && cancelAnimationFrame(frame.id);
|
|
73
|
-
|
|
74
|
-
const { parentNode } = renderer.domElement;
|
|
75
|
-
if (parentNode)
|
|
76
|
-
parentNode.removeChild(renderer.domElement);
|
|
77
|
-
return [];
|
|
78
|
-
};
|
|
79
|
-
|
|
80
|
-
const createObject = (diff) => {
|
|
81
|
-
const { type } = diff.next.element;
|
|
82
|
-
if (typeof type !== 'string')
|
|
83
|
-
return null;
|
|
84
|
-
if (type === 'act:null')
|
|
85
|
-
return null;
|
|
86
|
-
|
|
87
|
-
const nodeDef = nodeDefsByType.get(type);
|
|
88
|
-
if (!nodeDef)
|
|
89
|
-
throw new Error(`Unknown type ${type}`);
|
|
90
|
-
|
|
91
|
-
const nodeInstance = nodeDef.create();
|
|
92
|
-
objects.set(diff.next.id, nodeInstance);
|
|
93
|
-
|
|
94
|
-
return nodeInstance;
|
|
95
|
-
}
|
|
96
|
-
|
|
97
|
-
const removeObject = (nodeInstance) => {
|
|
98
|
-
nodeInstance.object.removeFromParent();
|
|
99
|
-
nodeInstance.dispose();
|
|
100
|
-
};
|
|
101
|
-
|
|
102
|
-
const setRef = (diff, reference) => {
|
|
103
|
-
if (diff.prev.pruned) {
|
|
104
|
-
const { ref } = (diff.next.element.props/*: any*/);
|
|
105
|
-
if (typeof ref === 'object')
|
|
106
|
-
ref.current = reference;
|
|
107
|
-
else if (typeof ref === 'function')
|
|
108
|
-
ref(reference);
|
|
109
|
-
} else if (diff.next.pruned) {
|
|
110
|
-
const { ref } = (diff.next.element.props/*: any*/);
|
|
111
|
-
if (typeof ref === 'object')
|
|
112
|
-
ref.current = null;
|
|
113
|
-
else if (typeof ref === 'function')
|
|
114
|
-
ref(null);
|
|
115
|
-
}
|
|
116
|
-
};
|
|
117
|
-
|
|
118
|
-
const renderObject = (diff/*: CommitDiff*/)/*: Object3D[]*/ => {
|
|
119
|
-
const nodeInstance = objects.get(diff.next.id) || createObject(diff);
|
|
120
|
-
|
|
121
|
-
setRef(diff, nodeInstance && nodeInstance.object)
|
|
122
|
-
const children = diff.diffs.map(renderObject).flat(1);
|
|
123
|
-
|
|
124
|
-
if (!nodeInstance)
|
|
125
|
-
return children;
|
|
126
|
-
|
|
127
|
-
if (diff.next.pruned)
|
|
128
|
-
return (removeObject(nodeInstance), []);
|
|
129
|
-
|
|
130
|
-
const props = calculatePropsDiff(diff.prev.element.props, diff.next.element.props);
|
|
131
|
-
nodeInstance.update([...props.values()]);
|
|
132
|
-
attachObjects(nodeInstance.object, children);
|
|
133
|
-
|
|
134
|
-
return [nodeInstance.object];
|
|
135
|
-
};
|
|
136
|
-
const attachObjects = (scene, children) => {
|
|
137
|
-
if (children.length > 0)
|
|
138
|
-
scene.add(...children);
|
|
139
|
-
};
|
|
140
|
-
|
|
141
|
-
const renderRoot = (diff/*: CommitDiff*/)/*: Node[]*/ => {
|
|
142
|
-
if (diff.next.element.type !== 'three')
|
|
143
|
-
return [];
|
|
144
|
-
|
|
145
|
-
const root = roots.get(diff.next.id) || createRoot(diff);
|
|
146
|
-
|
|
147
|
-
const children = diff.diffs.map(renderObject).flat(1);
|
|
148
|
-
|
|
149
|
-
if (diff.next.pruned)
|
|
150
|
-
return removeRenderer(diff, root);
|
|
151
|
-
|
|
152
|
-
const [renderer, scene, camera] = root;
|
|
153
|
-
setRootProps(diff, root);
|
|
154
|
-
attachObjects(scene, children);
|
|
155
|
-
setRef(diff, { renderer, scene, camera });
|
|
156
|
-
|
|
157
|
-
return [renderer.domElement];
|
|
158
|
-
}
|
|
159
|
-
|
|
160
|
-
return {
|
|
161
|
-
renderRoot,
|
|
162
|
-
}
|
|
163
|
-
};
|