diff --git a/.vscode/settings.json b/.vscode/settings.json index 15d3670..1f17729 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -3,4 +3,7 @@ "editor.codeActionsOnSave": { "source.fixAll": true }, + "[javascript]": { + "editor.defaultFormatter": "vscode.typescript-language-features" + }, } diff --git a/blocks/environment/components/Controls.js b/blocks/environment/components/Controls.js index 7288f06..1155511 100644 --- a/blocks/environment/components/Controls.js +++ b/blocks/environment/components/Controls.js @@ -1,15 +1,15 @@ import React, { useEffect, useRef, useState } from "react"; // import { Raycaster, Vector3, Math, Euler } from 'three'; -import * as THREE from "three"; +import { Euler, Raycaster, MathUtils } from "three"; import { useFrame, useThree } from "@react-three/fiber"; import { PointerLockControls } from "@react-three/drei"; // import previewOptions from "@wordpress/block-editor/build/components/preview-options"; import { useRapier, useRigidBody } from "@react-three/rapier"; -function touchStarted() { - getAudioContext().resume(); -} +// function touchStarted() { +// getAudioContext().resume(); +// } const Controls = (props) => { const p2pcf = window.p2pcf; @@ -21,8 +21,9 @@ const Controls = (props) => { const [moveBackward, setMoveBackward] = useState(false); const [moveLeft, setMoveLeft] = useState(false); const [moveRight, setMoveRight] = useState(false); - const [spawnPos, setSpawnPos] = useState(); + const [spawnPos, setSpawnPos] = useState(props.spawnPoint); const [jump, setJump] = useState(false); + const [thirdPerson, setThirdPerson] = useState(false); // Add this line const currentRigidbody = useRigidBody(); const { world, rapier } = useRapier(); const ray = new rapier.Ray({ x: 0, y: 0, z: 0 }, { x: 0, y: -1, z: 0 }); @@ -34,27 +35,39 @@ const Controls = (props) => { const { camera, scene } = useThree(); useEffect(() => { - setSpawnPos(props.spawnPoint); const playerThing = world.getRigidBody(props.something.current.handle); - // if (playerThing) { - // playerThing.setTranslation({ - // x: props.spawnPoint[0], - // y: props.spawnPoint[1], - // z: props.spawnPoint[2] - // }); - // } - - // controlsRef.current - // .getObject() - // .parent.position.set( - // props.spawnPoint[0], - // props.spawnPoint[1], - // props.spawnPoint[2] - // ); + const x = Number(spawnPos[0]); + const y = Number(spawnPos[1]); + const z = Number(spawnPos[2]); + + setTimeout(() => { + let finalPoints = []; + if (props.spawnPointsToAdd) { + props.spawnPointsToAdd.forEach((point) => { + finalPoints.push([Number(point[0]), Number(point[1]), Number(point[2])]); + }); + } + finalPoints.push([x, y, z]); + //pick a random point + let randomPoint = finalPoints[Math.floor(Math.random() * finalPoints.length)]; + // Check if the converted values are valid and finite + // Set the camera's position + camera.position.set(randomPoint[0], randomPoint[1], randomPoint[2]); + + playerThing.setTranslation({ + x: randomPoint[0], + y: randomPoint[1], + z: randomPoint[2] + }); + }, 20); }, []); - const raycaster = new THREE.Raycaster(); + + const raycaster = new Raycaster(); useFrame(() => { + + const playerThing = world.getRigidBody(props.something.current.handle); + // raycaster.set( camera.position, camera.getWorldDirection() ); // raycast forward from the camera and log hitting any objects // var intersects = raycaster.intersectObjects( scene.children ); @@ -62,7 +75,6 @@ const Controls = (props) => { // console.log(intersects[0].object); // } - const playerThing = world.getRigidBody(props.something.current.handle); const playerThingColliders = world.getCollider( props.something.current.handle ); @@ -98,11 +110,11 @@ const Controls = (props) => { true ); if (intersects.length > 0) { - console.log(intersects[0].object.name); + // console.log(intersects[0].object.name); const pointHitObject = scene.getObjectByName( intersects[0].object.name ); - console.log(pointHitObject); + // console.log(pointHitObject); // add a rigidbody at the point of intersection if (intersects[0].point) { const rigidBodyDesc = new rapier.RigidBodyDesc( @@ -142,64 +154,68 @@ const Controls = (props) => { setClick(false); } if (moveForward) { - // playerThing.applyImpulse({x:0, y:0, z:0.1}, true); - controlsRef.current.moveForward(velocity); - const hit = world - .raw() - .queryPipeline.castRay( - world.raw().colliders, - ray, - maxToi, - solid, - 0xfffffffff - ); - // const pointerHit = world - // .raw() - // .queryPipeline.castRay( - // world.raw().colliders, - // pointerRay, - // maxToi, - // solid, - // 0xfffffffff - // ); + if (playerThing) { + controlsRef.current.moveForward(velocity); + const hit = world + .raw() + .queryPipeline.castRay( + world.raw().colliders, + ray, + maxToi, + solid, + 0xfffffffff + ); - playerThing.lockRotations(true, true); - // playerThing.setRotation({x: 0, y: 1, z: 0, w: 0}, true); - // if (pointerHit){ - // console.log(pointerHit); - // const pointerHitPoint = pointerRay.pointAt(hit.toi); - // console.log(pointerHitPoint); + playerThing.lockRotations(true, true); + if (hit) { + const hitPoint = ray.pointAt(hit.toi); + playerThing.setTranslation({ + x: controlsRef.current.camera.position.x, + y: hitPoint.y, + z: controlsRef.current.camera.position.z + }); + camera.position.setY(hitPoint.y + 0.001); + } + if (p2pcf) { + const position = [ + controlsRef.current.camera.position.x, + controlsRef.current.camera.position.y, + controlsRef.current.camera.position.z + ]; + const rotation = [ + controlsRef.current.camera.rotation.x, + controlsRef.current.camera.rotation.y, + controlsRef.current.camera.rotation.z + ]; + const message = + `{ "${p2pcf.clientId}": [{ "position" : [` + + position + + `]},{ "rotation" : [` + + rotation + + `]},{ "profileImage" : ["` + + userData.profileImage + + `"]}]}`; + p2pcf.broadcast(new TextEncoder().encode(message)); + } - // } - if (hit) { - const hitPoint = ray.pointAt(hit.toi); - playerThing.setTranslation({ - x: controlsRef.current.camera.position.x, - y: hitPoint.y, - z: controlsRef.current.camera.position.z - }); - camera.position.setY(hitPoint.y + 0.001); - } - if (p2pcf) { - const position = [ - controlsRef.current.camera.position.x, - controlsRef.current.camera.position.y, - controlsRef.current.camera.position.z - ]; - const rotation = [ - controlsRef.current.camera.rotation.x, - controlsRef.current.camera.rotation.y, - controlsRef.current.camera.rotation.z - ]; - const message = - `{ "${p2pcf.clientId}": [{ "position" : [` + - position + - `]},{ "rotation" : [` + - rotation + - `]},{ "profileImage" : ["` + - userData.profileImage + - `"]}]}`; - p2pcf.broadcast(new TextEncoder().encode(message)); + // playerThing.applyImpulse({x:0, y:0, z:0.1}, true); + // const pointerHit = world + // .raw() + // .queryPipeline.castRay( + // world.raw().colliders, + // pointerRay, + // maxToi, + // solid, + // 0xfffffffff + // ); + + // playerThing.setRotation({x: 0, y: 1, z: 0, w: 0}, true); + // if (pointerHit){ + // console.log(pointerHit); + // const pointerHitPoint = pointerRay.pointAt(hit.toi); + // console.log(pointerHitPoint); + + // } } } else if (moveLeft) { playerThing.lockRotations(true, true); @@ -336,7 +352,6 @@ const Controls = (props) => { } else if (jump) { } }); - const onKeyDown = function (event) { switch (event.code) { case "ArrowUp": @@ -363,30 +378,42 @@ const Controls = (props) => { setLock(false); break; case "KeyR": - // @todo revisit the respawn logic - // console.log(props); - // if (props.something.current) { - // const playerThing = world.getRigidBody( - // props.something.current.handle - // ); - // if (playerThing) { - // playerThing.setTranslation({ - // x: props.spawnPoint[0], - // y: props.spawnPoint[1], - // z: props.spawnPoint[2] - // }); - // if (controlsRef.current) { - // console.log(controlsRef.current.getObject()); - // controlsRef.current - // .getObject() - // .parent.position.set( - // props.spawnPoint[0], - // props.spawnPoint[1], - // props.spawnPoint[2] - // ); - // } - // } - // } + if (props.something.current) { + const playerThing = world.getRigidBody(props.something.current.handle); + + const x = Number(spawnPos[0]); + const y = Number(spawnPos[1]); + const z = Number(spawnPos[2]); + if (props.spawnPointsToAdd) { + let finalPoints = []; + props.spawnPointsToAdd.forEach((point) => { + finalPoints.push([Number(point.position.x), Number(point.position.y), Number(point.position.z)]); + }); + finalPoints.push([x, y, z]); + //pick a random point + let randomPoint = finalPoints[Math.floor(Math.random() * finalPoints.length)]; + // Check if the converted values are valid and finite + // Set the camera's position + camera.position.set(randomPoint[0], randomPoint[1], randomPoint[2]); + + playerThing.setTranslation({ + x: randomPoint[0], + y: randomPoint[1], + z: randomPoint[2] + }); + + } else { + // Check if the converted values are valid and finite + // Set the camera's position + camera.position.set(x, y, z); + + playerThing.setTranslation({ + x: x, + y: y, + z: z + }); + } + } setLock(false); break; case "Space": @@ -407,7 +434,7 @@ const Controls = (props) => { setClick(true); }); - const onKeyUp = function (event, props) { + const onKeyUp = function (event) { switch (event.code) { case "ArrowUp": case "KeyW": @@ -427,10 +454,9 @@ const Controls = (props) => { setLock(true); break; - // case "KeyR": - // setLock(true); - // break; - + case "KeyR": + setLock(true); + break; case "Space": setJump(false); setLock(true); @@ -450,6 +476,7 @@ const Controls = (props) => { document.addEventListener("keyup", onKeyUp); return ( { if (controlsRef.current) { controlsRef.current.addEventListener("lock", () => { @@ -485,13 +512,13 @@ const Controls = (props) => { p2pcf.broadcast(new TextEncoder().encode(message)); } const rotatingPlayer = scene.getObjectByName("playerOne"); - const euler = new THREE.Euler(); + const euler = new Euler(); const rotation = euler.setFromQuaternion( controlsRef.current.camera.quaternion ); const radians = rotation.z > 0 ? rotation.z : 2 * Math.PI + rotation.z; - const degrees = THREE.MathUtils.radToDeg(radians); + const degrees = MathUtils.radToDeg(radians); rotatingPlayer.rotation.set(0, radians, 0); }} ref={controlsRef} diff --git a/blocks/environment/components/EnvironmentFront.js b/blocks/environment/components/EnvironmentFront.js index a6c1820..8bfcea8 100644 --- a/blocks/environment/components/EnvironmentFront.js +++ b/blocks/environment/components/EnvironmentFront.js @@ -1,4 +1,5 @@ import * as THREE from "three"; +// import { Reflector } from 'three/examples/jsm/objects/Reflector'; import React, { Suspense, useRef, useState, useEffect, useMemo } from "react"; import { useLoader, useThree } from "@react-three/fiber"; import { GLTFLoader } from "three/examples/jsm/loaders/GLTFLoader"; @@ -30,79 +31,20 @@ import defaultFont from "../../../inc/fonts/roboto.woff"; import { ItemBaseUI } from "@wordpress/components/build/navigation/styles/navigation-styles"; import { BoxGeometry } from "three"; -function parseMatrixUri(uri) { - const SegmentToSigil = { - u: "@", - user: "@", - r: "#", - room: "#", - roomid: "!" - }; - - const url = new URL(uri, window.location.href); - - if (url.protocol === "matrix:") { - const matches = url.pathname.match(/^(\/\/.+\/)?(.+)$/); - - let authority; - let path; - - if (matches) { - if (matches.length === 3) { - authority = matches[1]; - path = matches[2]; - } else if (matches.length === 2) { - path = matches[1]; - } - } - - if (!path) { - throw new Error(`Invalid matrix uri "${uri}": No path provided`); - } - - const segments = path.split("/"); - - if (segments.length !== 2 && segments.length !== 4) { - throw new Error( - `Invalid matrix uri "${uri}": Invalid number of segments` - ); - } - - const sigil1 = SegmentToSigil[segments[0]]; - - if (!sigil1) { - throw new Error( - `Invalid matrix uri "${uri}": Invalid segment ${segments[0]}` - ); - } - - if (!segments[1]) { - throw new Error(`Invalid matrix uri "${uri}": Empty segment`); - } - - const mxid1 = `${sigil1}${segments[1]}`; - - let mxid2; - - if (segments.length === 4) { - if ( - (sigil1 === "!" || sigil1 === "#") && - (segments[2] === "e" || segments[2] === "event") && - segments[3] - ) { - mxid2 = `$${segments[3]}`; - } else { - throw new Error( - `Invalid matrix uri "${uri}": Invalid segment ${segments[2]}` - ); - } - } - return { protocol: "matrix:", authority, mxid1, mxid2 }; - } - - return url; -} - +import { ThreeImage } from "./core/front/ThreeImage"; +import { ThreeVideo } from "./core/front/ThreeVideo"; +import { ModelObject } from "./core/front/ModelObject"; +import { Portal } from "./core/front/Portal"; +import { Sky } from "./core/front/Sky"; +import { TextObject } from "./core/front/TextObject"; + +/** + * Represents a participant in a virtual reality scene. + * + * @param {Object} participant - The props for the participant. + * + * @return {JSX.Element} The participant. + */ function Participant(participant) { // Participant VRM. const fallbackURL = threeObjectPlugin + defaultVRM; @@ -179,566 +121,6 @@ function Participant(participant) { } } -function ModelObject(model) { - const [clicked, setClickEvent] = useState(); - const [url, set] = useState(model.url); - useEffect(() => { - setTimeout(() => set(model.url), 2000); - }, []); - const [listener] = useState(() => new THREE.AudioListener()); - - useThree(({ camera }) => { - camera.add(listener); - }); - - const gltf = useLoader(GLTFLoader, url, (loader) => { - // const dracoLoader = new DRACOLoader(); - // dracoLoader.setDecoderPath( - // "https://www.gstatic.com/draco/v1/decoders/" - // ); - // loader.setDRACOLoader(dracoLoader); - - loader.register( - (parser) => new GLTFAudioEmitterExtension(parser, listener) - ); - if (openbrushEnabled === true) { - loader.register( - (parser) => - new GLTFGoogleTiltBrushMaterialExtension( - parser, - openbrushDirectory - ) - ); - } - loader.register((parser) => { - return new VRMLoaderPlugin(parser); - }); - }); - - const audioObject = gltf.scene.getObjectByProperty('type', 'Audio'); - - const { actions } = useAnimations(gltf.animations, gltf.scene); - const animationClips = gltf.animations; - const animationList = model.animations ? model.animations.split(",") : ""; - useEffect(() => { - if (animationList) { - animationList.forEach((name) => { - if (Object.keys(actions).includes(name)) { - console.log(actions[name].play()); - } - }); - } - }, []); - - const generator = gltf.asset.generator; - - // return tilt brush if tilt brush - if (String(generator).includes("Tilt Brush")) { - return ( - - ); - } - - if (gltf?.userData?.gltfExtensions?.VRM) { - const vrm = gltf.userData.vrm; - vrm.scene.position.set( - model.positionX, - model.positionY, - model.positionZ - ); - VRMUtils.rotateVRM0(vrm); - const rotationVRM = vrm.scene.rotation.y + parseFloat(0); - vrm.scene.rotation.set(0, rotationVRM, 0); - vrm.scene.scale.set(1, 1, 1); - vrm.scene.scale.set(model.scaleX, model.scaleY, model.scaleZ); - return ( - // - - // - ); - } - // gltf.scene.castShadow = true; - // enable shadows @todo figure this out - // gltf.scene.traverse(function (node) { - // if (node.isMesh) { - // node.castShadow = true; - // node.receiveShadow = true; - // } - // }); - - // @todo figure out how to clone gltf proper with extensions and animations - // const copyGltf = useMemo(() => gltf.scene.clone(), [gltf.scene]); - // const modelClone = SkeletonUtils.clone(gltf.scene); - // modelClone.scene.castShadow = true; - - //audioObject - // Add a triangle mesh on top of the video - const [triangle] = useState(() => { - const points = []; - points.push( - new THREE.Vector3(0, -3, 0), - new THREE.Vector3(0, 3, 0), - new THREE.Vector3(4, 0, 0) - ); - const geometry = new THREE.BufferGeometry().setFromPoints(points); - const material = new THREE.MeshBasicMaterial({ - color: 0x00000, - side: THREE.DoubleSide - }); - const triangle = new THREE.Mesh(geometry, material); - return triangle; - }); - - const [circle] = useState(() => { - const geometryCircle = new THREE.CircleGeometry(5, 32); - const materialCircle = new THREE.MeshBasicMaterial({ - color: 0xfffff, - side: THREE.DoubleSide - }); - const circle = new THREE.Mesh(geometryCircle, materialCircle); - return circle; - }); - - if (model.collidable === "1") { - return ( - <> - { - setClickEvent(!clicked); - if(audioObject){ - if (clicked) { - audioObject.play(); - triangle.material.visible = false; - circle.material.visible = false; - } else { - audioObject.pause(); - triangle.material.visible = true; - circle.material.visible = true; - } - } - }} - // onCollisionEnter={ ( props ) =>( - // // window.location.href = model.destinationUrl - // ) - // } - > - - - - ); - } - return ( - <> - - - ); -} - -function Portal(model) { - - if (model.object) { - return ( - <> - - - {model.label - ? model.label + ": " - : "" + model.destinationUrl} - - - { - const url = new URL( - model.destinationUrl, - window.location.href - ); - if (url.protocol === "matrix:") { - const destination = parseMatrixUri( - model.destinationUrl - ); - window.location.href = - "https://thirdroom.io/world/" + - destination.mxid1; - } else { - window.location.href = model.destinationUrl; - } - }} - > - - - - ); - } - const [url, set] = useState(model.url); - - useEffect(() => { - setTimeout(() => set(model.url), 2000); - }, []); - const [listener] = useState(() => new THREE.AudioListener()); - - useThree(({ camera }) => { - camera.add(listener); - }); - - const gltf = useLoader(GLTFLoader, url, (loader) => { - loader.register( - (parser) => new GLTFAudioEmitterExtension(parser, listener) - ); - loader.register((parser) => { - return new VRMLoaderPlugin(parser); - }); - }); - - const { actions } = useAnimations(gltf.animations, gltf.scene); - - const animationList = model.animations ? model.animations.split(",") : ""; - useEffect(() => { - if (animationList) { - animationList.forEach((name) => { - if (Object.keys(actions).includes(name)) { - actions[name].play(); - } - }); - } - }, []); - // gltf.scene.position.set( model.positionX, model.positionY, model.positionZ ); - // gltf.scene.rotation.set( 0, 0, 0 ); - // gltf.scene.scale.set(model.scaleX, model.scaleY, model.scaleZ); - // gltf.scene.rotation.set(model.rotationX , model.rotationY, model.rotationZ ); - const copyGltf = useMemo(() => gltf.scene.clone(), [gltf.scene]); - - return ( - <> - - (window.location.href = model.destinationUrl) - } - rotation={[model.rotationX, model.rotationY, model.rotationZ]} - position={[model.positionX, model.positionY, model.positionZ]} - scale={[model.scaleX, model.scaleY, model.scaleZ]} - > - - - {model.label + ": " + model.destinationUrl} - - - - - - ); -} - -function Sky(sky) { - const skyUrl = sky.src[0].querySelector("p.sky-block-url") - ? sky.src[0].querySelector("p.sky-block-url").innerText - : ""; - - const texture1 = useLoader(THREE.TextureLoader, skyUrl); - - return ( - - - - - ); -} - -function ThreeImage(threeImage) { - const texture2 = useLoader(THREE.TextureLoader, threeImage.url); - - return ( - - - {threeImage.transparent ? ( - - ) : ( - - )} - - ); -} - -function ThreeVideo(threeVideo) { - const play = threeVideo.autoPlay === "1" ? true : false; - const { scene } = useThree(); - const [clicked, setClickEvent] = useState(); - const [video] = useState(() => - Object.assign(document.createElement("video"), { - src: threeVideo.url, - crossOrigin: "Anonymous", - loop: true, - muted: true - }) - ); - // Add a triangle mesh on top of the video - const [triangle] = useState(() => { - const points = []; - points.push( - new THREE.Vector3(0, -3, 0), - new THREE.Vector3(0, 3, 0), - new THREE.Vector3(4, 0, 0) - ); - const geometry = new THREE.BufferGeometry().setFromPoints(points); - const material = new THREE.MeshBasicMaterial({ - color: 0x00000, - side: THREE.DoubleSide - }); - const triangle = new THREE.Mesh(geometry, material); - return triangle; - }); - - const [circle] = useState(() => { - const geometryCircle = new THREE.CircleGeometry(5, 32); - const materialCircle = new THREE.MeshBasicMaterial({ - color: 0xfffff, - side: THREE.DoubleSide - }); - const circle = new THREE.Mesh(geometryCircle, materialCircle); - return circle; - }); - - useEffect(() => { - if (play) { - triangle.material.visible = false; - circle.material.visible = false; - video.play(); - } else { - triangle.material.visible = true; - circle.material.visible = true; - } - }, [video, play]); - - return ( - // - ); -} - -function Floor(props) { - return ( - - - - - ); -} - -function TextObject(model) { - const htmlObj = useRef(); - return ( - <> - - - {model.textContent} - - - - ); -} - function Participants(props) { const [participants, setParticipant] = useState([]); const p2pcf = window.p2pcf; @@ -766,6 +148,13 @@ function Participants(props) { ); } +/** + * Represents a saved object in a virtual reality world. + * + * @param {Object} props - The props for the saved object. + * + * @return {JSX.Element} The saved object. + */ function SavedObject(props) { const meshRef = useRef(); const [url, set] = useState(props.url); @@ -804,6 +193,7 @@ function SavedObject(props) { const collidersToAdd = []; const meshesToAdd = []; const portalsToAdd = []; + const spawnPointsToAdd = []; let omiColliders; gltf.scene.scale.set(props.scale, props.scale, props.scale); @@ -834,11 +224,24 @@ function SavedObject(props) { } if (child.userData.gltfExtensions?.OMI_link) { portalsToAdd.push(child); + } else if (child.userData.gltfExtensions?.OMI_spawn_point) { + spawnPointsToAdd.push(child); } else { meshesToAdd.push(child); } }); + // Mirror logic. + // const mirror = new Reflector( + // new THREE.PlaneGeometry(10, 10), + // { + // color: new THREE.Color(0x7f7f7f), + // textureWidth: window.innerWidth * window.devicePixelRatio, + // textureHeight: window.innerHeight * window.devicePixelRatio + // } + // ) + // gltf.scene.add(mirror); + meshesToAdd.forEach((mesh) => { meshesScene.attach(mesh); }); @@ -851,6 +254,7 @@ function SavedObject(props) { setColliders(collidersToAdd); setMeshes(meshesScene); setPortals(portalsToAdd); + props.setSpawnPoints(spawnPointsToAdd); // End OMI_collider logic. }, []); @@ -866,11 +270,6 @@ function SavedObject(props) { }); } }, []); - // Always seem to need to log this... - // console.log(gltf); - // const loader = new THREE.ObjectLoader(); - // const object = await loader.loadAsync("models/json/lightmap/lightmap.json"); - // scene.add(object); return ( <> @@ -908,6 +307,8 @@ function SavedObject(props) { rotationZ={finalRotation.z} object={item.parent} label={props.label} + defaultFont={defaultFont} + threeObjectPlugin={threeObjectPlugin} destinationUrl={ item.userData.gltfExtensions.OMI_link.uri } @@ -974,6 +375,8 @@ function SavedObject(props) { export default function EnvironmentFront(props) { const [loaded, setLoaded] = useState(false); + const [spawnPoints, setSpawnPoints] = useState(); + if (loaded === true) { if (props.deviceTarget === "vr") { return ( @@ -1003,18 +406,18 @@ export default function EnvironmentFront(props) { */} {props.threeUrl && ( <> - + @@ -1039,6 +447,7 @@ export default function EnvironmentFront(props) { hasTip={props.hasTip} animations={props.animations} playerData={props.userData} + setSpawnPoints={setSpawnPoints} /> {Object.values(props.sky).map( (item, index) => { @@ -1059,7 +468,7 @@ export default function EnvironmentFront(props) { "p.image-block-positionX" ) ? item.querySelector( - "p.image-block-positionX" + "p.image-block-positionX" ).innerText : ""; @@ -1068,7 +477,7 @@ export default function EnvironmentFront(props) { "p.image-block-positionY" ) ? item.querySelector( - "p.image-block-positionY" + "p.image-block-positionY" ).innerText : ""; @@ -1077,7 +486,7 @@ export default function EnvironmentFront(props) { "p.image-block-positionZ" ) ? item.querySelector( - "p.image-block-positionZ" + "p.image-block-positionZ" ).innerText : ""; @@ -1086,7 +495,7 @@ export default function EnvironmentFront(props) { "p.image-block-scaleX" ) ? item.querySelector( - "p.image-block-scaleX" + "p.image-block-scaleX" ).innerText : ""; @@ -1095,7 +504,7 @@ export default function EnvironmentFront(props) { "p.image-block-scaleY" ) ? item.querySelector( - "p.image-block-scaleY" + "p.image-block-scaleY" ).innerText : ""; @@ -1104,7 +513,7 @@ export default function EnvironmentFront(props) { "p.image-block-scaleZ" ) ? item.querySelector( - "p.image-block-scaleZ" + "p.image-block-scaleZ" ).innerText : ""; @@ -1113,7 +522,7 @@ export default function EnvironmentFront(props) { "p.image-block-rotationX" ) ? item.querySelector( - "p.image-block-rotationX" + "p.image-block-rotationX" ).innerText : ""; @@ -1122,7 +531,7 @@ export default function EnvironmentFront(props) { "p.image-block-rotationY" ) ? item.querySelector( - "p.image-block-rotationY" + "p.image-block-rotationY" ).innerText : ""; @@ -1131,7 +540,7 @@ export default function EnvironmentFront(props) { "p.image-block-rotationZ" ) ? item.querySelector( - "p.image-block-rotationZ" + "p.image-block-rotationZ" ).innerText : ""; @@ -1140,7 +549,7 @@ export default function EnvironmentFront(props) { "p.image-block-url" ) ? item.querySelector( - "p.image-block-url" + "p.image-block-url" ).innerText : ""; @@ -1149,7 +558,7 @@ export default function EnvironmentFront(props) { "p.image-block-aspect-height" ) ? item.querySelector( - "p.image-block-aspect-height" + "p.image-block-aspect-height" ).innerText : ""; @@ -1158,7 +567,7 @@ export default function EnvironmentFront(props) { "p.image-block-aspect-width" ) ? item.querySelector( - "p.image-block-aspect-width" + "p.image-block-aspect-width" ).innerText : ""; @@ -1167,7 +576,7 @@ export default function EnvironmentFront(props) { "p.image-block-transparent" ) ? item.querySelector( - "p.image-block-transparent" + "p.image-block-transparent" ).innerText : false; return ( @@ -1209,7 +618,7 @@ export default function EnvironmentFront(props) { "p.video-block-positionX" ) ? item.querySelector( - "p.video-block-positionX" + "p.video-block-positionX" ).innerText : ""; @@ -1218,7 +627,7 @@ export default function EnvironmentFront(props) { "p.video-block-positionY" ) ? item.querySelector( - "p.video-block-positionY" + "p.video-block-positionY" ).innerText : ""; @@ -1227,7 +636,7 @@ export default function EnvironmentFront(props) { "p.video-block-positionZ" ) ? item.querySelector( - "p.video-block-positionZ" + "p.video-block-positionZ" ).innerText : ""; @@ -1236,7 +645,7 @@ export default function EnvironmentFront(props) { "p.video-block-scaleX" ) ? item.querySelector( - "p.video-block-scaleX" + "p.video-block-scaleX" ).innerText : ""; @@ -1245,7 +654,7 @@ export default function EnvironmentFront(props) { "p.video-block-scaleY" ) ? item.querySelector( - "p.video-block-scaleY" + "p.video-block-scaleY" ).innerText : ""; @@ -1254,7 +663,7 @@ export default function EnvironmentFront(props) { "p.video-block-scaleZ" ) ? item.querySelector( - "p.video-block-scaleZ" + "p.video-block-scaleZ" ).innerText : ""; @@ -1263,7 +672,7 @@ export default function EnvironmentFront(props) { "p.video-block-rotationX" ) ? item.querySelector( - "p.video-block-rotationX" + "p.video-block-rotationX" ).innerText : ""; @@ -1272,7 +681,7 @@ export default function EnvironmentFront(props) { "p.video-block-rotationY" ) ? item.querySelector( - "p.video-block-rotationY" + "p.video-block-rotationY" ).innerText : ""; @@ -1281,7 +690,7 @@ export default function EnvironmentFront(props) { "p.video-block-rotationZ" ) ? item.querySelector( - "p.video-block-rotationZ" + "p.video-block-rotationZ" ).innerText : ""; @@ -1290,7 +699,7 @@ export default function EnvironmentFront(props) { "div.video-block-url" ) ? item.querySelector( - "div.video-block-url" + "div.video-block-url" ).innerText : ""; @@ -1299,7 +708,7 @@ export default function EnvironmentFront(props) { "p.video-block-aspect-height" ) ? item.querySelector( - "p.video-block-aspect-height" + "p.video-block-aspect-height" ).innerText : ""; @@ -1308,7 +717,7 @@ export default function EnvironmentFront(props) { "p.video-block-aspect-width" ) ? item.querySelector( - "p.video-block-aspect-width" + "p.video-block-aspect-width" ).innerText : ""; @@ -1317,7 +726,7 @@ export default function EnvironmentFront(props) { "p.video-block-autoplay" ) ? item.querySelector( - "p.video-block-autoplay" + "p.video-block-autoplay" ).innerText : false; @@ -1359,7 +768,7 @@ export default function EnvironmentFront(props) { "p.model-block-position-x" ) ? model.querySelector( - "p.model-block-position-x" + "p.model-block-position-x" ).innerText : ""; @@ -1368,7 +777,7 @@ export default function EnvironmentFront(props) { "p.model-block-position-y" ) ? model.querySelector( - "p.model-block-position-y" + "p.model-block-position-y" ).innerText : ""; @@ -1377,7 +786,7 @@ export default function EnvironmentFront(props) { "p.model-block-position-z" ) ? model.querySelector( - "p.model-block-position-z" + "p.model-block-position-z" ).innerText : ""; @@ -1386,7 +795,7 @@ export default function EnvironmentFront(props) { "p.model-block-scale-x" ) ? model.querySelector( - "p.model-block-scale-x" + "p.model-block-scale-x" ).innerText : ""; @@ -1395,7 +804,7 @@ export default function EnvironmentFront(props) { "p.model-block-scale-y" ) ? model.querySelector( - "p.model-block-scale-y" + "p.model-block-scale-y" ).innerText : ""; @@ -1404,7 +813,7 @@ export default function EnvironmentFront(props) { "p.model-block-scale-z" ) ? model.querySelector( - "p.model-block-scale-z" + "p.model-block-scale-z" ).innerText : ""; @@ -1413,7 +822,7 @@ export default function EnvironmentFront(props) { "p.model-block-rotation-x" ) ? model.querySelector( - "p.model-block-rotation-x" + "p.model-block-rotation-x" ).innerText : ""; @@ -1422,7 +831,7 @@ export default function EnvironmentFront(props) { "p.model-block-rotation-y" ) ? model.querySelector( - "p.model-block-rotation-y" + "p.model-block-rotation-y" ).innerText : ""; @@ -1431,7 +840,7 @@ export default function EnvironmentFront(props) { "p.model-block-rotation-z" ) ? model.querySelector( - "p.model-block-rotation-z" + "p.model-block-rotation-z" ).innerText : ""; @@ -1439,7 +848,7 @@ export default function EnvironmentFront(props) { "p.model-block-url" ) ? model.querySelector( - "p.model-block-url" + "p.model-block-url" ).innerText : ""; @@ -1448,7 +857,7 @@ export default function EnvironmentFront(props) { "p.model-block-animations" ) ? model.querySelector( - "p.model-block-animations" + "p.model-block-animations" ).innerText : ""; @@ -1456,7 +865,7 @@ export default function EnvironmentFront(props) { "p.model-block-alt" ) ? model.querySelector( - "p.model-block-alt" + "p.model-block-alt" ).innerText : ""; @@ -1465,7 +874,7 @@ export default function EnvironmentFront(props) { "p.model-block-collidable" ) ? model.querySelector( - "p.model-block-collidable" + "p.model-block-collidable" ).innerText : false; @@ -1501,7 +910,7 @@ export default function EnvironmentFront(props) { "p.three-text-content" ) ? model.querySelector( - "p.three-text-content" + "p.three-text-content" ).innerText : ""; const rotationX = @@ -1509,7 +918,7 @@ export default function EnvironmentFront(props) { "p.three-text-rotationX" ) ? model.querySelector( - "p.three-text-rotationX" + "p.three-text-rotationX" ).innerText : ""; const rotationY = @@ -1517,7 +926,7 @@ export default function EnvironmentFront(props) { "p.three-text-rotationY" ) ? model.querySelector( - "p.three-text-rotationY" + "p.three-text-rotationY" ).innerText : ""; const rotationZ = @@ -1525,7 +934,7 @@ export default function EnvironmentFront(props) { "p.three-text-rotationZ" ) ? model.querySelector( - "p.three-text-rotationZ" + "p.three-text-rotationZ" ).innerText : ""; const positionX = @@ -1533,7 +942,7 @@ export default function EnvironmentFront(props) { "p.three-text-positionX" ) ? model.querySelector( - "p.three-text-positionX" + "p.three-text-positionX" ).innerText : ""; const positionY = @@ -1541,7 +950,7 @@ export default function EnvironmentFront(props) { "p.three-text-positionY" ) ? model.querySelector( - "p.three-text-positionY" + "p.three-text-positionY" ).innerText : ""; const positionZ = @@ -1549,7 +958,7 @@ export default function EnvironmentFront(props) { "p.three-text-positionZ" ) ? model.querySelector( - "p.three-text-positionZ" + "p.three-text-positionZ" ).innerText : ""; const scaleX = @@ -1557,7 +966,7 @@ export default function EnvironmentFront(props) { "p.three-text-scaleX" ) ? model.querySelector( - "p.three-text-scaleX" + "p.three-text-scaleX" ).innerText : ""; const scaleY = @@ -1565,7 +974,7 @@ export default function EnvironmentFront(props) { "p.three-text-scaleY" ) ? model.querySelector( - "p.three-text-scaleY" + "p.three-text-scaleY" ).innerText : ""; const scaleZ = @@ -1573,7 +982,7 @@ export default function EnvironmentFront(props) { "p.three-text-scaleZ" ) ? model.querySelector( - "p.three-text-scaleZ" + "p.three-text-scaleZ" ).innerText : ""; @@ -1582,7 +991,7 @@ export default function EnvironmentFront(props) { "p.three-text-color" ) ? model.querySelector( - "p.three-text-color" + "p.three-text-color" ).innerText : ""; @@ -1604,6 +1013,8 @@ export default function EnvironmentFront(props) { scaleX={scaleX} scaleY={scaleY} scaleZ={scaleZ} + defaultFont={defaultFont} + threeObjectPlugin={threeObjectPlugin} textColor={ textColor } @@ -1616,8 +1027,8 @@ export default function EnvironmentFront(props) { rotationZ={ rotationZ } - // alt={alt} - // animations={animations} + // alt={alt} + // animations={animations} /> ); } @@ -1630,7 +1041,7 @@ export default function EnvironmentFront(props) { "p.three-portal-block-position-x" ) ? model.querySelector( - "p.three-portal-block-position-x" + "p.three-portal-block-position-x" ).innerText : ""; @@ -1639,7 +1050,7 @@ export default function EnvironmentFront(props) { "p.three-portal-block-position-y" ) ? model.querySelector( - "p.three-portal-block-position-y" + "p.three-portal-block-position-y" ).innerText : ""; @@ -1648,7 +1059,7 @@ export default function EnvironmentFront(props) { "p.three-portal-block-position-z" ) ? model.querySelector( - "p.three-portal-block-position-z" + "p.three-portal-block-position-z" ).innerText : ""; @@ -1657,7 +1068,7 @@ export default function EnvironmentFront(props) { "p.three-portal-block-scale-x" ) ? model.querySelector( - "p.three-portal-block-scale-x" + "p.three-portal-block-scale-x" ).innerText : ""; @@ -1666,7 +1077,7 @@ export default function EnvironmentFront(props) { "p.three-portal-block-scale-y" ) ? model.querySelector( - "p.three-portal-block-scale-y" + "p.three-portal-block-scale-y" ).innerText : ""; @@ -1675,7 +1086,7 @@ export default function EnvironmentFront(props) { "p.three-portal-block-scale-z" ) ? model.querySelector( - "p.three-portal-block-scale-z" + "p.three-portal-block-scale-z" ).innerText : ""; @@ -1684,7 +1095,7 @@ export default function EnvironmentFront(props) { "p.three-portal-block-rotation-x" ) ? model.querySelector( - "p.three-portal-block-rotation-x" + "p.three-portal-block-rotation-x" ).innerText : ""; @@ -1693,7 +1104,7 @@ export default function EnvironmentFront(props) { "p.three-portal-block-rotation-y" ) ? model.querySelector( - "p.three-portal-block-rotation-y" + "p.three-portal-block-rotation-y" ).innerText : ""; @@ -1702,7 +1113,7 @@ export default function EnvironmentFront(props) { "p.three-portal-block-rotation-z" ) ? model.querySelector( - "p.three-portal-block-rotation-z" + "p.three-portal-block-rotation-z" ).innerText : ""; @@ -1710,7 +1121,7 @@ export default function EnvironmentFront(props) { "p.three-portal-block-url" ) ? model.querySelector( - "p.three-portal-block-url" + "p.three-portal-block-url" ).innerText : ""; @@ -1719,7 +1130,7 @@ export default function EnvironmentFront(props) { "p.three-portal-block-destination-url" ) ? model.querySelector( - "p.three-portal-block-destination-url" + "p.three-portal-block-destination-url" ).innerText : ""; @@ -1728,7 +1139,7 @@ export default function EnvironmentFront(props) { "p.three-portal-block-animations" ) ? model.querySelector( - "p.three-portal-block-animations" + "p.three-portal-block-animations" ).innerText : ""; @@ -1737,7 +1148,7 @@ export default function EnvironmentFront(props) { "p.three-portal-block-label" ) ? model.querySelector( - "p.three-portal-block-label" + "p.three-portal-block-label" ).innerText : ""; @@ -1746,7 +1157,7 @@ export default function EnvironmentFront(props) { "p.three-portal-block-label-offset-x" ) ? model.querySelector( - "p.three-portal-block-label-offset-x" + "p.three-portal-block-label-offset-x" ).innerText : ""; @@ -1755,7 +1166,7 @@ export default function EnvironmentFront(props) { "p.three-portal-block-label-offset-y" ) ? model.querySelector( - "p.three-portal-block-label-offset-y" + "p.three-portal-block-label-offset-y" ).innerText : ""; @@ -1764,7 +1175,7 @@ export default function EnvironmentFront(props) { "p.three-portal-block-label-offset-z" ) ? model.querySelector( - "p.three-portal-block-label-offset-z" + "p.three-portal-block-label-offset-z" ).innerText : ""; const labelTextColor = @@ -1772,7 +1183,7 @@ export default function EnvironmentFront(props) { "p.three-portal-block-label-text-color" ) ? model.querySelector( - "p.three-portal-block-label-text-color" + "p.three-portal-block-label-text-color" ).innerText : ""; @@ -1783,6 +1194,8 @@ export default function EnvironmentFront(props) { destinationUrl={ destinationUrl } + defaultFont={defaultFont} + threeObjectPlugin={threeObjectPlugin} positionX={modelPosX} positionY={modelPosY} animations={animations} @@ -1815,12 +1228,6 @@ export default function EnvironmentFront(props) { /> ); })} - {/* - - */} )} diff --git a/blocks/environment/components/Player.js b/blocks/environment/components/Player.js index 81fc348..725407f 100644 --- a/blocks/environment/components/Player.js +++ b/blocks/environment/components/Player.js @@ -116,7 +116,7 @@ export default function Player(props) { friction={0} ref={rigidRef} mass={0} - type={"dynamic"} + type={"static"} onCollisionEnter={({ manifold, target }) => { setRapierId(target.colliderSet.map.data[1]); setContactPoint(manifold.solverContactPoint(0)); @@ -134,6 +134,7 @@ export default function Player(props) { point={contactPoint} something={rigidRef} spawnPoint={props.spawnPoint} + spawnPointsToAdd={props.spawnPointsToAdd} /> { + const x = Number(spawnPos[0]); + const y = Number(spawnPos[1]) + 1.1; + const z = Number(spawnPos[2]); + + if (isPresenting) { + player.position.x = x + player.position.y = y + player.position.z = z + } + }, [isPresenting]) + + // Set a variable finding an object in the three.js scene that is named reticle. useEffect(() => { + // Remove the reticle when the controllers are registered. const reticle = scene.getObjectByName("reticle"); if (controllers.length > 0 && reticle) { diff --git a/blocks/environment/components/ThreeObjectEdit.js b/blocks/environment/components/ThreeObjectEdit.js index 5b77d6c..aabcee3 100644 --- a/blocks/environment/components/ThreeObjectEdit.js +++ b/blocks/environment/components/ThreeObjectEdit.js @@ -183,7 +183,7 @@ function Spawn(spawn) { scale={[1, 1, 1]} rotation={[0, 0, 0]} > - + { + setTimeout(() => set(model.url), 2000); + }, []); + const [listener] = useState(() => new AudioListener()); + + useThree(({ camera }) => { + camera.add(listener); + }); + + const gltf = useLoader(GLTFLoader, url, (loader) => { + // const dracoLoader = new DRACOLoader(); + // dracoLoader.setDecoderPath( + // "https://www.gstatic.com/draco/v1/decoders/" + // ); + // loader.setDRACOLoader(dracoLoader); + + loader.register( + (parser) => new GLTFAudioEmitterExtension(parser, listener) + ); + if (openbrushEnabled === true) { + loader.register( + (parser) => + new GLTFGoogleTiltBrushMaterialExtension( + parser, + openbrushDirectory + ) + ); + } + loader.register((parser) => { + return new VRMLoaderPlugin(parser); + }); + }); + + const audioObject = gltf.scene.getObjectByProperty('type', 'Audio'); + + const { actions } = useAnimations(gltf.animations, gltf.scene); + const animationClips = gltf.animations; + const animationList = model.animations ? model.animations.split(",") : ""; + useEffect(() => { + if (animationList) { + animationList.forEach((name) => { + if (Object.keys(actions).includes(name)) { + console.log(actions[name].play()); + } + }); + } + }, []); + + const generator = gltf.asset.generator; + + // return tilt brush if tilt brush + if (String(generator).includes("Tilt Brush")) { + return ( + + ); + } + + if (gltf?.userData?.gltfExtensions?.VRM) { + const vrm = gltf.userData.vrm; + vrm.scene.position.set( + model.positionX, + model.positionY, + model.positionZ + ); + VRMUtils.rotateVRM0(vrm); + const rotationVRM = vrm.scene.rotation.y + parseFloat(0); + vrm.scene.rotation.set(0, rotationVRM, 0); + vrm.scene.scale.set(1, 1, 1); + vrm.scene.scale.set(model.scaleX, model.scaleY, model.scaleZ); + return ( + // + + // + ); + } + // gltf.scene.castShadow = true; + // enable shadows @todo figure this out + // gltf.scene.traverse(function (node) { + // if (node.isMesh) { + // node.castShadow = true; + // node.receiveShadow = true; + // } + // }); + + // @todo figure out how to clone gltf proper with extensions and animations + // const copyGltf = useMemo(() => gltf.scene.clone(), [gltf.scene]); + // const modelClone = SkeletonUtils.clone(gltf.scene); + // modelClone.scene.castShadow = true; + + //audioObject + // Add a triangle mesh on top of the video + const [triangle] = useState(() => { + const points = []; + points.push( + new Vector3(0, -3, 0), + new Vector3(0, 3, 0), + new Vector3(4, 0, 0) + ); + const geometry = new BufferGeometry().setFromPoints(points); + const material = new MeshBasicMaterial({ + color: 0x00000, + side: DoubleSide + }); + const triangle = new Mesh(geometry, material); + return triangle; + }); + + const [circle] = useState(() => { + const geometryCircle = new CircleGeometry(5, 32); + const materialCircle = new MeshBasicMaterial({ + color: 0xfffff, + side: DoubleSide + }); + const circle = new Mesh(geometryCircle, materialCircle); + return circle; + }); + + if (model.collidable === "1") { + return ( + <> + { + setClickEvent(!clicked); + if(audioObject){ + if (clicked) { + audioObject.play(); + triangle.material.visible = false; + circle.material.visible = false; + } else { + audioObject.pause(); + triangle.material.visible = true; + circle.material.visible = true; + } + } + }} + // onCollisionEnter={ ( props ) =>( + // // window.location.href = model.destinationUrl + // ) + // } + > + + + + ); + } + return ( + <> + + + ); +} \ No newline at end of file diff --git a/blocks/environment/components/core/front/Portal.js b/blocks/environment/components/core/front/Portal.js new file mode 100644 index 0000000..4c2c283 --- /dev/null +++ b/blocks/environment/components/core/front/Portal.js @@ -0,0 +1,244 @@ +import React, { useState, useEffect, useMemo } from "react"; +import { useLoader, useThree } from "@react-three/fiber"; +import { GLTFLoader } from "three/examples/jsm/loaders/GLTFLoader"; +import { DRACOLoader } from "three/examples/jsm/loaders/DRACOLoader"; +import { AudioListener, Vector3, BufferGeometry, MeshBasicMaterial, DoubleSide, Mesh, CircleGeometry, sRGBEncoding } from "three"; +import { RigidBody } from "@react-three/rapier"; +import { + useAnimations, + Billboard, + Text +} from "@react-three/drei"; +import { GLTFAudioEmitterExtension } from "three-omi"; +import { GLTFGoogleTiltBrushMaterialExtension } from "three-icosa"; +import { VRMUtils, VRMLoaderPlugin } from "@pixiv/three-vrm"; + +/** + * Parses a Matrix URI and returns a matrix ID. + * + * @param {string} uri - The Matrix URI to parse. + * @return {string} The matrix ID extracted from the URI. + * + * @throws {Error} If the provided URI is invalid or has an unsupported format. + */ + function parseMatrixUri(uri) { + const SegmentToSigil = { + u: "@", + user: "@", + r: "#", + room: "#", + roomid: "!" + }; + + const url = new URL(uri, window.location.href); + + if (url.protocol === "matrix:") { + const matches = url.pathname.match(/^(\/\/.+\/)?(.+)$/); + + let authority; + let path; + + if (matches) { + if (matches.length === 3) { + authority = matches[1]; + path = matches[2]; + } else if (matches.length === 2) { + path = matches[1]; + } + } + + if (!path) { + throw new Error(`Invalid matrix uri "${uri}": No path provided`); + } + + const segments = path.split("/"); + + if (segments.length !== 2 && segments.length !== 4) { + throw new Error( + `Invalid matrix uri "${uri}": Invalid number of segments` + ); + } + + const sigil1 = SegmentToSigil[segments[0]]; + + if (!sigil1) { + throw new Error( + `Invalid matrix uri "${uri}": Invalid segment ${segments[0]}` + ); + } + + if (!segments[1]) { + throw new Error(`Invalid matrix uri "${uri}": Empty segment`); + } + + const mxid1 = `${sigil1}${segments[1]}`; + + let mxid2; + + if (segments.length === 4) { + if ( + (sigil1 === "!" || sigil1 === "#") && + (segments[2] === "e" || segments[2] === "event") && + segments[3] + ) { + mxid2 = `$${segments[3]}`; + } else { + throw new Error( + `Invalid matrix uri "${uri}": Invalid segment ${segments[2]}` + ); + } + } + return { protocol: "matrix:", authority, mxid1, mxid2 }; + } + + return url; +} + + +/** + * Represents a portal in a virtual reality scene. + * + * @param {Object} model - The props for the portal. + * + * @return {JSX.Element} The portal. + */ +export function Portal(model) { + if (model.object && model.defaultFont) { + return ( + <> + + + {model.label + ? model.label + ": " + : "" + model.destinationUrl} + + + { + const url = new URL( + model.destinationUrl, + window.location.href + ); + if (url.protocol === "matrix:") { + const destination = parseMatrixUri( + model.destinationUrl + ); + window.location.href = + "https://thirdroom.io/world/" + + destination.mxid1; + } else { + window.location.href = model.destinationUrl; + } + }} + > + + + + ); + } + const [url, set] = useState(model.url); + + useEffect(() => { + setTimeout(() => set(model.url), 2000); + }, []); + const [listener] = useState(() => new AudioListener()); + + useThree(({ camera }) => { + camera.add(listener); + }); + + const gltf = useLoader(GLTFLoader, url, (loader) => { + loader.register( + (parser) => new GLTFAudioEmitterExtension(parser, listener) + ); + loader.register((parser) => { + return new VRMLoaderPlugin(parser); + }); + }); + + const { actions } = useAnimations(gltf.animations, gltf.scene); + + const animationList = model.animations ? model.animations.split(",") : ""; + useEffect(() => { + if (animationList) { + animationList.forEach((name) => { + if (Object.keys(actions).includes(name)) { + actions[name].play(); + } + }); + } + }, []); + // gltf.scene.position.set( model.positionX, model.positionY, model.positionZ ); + // gltf.scene.rotation.set( 0, 0, 0 ); + // gltf.scene.scale.set(model.scaleX, model.scaleY, model.scaleZ); + // gltf.scene.rotation.set(model.rotationX , model.rotationY, model.rotationZ ); + const copyGltf = useMemo(() => gltf.scene.clone(), [gltf.scene]); + + return ( + <> + + (window.location.href = model.destinationUrl) + } + rotation={[model.rotationX, model.rotationY, model.rotationZ]} + position={[model.positionX, model.positionY, model.positionZ]} + scale={[model.scaleX, model.scaleY, model.scaleZ]} + > + + + {model.label + ": " + model.destinationUrl} + + + + + + ); +} \ No newline at end of file diff --git a/blocks/environment/components/core/front/Sky.js b/blocks/environment/components/core/front/Sky.js new file mode 100644 index 0000000..af49c56 --- /dev/null +++ b/blocks/environment/components/core/front/Sky.js @@ -0,0 +1,30 @@ +import React from "react"; +import { useLoader } from "@react-three/fiber"; +import { TextureLoader, DoubleSide } from "three"; + +/** + * Represents a sky in a virtual reality scene. + * + * @param {Object} sky - The props for the sky. + * + * @return {JSX.Element} The sky. + */ +export function Sky(sky) { + const skyUrl = sky.src[0].querySelector("p.sky-block-url") + ? sky.src[0].querySelector("p.sky-block-url").innerText + : ""; + +const texture1 = useLoader(TextureLoader, skyUrl); + +return ( + + + + +); +} diff --git a/blocks/environment/components/core/front/TextObject.js b/blocks/environment/components/core/front/TextObject.js new file mode 100644 index 0000000..0711ab1 --- /dev/null +++ b/blocks/environment/components/core/front/TextObject.js @@ -0,0 +1,38 @@ +import React, { useRef } from "react"; +import { + Text, +} from "@react-three/drei"; + +/** + * Represents a text object in a virtual reality scene. + * + * @param {Object} model - The props for the text object. + * + * @return {JSX.Element} The text object. + */ +export function TextObject(model) { + const htmlObj = useRef(); + return ( + <> + + + {model.textContent} + + + + ); +} diff --git a/blocks/environment/components/core/front/ThreeImage.js b/blocks/environment/components/core/front/ThreeImage.js new file mode 100644 index 0000000..f3bd59c --- /dev/null +++ b/blocks/environment/components/core/front/ThreeImage.js @@ -0,0 +1,46 @@ +import React from "react"; +import { useLoader, useThree } from "@react-three/fiber"; +import { TextureLoader, DoubleSide } from "three"; + +/** + * Renders an image in a three.js scene. + * + * @param {Object} threeImage - The props for the image. + * + * @return {JSX.Element} The image. + */ +export function ThreeImage(threeImage) { + const texture2 = useLoader(TextureLoader, threeImage.url); + return ( + + + {threeImage.transparent == "1" ? ( + + ) : ( + + )} + + ); +} \ No newline at end of file diff --git a/blocks/environment/components/core/front/ThreeVideo.js b/blocks/environment/components/core/front/ThreeVideo.js new file mode 100644 index 0000000..39afcb2 --- /dev/null +++ b/blocks/environment/components/core/front/ThreeVideo.js @@ -0,0 +1,137 @@ +import React, { useState, useEffect } from "react"; +import { useLoader, useThree } from "@react-three/fiber"; +import { Vector3, BufferGeometry, MeshBasicMaterial, DoubleSide, Mesh, CircleGeometry, sRGBEncoding } from "three"; +import { RigidBody } from "@react-three/rapier"; + +/** + * Renders a video in a three.js scene. + * + * @param {Object} threeVideo - The props for the video. + * + * @return {JSX.Element} The video. + */ +export function ThreeVideo(threeVideo) { + const play = threeVideo.autoPlay === "1" ? true : false; + const { scene } = useThree(); + const [clicked, setClickEvent] = useState(); + const [video] = useState(() => + Object.assign(document.createElement("video"), { + src: threeVideo.url, + crossOrigin: "Anonymous", + loop: true, + muted: true + }) + ); + // Add a triangle mesh on top of the video + const [triangle] = useState(() => { + const points = []; + points.push( + new Vector3(0, -3, 0), + new Vector3(0, 3, 0), + new Vector3(4, 0, 0) + ); + const geometry = new BufferGeometry().setFromPoints(points); + const material = new MeshBasicMaterial({ + color: 0x00000, + side: DoubleSide + }); + const triangle = new Mesh(geometry, material); + return triangle; + }); + + const [circle] = useState(() => { + const geometryCircle = new CircleGeometry(5, 32); + const materialCircle = new MeshBasicMaterial({ + color: 0xfffff, + side: DoubleSide + }); + const circle = new Mesh(geometryCircle, materialCircle); + return circle; + }); + + useEffect(() => { + if (play) { + triangle.material.visible = false; + circle.material.visible = false; + video.play(); + } else { + triangle.material.visible = true; + circle.material.visible = true; + } + }, [video, play]); + + return ( + // + ); +} \ No newline at end of file diff --git a/blocks/three-audio-block/Edit.js b/blocks/three-audio-block/Edit.js index 50d682a..8739332 100644 --- a/blocks/three-audio-block/Edit.js +++ b/blocks/three-audio-block/Edit.js @@ -21,7 +21,6 @@ import { more } from "@wordpress/icons"; export default function Edit({ attributes, setAttributes, isSelected }) { const onImageSelect = (imageObject) => { - console.log(imageObject); setAttributes({ videoUrl: null }); setAttributes({ videoUrl: imageObject.url, @@ -110,7 +109,6 @@ export default function Edit({ attributes, setAttributes, isSelected }) { function handleClick(objectURL) { if (objectURL) { - console.log("success good job", objectURL); onImageSelect(objectURL); } console.log("fail", objectURL); diff --git a/blocks/three-image-block/Edit.js b/blocks/three-image-block/Edit.js index e0635a3..bd7b947 100644 --- a/blocks/three-image-block/Edit.js +++ b/blocks/three-image-block/Edit.js @@ -21,7 +21,6 @@ import { more } from "@wordpress/icons"; export default function Edit({ attributes, setAttributes, isSelected }) { const onImageSelect = (imageObject) => { - console.log(imageObject); setAttributes({ imageUrl: null }); setAttributes({ imageUrl: imageObject.url, @@ -63,8 +62,8 @@ export default function Edit({ attributes, setAttributes, isSelected }) { setAttributes({ collidable: setting }); }; - const onChangeTransparent = (transparent) => { - setAttributes({ transparent }); + const onChangeTransparent = (transparentSetting) => { + setAttributes({ transparent: transparentSetting }); }; const { mediaUpload } = wp.editor; @@ -93,7 +92,6 @@ export default function Edit({ attributes, setAttributes, isSelected }) { function handleClick(objectURL) { if (objectURL) { - console.log("success good job", objectURL); onImageSelect(objectURL); } console.log("fail", objectURL); diff --git a/blocks/three-image-block/Save.js b/blocks/three-image-block/Save.js index 3f75665..776c9f9 100644 --- a/blocks/three-image-block/Save.js +++ b/blocks/three-image-block/Save.js @@ -35,7 +35,7 @@ export default function save({ attributes }) { {attributes.aspectWidth}

