Last active
November 16, 2023 10:38
-
-
Save colelawrence/ae9cd23f62b7412520c00e0c2e5829c2 to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import { | |
Object3D, PerspectiveCamera, Scene, | |
WebGLRenderer | |
} from "three"; | |
import { CleanupFn } from "./CleanupFn"; | |
import { DisposePool, isDisposable } from "./DisposePool"; | |
import { merge } from "./merge"; | |
import { ISheet } from "@theatre/core"; | |
import { TheatreMergeable, theatreMerge } from "./theatreMerge"; | |
import { EventsPool } from "./EventsPool"; | |
type SceneItemMutHelper<T> = { | |
self: T; | |
cleanup: CleanupFn; | |
}; | |
type SceneAddOverrides<T extends object> = ({ theatreSheet: ISheet; name: string; } | | |
{ theatreSheet?: never; }) & | |
TheatreMergeable<T>; | |
type FrameFn = (deltaMs: number) => void; | |
export class SceneManager { | |
/** This scene needs to be rendered */ | |
#invalid = false; | |
#pool: DisposePool; | |
#lastFrame = Date.now(); | |
#onFrame: FrameFn[] = []; | |
#mutCleanup = (callback: () => void) => { | |
this.#pool.addfn(callback); | |
}; | |
public readonly scene = new Scene(); | |
public readonly camera: PerspectiveCamera; | |
constructor( | |
public readonly mount: HTMLElement, | |
public readonly renderer: WebGLRenderer, | |
camera: SceneAddOverrides<PerspectiveCamera> | |
) { | |
const pool = (this.#pool = new DisposePool()); | |
this.render = this.render.bind(this); | |
pool.add(this.renderer); | |
this.camera = this.add(new PerspectiveCamera(), camera); | |
merge(this.renderer.domElement.style, { | |
position: "absolute", | |
inset: "0", | |
}); | |
mount.appendChild(this.renderer.domElement); | |
this.#pool.addfn(mount.removeChild.bind(mount, this.renderer.domElement)); | |
} | |
dispose() { | |
this.#pool.dispose(); | |
} | |
events<T extends EventTarget>(target: T): EventsPool<T> { | |
const pool = new EventsPool(target); | |
this.#pool.add(pool); | |
return pool; | |
} | |
onFrame(fn: FrameFn) { | |
this.#onFrame.push(fn); | |
return () => { | |
const idx = this.#onFrame.indexOf(fn); | |
if (idx >= 0) { | |
this.#onFrame.splice(idx, 1); | |
} | |
}; | |
} | |
add<T extends object>( | |
self: T, | |
overrides?: SceneAddOverrides<T>, | |
mut?: (helpers: SceneItemMutHelper<T>) => void | |
) { | |
if (overrides) { | |
const { theatreSheet, ...nonTheatreOverrides } = overrides; | |
// @ts-ignore | |
const name = nonTheatreOverrides.name; | |
const { groups } = theatreMerge(self, nonTheatreOverrides as any); | |
if (theatreSheet) { | |
for (const { keys, target, compoundProps } of groups) { | |
const obj = theatreSheet.object( | |
[name, keys.join(".")].filter(Boolean).join(" / "), | |
compoundProps, | |
{ reconfigure: true } | |
); | |
this.#pool.addfn( | |
obj.onValuesChange( | |
// create the function directly and bind the values for the on values change | |
// I'm not sure this is faster, but I'm pretty sure it is to compile it since | |
// we always know the exact values that will be updated. | |
new Function( | |
"t", | |
"v", | |
[ | |
...Object.keys(compoundProps).map((k) => `t.${k} = v.${k}`), | |
target instanceof PerspectiveCamera | |
? "t.updateProjectionMatrix()" | |
: "", | |
"this.invalidate()", | |
].join(";") | |
).bind(this, target) | |
) | |
); | |
} | |
} else if (groups.length > 0) { | |
console.error( | |
"No theatreSheet provided for creating object theatre props", | |
groups | |
); | |
} | |
} | |
if (self instanceof Object3D) { | |
this.scene.add(self); | |
this.#pool.addfn(this.scene.remove.bind(this.scene, self)); | |
} | |
if (isDisposable(self)) { | |
this.#pool.add(self); | |
} | |
mut?.({ | |
self, | |
cleanup: this.#mutCleanup, | |
}); | |
return self; | |
} | |
render = () => { | |
if (this.#pool.disposed || !this.#invalid) return; | |
const last = this.#lastFrame; | |
const now = Date.now(); | |
const delta = now - last; | |
this.#lastFrame = now; | |
for (let i = 0; i < this.#onFrame.length; i++) { | |
this.#onFrame[i](delta); | |
} | |
this.#invalid = false; | |
this.renderer.render(this.scene, this.camera); | |
}; | |
/** Schedule a render due to changed values */ | |
invalidate() { | |
if (this.#invalid) return; | |
this.#invalid = true; | |
requestAnimationFrame(this.render); | |
} | |
/** Handle window resize event. */ | |
onWindowResize() { | |
const width = this.mount.clientWidth; | |
const height = this.mount.clientHeight; | |
this.camera.aspect = width / height; | |
this.camera.updateProjectionMatrix(); | |
this.renderer.setSize(width, height); | |
this.render(); | |
} | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import { ISheet, getProject, types } from "@theatre/core"; | |
const tn = (def: number, nudgeMultiplier = 0.01) => | |
types.number(def, { nudgeMultiplier }); | |
const t0n = (def: number, max: number) => | |
types.number(def, { range: [0, max] }); | |
const t01 = (def: number) => t0n(def, 1); | |
const tpos = (x: number, y: number, z: number) => ({ | |
x: tn(x), | |
y: tn(y), | |
z: tn(z), | |
}); | |
const pos = (x: number, y: number, z: number) => ({ x, y, z }); | |
const trot = (x: number, y: number, z: number) => ({ | |
x: tn(x, 0.01), | |
y: tn(y, 0.01), | |
z: tn(z, 0.01), | |
}); | |
function addBoxes(ctx: SceneManager, sheet: ISheet) { | |
const geom = ctx.add(new BoxGeometry()); | |
const mat = ctx.add(new MeshStandardMaterial(), { | |
name: "Box Material", | |
theatreSheet: sheet, | |
color: { | |
r: t0n(255, 255), | |
g: t0n(128, 255), | |
b: t0n(255, 255), | |
}, | |
}); | |
ctx.add(new Mesh(geom, mat), { | |
name: "box", | |
theatreSheet: sheet, | |
position: tpos(0, 0, 0), | |
rotation: tpos(-5, 0, 0), | |
castShadow: false, | |
receiveShadow: false, | |
}); | |
ctx.add(new Mesh(geom, mat), { | |
name: "box2", | |
theatreSheet: sheet, | |
position: tpos(0, 0, 0), | |
rotation: tpos(-5, 0, 0), | |
castShadow: false, | |
receiveShadow: false, | |
}); | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment