UNPKG

threed-garden

Version:

ThreeD Garden: WebGL 3D Environment Interface for Next.JS React TypeScript Three.JS React-Three Physics, 2D Paper.JS; APIs: Apollo GraphQL, WordPress; CSS: Tailwind, Radix-UI; Libraries: FarmBot 3D; AI: OpenAI, DeepSeek

1,144 lines (1,026 loc) • 43.1 kB
// 'use client' // ================================================ // RESOURCES // ** VALTIO (State) Imports import { proxy, useSnapshot, } from 'valtio' // ** REACT Imports import { useEffect, useState, useRef, Suspense, } from 'react' // ** REACT THREE Imports import { useThree, useFrame, useLoader, } from '@react-three/fiber' import { useCursor, useGLTF, useFBX, useAnimations, useTexture, // Bounds, useBounds, // inside Model } from '@react-three/drei' // Three Loaders import { TextureLoader } from 'three/src/loaders/TextureLoader' import { FBXLoader } from 'three/examples/jsm/loaders/FBXLoader' import { TGALoader } from 'three/examples/jsm/loaders/TGALoader' import { GLTFLoader } from 'three/examples/jsm/loaders/GLTFLoader' // import { DRACOLoader } from 'three/examples/jsm/loaders/DRACOLoader' import { OBJLoader } from 'three/examples/jsm/loaders/OBJLoader' // import { MTLLoader } from 'three/examples/jsm/loaders/MTLLoader' // React Spring (for actions) // import { useSpring, a, Globals } from '@react-spring/three' // Globals.assign({ // frameLoop: 'always', // }) // ** PHYSICS Imports import { RigidBody } from '@react-three/rapier' // ** EXAMPLES Imports import CoffeeCup from '#/lib/threed/components/examples/CoffeeCup/CoffeeCup' // HELPERS Imports // ** UUID Generator import { v4 as newUUID } from 'uuid' // ** COLORFUL CONSOLE MESSAGES (ccm) import ccm from '#/lib/utils/console-colors' // ** SPINNER // import Spinner from '#/layout/ui/spinner' // ** CANVAS LOADER // import Loader from '@react-three/drei' // ** CANVAS HTML // import Html from '@react-three/drei' import { Html, Loader, } from '@react-three/drei' // ========================================== // ** VARIABLES const debug = true // false | true // ts: boolean const DEBUG = true // false | true // ts: boolean // ========================================== // ** ThreeD == Group of > Files of > Nodes of > 3DObjects // interface IThreeD { // ** TODO let IThreeD = { name: 'THREED DEFAULT: FILE (GROUP OF) NODES TO RENDER', data: {}, // original data records from db/api group: { group_id: newUUID(), // NOTE: 1.570796 radians = Math.PI / 2 = 90 degrees group_position: [0, 0, 0], group_rotation: [0, 0, 0], // [-Math.PI / 2, 0, 0], group_scale: 1.0, // 0.01 | 0.05 | 0.5 | 1.0 | 5.0 | 50.0 | 100.0 }, file: { __typename: 'File', file: null, fileId: 0, fileType: 'tbd', isObject3d: false, isUrl: false, modified: '', title: '', uri: '', url: '', }, fileUrl: '', nodes: [], // nodes from file(s) == threed.nodes actionModes: [ 'translate', 'rotate', 'scale' ] } // ** ThreeD Object -- Constructor Function // -- returns new ThreeD // function ThreeD(this: IThreeD, _type: string = 'ThreeD') { // ** TODO: interface IThreeD function ThreeD(_type = 'ThreeD') { // object params this.name = 'ThreeD: Default FILE (GROUP OF) NODES TO RENDER' this.data = {} // original data records from db/api this.group = { group_id: newUUID(), // NOTE: 1.570796 radians = Math.PI / 2 = 90 degrees group_position: [0, 0, 0], group_rotation: [0, 0, 0], // [-Math.PI / 2, 0, 0], group_scale: 1.0, // 0.01 | 0.05 | 0.5 | 1.0 | 5.0 | 50.0 | 100.0 } this.file = { __typename: 'File', file: null, fileId: 0, fileType: 'tbd', isObject3d: false, isUrl: false, modified: '', title: '', uri: '', url: '', } this.fileUrl = '' this.nodes = [] // nodes from file(s) == threed.nodes this.actionModes = [ 'translate', 'rotate', 'scale' ] } // ** ThreeD Model -||- const Model = ({ threed = new ThreeD(), // name = ThreeD.name, // group = ThreeD.group, // file = ThreeD.file }) => { // ** // ** for testing only // ** return 1 ThreeD Object in JSX // return <><CoffeeCup /></> // get Reactive state on each model (using valtio) // const modelVState = proxy({ current: null, mode: 0 }) // const modelVState = proxy({ current: null, mode: 0 }) // Ties this component to the model state // const snap = useSnapshot(modelVState) // ** // if (debug || DEBUG) console.debug(`%c====================================`, ccm.black) if (debug || DEBUG) console.debug('%cšŸ–Šļø Model props.threed', ccm.red, threed.name, threed) // if (debug || DEBUG) console.debug('%cšŸ–Šļø Model props.threed.name', ccm.red, threed.name) // if (debug || DEBUG) console.debug('%cšŸ–Šļø Model props.threed.group', ccm.red, threed.group) // if (debug || DEBUG) console.debug('%cšŸ–Šļø Model props.threed.file', ccm.red, threed.file) // if (debug || DEBUG) console.debug(`%c====================================`, ccm.black) // if (debug || DEBUG) console.debug('%cšŸ–Šļø Model modelVState', ccm.red, modelVState) // if (debug || DEBUG) console.debug('%cšŸ–Šļø Model snap', ccm.red, snap) // if (debug || DEBUG) console.debug('Model props.sceneState', sceneState) // if (debug || DEBUG) console.debug('Model props.storeState', storeState) // set a default file to load for Model (for testing) // fileUrlDefault: '/assets/examples/compressed.glb' | '/assets/examples/compressed-v002.glb' | const fileUrlDefault = // '' // '/assets/threeds/synty/polygon/farm/Demo/Polygon_Farm_Demo_FBX.glb' // 'https://threedpublic.s3.us-west-2.amazonaws.com/assets/threeds/synty/polygon/farm/Demo/Polygon_Farm_Demo_FBX.glb' // '/assets/threeds/synty/polygon/farm/Demo/Polygon_Farm_Demo_FBX.fbx' // 'https://threedpublic.s3.us-west-2.amazonaws.com/assets/threeds/synty/polygon/farm/Demo/Polygon_Farm_Demo_FBX.fbx' // '/assets/examples/coffee-transformed.glb' // 'https://threedpublic.s3.us-west-2.amazonaws.com/assets/threeds/synty/polygon/farm/FBX/SM_Prop_Carrot_01.fbx' 'https://threedpublic.s3.us-west-2.amazonaws.com/assets/threeds/synty/polygon/farm/FBX/SM_Prop_Plant_Corn_01.fbx' // const fileUrl = threed.file?.isUrl ? threed.file.url : fileUrlDefault let fileUrl = fileUrlDefault if (threed.data.modelFiles.nodes[0].url) { fileUrl = threed.data.modelFiles.nodes[0].url } const textureUrlDefault = '' // blank string const textureUrl = threed.file?.isUrl && threed.file?.isTexture ? threed.file.url : textureUrlDefault // this model = threed_model -||- const model = { ref: useRef(null), name: threed.name, file: fileUrl, // threed.data.modelFiles.nodes[0].url, group: threed.group, nodes: threed.nodes, //** custom fields __typename: threed.data.__typename, // 'Threed' content: threed.data.content, files: threed.data.files, threedId: threed.data.threedId, title: threed.data.title, // 'ThreeD Demo: McGee Ranch (FBX)', uri: threed.data.uri, // '/threed-threed/threed-demo-mcgee-ranch-fbx/', version: threed.data.version, // '0.0.2', modified: threed.data.modified, // '2024-03-13T04:18:47', positionX: threed.data.positionX, positionY: threed.data.positionY, positionZ: threed.data.positionZ, rotationX: threed.data.rotationX, rotationY: threed.data.rotationY, rotationZ: threed.data.rotationZ, scaleX: threed.data.scaleX, scaleY: threed.data.scaleY, scaleZ: threed.data.scaleZ, texture: textureUrl, // state: modelVState, // for funzees ?? // sceneState: sceneState, // for funzees ?? // storeState: storeState, // for funzees ?? // file type? type: 'tbd', // fbx | gltf | obj | threed | threed_node | png | texture // ** decide file type from file extension (and other qualifiers) // const testExt = /\.(glb|gltf|fbx|obj|mtl|gif|jpe?g|tiff?|png|webp|bmp)$/i.test(fileUrl) // if (debug || DEBUG) console.debug('testExt', testExt) is: { // is Ready to go? isReadyForCanvas: false, // file type is.isSupported? isSupported: /\.(glb|gltf|fbx|obj|mtl|gif|jpe?g|png|webp)$/i.test(fileUrl), // 3D Objects isObject3D: /\.(glb|gltf|fbx|obj)$/i.test(fileUrl), isGLTF: /\.(glb|gltf)$/i.test(fileUrl), isFBX: /\.(fbx)$/i.test(fileUrl), isOBJ: /\.(obj)$/i.test(fileUrl), isMTL: /\.(mtl)$/i.test(fileUrl), // meta file for OBJ // 2D Files isImage: /\.(gif|jpe?g|tiff?|png|webp|bmp)$/i.test(fileUrl), isPNG: /\.(png)$/i.test(fileUrl), isJPG: /\.(jpe?g)$/i.test(fileUrl), isGIF: /\.(gif)$/i.test(fileUrl), isSVG: /\.(svg)$/i.test(fileUrl), isWEBP: /\.(webp)$/i.test(fileUrl), // Texture Files isTexture: textureUrl ? true : false, // ThreeD Nodes isThreeD: false, isThreeDNode: false, }, // animations ani: { ref: null, // useRef(null), actions: [], names: [], clips: [], mixer: {}, }, } // fetch the file (GLTF, FBX, OBJ, etc) if (model.is.isSupported) { if (debug || DEBUG) console.debug('%c🌱 model.is.isSupported: true', ccm.darkgreen, model.name, model.file) // if (debug || DEBUG) console.debug(`%c======================================`, ccm.darkgreen) if (model.is.isObject3D) { // const OBJModel = ({ file, textureFile, ...props }) => { // const obj = useLoader(OBJLoader, file) // const texture = useTexture(textureFile) // useEffect(() => { // obj.traverse(child => { // if (child.isMesh) child.material.map = texture // }) // }, [obj]) // return <primitive object={obj} {...props} /> // } // Which can be used with something like // <OBJModel file='./a.obj' textureFile='./a.png' position-z={10} /> // ** FBX if (model.is.isFBX) { model.type = 'fbx' // const { nodes, animations } = useFBX(model.file) // const fbx = useFBX(model.file) const fbx = useLoader(FBXLoader, model.file, loader => { loader.manager.addHandler(/\.tga$/i, new TGALoader()) // loader.manager.addHandler(/\.png$/i, new TextureLoader()) }) if (debug || DEBUG) console.debug('%cšŸ“ FBX NODES: fbx', ccm.green, model.name, fbx) // if (debug || DEBUG) console.debug(`%c======================================`, ccm.darkgreen) if (fbx) { // ** NODES // model.node = fbx model.nodes = fbx // if (debug || DEBUG) console.debug('RETURN ONLY NODE AS NODES: true', model.nodes) if (model.texture) { if (debug || DEBUG) console.debug(`%c LOADING TEXTURE ...`, ccm.orangeAlert, model.texture) let texture = useTexture(TextureLoader, model.texture, loader => { loader.manager.addHandler(/\.tga$/i, new TGALoader()) // loader.manager.addHandler(/\.png$/i, new TextureLoader()) }) if (debug || DEBUG) console.debug(`%c TEXTURE`, ccm.orangeAlert, texture) } // ** ANIMATIONS if (fbx.animations) { if (debug || DEBUG) console.debug('%c FBX animations', ccm.orangeAlert, fbx.animations) // Extract animation actions const { ref, mixer, actions, names, clips } = useAnimations(fbx.animations) // if (debug || DEBUG) console.debug('FBX useAnimations', ref, mixer, actions, names, clips) model.ani.ref = ref model.ani.mixer = mixer model.ani.actions = actions model.ani.names = names model.ani.clips = clips } } } // ** GLTF else if (model.is.isGLTF) { model.type = 'gltf' // nodes[] is an array of all the meshes // file is cached/memoized; it only gets loaded and parsed once // const gltf = useGLTF(model.file) const gltf = useLoader(GLTFLoader, model.file) // , loader => { // loader.manager.addHandler(/\.tga$/i, new TGALoader()) // }) // if (debug || DEBUG) console.debug('%cšŸ“ GLB NODES: gltf', ccm.greenAlert, model.name, gltf) // if (debug || DEBUG) console.debug(`%c======================================`, ccm.darkgreen) if (gltf) { // ** NODES // if (gltf.nodes?.RootNode) { // model.nodes = gltf.nodes.RootNode.children // if (debug || DEBUG) console.debug('%cšŸ“ RETURN RootNode CHILDREN NODES: true', ccm.orangeAlert, model.name, model.nodes) // if (debug || DEBUG) console.debug(`%c======================================`, ccm.orange) // } // else if (model.nodes[model.name]) { // model.nodes[model.name] = gltf[model.name] // if (debug || DEBUG) console.debug('%cšŸ“ RETURN ONE NODE: true', ccm.orangeAlert, model.name, model.nodes[model.name]) // if (debug || DEBUG) console.debug(`%c======================================`, ccm.orange) // } // else if (gltf.nodes) { // let gltfAsArray = new Array() // gltfAsArray.push([...Array(gltf.nodes)]) // for (const [key, value] of Object.entries(gltf.nodes)) { // if (debug || DEBUG) console.debug(`key: ${key}, value: ${value}`) // gltfAsArray.push([key: value]) // } // let gltfObjectParent = gltf let gltfObject = gltf // gltf | gltf.nodes model.nodes = gltfObject // let gltfObjectNodes = gltf.nodes // gltf | gltf.nodes // let gltfObjectKeys = new Array() // let gltfAsArray = new Array() // let gltfObjectCount = 48 // maximum to return // gltfObjectKeys = Object.keys(gltfObject) // returns all keys [5446] // // if (debug || DEBUG) console.debug('%cšŸ“ gltfObjectKeys', ccm.orangeAlert, gltfObjectKeys) // let gltfObjectectAsArray = gltfObjectKeys.slice(0, gltfObjectCount).map((key, index) => { // // let kvpair = {} // // // kvpair[key] = gltfObject[key] // // kvpair[index] = gltfObject[key] // // return kvpair // return gltfObject[key] // the value of the object at this key // }) // gltfAsArray = objectAsArray // // model.nodes = gltfAsArray // // if (debug || DEBUG) console.debug('%cšŸ“ gltfAsArray', ccm.orangeAlert, gltfAsArray) // // model.nodes = gltf.nodes // // model.nodes = gltf if (debug || DEBUG) console.debug('%cšŸ“ RETURN ALL GLTF NODES: default', ccm.green, model.name, model.nodes) // if (debug || DEBUG) console.debug(`%c======================================`, ccm.darkgreen) } else { model.nodes = gltf.nodes // [] if (debug || DEBUG) console.debug('%cšŸ“ RETURN ALL/BLANK NODES: default', ccm.redAlert, model.name, model.nodes) // if (debug || DEBUG) console.debug(`%c======================================`, ccm.red) } // ** ANIMATIONS if (gltf.animations) { if (debug || DEBUG) console.debug('%c GLTF animations', ccm.orangeAlert, gltf.animations) // Extract animation actions const { ref, mixer, actions, names, clips } = useAnimations(gltf.animations) // if (debug || DEBUG) console.debug('GLTF useAnimations', ref, mixer, actions, names, clips) model.ani.ref = ref model.ani.mixer = mixer model.ani.actions = actions model.ani.names = names model.ani.clips = clips } } } // ** OBJ else if (model.is.isOBJ) { model.type = 'obj' // const nodes = useOBJ(model.file) const obj = new OBJLoader().load(model.file) if (debug || DEBUG) console.debug('%c🌱 OBJ NODES: obj', ccm.orange, obj) // if (debug || DEBUG) console.debug(`%c======================================`, ccm.orange) if (obj) { model.nodes = obj // if (debug || DEBUG) console.debug('RETURN ONLY NODE AS NODES: true') } } // finally, decide if ready for _r3f canvas if (model.nodes && model.is.isFBX) { model.is.isReadyForCanvas = true // true | false if (debug || DEBUG) console.debug('%cāœ”ļøšŸ“ THREED MODEL IS READY FOR CANVAS', ccm.greenAlert, model.name, model.file) // if (debug || DEBUG) console.debug(`%c==========================================`, ccm.greenAlert) } else if (model.nodes && model.is.isGLTF) { model.is.isReadyForCanvas = true // true | false if (debug || DEBUG) console.debug('%cāœ”ļøšŸ“ THREED MODEL IS READY FOR CANVAS', ccm.orangeAlert, model.name, model.file) // if (debug || DEBUG) console.debug(`%c==========================================`, ccm.orangeAlert) } else { // if (model.nodes && model.is.isOBJ) { model.is.isReadyForCanvas = false if (debug || DEBUG) console.debug('%cāœ–ļøšŸ“ THREED MODEL IS NOT READY FOR CANVAS', ccm.redAlert, model.name, model.file) // if (debug || DEBUG) console.debug(`%c==========================================`, ccm.redAlert) } } // if (debug || DEBUG) console.debug(`%c======================================`, ccm.black) } else { if (debug || DEBUG) console.debug('%cāœ–ļøšŸ“ MODEL.is.isSupported: false', ccm.redAlert, model.name) // if (debug || DEBUG) console.debug(`%c==========================================`, ccm.red) } if (debug || DEBUG) console.debug('%cmodel', ccm.greenAlert, model) // if (debug || DEBUG) console.debug(`%c======================================`, ccm.greenAlert) const [index, setIndex] = useState(0) // if (debug || DEBUG) console.debug(`%c======================================`, ccm.greenAlert) // ========================================== // ========================================== // ========================================== // ** HOVER STATE (w support for ANIMATIONS ...) // Feed hover state into useCursor, which sets document.body.style.cursor to pointer|auto // const [isHovered, setIsHovered] = useState(false) // useCursor(isHovered) // // Animate the selection halo // const { color, scale } = useSpring({ // scale: isHovered ? [1.15, 1.15, 1] : [1, 1, 1], // color: isHovered ? '#ff6d6d' : '#569AFF', // }) // // Change cursor on hover-state // useEffect( // () => void (document.body.style.cursor = isHovered ? 'pointer' : 'auto'), [isHovered]) // ========================================== // ========================================== // ========================================== // ** ANIMATIONS // let index = 0 // testing // if (debug || DEBUG) console.debug('index', index) // Change animation when the index changes useEffect(() => { if (model.ani.actions != undefined && model.ani.names != undefined && model.ani.names[index] != undefined // && model.ani.actions[model.ani.names[index]] != undefined ) { if (debug || DEBUG) console.debug('index', index) if (debug || DEBUG) console.debug('model', model) // if (debug || DEBUG) console.debug('model.ani', model.ani) // if (debug || DEBUG) console.debug('model.ani.actions', model.ani.actions) // if (debug || DEBUG) console.debug('model.ani.names', model.ani.names) // if (debug || DEBUG) console.debug('model.ani.names[index]', model.ani.names[index]) // if (debug || DEBUG) console.debug('model.ani.actions[model.ani.names[index]]', model.ani.actions[model.ani.names[index]]) // if (debug || DEBUG) console.debug('model.ani.actions["Take 001"]', model.ani.actions["Take 001"])) if (debug || DEBUG) console.debug('Object.values(model.ani.actions)', Object.values(model.ani.actions)) if (debug || DEBUG) console.debug('Object.keys(model.ani.actions)', Object.keys(model.ani.actions)) // if (debug || DEBUG) console.debug('Object.keys(model.ani.actions)[index]', Object.keys(model.ani.actions)[index]) let theAnimation = Object.keys(model.ani.actions)[index] // if (model.ani.actions[model.ani.names[index]]) { // if (debug || DEBUG) console.debug('model.ani.actions[model.ani.names[index]]', model.ani.actions[model.ani.names[index]]) if (theAnimation) { if (debug || DEBUG) console.debug('Object.keys(model.ani.actions)[index] EXISTS', theAnimation) if (debug || DEBUG) console.debug('model.ani.actions[theAnimation]', model.ani.actions[theAnimation]) try { // model.ani.actions[model.ani.names[index]] // model.ani.actions[Object.keys(model.ani.actions)[index]] // model.ani.actions[theAnimation] // theAnimation // ** Reset and fade in animation after an index has been changed // .reset() // .fadeIn(0.5) // Play the animation // .play() } catch (ERROR) { console.error('ERROR: theAnimation.play()', ERROR) } // In the clean-up phase, fade it out // (page route may have changed) // return () => { // try { // model.ani.actions[model.ani.names[index]] // .fadeOut(0.5) // } catch (ERROR) { // console.error('ERROR: model.ani.actions[model.ani.names[index]]', ERROR) // } // } } } // return undefined // }, [index, model.ani.actions, model.ani.names]) }, [index]) // }, []) // ========================================== // ANIMATIONS (FOR ALL MODELS !!!) const getThreeDClock = useFrame(({ clock }) => { const ThreeDClock = clock // const a = clock.getElapsedTime() // model.ref.current.rotation.z = a return ThreeDClock }) // ========================================== // ** RETURN JSX if (model.is.isReadyForCanvas) { // if (debug || DEBUG) console.debug(`%c==========================================`, ccm.blue) // if (debug || DEBUG) console.debug(`%cāœ”ļøšŸ“ DRAW MODEL ${model.type}`, ccm.blue, model.name) // , model if (debug || DEBUG) console.debug(`%cāœ”ļøšŸ“ DRAW MODEL.nodes: ${model.type}`, ccm.blue, model.name, model.nodes) // if (debug || DEBUG) console.debug(`%c==========================================`, ccm.blue) // ** FBX // return FBX node if (model.is.isFBX) { return ( <group key={model.group.group_id} // newUUID() // ref={model.ref} // ref={model.ani.ref} ref={model.ani.ref ? model.ani.ref : model.ref} position={model.group.group_position} rotation={model.group.group_rotation} scale={model.group.group_scale} // rotationX dispose={null} > <primitive name={model.name ? model.name : 'no name'} // ref={model.ref} // ref={model.ani.ref} // ref={model.ani.ref ? model.ani.ref : model.ref} object={model.nodes} dispose={null} map={model.texture} /> </group> ) } // ** GLTF // return GLTF node else if (model.is.isGLTF) { // if (debug || DEBUG) console.debug('GLTF: model.nodes', model.name, model.nodes) // if (debug || DEBUG) console.debug('GLTF: model.nodes.scene', model.name, model.nodes.scene) return ( // LOOP OVER NODE ARRAY TO RETURN MULTIPLE MESHES ([5000]) <group key={model.group.group_id} // newUUID() // ref={model.ref} // ref={model.ani.ref} ref={model.ani.ref ? model.ani.ref : model.ref} position={model.group.group_position} rotation={model.group.group_rotation} scale={model.group.group_scale} dispose={null} > {/* const Model = () => { const gltf = useLoader(GLTFLoader, "./bomb.gltf"); return <primitive object={gltf.scene} scale={0.4} />; }; */} <primitive name={model.name ? model.name : 'no name'} // ref={model.ref} // ref={model.ani.ref} // ref={model.ani.ref ? model.ani.ref : model.ref} object={model.nodes.scene} // gltf.scene dispose={null} // scale={1.0} map={model.texture} /> {/* {model.nodes.map((_model_node, index) => ( <mesh key={model.group.group_id + '-' + index} // newUUID() | index // name={_model_node.name} // ref={_model_node.ref} // // Click sets the mesh as the new target // onClick={(e) => (e.stopPropagation(), (modelVState.current = _model_node.name))} // // If a click happened but this mesh wasn't hit we null out the target, // // This works because missed pointers fire before the actual hits // onPointerMissed={(e) => e.type === 'click' && (modelVState.current = null)} // // Right click cycles through the transform ThreeD.actionModes // onContextMenu={(e) => // snap.current === _model_node.name && (e.stopPropagation(), (modelVState.mode = (snap.mode + 1) % ThreeD.actionModes.length)) // } // onPointerOver={(e) => (e.stopPropagation(), setIsHovered(true))} // onPointerOut={(e) => setIsHovered(false)} geometry={_model_node.geometry} material={_model_node.material} // material-color={snap.current === _model_node.name ? '#ff7070' : '#ababab'} // {...props} dispose={null} /> ))} */} </group> ) } // ** OBJ // return OBJ node else if (model.is.isOBJ) { return ( <mesh key={newUUID()} // ref={model.ref} // ref={model.ani.ref} ref={model.ani.ref ? model.ani.ref : model.ref} dispose={null} map={model.texture} /> ) } } // DEFAULT return mesh // 'error sphere' mesh object, with original model.name and props else { // !model.is.isReadyForCanvas return ( <mesh key={newUUID()} name={model.name} // ref={model.ref} // ref={model.ani.ref} ref={model.ani.ref ? model.ani.ref : model.ref} // // Click sets the mesh as the new target // onClick={(e) => ( // e.stopPropagation(), // (modelVState.current = model.name), // if (debug || DEBUG) console.debug('modelVState.current', model.name), // if (debug || DEBUG) console.debug('snap.current', snap.current) // )} // // If a click happened but this mesh wasn't hit we null out the target, // // This works because missed pointers fire before the actual hits // onPointerMissed={(e) => ( // e.type === 'click', // (modelVState.current = null), // if (debug || DEBUG) console.debug('modelVState.current', null), // if (debug || DEBUG) console.debug('snap.current', snap.current) // )} // // Right click cycles through the transform ThreeD.actionModes // onContextMenu={(e) => // snap.current === model.name && // (e.stopPropagation(), // (modelVState.mode = (snap.mode + 1) % ThreeD.actionModes.length), // if (debug || DEBUG) console.debug('modelVState.mode', modelVState.mode), // if (debug || DEBUG) console.debug('snap.current', snap.current)) // } // onPointerOver={(e) => (e.stopPropagation(), setIsHovered(true))} // onPointerOut={(e) => setIsHovered(false)} position={[-4, 0, 4]} // put in front of center:center, for easier viewing dispose={null} > <sphereGeometry args={[3, 96]} /> <meshPhysicalMaterial color={model.is.isGLTF ? 'darkRed' : model.is.isOBJ ? 'darkOrange' : model.is.isFBX ? 'darkGreen' : 'darkPink'} /> </mesh> ) } } // Controls function ThreeDControls() { // ** // get Reactive state on each model (using valtio) const modelVState = proxy({ current: null, mode: 0 }) // get Apollo Stores state // const storeState = undefined const sceneState = useThree((sceneState) => sceneState.scene) if (sceneState) { if (debug || DEBUG) if (debug || DEBUG) console.debug('%c sceneState to load to ThreeDCanvas', ccm.yellow, sceneState) if (sceneState.length) { // if (debug) if (debug || DEBUG) console.debug('sceneState.length', sceneState.length) } } // Get 'snap' notified on changes to modelVState + sceneState const snap = useSnapshot(modelVState) return ( <group> {/* As of drei@7.13 transform-controls can refer to the target by children, or the object prop */} {snap.current && ( <TransformControls object={sceneState.getObjectByName(snap.current)} mode={ThreeD.actionModes[snap.mode]} /> )} </group> ) } // ** OLD JOYSTICK + ANIMATIONS ************************ // let joystick = new JoyStick({ // onMove: playerControl, // game: container // }) /** ANIMATE + RENDER (continuous rendering) ******************************************** */ // let animate = function () { // const dt = getThreeDClock.getDelta() // // watchPointer(camera, plane.children) // // controls.update() // // TWEEN.update() // requestAnimationFrame(animate) // if ( params.ANIMATE ) { // // plane.rotation.x += 0.002 // // plane.rotation.y += 0.002 // plane.rotation.z -= 0.0007 // } // /** PLAYER CHARACTER */ // if ( player.mixer !== undefined ) { // player.mixer.update(dt) // } // if ( player.action == "Walking" ) { // const elapsedTime = Date.now() - player.actionTime // if ( elapsedTime > 2000 && player.move.forward > 0.7 ){ // setAction("Running") // } // } // if ( player.move !== undefined ) { // movePlayer(dt) // } // if ( player.cameras != undefined && player.cameras.active != undefined ) { // camera.position.lerp( player.cameras.active.getWorldPosition(new THREE.Vector3()), 0.05 ) // const pos = player.object.position.clone() // pos.y += 200 // camera.lookAt(pos) // } // if (directionalLight != undefined){ // // directionalLight.position.x = player.object.position.x // // directionalLight.position.y = player.object.position.y + 200 // // directionalLight.position.z = player.object.position.z + 100 // // directionalLight.target = player.object // } // renderer.render(scene, camera) // } // animate() // let loadNextAnim = function (loader) { // let anim = anims.pop() // // if (debug || DEBUG) console.debug("-----------------------") // // if (debug || DEBUG) console.debug("anim-------------------") // // if (debug || DEBUG) console.debug(anim) // // if (debug || DEBUG) console.debug("-----------------------") // loader.load( `${params.assetsPath}fbx/anims2/${anim}.fbx`, function(object) { // // if (debug || DEBUG) console.debug("-----------------------") // // if (debug || DEBUG) console.debug("object-----------------") // // if (debug || DEBUG) console.debug(object) // // if (debug || DEBUG) console.debug("-----------------------") // animations[anim] = object.animations[0] // if (anims.length > 0){ // // if (debug || DEBUG) console.debug("-----------------------") // // if (debug || DEBUG) console.debug("anims.length-----------") // // if (debug || DEBUG) console.debug(anims.length) // // if (debug || DEBUG) console.debug("-----------------------") // // if (debug || DEBUG) console.debug("-----------------------") // // if (debug || DEBUG) console.debug("getAction()-----------") // // if (debug || DEBUG) console.debug(getAction()) // // if (debug || DEBUG) console.debug("-----------------------") // // if (debug || DEBUG) console.debug("player.action----------") // // if (debug || DEBUG) console.debug(player.action) // // if (debug || DEBUG) console.debug("-----------------------") // loadNextAnim(loader) // } // else { // // if (debug || DEBUG) console.debug("-----------------------") // // if (debug || DEBUG) console.debug("anims.length = 0-------") // // if (debug || DEBUG) console.debug(anims.length) // // if (debug || DEBUG) console.debug("-----------------------") // anims = [] // setAction("Idle") // animate() // } // }) // } // //return scene // // } // function setAction(name) { // const action = player.mixer.clipAction( animations[name] ) // action.time = 0 // if (debug || DEBUG) console.debug("-----------------------") // if (debug || DEBUG) console.debug("animations-----------------") // if (debug || DEBUG) console.debug(animations) // if (debug || DEBUG) console.debug("-----------------------") // player.mixer.stopAllAction() // player.action = name // player.actionTime = Date.now() // // if (debug || DEBUG) console.debug("-----------------------") // // if (debug || DEBUG) console.debug("player-----------------") // // if (debug || DEBUG) console.debug(player) // // if (debug || DEBUG) console.debug("-----------------------") // action.fadeIn(0.5) // action.play() // } // function getAction() { // if (player === undefined || player.action === undefined) { // return "doesn't exist yet" // } // return player.action // } // function toggleAnimation() { // if ( player.action == "Idle" ) { // setAction("Pointing Gesture") // } // else { // setAction("Idle") // } // } // function movePlayer1(dt) { // // if (debug || DEBUG) console.debug("-------------------------") // // if (debug || DEBUG) console.debug("player.move.forward------") // // if (debug || DEBUG) console.debug(player.move.forward) // // if (debug || DEBUG) console.debug("-------------------------") // if ( player.move.forward > 0 ) { // const speed = ( player.action == "Running" ) ? 24 : 8 // player.object.translateZ( dt * speed ) // } // else if ( player.move.forward < 0 ) { // player.object.translateZ( -dt * 2) // } // player.object.rotateY( player.move.turn * dt ) // } // function movePlayer(dt){ // const pos = player.object.position.clone() // pos.y += 60 // let dir = new THREE.Vector3() // player.object.getWorldDirection(dir) // if (player.move.forward<0) dir.negate() // let raycaster = new THREE.Raycaster(pos, dir) // let blocked = false // const colliders = params.colliders // // if (colliders!==undefined){ // // const intersect = raycaster.intersectObjects(colliders) // // if (intersect.length>0){ // // if (intersect[0].distance<50) blocked = true // // } // // } // if (!blocked){ // if (player.move.forward>0){ // const speed = (player.action=="Running") ? 24 : 8 // player.object.translateZ(dt*speed) // } // else if ( player.move.forward < 0 ) { // player.object.translateZ(-dt*2) // } // } // /* // if (colliders!==undefined){ // //cast left // dir.set(-1,0,0) // dir.applyMatrix4(player.object.matrix) // dir.normalize() // raycaster = new THREE.Raycaster(pos, dir) // let intersect = raycaster.intersectObjects(colliders) // if (intersect.length>0){ // if (intersect[0].distance<50) player.object.translateX(100-intersect[0].distance) // } // //cast right // dir.set(1,0,0) // dir.applyMatrix4(player.object.matrix) // dir.normalize() // raycaster = new THREE.Raycaster(pos, dir) // intersect = raycaster.intersectObjects(colliders) // if (intersect.length>0){ // if (intersect[0].distance<50) player.object.translateX(intersect[0].distance-100) // } // //cast down // dir.set(0,-1,0) // pos.y += 200 // raycaster = new THREE.Raycaster(pos, dir) // const gravity = 30 // intersect = raycaster.intersectObjects(colliders) // if (intersect.length>0){ // const targetY = pos.y - intersect[0].distance // if (targetY > player.object.position.y){ // //Going up // player.object.position.y = 0.8 * player.object.position.y + 0.2 * targetY // player.velocityY = 0 // }else if (targetY < player.object.position.y){ // //Falling // if (player.velocityY==undefined) player.velocityY = 0 // player.velocityY += dt * gravity // player.object.position.y -= player.velocityY // if (player.object.position.y < targetY){ // player.velocityY = 0 // player.object.position.y = targetY // } // } // }else if (player.object.position.y>0){ // if (player.velocityY==undefined) player.velocityY = 0 // player.velocityY += dt * gravity // player.object.position.y -= player.velocityY // if (player.object.position.y < 0){ // player.velocityY = 0 // player.object.position.y = 0 // } // } // } // */ // player.object.rotateY(player.move.turn*dt) // } // function playerControl(forward, turn) { // turn = -turn // if ( forward > 0.2 ) { // if ( player.action != "Walking" && player.action != "Running" ) { // setAction("Walking") // } // } // else if ( forward < -0.2 ) { // if ( player.action != "Walking Backwards" ) { // setAction("Walking Backwards") // } // } // else { // forward = 0 // if ( Math.abs(turn) > 0.05 ) { // if ( player.action != "Left Turn" ) { // setAction("Left Turn") // } // } // else if ( player.action != "Idle" ) { // setAction("Idle") // } // // else { // // setAction("Idle") // // } // } // // if ( forward == 0 && turn == 0 ) { // // player.move = {} // // } // // else { // player.move = { forward, turn } // // } // } // =========================================== // EXAMPLE -- LOOP OVER ARRAY OF NODES TO CREATE INDIVIDUAL MODELS // ** // interface IThreeD { // name: string // threedId: number // } // export const Elements = (props: { threeds: Array<IThreeD> }) => { // return ( // <> // {props.threeds.map((_threed: IThreeD) => <h1>{_threed.name} | {_threed.threedId}</h1>)} // </> // ) // } // export default const ElementCaller = () => { // var threedsArray: IThreeD[] = [] // return (<Elements threeds={threedsArray} />) // } // =========================================== // ** const ThreeDModels = ({ threeds }) => { // ** if (debug || DEBUG) console.debug(`%c======================================`, ccm.darkredAlert) if (debug || DEBUG) console.debug('%c🌱 threeds[i]', ccm.darkredAlert, threeds) if (debug || DEBUG) console.debug('%c🌱 threeds ==========================', ccm.darkredAlert) if (debug || DEBUG) console.debug(`%c======================================`, ccm.darkredAlert) // ** // ** TESTING // return <CoffeeCup /> // DEFAULT 0, RETURN BLANK JSX (no need to render this component) if (!threeds.length) { // return <></> return ( <Html center> {/* <Spinner /> */} <Loader /> </Html> ) } // RENDER THIS COMPONENT return ( <group key='threed_models' position={[ 8, -1, 0 ]} > {/* <CoffeeCup /> */} {/* <ThreeDControls /> */} {/* THREED: LOOP OVER NODES FOR EACH FILE = MODEL */} {/* <Suspense fallback={null}> */} <> {threeds.map((_threed, index) => { if (debug || DEBUG) console.debug('_threed', index + ': ', _threed) // if (debug || DEBUG) console.debug(`%c======================================`, ccm.red) const threed = new ThreeD() threed.name = _threed.title threed.data = _threed // threed.group = threed.group threed.group.group_position = [_threed.positionX, _threed.positionY, _threed.positionZ] threed.group.group_rotation = [_threed.rotationX, _threed.rotationY, _threed.rotationZ] threed.group.group_scale = [_threed.scaleX, _threed.scaleY, _threed.scaleZ] return ( <group // key={newUUID()} // key={index} key={threed.group.group_id + '_' + index + '_' + newUUID()} // ref={ref} // ref={threed.group.group_ref} // position={threed.group.group_position} // rotation={threed.group.group_rotation} // scale={threed.group.group_scale} // quaternion={threed.group.group_quaternion} > { _threed.files.nodes && _threed.files.nodes.map((_file, index) => { if (debug || DEBUG) console.debug('%c MODEL _file', ccm.redAlert, index + ': ', _file) // if (debug || DEBUG) console.debug(`%c ================================`, ccm.redAlert) // const threed = new ThreeD() threed.name = _file.title threed.file = _file // threed.group = threed.group return ( <RigidBody type='fixed' key={'RigidBody' + index + '_' + newUUID()} > <Model // key={_file.fileId} // no, duplicates // key={newUUID()} // key={index} // key={index + '_' + newUUID()} key={threed.name + '_' + index + '_' + newUUID()} threed={threed} /> </RigidBody> ) })} </group> ) })} </> {/* </Suspense> */} </group> ) } // const ThreeDModel_UseClient = dynamic(() => Promise.resolve(ThreeDModels), { // ssr: false // }) // export default ThreeDModel_UseClient export default ThreeDModels