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 path from 'path'
|
||||||
import {definedGlobals} from '../../../theatre/devEnv/buildUtils'
|
import {definedGlobals} from '../../../theatre/devEnv/buildUtils'
|
||||||
|
|
||||||
|
@ -5,6 +6,27 @@ const playgroundDir = path.join(__dirname, '..')
|
||||||
|
|
||||||
const port = 8080
|
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')
|
require('esbuild')
|
||||||
.serve(
|
.serve(
|
||||||
{
|
{
|
||||||
|
@ -12,7 +34,7 @@ require('esbuild')
|
||||||
servedir: path.join(playgroundDir, 'src'),
|
servedir: path.join(playgroundDir, 'src'),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
entryPoints: [path.join(playgroundDir, 'src/index.tsx')],
|
entryPoints: [playgroundEntry],
|
||||||
target: ['firefox88'],
|
target: ['firefox88'],
|
||||||
loader: {
|
loader: {
|
||||||
'.png': 'file',
|
'.png': 'file',
|
||||||
|
@ -26,5 +48,5 @@ require('esbuild')
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
.then((server: unknown) => {
|
.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 studio from '@theatre/studio'
|
||||||
import {getProject} from '@theatre/core'
|
import {getProject} from '@theatre/core'
|
||||||
import {Scene} from './Scene'
|
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()
|
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 React, {useMemo, useState} from 'react'
|
||||||
import {render} from 'react-dom'
|
import {render} from 'react-dom'
|
||||||
import {getProject} from '@theatre/core'
|
import {getProject} from '@theatre/core'
|
||||||
|
@ -30,9 +33,9 @@ const TurtleExample: React.FC<{}> = (props) => {
|
||||||
style={{
|
style={{
|
||||||
position: 'fixed',
|
position: 'fixed',
|
||||||
top: '0',
|
top: '0',
|
||||||
right: '20vw',
|
right: '0',
|
||||||
bottom: '30vh',
|
bottom: '0',
|
||||||
left: '20vw',
|
left: '0',
|
||||||
background: 'black',
|
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