playground now has a shared folder and a personal folder
This commit is contained in:
parent
1647d91dc5
commit
4d49a8bdd6
18 changed files with 39 additions and 383 deletions
|
@ -1,3 +1,4 @@
|
|||
import {existsSync, writeFileSync} from 'fs'
|
||||
import path from 'path'
|
||||
import {definedGlobals} from '../../../theatre/devEnv/buildUtils'
|
||||
|
||||
|
@ -5,6 +6,27 @@ const playgroundDir = path.join(__dirname, '..')
|
|||
|
||||
const port = 8080
|
||||
|
||||
const playgroundIndexContent = `
|
||||
/**
|
||||
* This file is created automatically and won't be comitted to the repo.
|
||||
* You can change the import statement and import your own playground code.
|
||||
*
|
||||
* Your own playground code should reside in './personal', which is a folder
|
||||
* that won't be committed to the repo.
|
||||
*
|
||||
* The shared playgrounds which other contributors can use are in the './shared' folder,
|
||||
* which are comitted to the repo.
|
||||
*
|
||||
* Happy playing!
|
||||
* */
|
||||
import './shared/r3f-rocket'
|
||||
`
|
||||
|
||||
const playgroundEntry = path.join(playgroundDir, 'src/index.ts')
|
||||
if (!existsSync(playgroundEntry)) {
|
||||
writeFileSync(playgroundEntry, playgroundIndexContent, {encoding: 'utf-8'})
|
||||
}
|
||||
|
||||
require('esbuild')
|
||||
.serve(
|
||||
{
|
||||
|
@ -12,7 +34,7 @@ require('esbuild')
|
|||
servedir: path.join(playgroundDir, 'src'),
|
||||
},
|
||||
{
|
||||
entryPoints: [path.join(playgroundDir, 'src/index.tsx')],
|
||||
entryPoints: [playgroundEntry],
|
||||
target: ['firefox88'],
|
||||
loader: {
|
||||
'.png': 'file',
|
||||
|
@ -26,5 +48,5 @@ require('esbuild')
|
|||
},
|
||||
)
|
||||
.then((server: unknown) => {
|
||||
console.log('serving', 'http://localhost:' + port)
|
||||
console.log('Playground running at', 'http://localhost:' + port)
|
||||
})
|
||||
|
|
2
packages/playground/src/.gitignore
vendored
Normal file
2
packages/playground/src/.gitignore
vendored
Normal file
|
@ -0,0 +1,2 @@
|
|||
personal
|
||||
index.ts
|
|
@ -1,40 +0,0 @@
|
|||
import {getProject} from '@theatre/core'
|
||||
import studio from '@theatre/studio'
|
||||
|
||||
studio.initialize()
|
||||
|
||||
const proj = getProject('Musical project')
|
||||
const sheet = proj.sheet('Scene')
|
||||
sheet.object('An object', {x: 0})
|
||||
|
||||
setTimeout(async () => {
|
||||
// const d = defer()
|
||||
// window.addEventListener('click', () => {
|
||||
// d.resolve(null)
|
||||
// })
|
||||
// await d.promise
|
||||
const {gainNode, audioContext} = await sheet.sequence.attachAudio({
|
||||
source: 'http://localhost:5000/audio.mp3',
|
||||
})
|
||||
|
||||
const lowerGain = audioContext.createGain()
|
||||
gainNode.disconnect()
|
||||
gainNode.connect(lowerGain)
|
||||
|
||||
lowerGain.gain.setValueAtTime(0.01, audioContext.currentTime)
|
||||
lowerGain.connect(audioContext.destination)
|
||||
|
||||
sheet.sequence.position = 11
|
||||
await sheet.sequence.play({
|
||||
iterationCount: 4,
|
||||
range: [10, 14],
|
||||
direction: 'normal',
|
||||
rate: 2,
|
||||
})
|
||||
// await sheet.sequence.play({
|
||||
// iterationCount: 2,
|
||||
// range: [20, 22],
|
||||
// direction: 'normal',
|
||||
// rate: 4,
|
||||
// })
|
||||
}, 10)
|
|
@ -1 +0,0 @@
|
|||
import './audio'
|
|
@ -1,149 +0,0 @@
|
|||
import {editable as e, SheetProvider} from '@theatre/r3f'
|
||||
import {getProject} from '@theatre/core'
|
||||
import * as THREE from 'three'
|
||||
import React, {useState, useEffect, useRef} from 'react'
|
||||
import type {Color} from '@react-three/fiber'
|
||||
import {Canvas, useFrame} from '@react-three/fiber'
|
||||
import {softShadows, Shadow} from '@react-three/drei'
|
||||
import type {DirectionalLight} from 'three'
|
||||
|
||||
// Soft shadows are expensive, comment and refresh when it's too slow
|
||||
softShadows()
|
||||
|
||||
// credit: https://codesandbox.io/s/camera-pan-nsb7f
|
||||
|
||||
function Button() {
|
||||
const vec = new THREE.Vector3()
|
||||
const light = useRef<DirectionalLight>(undefined as any)
|
||||
const [active, setActive] = useState(false)
|
||||
const [zoom, set] = useState(true)
|
||||
useEffect(
|
||||
() => void (document.body.style.cursor = active ? 'pointer' : 'auto'),
|
||||
[active],
|
||||
)
|
||||
|
||||
useFrame((state) => {
|
||||
const step = 0.1
|
||||
const camera = state.camera as THREE.PerspectiveCamera
|
||||
camera.fov = (THREE as any).MathUtils.lerp(camera.fov, zoom ? 10 : 42, step)
|
||||
camera.position.lerp(
|
||||
vec.set(zoom ? 25 : 10, zoom ? 1 : 5, zoom ? 0 : 10),
|
||||
step,
|
||||
)
|
||||
state.camera.lookAt(0, 0, 0)
|
||||
state.camera.updateProjectionMatrix()
|
||||
|
||||
light.current.position.lerp(
|
||||
vec.set(zoom ? 4 : 0, zoom ? 3 : 8, zoom ? 3 : 5),
|
||||
step,
|
||||
)
|
||||
})
|
||||
|
||||
return (
|
||||
<e.mesh
|
||||
receiveShadow
|
||||
castShadow
|
||||
onClick={() => set(!zoom)}
|
||||
onPointerOver={() => setActive(true)}
|
||||
onPointerOut={() => setActive(false)}
|
||||
uniqueName="The Button"
|
||||
>
|
||||
<sphereBufferGeometry args={[0.75, 64, 64]} />
|
||||
<meshPhysicalMaterial
|
||||
color={active ? 'purple' : '#e7b056'}
|
||||
clearcoat={1}
|
||||
clearcoatRoughness={0}
|
||||
/>
|
||||
<Shadow
|
||||
position-y={-0.79}
|
||||
rotation-x={-Math.PI / 2}
|
||||
opacity={0.6}
|
||||
scale={[0.8, 0.8, 1]}
|
||||
/>
|
||||
<directionalLight
|
||||
ref={light}
|
||||
castShadow
|
||||
intensity={1.5}
|
||||
shadow-camera-far={70}
|
||||
/>
|
||||
</e.mesh>
|
||||
)
|
||||
}
|
||||
|
||||
function Plane({
|
||||
color,
|
||||
uniqueName,
|
||||
...props
|
||||
}: {color: Color; uniqueName: string} & Parameters<typeof e.mesh>[0]) {
|
||||
return (
|
||||
<e.mesh receiveShadow castShadow {...props} uniqueName={uniqueName}>
|
||||
<boxBufferGeometry />
|
||||
<meshStandardMaterial color={color as any} />
|
||||
</e.mesh>
|
||||
)
|
||||
}
|
||||
|
||||
function App() {
|
||||
return (
|
||||
<div>
|
||||
<Canvas
|
||||
// @ts-ignore
|
||||
shadowMap
|
||||
>
|
||||
<SheetProvider
|
||||
getSheet={() => getProject('Playground - R3F').sheet('R3F-Canvas')}
|
||||
>
|
||||
{/* @ts-ignore */}
|
||||
<e.perspectiveCamera makeDefault uniqueName="Camera" />
|
||||
<ambientLight intensity={0.4} />
|
||||
<e.pointLight
|
||||
position={[-10, -10, 5]}
|
||||
intensity={2}
|
||||
color="#ff20f0"
|
||||
uniqueName="Light 1"
|
||||
/>
|
||||
<e.pointLight
|
||||
position={[0, 0.5, -1]}
|
||||
distance={1}
|
||||
intensity={2}
|
||||
color="#e4be00"
|
||||
uniqueName="Light 2"
|
||||
/>
|
||||
<group position={[0, -0.9, -3]}>
|
||||
<Plane
|
||||
color="hotpink"
|
||||
rotation-x={-Math.PI / 2}
|
||||
position-z={2}
|
||||
scale={[4, 20, 0.2]}
|
||||
uniqueName="plane1"
|
||||
/>
|
||||
<Plane
|
||||
color="#e4be00"
|
||||
rotation-x={-Math.PI / 2}
|
||||
position-y={1}
|
||||
scale={[4.2, 0.2, 4]}
|
||||
uniqueName="plane2"
|
||||
/>
|
||||
<Plane
|
||||
color="#736fbd"
|
||||
rotation-x={-Math.PI / 2}
|
||||
position={[-1.7, 1, 3.5]}
|
||||
scale={[0.5, 4, 4]}
|
||||
uniqueName="plane3"
|
||||
/>
|
||||
<Plane
|
||||
color="white"
|
||||
rotation-x={-Math.PI / 2}
|
||||
position={[0, 4.5, 3]}
|
||||
scale={[2, 0.03, 4]}
|
||||
uniqueName="plane4"
|
||||
/>
|
||||
</group>
|
||||
<Button />
|
||||
</SheetProvider>
|
||||
</Canvas>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default App
|
|
@ -1,149 +0,0 @@
|
|||
import type {UseDragOpts} from '@theatre/studio/uiComponents/useDrag'
|
||||
import useDrag from '@theatre/studio/uiComponents/useDrag'
|
||||
import React, {useLayoutEffect, useMemo, useState} from 'react'
|
||||
import studio from '@theatre/studio'
|
||||
import type {IProject, ISheet} from '@theatre/core'
|
||||
import type {IScrub, IStudio} from '@theatre/studio'
|
||||
|
||||
studio.initialize()
|
||||
|
||||
const boxObjectConfig = {
|
||||
position: {
|
||||
x: 0,
|
||||
y: 0,
|
||||
z: 0,
|
||||
},
|
||||
scale: {
|
||||
x: 0,
|
||||
y: 0,
|
||||
z: 0,
|
||||
origin: {
|
||||
x: 0,
|
||||
y: 0,
|
||||
},
|
||||
w: 0,
|
||||
},
|
||||
}
|
||||
|
||||
const Box: React.FC<{
|
||||
id: string
|
||||
sheet: ISheet
|
||||
selection: IStudio['selection']
|
||||
}> = ({id, sheet, selection: selection}) => {
|
||||
// This is cheap to call and always returns the same value, so no need for useMemo()
|
||||
const obj = sheet.object('object ' + id, boxObjectConfig)
|
||||
|
||||
const isSelected = selection.includes(obj)
|
||||
|
||||
const [pos, setPos] = useState<{x: number; y: number}>({x: 0, y: 0})
|
||||
|
||||
useLayoutEffect(() => {
|
||||
const unsubscribeFromChanges = obj.onValuesChange((newValues) => {
|
||||
setPos(newValues.position)
|
||||
})
|
||||
return unsubscribeFromChanges
|
||||
}, [id])
|
||||
|
||||
const [divRef, setDivRef] = useState<HTMLElement | null>(null)
|
||||
|
||||
const dragOpts = useMemo((): UseDragOpts => {
|
||||
let scrub: IScrub | undefined
|
||||
let initial: typeof obj.value.position
|
||||
let firstOnDragCalled = false
|
||||
return {
|
||||
onDragStart() {
|
||||
scrub = studio.scrub()
|
||||
initial = obj.value.position
|
||||
firstOnDragCalled = false
|
||||
},
|
||||
onDrag(x, y) {
|
||||
if (!firstOnDragCalled) {
|
||||
studio.setSelection([obj])
|
||||
firstOnDragCalled = true
|
||||
}
|
||||
scrub!.capture(({set}) => {
|
||||
set(obj.props.position, {
|
||||
x: x + initial.x,
|
||||
y: y + initial.y,
|
||||
z: 0,
|
||||
})
|
||||
})
|
||||
},
|
||||
onDragEnd(dragHappened) {
|
||||
if (dragHappened) {
|
||||
scrub!.commit()
|
||||
} else {
|
||||
scrub!.discard()
|
||||
}
|
||||
},
|
||||
lockCursorTo: 'move',
|
||||
}
|
||||
}, [])
|
||||
|
||||
useDrag(divRef, dragOpts)
|
||||
|
||||
return (
|
||||
<div
|
||||
onClick={() => {
|
||||
studio.setSelection([obj])
|
||||
}}
|
||||
ref={setDivRef}
|
||||
style={{
|
||||
width: 100,
|
||||
height: 100,
|
||||
background: 'gray',
|
||||
position: 'absolute',
|
||||
left: pos.x + 'px',
|
||||
top: pos.y + 'px',
|
||||
boxSizing: 'border-box',
|
||||
border: isSelected ? '1px solid #5a92fa' : '1px solid transparent',
|
||||
}}
|
||||
></div>
|
||||
)
|
||||
}
|
||||
|
||||
let lastBoxId = 1
|
||||
|
||||
export const Scene: React.FC<{project: IProject}> = ({project}) => {
|
||||
const [boxes, setBoxes] = useState<Array<string>>(['0', '1'])
|
||||
|
||||
// This is cheap to call and always returns the same value, so no need for useMemo()
|
||||
const sheet = project.sheet('Scene', 'default')
|
||||
const [selection, setSelection] = useState<IStudio['selection']>()
|
||||
|
||||
useLayoutEffect(() => {
|
||||
return studio.onSelectionChange((newState) => {
|
||||
setSelection(newState)
|
||||
})
|
||||
})
|
||||
|
||||
return (
|
||||
<div
|
||||
style={{
|
||||
position: 'absolute',
|
||||
left: '20vw',
|
||||
right: '20vw',
|
||||
top: 0,
|
||||
bottom: '30vh',
|
||||
background: 'black',
|
||||
display: 'none',
|
||||
}}
|
||||
>
|
||||
<button
|
||||
onClick={() => {
|
||||
setBoxes((boxes) => [...boxes, String(++lastBoxId)])
|
||||
}}
|
||||
>
|
||||
Add
|
||||
</button>
|
||||
{boxes.map((id) => (
|
||||
<Box
|
||||
key={'box' + id}
|
||||
id={id}
|
||||
sheet={sheet}
|
||||
selection={selection ?? []}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
)
|
||||
}
|
|
@ -1,33 +0,0 @@
|
|||
import React from 'react'
|
||||
import ReactDOM from 'react-dom'
|
||||
import studio from '@theatre/studio'
|
||||
import {getProject} from '@theatre/core'
|
||||
import {Scene} from './Scene'
|
||||
import bg from '../../xeno/bgs/8.png'
|
||||
|
||||
studio.initialize()
|
||||
|
||||
document.body.style.cssText = `
|
||||
background-image: url(${bg});
|
||||
background-color: #3A3A39;
|
||||
background-position: center bottom;
|
||||
background-size: calc(100% - 0px);
|
||||
background-repeat: no-repeat;
|
||||
`
|
||||
;(function renderDragArea() {
|
||||
const div = document.createElement('div')
|
||||
div.style.cssText = `
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 80px;
|
||||
height: 20px;
|
||||
-webkit-app-region: drag;
|
||||
`
|
||||
document.body.appendChild(div)
|
||||
})()
|
||||
|
||||
ReactDOM.render(
|
||||
<Scene project={getProject('Sample project')} />,
|
||||
document.getElementById('root'),
|
||||
)
|
|
@ -3,6 +3,12 @@ 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 for manipulating the DOM.
|
||||
*
|
||||
* It also uses {@link IStudio.selection | studio.selection} to customize
|
||||
* the selection behavior.
|
||||
*/
|
||||
|
||||
studio.initialize()
|
||||
|
|
@ -1,4 +1,7 @@
|
|||
// sdf
|
||||
/**
|
||||
* A super basic Turtle geometry renderer hooked up to Theatre, so the parameters
|
||||
* can be tweaked and animated.
|
||||
*/
|
||||
import React, {useMemo, useState} from 'react'
|
||||
import {render} from 'react-dom'
|
||||
import {getProject} from '@theatre/core'
|
||||
|
@ -30,9 +33,9 @@ const TurtleExample: React.FC<{}> = (props) => {
|
|||
style={{
|
||||
position: 'fixed',
|
||||
top: '0',
|
||||
right: '20vw',
|
||||
bottom: '30vh',
|
||||
left: '20vw',
|
||||
right: '0',
|
||||
bottom: '0',
|
||||
left: '0',
|
||||
background: 'black',
|
||||
}}
|
||||
>
|
|
@ -1,5 +0,0 @@
|
|||
import React from 'react'
|
||||
import ReactDOM from 'react-dom'
|
||||
import App from './App'
|
||||
|
||||
ReactDOM.render(<App />, document.getElementById('root'))
|
Loading…
Reference in a new issue