feat(example): three-basic (#418)
This commit is contained in:
parent
4c375672af
commit
e7f23cfa4e
5 changed files with 382 additions and 0 deletions
79
packages/playground/src/shared/dom-basic/Box3D.tsx
Normal file
79
packages/playground/src/shared/dom-basic/Box3D.tsx
Normal file
|
@ -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<HTMLDivElement>(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 (
|
||||||
|
<div ref={elementRef} style={Box3DCSS}>
|
||||||
|
<span style={Box3DTextCSS}>{name}</span>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
53
packages/playground/src/shared/dom-basic/Scene.tsx
Normal file
53
packages/playground/src/shared/dom-basic/Scene.tsx
Normal file
|
@ -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<HTMLDivElement>(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 (
|
||||||
|
<div ref={containerRef} style={SceneCSS}>
|
||||||
|
<Box3D sheet={sheet} name="Top Left" x={padding} y={padding} />
|
||||||
|
<Box3D sheet={sheet} name="Top Right" x={right} y={padding} />
|
||||||
|
<Box3D sheet={sheet} name="Bottom Left" x={padding} y={bottom} />
|
||||||
|
<Box3D sheet={sheet} name="Bottom Right" x={right} y={bottom} />
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
15
packages/playground/src/shared/dom-basic/index.tsx
Normal file
15
packages/playground/src/shared/dom-basic/index.tsx
Normal file
|
@ -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(
|
||||||
|
<Scene project={getProject('Sample project')} />,
|
||||||
|
document.getElementById('root'),
|
||||||
|
)
|
223
packages/playground/src/shared/three-basic/ThreeScene.tsx
Normal file
223
packages/playground/src/shared/three-basic/ThreeScene.tsx
Normal file
|
@ -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<HTMLCanvasElement>(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 (
|
||||||
|
<div style={{overflow: 'hidden'}}>
|
||||||
|
<canvas ref={canvasRef} />
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
position: 'absolute',
|
||||||
|
left: '100px',
|
||||||
|
top: '0',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<button onClick={animateMaterial}>Animate Material</button>
|
||||||
|
<button onClick={animateTransform}>Animate Transform</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
12
packages/playground/src/shared/three-basic/index.tsx
Normal file
12
packages/playground/src/shared/three-basic/index.tsx
Normal file
|
@ -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(
|
||||||
|
<ThreeScene project={getProject('Three Basic')} />,
|
||||||
|
document.getElementById('root'),
|
||||||
|
)
|
Loading…
Reference in a new issue