import { useIdeContext } from 'src/helpers/hooks/useIdeContext' import { useRef, useState, useEffect, useLayoutEffect } from 'react' import { Canvas, extend, useFrame, useThree } from '@react-three/fiber' import { PerspectiveCamera, useEdgeSplit } from '@react-three/drei' import { OrbitControls } from 'three/examples/jsm/controls/OrbitControls' import { Vector3 } from 'three' import { requestRender } from 'src/helpers/hooks/useIdeState' import texture from './dullFrontLitMetal.png' import { TextureLoader } from 'three/src/loaders/TextureLoader' import Customizer from 'src/components/Customizer/Customizer' import DelayedPingAnimation from 'src/components/DelayedPingAnimation/DelayedPingAnimation' const loader = new TextureLoader() const colorMap = loader.load(texture) extend({ OrbitControls }) function Asset({ geometry: incomingGeo }) { const mesh = useEdgeSplit(12*Math.PI/180, true) const ref = useRef({}) useLayoutEffect(() => { if (incomingGeo?.attributes) { ref.current.attributes = incomingGeo.attributes } }, [incomingGeo]) if (!incomingGeo) return null if (incomingGeo.length) return incomingGeo.map((shape, index) => ( )) return ( ) } let debounceTimeoutId function Controls({ onCameraChange, onDragStart, onInit }) { const controls = useRef() const threeInstance = useThree() const { camera, gl } = threeInstance useEffect(() => { onInit(threeInstance) // init camera position camera.position.x = 200 camera.position.y = 140 camera.position.z = 20 camera.far = 10000 camera.fov = 22.5 // matches default openscad fov camera.updateProjectionMatrix() // Order matters with Euler rotations // We want it to rotate around the z or vertical axis first then the x axis to match openscad // in Three.js Y is the vertical axis (Z for openscad) camera.rotation._order = 'ZYX' const getRotations = (): number[] => { const { x, y, z } = camera?.rotation || {} return [x, y, z].map((rot) => (rot * 180) / Math.PI) } const getPositions = () => { // Difficult to make this clean since I'm not sure why it works // The OpenSCAD camera seems hard to work with but maybe it's just me // this gives us a vector the same length as the camera.position const cameraViewVector = new Vector3(0, 0, 1) .applyQuaternion(camera.quaternion) // make unit vector of the camera .multiplyScalar(camera.position.length()) // make it the same length as the position vector // make a vector from the position vector to the cameraView vector const head2Head = new Vector3().subVectors( camera.position, cameraViewVector ) const { x, y, z } = head2Head.add(camera.position) return { position: { x, y, z }, dist: camera.position.length(), } } if (controls.current) { const dragCallback = () => { clearTimeout(debounceTimeoutId) debounceTimeoutId = setTimeout(() => { const [x, y, z] = getRotations() const { position, dist } = getPositions() onCameraChange({ position, rotation: { x, y, z }, dist, }) }, 400) } const dragStart = () => { onDragStart() clearTimeout(debounceTimeoutId) } controls?.current?.addEventListener('end', dragCallback) controls?.current?.addEventListener('start', dragStart) const oldCurrent = controls.current dragCallback() return () => { oldCurrent.removeEventListener('end', dragCallback) oldCurrent.removeEventListener('start', dragStart) } } }, [camera, controls]) useFrame(() => controls.current?.update()) return ( ) } function Box(props) { // This reference will give us direct access to the mesh const mesh = useRef() return ( ) } function Sphere(props) { const mesh = useRef() return ( ) } const IdeViewer = ({ Loading }) => { const { state, thunkDispatch } = useIdeContext() const [isDragging, setIsDragging] = useState(false) const [image, setImage] = useState() const onInit = (threeInstance) => { thunkDispatch({ type: 'setThreeInstance', payload: threeInstance }) } useEffect(() => { setImage(state.objectData?.type === 'png' && state.objectData?.data) setIsDragging(false) }, [state.objectData?.type, state.objectData?.data]) // the following are tailwind colors in hex, can't use these classes to color three.js meshes. const pink400 = '#F472B6' const indigo300 = '#A5B4FC' const indigo900 = '#312E81' return (
{state.isLoading && Loading} {image && (
code-cad preview
)}
setIsDragging(true)} > setIsDragging(true)} onInit={onInit} onCameraChange={(camera) => { thunkDispatch({ type: 'updateCamera', payload: { camera }, }) thunkDispatch((dispatch, getState) => { const state = getState() if (['png', 'INIT'].includes(state.objectData?.type)) { dispatch({ type: 'setLoading' }) requestRender({ state, dispatch, code: state.code, viewerSize: state.viewerSize, camera, parameters: state.currentParameters, }) } }) }} /> {state.objectData?.type === 'png' && ( <> )}
) } export default IdeViewer