diff --git a/packages/playground/src/shared/dom-basic/Box3D.tsx b/packages/playground/src/shared/dom-basic/Box3D.tsx new file mode 100644 index 0000000..a7fed82 --- /dev/null +++ b/packages/playground/src/shared/dom-basic/Box3D.tsx @@ -0,0 +1,79 @@ +import React, {useEffect, useRef} from 'react' +import type {CSSProperties} from 'react' +import {types} from '@theatre/core' +import type {ISheet} from '@theatre/core' + +// Box element +export const BoxSize = 100 + +const Box3DCSS: CSSProperties = { + border: '1px solid #999', + position: 'absolute', + width: `${BoxSize}px`, + height: `${BoxSize}px`, +} + +const Box3DTextCSS: CSSProperties = { + margin: '0', + padding: '0', + position: 'absolute', + left: '50%', + top: '50%', + transform: 'translate(-50%, -50%)', + textAlign: 'center', + width: '100%', +} + +export const Box3D: React.FC<{ + sheet: ISheet + name: string + x: number + y: number +}> = ({sheet, name, x, y}) => { + const elementRef = useRef(null) + + // Animation + useEffect(() => { + const element = elementRef.current! + const sheetObj = sheet.object(`Box - ${name}`, { + background: types.rgba({r: 16 / 255, g: 16 / 255, b: 16 / 255, a: 1}), + opacity: types.number(1, {range: [0, 1]}), + position: { + x: x, + y: y, + z: 0, + }, + rotation: { + x: types.number(0, {range: [-360, 360]}), + y: types.number(0, {range: [-360, 360]}), + z: types.number(0, {range: [-360, 360]}), + }, + scale: { + x: 1, + y: 1, + z: 1, + }, + }) + const unsubscribe = sheetObj.onValuesChange((values: any) => { + const {background, opacity, position, rotation, scale} = values + element.style.backgroundColor = `rgba(${background.r * 255}, ${ + background.g * 255 + }, ${background.b * 255}, 1)` + element.style.opacity = opacity + const translate3D = `translate3d(${position.x}px, ${position.y}px, ${position.z}px)` + const rotate3D = `rotateX(${rotation.x}deg) rotateY(${rotation.y}deg) rotateZ(${rotation.z}deg)` + const scale3D = `scaleX(${scale.x}) scaleY(${scale.y}) scaleZ(${scale.z})` + const transform = `${scale3D} ${translate3D} ${rotate3D}` + element.style.transform = transform + }) + return () => { + unsubscribe() + } + }, []) + + return ( +
+ {name} +
+ ) +} diff --git a/packages/playground/src/shared/dom-basic/Scene.tsx b/packages/playground/src/shared/dom-basic/Scene.tsx new file mode 100644 index 0000000..295659a --- /dev/null +++ b/packages/playground/src/shared/dom-basic/Scene.tsx @@ -0,0 +1,53 @@ +import React, {useEffect, useRef} from 'react' +import type {CSSProperties} from 'react' +import {types} from '@theatre/core' +import type {IProject} from '@theatre/core' +import {Box3D, BoxSize} from './Box3D' + +// Scene + +const SceneCSS: CSSProperties = { + overflow: 'hidden', + position: 'absolute', + left: '0', + right: '0', + top: '0', + bottom: '0', +} + +export const Scene: React.FC<{project: IProject}> = ({project}) => { + const containerRef = useRef(null!) + const sheet = project.sheet('DOM') + + useEffect(() => { + const container = containerRef.current! + const sheetObj = sheet.object('Container', { + perspective: types.number( + Math.max(window.innerWidth, window.innerHeight), + {range: [0, 2000]}, + ), + originX: types.number(50, {range: [0, 100]}), + originY: types.number(50, {range: [0, 100]}), + }) + const unsubscribe = sheetObj.onValuesChange((values: any) => { + container.style.perspective = `${values.perspective}px` + container.style.perspectiveOrigin = `${values.originX}% ${values.originY}%` + }) + return () => { + unsubscribe() + } + }, []) + + const padding = 100 + const right = window.innerWidth - padding - BoxSize + const bottom = window.innerHeight - padding - BoxSize + + return ( +
+ + + + +
+ ) +} diff --git a/packages/playground/src/shared/dom-basic/index.tsx b/packages/playground/src/shared/dom-basic/index.tsx new file mode 100644 index 0000000..3d1ddf0 --- /dev/null +++ b/packages/playground/src/shared/dom-basic/index.tsx @@ -0,0 +1,15 @@ +import React from 'react' +import ReactDOM from 'react-dom' +import studio from '@theatre/studio' +import {getProject} from '@theatre/core' +import {Scene} from './Scene' +/** + * This is a basic example of using Theatre.js for manipulating the DOM. + */ + +studio.initialize() + +ReactDOM.render( + , + document.getElementById('root'), +) diff --git a/packages/playground/src/shared/three-basic/ThreeScene.tsx b/packages/playground/src/shared/three-basic/ThreeScene.tsx new file mode 100644 index 0000000..c971581 --- /dev/null +++ b/packages/playground/src/shared/three-basic/ThreeScene.tsx @@ -0,0 +1,223 @@ +import React, {useEffect, useRef} from 'react' +import {types} from '@theatre/core' +import type {ISheetObject, IProject} from '@theatre/core' +import { + Color, + DirectionalLight, + Mesh, + MeshPhongMaterial, + PerspectiveCamera, + RawShaderMaterial, + Scene, + ShaderMaterial, + SphereBufferGeometry, + Vector2, + Vector3, + WebGLRenderer, +} from 'three' + +type ThreeSceneProps = { + project: IProject +} + +export default function ThreeScene(props: ThreeSceneProps) { + const canvasRef = useRef(null) + const sheet = props.project.sheet('Sphere') + + // Animation + let sheetObj: ISheetObject | undefined = undefined + let mesh: Mesh | undefined = undefined + + function animate(key: string, props: any) { + if (sheetObj === undefined) { + sheetObj = sheet.object(key, props) + } else { + sheetObj = sheet.object( + key, + {...props, ...sheetObj.value}, + {reconfigure: true}, + ) + } + return sheetObj + } + + function animateMaterial() { + if (mesh === undefined) return + const keys = {} + // Cycle through props + for (const i in mesh.material) { + // @ts-ignore + const value = mesh.material[i] + if (typeof value === 'number') { + // @ts-ignore + keys[i] = value + } else if (value instanceof Vector2) { + // @ts-ignore + keys[i] = {x: value.x, y: value.y} + } else if (value instanceof Vector3) { + // @ts-ignore + keys[i] = {x: value.x, y: value.y, z: value.z} + } else if (value instanceof Color) { + // @ts-ignore + keys[i] = types.rgba({ + r: value.r * 255, + g: value.g * 255, + b: value.b * 255, + a: 1, + }) + } + } + + // Uniforms + if ( + mesh.material instanceof ShaderMaterial || + mesh.material instanceof RawShaderMaterial + ) { + const uniforms = mesh.material.uniforms + // @ts-ignore + keys.uniforms = {} + for (const i in uniforms) { + const uniform = uniforms[i].value + if (typeof uniform === 'number') { + // @ts-ignore + keys.uniforms[i] = uniform + } else if (uniform instanceof Vector2) { + const value = uniform as Vector2 + // @ts-ignore + keys.uniforms[i] = {x: value.x, y: value.y} + } else if (uniform instanceof Vector3) { + const value = uniform as Vector3 + // @ts-ignore + keys.uniforms[i] = {x: value.x, y: value.y, z: value.z} + } else if (uniform instanceof Color) { + const value = uniform as Color + // @ts-ignore + keys.uniforms[i] = {r: value.r, g: value.g, b: value.b} + } + } + } + + // Animate + animate('Material', {material: keys}).onValuesChange((values: any) => { + const {material} = values + for (const key in material) { + if (key === 'uniforms') { + const uniforms = material[key] + for (const uniKey in uniforms) { + const uniform = uniforms[uniKey] + if (typeof uniform === 'number') { + // @ts-ignore + mesh.material.uniforms[uniKey].value = uniform + } else { + // @ts-ignore + mesh.material.uniforms[uniKey].value.copy(uniform) + } + } + } else { + const value = material[key] + if (typeof value === 'number') { + // @ts-ignore + mesh.material[key] = value + } else if (value.r !== undefined) { + // color + // @ts-ignore + mesh.material[key].copy(value) + } else if (value.x !== undefined) { + // vector + // @ts-ignore + mesh.material[key].copy(value) + } + } + } + }) + } + + function animateTransform() { + if (mesh === undefined) return + animate('Transform', { + transform: { + position: { + x: mesh.position.x, + y: mesh.position.y, + z: mesh.position.z, + }, + rotation: { + x: mesh.rotation.x, + y: mesh.rotation.y, + z: mesh.rotation.z, + }, + scale: { + x: mesh.scale.x, + y: mesh.scale.y, + z: mesh.scale.z, + }, + visible: mesh.visible, + }, + }).onValuesChange((values: any) => { + if (mesh === undefined) return + const {transform} = values + mesh.position.set( + transform.position.x, + transform.position.y, + transform.position.z, + ) + mesh.rotation.set( + transform.rotation.x, + transform.rotation.y, + transform.rotation.z, + ) + mesh.scale.set(transform.scale.x, transform.scale.y, transform.scale.z) + mesh.visible = transform.visible + }) + } + + useEffect(() => { + // Basic Three + let raf = -1 + const width = window.innerWidth + const height = window.innerHeight + const renderer = new WebGLRenderer({ + antialias: true, + canvas: canvasRef.current!, + }) + renderer.setPixelRatio(devicePixelRatio) + renderer.setSize(width, height) + const scene = new Scene() + const camera = new PerspectiveCamera(60, width / height) + camera.position.z = 10 + + const light = new DirectionalLight() + light.position.set(1, 5, 4) + scene.add(light) + + mesh = new Mesh(new SphereBufferGeometry(3), new MeshPhongMaterial()) + scene.add(mesh) + + // RAF + function render() { + raf = requestAnimationFrame(render) + renderer.render(scene, camera) + } + render() + return () => { + cancelAnimationFrame(raf) + renderer.dispose() + } + }, []) + + return ( +
+ +
+ + +
+
+ ) +} diff --git a/packages/playground/src/shared/three-basic/index.tsx b/packages/playground/src/shared/three-basic/index.tsx new file mode 100644 index 0000000..2d83893 --- /dev/null +++ b/packages/playground/src/shared/three-basic/index.tsx @@ -0,0 +1,12 @@ +import React from 'react' +import ReactDOM from 'react-dom' +import studio from '@theatre/studio' +import {getProject} from '@theatre/core' +import ThreeScene from './ThreeScene' + +studio.initialize() + +ReactDOM.render( + , + document.getElementById('root'), +)