129 lines
4.2 KiB
JavaScript
129 lines
4.2 KiB
JavaScript
import _extends from '@babel/runtime/helpers/esm/extends';
|
|
import * as React from 'react';
|
|
import * as THREE from 'three';
|
|
import { applyProps } from '@react-three/fiber';
|
|
import { DecalGeometry } from 'three-stdlib';
|
|
|
|
function isArray(vec) {
|
|
return Array.isArray(vec);
|
|
}
|
|
function vecToArray(vec = [0, 0, 0]) {
|
|
if (isArray(vec)) {
|
|
return vec;
|
|
} else if (vec instanceof THREE.Vector3 || vec instanceof THREE.Euler) {
|
|
return [vec.x, vec.y, vec.z];
|
|
} else {
|
|
return [vec, vec, vec];
|
|
}
|
|
}
|
|
const Decal = /* @__PURE__ */React.forwardRef(function Decal({
|
|
debug,
|
|
depthTest = false,
|
|
polygonOffsetFactor = -10,
|
|
map,
|
|
mesh,
|
|
children,
|
|
position,
|
|
rotation,
|
|
scale,
|
|
...props
|
|
}, forwardRef) {
|
|
const ref = React.useRef(null);
|
|
React.useImperativeHandle(forwardRef, () => ref.current);
|
|
const helper = React.useRef(null);
|
|
const state = React.useRef({
|
|
position: new THREE.Vector3(),
|
|
rotation: new THREE.Euler(),
|
|
scale: new THREE.Vector3(1, 1, 1)
|
|
});
|
|
React.useLayoutEffect(() => {
|
|
const parent = (mesh == null ? void 0 : mesh.current) || ref.current.parent;
|
|
const target = ref.current;
|
|
if (!(parent instanceof THREE.Mesh)) {
|
|
throw new Error('Decal must have a Mesh as parent or specify its "mesh" prop');
|
|
}
|
|
if (parent) {
|
|
applyProps(state.current, {
|
|
position,
|
|
scale
|
|
});
|
|
|
|
// Zero out the parents matrix world for this operation
|
|
const matrixWorld = parent.matrixWorld.clone();
|
|
parent.matrixWorld.identity();
|
|
if (!rotation || typeof rotation === 'number') {
|
|
const o = new THREE.Object3D();
|
|
o.position.copy(state.current.position);
|
|
|
|
// Thanks https://x.com/N8Programs !
|
|
const vertices = parent.geometry.attributes.position.array;
|
|
if (parent.geometry.attributes.normal === undefined) parent.geometry.computeVertexNormals();
|
|
const normal = parent.geometry.attributes.normal.array;
|
|
let distance = Infinity;
|
|
let closestNormal = new THREE.Vector3();
|
|
const ox = o.position.x;
|
|
const oy = o.position.y;
|
|
const oz = o.position.z;
|
|
const vLength = vertices.length;
|
|
let chosenIdx = -1;
|
|
for (let i = 0; i < vLength; i += 3) {
|
|
const x = vertices[i];
|
|
const y = vertices[i + 1];
|
|
const z = vertices[i + 2];
|
|
const xDiff = x - ox;
|
|
const yDiff = y - oy;
|
|
const zDiff = z - oz;
|
|
const distSquared = xDiff * xDiff + yDiff * yDiff + zDiff * zDiff;
|
|
if (distSquared < distance) {
|
|
distance = distSquared;
|
|
chosenIdx = i;
|
|
}
|
|
}
|
|
closestNormal.fromArray(normal, chosenIdx);
|
|
|
|
// Get vector tangent to normal
|
|
o.lookAt(o.position.clone().add(closestNormal));
|
|
o.rotateZ(Math.PI);
|
|
o.rotateY(Math.PI);
|
|
if (typeof rotation === 'number') o.rotateZ(rotation);
|
|
applyProps(state.current, {
|
|
rotation: o.rotation
|
|
});
|
|
} else {
|
|
applyProps(state.current, {
|
|
rotation
|
|
});
|
|
}
|
|
if (helper.current) {
|
|
applyProps(helper.current, state.current);
|
|
}
|
|
target.geometry = new DecalGeometry(parent, state.current.position, state.current.rotation, state.current.scale);
|
|
// Reset parents matix-world
|
|
parent.matrixWorld = matrixWorld;
|
|
return () => {
|
|
target.geometry.dispose();
|
|
};
|
|
}
|
|
}, [mesh, ...vecToArray(position), ...vecToArray(scale), ...vecToArray(rotation)]);
|
|
React.useLayoutEffect(() => {
|
|
if (helper.current) {
|
|
// Prevent the helpers from blocking rays
|
|
helper.current.traverse(child => child.raycast = () => null);
|
|
}
|
|
}, [debug]);
|
|
return /*#__PURE__*/React.createElement("mesh", _extends({
|
|
ref: ref,
|
|
"material-transparent": true,
|
|
"material-polygonOffset": true,
|
|
"material-polygonOffsetFactor": polygonOffsetFactor,
|
|
"material-depthTest": depthTest,
|
|
"material-map": map
|
|
}, props), children, debug && /*#__PURE__*/React.createElement("mesh", {
|
|
ref: helper
|
|
}, /*#__PURE__*/React.createElement("boxGeometry", null), /*#__PURE__*/React.createElement("meshNormalMaterial", {
|
|
wireframe: true
|
|
}), /*#__PURE__*/React.createElement("axesHelper", null)));
|
|
});
|
|
|
|
export { Decal };
|