diff --git a/package.json b/package.json index cae88c47526aef2ed515ceaa4fda82d1a2812622..d21ad9c0e5e7f7bd00ec9695b6cac65f4dda8eb9 100644 --- a/package.json +++ b/package.json @@ -26,7 +26,7 @@ "aframe-input-mapping-component": "https://github.com/mozillareality/aframe-input-mapping-component#hubs/master", "aframe-motion-capture-components": "https://github.com/mozillareality/aframe-motion-capture-components#1ca616fa67b627e447b23b35a09da175d8387668", "aframe-physics-extras": "^0.1.3", - "aframe-physics-system": "github:donmccurdy/aframe-physics-system", + "aframe-physics-system": "https://github.com/mozillareality/aframe-physics-system#hubs/master", "aframe-rounded": "^1.0.3", "aframe-slice9-component": "^1.0.0", "aframe-teleport-controls": "https://github.com/mozillareality/aframe-teleport-controls#hubs/master", diff --git a/src/components/auto-box-collider.js b/src/components/auto-box-collider.js new file mode 100644 index 0000000000000000000000000000000000000000..e902e347f8c68fa35046e88d4c8b9c2dbbd332c2 --- /dev/null +++ b/src/components/auto-box-collider.js @@ -0,0 +1,69 @@ +AFRAME.registerComponent("auto-box-collider", { + schema: { + setInitialScale: { default: false } + }, + + init() { + this.onLoaded = this.onLoaded.bind(this); + this.el.addEventListener("model-loaded", this.onLoaded); + }, + + onLoaded() { + this.el.removeEventListener("model-loaded", this.onLoaded); + if (this.data.setInitialScale) { + this.setInitialScale(); + } + + const initialRotation = this.el.object3D.rotation.clone(); + this.el.setAttribute("rotation", { x: 0, y: 0, z: 0 }); + + const worldBox = new THREE.Box3().setFromObject(this.el.object3D); + const dX = Math.abs(worldBox.max.x - worldBox.min.x); + const dY = Math.abs(worldBox.max.y - worldBox.min.y); + const dZ = Math.abs(worldBox.max.z - worldBox.min.z); + const scale = this.el.getAttribute("scale"); + const inverseScale = { x: 1 / scale.x, y: 1 / scale.y, z: 1 / scale.z }; + + const localHalfExtents = { + x: dX * 0.5 * inverseScale.x, + y: dY * 0.5 * inverseScale.y, + z: dZ * 0.5 * inverseScale.z + }; + const center = new THREE.Vector3().addVectors(worldBox.min, worldBox.max).multiplyScalar(0.5); + const worldPosition = new THREE.Vector3(); + this.el.object3D.getWorldPosition(worldPosition); + const localOffset = { + x: (center.x - worldPosition.x) * inverseScale.x, + y: (center.y - worldPosition.y) * inverseScale.y, + z: (center.z - worldPosition.z) * inverseScale.z + }; + + // Set the shape component halfExtents + this.el.setAttribute("shape", { + shape: "box", + halfExtents: localHalfExtents, + offset: localOffset + }); + + this.el.setAttribute("rotation", initialRotation); + }, + + // Adjust the scale such that the object fits within a box whose longest component axis is lengthOfLongestComponent meters. + // In other words, a tall model will be scaled down to be only half a meter tall, a wide model will be scaled down to be only + // half a meter wide, a long model will be scaled down to be only half a meter long. + setInitialScale() { + const worldBox = new THREE.Box3().setFromObject(this.el.object3D); + const dX = Math.abs(worldBox.max.x - worldBox.min.x); + const dY = Math.abs(worldBox.max.y - worldBox.min.y); + const dZ = Math.abs(worldBox.max.z - worldBox.min.z); + const max = Math.max(dX, dY, dZ); + const scale = this.el.getAttribute("scale"); + const lengthOfLongestComponent = 0.5; + const correctiveFactor = lengthOfLongestComponent / max; + this.el.setAttribute("scale", { + x: scale.x * correctiveFactor, + y: scale.y * correctiveFactor, + z: scale.z * correctiveFactor + }); + } +}); diff --git a/src/components/gltf-model-plus.js b/src/components/gltf-model-plus.js index 55d62b10e997a943d39c6e593cb35256c67b0e6c..686245b492555beaf311a7f2f9b94c07dd600d2a 100644 --- a/src/components/gltf-model-plus.js +++ b/src/components/gltf-model-plus.js @@ -203,11 +203,11 @@ function cachedLoadGLTF(src, preferredTechnique, onProgress) { AFRAME.registerComponent("gltf-model-plus", { schema: { src: { type: "string" }, - inflate: { default: false }, - preferredTechnique: { default: AFRAME.utils.device.isMobile() ? "KHR_materials_unlit" : "pbrMetallicRoughness" } + inflate: { default: false } }, init() { + this.preferredTechnique = AFRAME.utils.device.isMobile() ? "KHR_materials_unlit" : "pbrMetallicRoughness"; this.loadTemplates(); }, @@ -245,7 +245,7 @@ AFRAME.registerComponent("gltf-model-plus", { } const gltfPath = THREE.LoaderUtils.extractUrlBase(src); - const model = await cachedLoadGLTF(src, this.data.preferredTechnique); + const model = await cachedLoadGLTF(src, this.preferredTechnique); // If we started loading something else already // TODO: there should be a way to cancel loading instead diff --git a/src/components/spawn-in-front-of-object.js b/src/components/spawn-in-front-of-object.js new file mode 100644 index 0000000000000000000000000000000000000000..0a5d0c0d6a63aab2e845acdae548fde881d317cb --- /dev/null +++ b/src/components/spawn-in-front-of-object.js @@ -0,0 +1,14 @@ +AFRAME.registerComponent("spawn-in-front-of-object", { + schema: {}, + init() { + this.onLoaded = this.onLoaded.bind(this); + this.el.addEventListener("model-loaded", this.onLoaded); + }, + onLoaded() { + const billboardTarget = document.querySelector("#player-camera").object3D; + const worldPos = new THREE.Vector3().copy({ x: 0, y: 0, z: -1.5 }); + billboardTarget.localToWorld(worldPos); + this.el.object3D.position.copy(worldPos); + billboardTarget.getWorldQuaternion(this.el.object3D.quaternion); + } +}); diff --git a/src/components/sticky-object.js b/src/components/sticky-object.js index 99f93ffafcfcfb2c8e7ff4045d4ac4e758663daa..e6f1cf764b9e32fe383bad8475fe91d7ea86d159 100644 --- a/src/components/sticky-object.js +++ b/src/components/sticky-object.js @@ -85,7 +85,6 @@ AFRAME.registerComponent("sticky-object-zone", { this.bootImpulse.copy(dir); this.el.addEventListener("collisions", e => { - console.log("collisions", e.detail.els, e.detail.clearedEls); e.detail.els.forEach(el => { const stickyObject = el.components["sticky-object"]; if (!stickyObject) return; diff --git a/src/hub.html b/src/hub.html index 9863b8361c91da6eaf2132597ade00307b14195b..639a6f21acdcaed0857d13ffff0bee490995a940 100644 --- a/src/hub.html +++ b/src/hub.html @@ -18,7 +18,7 @@ <script src="https://cdn.rawgit.com/aframevr/aframe/3e7a4b3/dist/aframe-master.js" integrity="sha384-EaMOuyBOi9ERV/lVDwQgz/yFWBDWPsIju5Co6oCZZHXuvbLBO81yPWn80q0BbBn3" crossorigin="anonymous"></script> <% } %> - + <!-- HACK: this has to run after A-Frame but before our bundle, since A-Frame blows away the local storage setting --> <script src="https://cdn.rawgit.com/gfodor/ba8f88d9f34fe9cbe59a01ce3c48420d/raw/03e31f0ef7b9eac5e947bd39e440f34df0701f75/naf-janus-adapter-logging.js" integrity="sha384-4q1V8Q88oeCFriFefFo5uEUtMzbw6K116tFyC9cwbiPr6wEe7050l5HoJUxMvnzj" crossorigin="anonymous"></script> </head> @@ -162,22 +162,37 @@ gltf-model-plus="src: #interactable-duck; inflate: true;" class="interactable" super-networked-interactable="counter: #counter; mass: 1;" - body="type: dynamic; shape: none; mass: 1;" + body="type: dynamic; shape: none; mass: 1; monitorScale: true" grabbable - stretchable="useWorldPosition: true;" + stretchable="useWorldPosition: true; usePhysics: never" hoverable duck - sticky-object + sticky-object="autoLockOnRelease: true;" ></a-entity> </template> + <template id="interactable-model"> + <a-entity + gltf-model-plus="inflate: false;" + class="interactable" + super-networked-interactable="counter: #media-counter; mass: 1;" + body="type: dynamic; shape: none; mass: 1; monitorScale: true" + grabbable + stretchable="useWorldPosition: true; usePhysics: never" + hoverable + auto-box-collider + sticky-object="autoLockOnRelease: true; autoLockOnLoad: true;" + > + </a-entity> + </template> + <template id="interactable-image"> <a-entity class="interactable" super-networked-interactable="counter: #media-counter; mass: 1;" - body="type: dynamic; shape: none; mass: 1;" + body="type: dynamic; shape: none; mass: 1; monitorScale: true" grabbable - stretchable="useWorldPosition: true;" + stretchable="useWorldPosition: true; usePhysics: never" hoverable geometry="primitive: plane" material="shader: flat; side: double;" @@ -338,6 +353,7 @@ collisionEntities: [nav-mesh]; drawIncrementally: true; incrementalDrawMs: 600; + curveShootingSpeed: 5; hitOpacity: 0.3; missOpacity: 0.2;" haptic-feedback diff --git a/src/hub.js b/src/hub.js index ed9f861b9b399ba60f83e88aa46f1fa325762e22..fa2b7dfc01565b564698b7b46c164e9bd430a392 100644 --- a/src/hub.js +++ b/src/hub.js @@ -64,6 +64,8 @@ import "./components/css-class"; import "./components/scene-shadow"; import "./components/avatar-replay"; import "./components/image-plus"; +import "./components/auto-box-collider"; +import "./components/spawn-in-front-of-object"; import "./components/pinch-to-move"; import "./components/look-on-mobile"; import "./components/pitch-yaw-rotator"; @@ -300,15 +302,25 @@ const onReady = async () => { document.addEventListener("paste", e => { const scene = AFRAME.scenes[0]; - const imgUrl = e.clipboardData.getData("text"); - console.log("Pasted: ", imgUrl); - - const image = document.createElement("a-entity"); - image.id = "interactable-image-" + Date.now(); - image.setAttribute("position", { x: 0, y: 2, z: 1 }); - image.setAttribute("image-plus", "src", imgUrl); - image.setAttribute("networked", { template: "#interactable-image" }); - scene.appendChild(image); + const mediaUrl = e.clipboardData.getData("text"); + console.log("Pasted: ", mediaUrl); + if (mediaUrl.endsWith(".gltf") || mediaUrl.endsWith(".glb")) { + const model = document.createElement("a-entity"); + model.id = "interactable-model-" + Date.now(); + model.setAttribute("position", { x: 0, y: 2, z: 1 }); + model.setAttribute("spawn-in-front-of-object", ""); + model.setAttribute("gltf-model-plus", "src", mediaUrl); + model.setAttribute("auto-box-collider", "setInitialScale", true); + model.setAttribute("networked", { template: "#interactable-model" }); + scene.appendChild(model); + } else { + const image = document.createElement("a-entity"); + image.id = "interactable-image-" + Date.now(); + image.setAttribute("position", { x: 0, y: 2, z: 1 }); + image.setAttribute("image-plus", "src", mediaUrl); + image.setAttribute("networked", { template: "#interactable-image" }); + scene.appendChild(image); + } }); if (!qsTruthy("offline")) { diff --git a/src/network-schemas.js b/src/network-schemas.js index a1c91e93357c21267ba40a9d25d71d2bedfc475e..2c9248d51347a8673a792bc8d9f2e83bf5ed278d 100644 --- a/src/network-schemas.js +++ b/src/network-schemas.js @@ -106,6 +106,11 @@ function registerNetworkSchemas() { "image-plus" ] }); + + NAF.schemas.add({ + template: "#interactable-model", + components: ["position", "rotation", "scale", "gltf-model-plus"] + }); } export default registerNetworkSchemas;