@lukekaalim/act-three 3.9.4 → 5.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,26 @@
1
+ // @flow strict
2
+ /*:: import type { Component, Ref } from '@lukekaalim/act'; */
3
+ /*:: import type { Euler, Group } 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 LookAtGroup/*: Component<{ ...$Diff<GroupProps, { rotation?: Euler, ref?: RefProp<Group> }>, target: Vector3 }>*/ = ({
11
+ target,
12
+ children,
13
+ position = new Vector3(0, 0, 0),
14
+ ...groupProps
15
+ }) => {
16
+ const [groupNode, setGroup] = useState(null);
17
+ useEffect(() => {
18
+ if (!groupNode)
19
+ return;
20
+
21
+ groupNode.setRotationFromMatrix(new Matrix4().lookAt(position, target, new Vector3(0, 1, 0)));
22
+ groupNode.updateMatrix();
23
+ }, [groupNode, target.x, target.y, target.z, position.x, position.y, position.z]);
24
+
25
+ return h(group, { ...groupProps, position, ref: setGroup }, children);
26
+ }
@@ -0,0 +1 @@
1
+ // @flow strict
package/components.js CHANGED
@@ -1,7 +1,8 @@
1
1
  // @flow strict
2
- /*:: import type { Component as ActComponent } from '@lukekaalim/act'; */
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,
@@ -9,27 +10,52 @@
9
10
  Texture,
10
11
  Color,
11
12
  Group,
13
+ Camera,
14
+ PerspectiveCamera,
15
+ Euler,
16
+ Scene,
17
+ CubeTexture,
18
+ Fog,
19
+ WebGLRenderer,
12
20
  } from "three"; */
13
21
  /*:: import * as Three from 'three'; */
14
22
 
15
23
  /*::
24
+ export type RefProp<T> = ((reference: T) => mixed) | { current: T | any }
25
+
16
26
  export type Object3DProps<T> = {
17
- ref?: ((reference: T) => mixed) | { current: T | any },
27
+ ref?: RefProp<?T>,
18
28
 
19
29
  name?: string,
20
30
  position?: Vector3,
31
+ rotation?: Euler,
21
32
  quaternion?: Quaternion,
22
33
  visible?: boolean,
23
34
  scale?: Vector3,
24
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
+
25
46
  export type MeshProps = {
26
47
  ...Object3DProps<Three.Mesh>,
27
48
  geometry?: BufferGeometry,
28
49
  material?: Material,
29
50
  };
30
- export type InstanceMeshProps = {
51
+ export type SpriteProps = {
52
+ ...Object3DProps<Three.Sprite>,
53
+ material: Material;
54
+ center: Vector2;
55
+ };
56
+ export type InstancedMeshProps = {
31
57
  ...MeshProps,
32
- ...Object3DProps<Three.InstanceMesh>,
58
+ ...Object3DProps<Three.InstancedMesh>,
33
59
  };
34
60
  export type PointLightsProps = {
35
61
  ...Object3DProps<Three.PointLight>,
@@ -38,32 +64,60 @@ export type PointLightsProps = {
38
64
  intensity?: number,
39
65
  power?: number,
40
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
+ };
41
79
  export type PointsProps = {
42
80
  ...Object3DProps<Three.Points>,
43
81
  geometry?: BufferGeometry,
44
82
  material?: Material,
45
83
  };
46
- export type ThreeProps = {
47
- ref?: ((reference: Three.Scene) => mixed) | { current: Three.Scene | any },
48
- width: number,
49
- height: number,
50
- setStyle?: boolean,
51
- background?: Color | null | Texture,
52
- alpha?: boolean,
53
- onRender?: (timestamp: number) => void,
54
- };
55
84
  export type GroupProps = {
56
85
  ...Object3DProps<Three.Group>,
57
86
  group?: ?Three.Object3D,
58
87
  }
88
+ export type PerspectiveCameraProps = {
89
+ ...Object3DProps<Three.PerspectiveCamera>,
90
+ aspect?: number,
91
+ near?: number,
92
+ far?: number,
93
+ fov?: number,
94
+ zoom?: number,
95
+ }
96
+ export type OrthographicCameraProps = {
97
+ ...Object3DProps<Three.OrthographicCamera>,
98
+ aspect?: number,
99
+ near?: number,
100
+ far?: number,
101
+ zoom?: number,
102
+ }
59
103
  */