- {attributes.transparent} + {attributes.transparent ? 1 : 0}

diff --git a/blocks/three-object-block/Edit.js b/blocks/three-object-block/Edit.js index 004aa28..2d0116f 100644 --- a/blocks/three-object-block/Edit.js +++ b/blocks/three-object-block/Edit.js @@ -93,7 +93,6 @@ export default function Edit( { attributes, setAttributes, isSelected } ) { function handleClick(objectURL){ if(objectURL){ - console.log("success good job", objectURL); onImageSelect(objectURL); } console.log("fail", objectURL); diff --git a/blocks/three-portal-block/Edit.js b/blocks/three-portal-block/Edit.js index 12a0dfa..3c0f8dc 100644 --- a/blocks/three-portal-block/Edit.js +++ b/blocks/three-portal-block/Edit.js @@ -121,7 +121,6 @@ export default function Edit({ attributes, setAttributes, isSelected }) { function handleClick(objectURL) { if (objectURL) { - console.log("success good job", objectURL); onImageSelect(objectURL); } console.log("fail", objectURL); diff --git a/blocks/three-text-block/Edit.js b/blocks/three-text-block/Edit.js index c4701f1..9031b20 100644 --- a/blocks/three-text-block/Edit.js +++ b/blocks/three-text-block/Edit.js @@ -106,7 +106,6 @@ export default function Edit({ attributes, setAttributes, isSelected }) { function handleClick(objectURL) { if (objectURL) { - console.log("success good job", objectURL); onImageSelect(objectURL); } console.log("fail", objectURL); diff --git a/blocks/three-video-block/Edit.js b/blocks/three-video-block/Edit.js index 319fe5c..4d4ff83 100644 --- a/blocks/three-video-block/Edit.js +++ b/blocks/three-video-block/Edit.js @@ -21,7 +21,6 @@ import { more } from "@wordpress/icons"; export default function Edit({ attributes, setAttributes, isSelected }) { const onImageSelect = (imageObject) => { - console.log(imageObject); setAttributes({ videoUrl: null }); setAttributes({ videoUrl: imageObject.url, @@ -89,7 +88,6 @@ export default function Edit({ attributes, setAttributes, isSelected }) { function handleClick(objectURL) { if (objectURL) { - console.log("success good job", objectURL); onImageSelect(objectURL); } console.log("fail", objectURL); diff --git a/readme.txt b/readme.txt index ba15c51..3e27596 100644 --- a/readme.txt +++ b/readme.txt @@ -2,7 +2,7 @@ Requires at least: 5.7 Tested up to: 6.1 Requires PHP: 7.2 -Stable tag: 1.0.8 +Stable tag: 1.0.9 License: GPLv2 or later License URI: https://www.gnu.org/licenses/gpl-2.0.html Author: antpb @@ -46,6 +46,10 @@ It can also be installed manually using a zip file. == Changelog == += 1.0.9 = +Fixed: Spawn - Spawn block now properly loads a player in the spawn location. +Added: Initial support for OMI_spawn_point. + = 1.0.8 = Added: Click event for audio objects that contain KHR_audio. Improvements to come. To set a KHR_audio object to be interactable simply make the object collibable. Added: Video - Interactions to play/pause a video. A paused video will now show a play icon to resume the video source. Video audio to come soon! diff --git a/three-object-viewer.php b/three-object-viewer.php index ee4b38c..f45912c 100644 --- a/three-object-viewer.php +++ b/three-object-viewer.php @@ -3,7 +3,7 @@ * Plugin Name: Three Object Viewer * Plugin URI: https://3ov.xyz/ * Description: A plugin for viewing 3D files with support for WebXR and Open Metaverse Interoperability GLTF Extensions. -* Version: 1.0.8 +* Version: 1.0.9 * Requires at least: 5.7 * Requires PHP: 7.1.0 * Author: antpb