playground now has a shared folder and a personal folder

This commit is contained in:
Aria Minaei 2021-10-04 20:25:38 +02:00
parent 1647d91dc5
commit 4d49a8bdd6
18 changed files with 39 additions and 383 deletions

View file

@ -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
View file

@ -0,0 +1,2 @@
personal
index.ts

View file

@ -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)

View file

@ -1 +0,0 @@
import './audio'

View file

@ -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

View file

@ -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>
)
}

View file

@ -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'),
)

View file

@ -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()

View file

@ -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',
}} }}
> >

View file

@ -1,5 +0,0 @@
import React from 'react'
import ReactDOM from 'react-dom'
import App from './App'
ReactDOM.render(<App />, document.getElementById('root'))