60
104
 
61
- export const Component = {
62
- mesh: ('mesh'/*: ActComponent<MeshProps>*/),
63
- instanceMesh: ('instanceMesh'/*: ActComponent<InstanceMeshProps>*/),
64
- pointLight: ('pointLight'/*: ActComponent<PointLightsProps>*/),
65
- points: ('points'/*: ActComponent<PointsProps>*/),
66
- three: ('three'/*: ActComponent<ThreeProps>*/),
67
- group: ('group'/*: ActComponent<GroupProps>*/),
68
- };
69
- export const C = Component;
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
@@ -0,0 +1,7 @@
1
+ // @flow strict
2
+
3
+ export * from './reconciler.js';
4
+ export * from './renderer.js';
5
+ export * from './objects.js';
6
+ export * from './components.js';
7
+ export * from './hooks.js';
package/hooks.js ADDED
@@ -0,0 +1,165 @@
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
+ canvas/*: ?HTMLCanvasElement*/,
25
+ options/*: WebGLOptions*/ = {},
26
+ deps/*: mixed[]*/ = []
27
+ )/*: ?WebGLRenderer*/ => {
28
+ const [renderer, setRenderer] = useState/*:: <?WebGLRenderer>*/(null);
29
+ useEffect(() => {
30
+ if (!canvas)
31
+ return;
32
+
33
+ const renderer = new WebGLRenderer({ ...options, canvas });
34
+ renderer.shadowMap.enabled = options.shadowMap?.enabled || false;
35
+ setRenderer(renderer);
36
+
37
+ return () => {
38
+ setRenderer(null);
39
+ renderer.dispose();
40
+ }
41
+ }, [canvas, ...deps]);
42
+
43
+ return renderer;
44
+ };
45
+
46
+ // Resize the canvas's resolution whenever it changes size
47
+ // useful for when the canvas is some percentage of the screen
48
+ // like: width: 100%
49
+ export const useResizingRenderer = (canvas/*: ?HTMLCanvasElement*/, renderer/*: ?WebGLRenderer*/)/*: ?Vector2*/ => {
50
+ const [size, setSize] = useState(null)
51
+ useEffect(() => {
52
+ if (!canvas || !renderer)
53
+ return null;
54
+
55
+ const observer = new ResizeObserver((entries) => {
56
+ for (const entry of entries.filter(e => e.target === canvas)) {
57
+ const width = entry.contentBoxSize[0].inlineSize;
58
+ const height = entry.contentBoxSize[0].blockSize;
59
+ const size = new Vector2(width, height);
60
+ setSize(size);
61
+ renderer.setSize(width, height, false);
62
+ }
63
+ });
64
+ observer.observe(canvas, { box: 'content-box' });
65
+
66
+ return () => {
67
+ observer.unobserve(canvas);
68
+ setSize(null);
69
+ }
70
+ }, [canvas, renderer]);
71
+
72
+ return size;
73
+ }
74
+
75
+ export const useAnimationFrame = (
76
+ onAnimate/*: (now: DOMHighResTimeStamp, delta: number) => mixed*/,
77
+ deps/*: mixed[]*/ = []
78
+ ) => {
79
+ useEffect(() => {
80
+ let lastFrame = performance.now();
81
+ const animate = (now) => {
82
+ const delta = now - lastFrame;
83
+ lastFrame = now;
84
+
85
+ onAnimate(now, delta);
86
+ id = requestAnimationFrame(animate);
87
+ };
88
+ let id = requestAnimationFrame(animate);
89
+
90
+ return () => {
91
+ if (id)
92
+ cancelAnimationFrame(id);
93
+ }
94
+ }, deps);
95
+ }
96
+
97
+ export const useRenderLoop = (
98
+ renderer/*: ?WebGLRenderer*/,
99
+ camera/*: ?Camera*/,
100
+ scene/*: ?Scene*/,
101
+ onAnimate/*: (now: DOMHighResTimeStamp, delta: number) => mixed*/ = (_, __) => {},
102
+ deps/*: mixed[]*/ = []
103
+ ) => {
104
+ const renderFunction = renderer && camera && scene ? (now, delta) => {
105
+ onAnimate(now, delta);
106
+ renderer.render(scene, camera);
107
+ } : () => {};
108
+
109
+ useEffect(() => {
110
+ if (scene && camera && renderer)
111
+ renderer.render(scene, camera);
112
+ }, [renderer, camera, scene, ...deps])
113
+
114
+ useAnimationFrame(renderFunction, [renderer, camera, scene, ...deps])
115
+ }
116
+
117
+ /*::
118
+ export interface Disposable {
119
+ dispose(): void;
120
+ }
121
+ */
122
+
123
+ export const useDisposable = /*:: <T: Disposable>*/(
124
+ create/*: () => T*/,
125
+ deps/*: mixed[]*/ = []
126
+ )/*: T*/ => {
127
+ const [resource, setResource] = useState/*:: <T>*/(() => create());
128
+ const disposedRef = useRef(false);
129
+
130
+ useEffect(() => {
131
+ const { current: disposed } = disposedRef
132
+
133
+ if (disposed) {
134
+ setResource(create());
135
+ disposedRef.current = false;
136
+ }
137
+
138
+ return () => {
139
+ resource.dispose();
140
+ disposedRef.current = true;
141
+ }
142
+ }, deps);
143
+ return resource;
144
+ };
145
+
146
+ export const useTexture = (url/*: string*/)/*: Texture*/ => {
147
+ const [texture, setTexture] = useState(null)
148
+ useEffect(() => {
149
+ const loader = new TextureLoader();
150
+
151
+ const texture = loader.load(url);
152
+ setTexture(texture);
153
+
154
+ console.log(loader);
155
+
156
+ return () => {
157
+ texture.dispose();
158
+ }
159
+ }, []);
160
+
161
+ if (!texture)
162
+ throw { type: 'loading' };
163
+
164
+ return texture;
165
+ };
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.9.4",
3
+ "version": "5.1.0",
4
4
  "description": "Render threejs content using act",
