@threlte/core
Version:
A 3D framework for the web, built on top of Svelte and Three.js
130 lines (129 loc) • 4.57 kB
JavaScript
import { getContext, onDestroy, setContext } from 'svelte';
import { AgXToneMapping, ColorManagement, PCFSoftShadowMap, WebGLRenderer } from 'three';
import { useTask } from '../../hooks/useTask.svelte.js';
import { currentWritable, watch } from '../../utilities/index.js';
import { useCamera } from './camera.js';
import { useDisposal } from './disposal.js';
import { useDOM } from './dom.js';
import { useScene } from './scene.js';
import { useScheduler } from './scheduler.svelte.js';
export const createRendererContext = (options) => {
const { dispose } = useDisposal();
const { camera } = useCamera();
const { scene } = useScene();
const { invalidate, renderStage, autoRender, scheduler, resetFrameInvalidation } = useScheduler();
const { size, canvas } = useDOM();
const renderer = options.createRenderer
? options.createRenderer(canvas)
: new WebGLRenderer({
canvas,
powerPreference: 'high-performance',
antialias: true,
alpha: true
});
const autoRenderTask = renderStage.createTask(Symbol('threlte-auto-render-task'), () => {
renderer.render(scene, camera.current);
});
const context = {
renderer: renderer,
colorManagementEnabled: currentWritable(options.colorManagementEnabled ?? true),
colorSpace: currentWritable(options.colorSpace ?? 'srgb'),
dpr: currentWritable(options.dpr ?? window.devicePixelRatio),
shadows: currentWritable(options.shadows ?? PCFSoftShadowMap),
toneMapping: currentWritable(options.toneMapping ?? AgXToneMapping),
autoRenderTask
};
setContext('threlte-renderer-context', context);
watch([context.colorManagementEnabled], ([colorManagementEnabled]) => {
ColorManagement.enabled = colorManagementEnabled;
});
watch([context.colorSpace], ([colorSpace]) => {
if ('outputColorSpace' in renderer) {
renderer.outputColorSpace = colorSpace;
}
});
watch([context.dpr], ([dpr]) => {
if ('setPixelRatio' in renderer) {
renderer.setPixelRatio(dpr);
}
});
// Resize the renderer when the size changes
const { start, stop } = useTask(() => {
if (!('xr' in renderer) || renderer.xr?.isPresenting)
return;
renderer.setSize(size.current.width, size.current.height);
invalidate();
stop();
}, {
before: autoRenderTask,
autoStart: false,
autoInvalidate: false
});
watch([size], () => {
start();
});
watch([context.shadows], ([shadows]) => {
if (!('shadowMap' in renderer))
return;
renderer.shadowMap.enabled = !!shadows;
if (shadows && shadows !== true) {
renderer.shadowMap.type = shadows;
}
else if (shadows === true) {
renderer.shadowMap.type = PCFSoftShadowMap;
}
});
watch([context.toneMapping], ([toneMapping]) => {
if (!('toneMapping' in renderer))
return;
renderer.toneMapping = toneMapping;
});
watch([autoRender], ([autoRender]) => {
if (autoRender) {
context.autoRenderTask.start();
}
else {
context.autoRenderTask.stop();
}
return () => {
context.autoRenderTask.stop();
};
});
if ('setAnimationLoop' in context.renderer) {
const renderer = context.renderer;
renderer.setAnimationLoop((time) => {
dispose();
scheduler.run(time);
resetFrameInvalidation();
});
}
onDestroy(() => {
if ('dispose' in context.renderer) {
const dispose = context.renderer.dispose;
dispose();
}
});
$effect.pre(() => {
context.colorManagementEnabled.set(options.colorManagementEnabled ?? true);
});
$effect.pre(() => {
context.colorSpace.set(options.colorSpace ?? 'srgb');
});
$effect.pre(() => {
context.toneMapping.set(options.toneMapping ?? AgXToneMapping);
});
$effect.pre(() => {
context.shadows.set(options.shadows ?? PCFSoftShadowMap);
});
$effect.pre(() => {
context.dpr.set(options.dpr ?? window.devicePixelRatio);
});
return context;
};
export const useRenderer = () => {
const context = getContext('threlte-renderer-context');
if (!context) {
throw new Error('useRenderer can only be used in a child component to <Canvas>.');
}
return context;
};