UNPKG

@codyjasonbennett/react-ogl

Version:

A barebones react renderer for OGL.

185 lines (184 loc) 6.39 kB
import * as OGL from "ogl"; import * as React from "react"; import { ConcurrentRoot } from "react-reconciler/constants"; import create from "zustand"; import { reconciler } from "./reconciler.mjs"; import { OGLContext, useStore } from "./hooks.mjs"; import { applyProps, calculateDpr } from "./utils.mjs"; const roots = /* @__PURE__ */ new Map(); function render(element, target, { dpr = [1, 2], size = { width: ((_b) => (_b = ((_a) => (_a = target.parentElement) == null ? void 0 : _a.clientWidth)()) != null ? _b : 0)(), height: ((_d) => (_d = ((_c) => (_c = target.parentElement) == null ? void 0 : _c.clientHeight)()) != null ? _d : 0)() }, frameloop = "always", orthographic = false, events, ...config } = {}) { var _a2, _b2, _c2; let root = roots.get(target); if (!root) { let animate = function(time = 0, frame) { var _a3, _b3; const state3 = store.getState(); const mode = (_a3 = state3.xr.session) != null ? _a3 : window; if (state3.frameloop === "never") return mode.cancelAnimationFrame(nextFrame); nextFrame = mode.requestAnimationFrame(animate); for (const ref of state3.subscribed) (_b3 = ref.current) == null ? void 0 : _b3.call(ref, state3, time, frame); if (state3.priority) return; state3.renderer.render({ scene: state3.scene, camera: state3.camera }); }, onResize = function(state3) { const { width, height } = state3.size; const projection = state3.orthographic ? "orthographic" : "perspective"; if (state3.renderer.width !== width || state3.renderer.height !== height || state3.camera.type !== projection) { state3.renderer.setSize(width, height); state3.camera[projection]({ aspect: width / height }); } }; const store = create((set, get) => { var _a3; const renderer = config.renderer instanceof OGL.Renderer ? config.renderer : typeof config.renderer === "function" ? config.renderer(target) : new OGL.Renderer({ alpha: true, antialias: true, powerPreference: "high-performance", ...config.renderer, canvas: target }); if (config.renderer && typeof config.renderer !== "function") applyProps(renderer, config.renderer); renderer.dpr = calculateDpr(dpr); const gl = renderer.gl; gl.clearColor(1, 1, 1, 0); const camera = config.camera instanceof OGL.Camera ? config.camera : new OGL.Camera(gl, { fov: 75, near: 1, far: 1e3, ...config.camera }); camera.position.z = 5; if (config.camera) applyProps(camera, config.camera); return { size, xr: { session: null, setSession(session) { set((state3) => ({ xr: { ...state3.xr, session } })); }, connect(session) { get().xr.setSession(session); }, disconnect() { get().xr.setSession(null); } }, renderer, frameloop, orthographic, gl, camera, scene: (_a3 = config.scene) != null ? _a3 : new OGL.Transform(), priority: 0, subscribed: [], subscribe(refCallback, renderPriority = 0) { const { subscribed } = get(); subscribed.push(refCallback); set((state3) => ({ priority: state3.priority + renderPriority })); }, unsubscribe(refCallback, renderPriority = 0) { const { subscribed } = get(); const index = subscribed.indexOf(refCallback); if (index !== -1) subscribed.splice(index, 1); set((state3) => ({ priority: state3.priority - renderPriority })); }, events, mouse: new OGL.Vec2(), raycaster: new OGL.Raycast(gl), hovered: /* @__PURE__ */ new Map(), set, get }; }); const state2 = store.getState(); if (((_a2 = state2.events) == null ? void 0 : _a2.connect) && !((_b2 = state2.events) == null ? void 0 : _b2.connected)) state2.events.connect(target, state2); (_c2 = config.onCreated) == null ? void 0 : _c2.call(config, state2); let nextFrame; if (state2.frameloop !== "never") animate(); store.subscribe(onResize); onResize(state2); const logRecoverableError = typeof reportError === "function" ? reportError : console.error; const container = reconciler.createContainer( store, ConcurrentRoot, null, false, null, "", logRecoverableError, null ); root = { container, store }; roots.set(target, root); } const state = root.store.getState(); if (state.size.width !== size.width || state.size.height !== size.height) state.set(() => ({ size })); if (state.frameloop !== frameloop) state.set(() => ({ frameloop })); if (state.orthographic !== orthographic) state.set(() => ({ orthographic })); reconciler.updateContainer( /* @__PURE__ */ React.createElement(OGLContext.Provider, { value: root.store }, /* @__PURE__ */ React.createElement("primitive", { object: state.scene }, element)), root.container, null, () => void 0 ); return root.store; } function unmountComponentAtNode(target) { const root = roots.get(target); if (!root) return; reconciler.updateContainer(null, root.container, null, () => { var _a; roots.delete(target); const state = root.store.getState(); state.set(() => ({ frameloop: "never" })); if ((_a = state.events) == null ? void 0 : _a.disconnect) state.events.disconnect(target, state); }); } const createRoot = (target, config) => ({ render: (element) => render(element, target, config), unmount: () => unmountComponentAtNode(target) }); function PortalRoot({ children, target, container }) { const store = useStore(); React.useMemo(() => Object.assign(container, store), [container, store]); return /* @__PURE__ */ React.createElement("primitive", { object: target }, children); } function createPortal(children, target) { const container = {}; return reconciler.createPortal( /* @__PURE__ */ React.createElement(PortalRoot, { target, container }, children), container, null, null ); } export { createPortal, createRoot, render, unmountComponentAtNode }; //# sourceMappingURL=renderer.mjs.map