@codyjasonbennett/react-ogl
Version:
A barebones react renderer for OGL.
166 lines (165 loc) • 6.24 kB
JavaScript
import * as React from "react";
import * as OGL from "ogl";
import { RESERVED_PROPS, INSTANCE_PROPS, POINTER_EVENTS } from "./constants.mjs";
import { useIsomorphicLayoutEffect } from "./hooks.mjs";
const toPascalCase = (str) => str.charAt(0).toUpperCase() + str.substring(1);
const classExtends = (a, b) => Object.prototype.isPrototypeOf.call(a, b) || a === b;
const calculateDpr = (dpr) => Array.isArray(dpr) ? Math.min(Math.max(dpr[0], window.devicePixelRatio), dpr[1]) : dpr;
function resolve(root, key) {
let target = root[key];
if (!key.includes("-"))
return { root, key, target };
const chain = key.split("-");
target = chain.reduce((acc, key2) => acc[key2], root);
key = chain.pop();
if (!(target == null ? void 0 : target.set))
root = chain.reduce((acc, key2) => acc[key2], root);
return { root, key, target };
}
const INDEX_REGEX = /-\d+$/;
function attach(parent, child) {
if (typeof child.props.attach === "string") {
if (INDEX_REGEX.test(child.props.attach)) {
const target = child.props.attach.replace(INDEX_REGEX, "");
const { root: root2, key: key2 } = resolve(parent.object, target);
if (!Array.isArray(root2[key2]))
root2[key2] = [];
}
const { root, key } = resolve(parent.object, child.props.attach);
child.object.__previousAttach = root[key];
root[key] = child.object;
child.object.__currentAttach = parent.object.__currentAttach = root[key];
} else if (typeof child.props.attach === "function") {
child.object.__previousAttach = child.props.attach(parent.object, child.object);
}
}
function detach(parent, child) {
if (typeof child.props.attach === "string") {
if (parent.object.__currentAttach === child.object.__currentAttach) {
const { root, key } = resolve(parent.object, child.props.attach);
root[key] = child.object.__previousAttach;
}
} else {
child.object.__previousAttach(parent.object, child.object);
}
delete child.object.__previousAttach;
delete child.object.__currentAttach;
delete parent.object.__currentAttach;
}
function applyProps(object, newProps, oldProps) {
var _a, _b;
for (const prop in newProps) {
if (RESERVED_PROPS.includes(prop))
continue;
if (INSTANCE_PROPS.includes(prop))
continue;
if (newProps[prop] === (oldProps == null ? void 0 : oldProps[prop]))
continue;
const isHandler = POINTER_EVENTS.includes(prop);
if (isHandler) {
object.__handlers = { ...object.__handlers, [prop]: newProps[prop] };
continue;
}
const value = newProps[prop];
const { root, key, target } = resolve(object, prop);
const isMathClass = typeof (target == null ? void 0 : target.set) === "function" && typeof (target == null ? void 0 : target.copy) === "function";
if (!ArrayBuffer.isView(value) && isMathClass) {
if (target.constructor === value.constructor) {
target.copy(value);
} else if (Array.isArray(value)) {
target.set(...value);
} else {
const scalar = new Array(target.length).fill(value);
target.set(...scalar);
}
} else {
const uniformList = value;
if (key === "uniforms") {
for (const uniform in uniformList) {
let uniformValue = (_b = (_a = uniformList[uniform]) == null ? void 0 : _a.value) != null ? _b : uniformList[uniform];
if (typeof uniformValue === "string") {
uniformValue = new OGL.Color(uniformValue);
} else if ((uniformValue == null ? void 0 : uniformValue.constructor) === Array && uniformValue.every((v) => typeof v === "number")) {
uniformValue = new OGL[`Vec${uniformValue.length}`](...uniformValue);
}
root.uniforms[uniform] = { value: uniformValue };
}
} else {
root[key] = value;
}
}
}
}
function createEvents(state) {
const handleEvent = (event, type) => {
var _a, _b, _c;
state.mouse.x = event.offsetX / state.size.width * 2 - 1;
state.mouse.y = -(event.offsetY / state.size.height) * 2 + 1;
const interactive = [];
state.scene.traverse((node) => {
var _a2, _b2;
if (node instanceof OGL.Mesh && node.__handlers && ((_b2 = (_a2 = node.geometry) == null ? void 0 : _a2.attributes) == null ? void 0 : _b2.position))
interactive.push(node);
});
state.raycaster.castMouse(state.camera, state.mouse);
const intersects = state.raycaster.intersectMeshes(interactive);
const isHoverEvent = type === "onPointerMove";
for (const object of intersects) {
const handlers = object.__handlers;
if (!handlers)
continue;
if (isHoverEvent && !state.hovered.get(object.id)) {
state.hovered.set(object.id, object);
(_a = handlers.onPointerMove) == null ? void 0 : _a.call(handlers, { ...object.hit, nativeEvent: event });
(_b = handlers.onPointerOver) == null ? void 0 : _b.call(handlers, { ...object.hit, nativeEvent: event });
} else {
(_c = handlers[type]) == null ? void 0 : _c.call(handlers, { ...object.hit, nativeEvent: event });
}
}
if (isHoverEvent || type === "onPointerDown") {
state.hovered.forEach((object) => {
const handlers = object.__handlers;
if (!intersects.length || !intersects.find((i) => i === object)) {
state.hovered.delete(object.id);
if (handlers == null ? void 0 : handlers.onPointerOut)
handlers.onPointerOut({ ...object.hit, nativeEvent: event });
}
});
}
return intersects;
};
return { handleEvent };
}
function Block({ set }) {
useIsomorphicLayoutEffect(() => {
set(new Promise(() => null));
return () => set(false);
}, []);
return null;
}
class ErrorBoundary extends React.Component {
constructor() {
super(...arguments);
this.state = { error: false };
}
componentDidCatch(error) {
this.props.set(error);
}
render() {
return this.state.error ? null : this.props.children;
}
}
ErrorBoundary.getDerivedStateFromError = () => ({ error: true });
export {
Block,
ErrorBoundary,
applyProps,
attach,
calculateDpr,
classExtends,
createEvents,
detach,
resolve,
toPascalCase
};
//# sourceMappingURL=utils.mjs.map