5
- "main": "main.js",
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-reconciler": "^2.0.4",
20
- "@lukekaalim/act-web": "^0.4.2"
19
+ "@lukekaalim/act-renderer-core": "^3.0.0",
20
+ "@lukekaalim/act-reconciler": "^2.1.0",
21
+ "@lukekaalim/act-web": "^1.1.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,26 @@
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
+ };
25
+ const tree = createTree(element, options);
26
+ };
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,87 +0,0 @@
1
- // @flow strict
2
- /*:: import type { Object3D } from "three"; */
3
- /*:: import type { PropDiff } from '@lukekaalim/act-reconciler'; */
4
- import { Group, Mesh, 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 = new 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', Mesh],
79
- ['pointLight', PointLight],
80
- ['points', Points],
81
- ].map(createNodeDefinitionFromConstructor);
82
-
83
-
84
- export const threeNodes/*: NodeDefinition[]*/ = [
85
- ...constructorNodes,
86
- groupDefinition,
87
- ];
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
- setRef(diff, root[1]);
148
- const children = diff.diffs.map(renderObject).flat(1);
149
-
150
- if (diff.next.pruned)
151
- return removeRenderer(diff, root);
152
-
153
- const [renderer, scene] = root;
154
- setRootProps(diff, root);
155
- attachObjects(scene, children);
156
-
157
- return [renderer.domElement];
158
- }
159
-
160
- return {
161
- renderRoot,
162
- }
163
- };