UNPKG

three.ar.js

Version:

A helper three.js library for building AR web experiences that run in WebARonARKit and WebARonARCore

315 lines (280 loc) 9.98 kB
<!-- /* * Copyright 2017 Google Inc. All Rights Reserved. * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ --> <!DOCTYPE html> <html lang="en"> <head> <title>three.ar.js - Load Model</title> <meta charset="utf-8"> <meta name="viewport" content="width=device-width, user-scalable=no, minimum-scale=1.0, maximum-scale=1.0"> <style> body { font-family: monospace; margin: 0; overflow: hidden; position: fixed; width: 100%; height: 100vh; -webkit-user-select: none; user-select: none; } #info { position: absolute; left: 50%; bottom: 0; transform: translate(-50%, 0); margin: 1em; z-index: 10; display: block; width: 100%; line-height: 2em; text-align: center; } #info * { color: #fff; } .title { background-color: rgba(40, 40, 40, 0.4); padding: 0.4em 0.6em; border-radius: 0.1em; } .links { background-color: rgba(40, 40, 40, 0.6); padding: 0.4em 0.6em; border-radius: 0.1em; } canvas { position: absolute; top: 0; left: 0; } </style> </head> <body> <div id="info"> <span class="title">Tap to spawn an object from <a href="https://poly.google.com">Poly</a>.</span><br/> <span class="title"> <a href="https://poly.google.com/view/dK08uQ8-Zm9">Model</a> by <a href="https://poly.google.com/user/f8cGQY15_-g">Naomi Chen</a> / <a href="https://creativecommons.org/licenses/by/2.0/">CC-BY</a> </span><br /> <span class="links"> <a href="https://github.com/google-ar/three.ar.js">three.ar.js</a> - <a href="https://developers.google.com/ar/develop/web/getting-started#examples">examples</a> </span> </div> <script src="../third_party/three.js/three.js"></script> <script src="../third_party/three.js/VRControls.js"></script> <script src="../third_party/three.js/OBJLoader.js"></script> <script src="../third_party/three.js/MTLLoader.js"></script> <script src="../dist/three.ar.js"></script> <script> var vrDisplay; var vrControls; var arView; var canvas; var camera; var scene; var renderer; var model; var shadowMesh; var planeGeometry; var light; var directionalLight; var OBJ_PATH = './assets/ArcticFox_Posed.obj'; var MTL_PATH = './assets/ArcticFox_Posed.mtl'; var SCALE = 0.1; /** * Use the `getARDisplay()` utility to leverage the WebVR API * to see if there are any AR-capable WebVR VRDisplays. Returns * a valid display if found. Otherwise, display the unsupported * browser message. */ THREE.ARUtils.getARDisplay().then(function (display) { if (display) { vrDisplay = display; init(); } else { THREE.ARUtils.displayUnsupportedMessage(); } }); function init() { // Turn on the debugging panel var arDebug = new THREE.ARDebug(vrDisplay); document.body.appendChild(arDebug.getElement()); // Setup the three.js rendering environment renderer = new THREE.WebGLRenderer({ alpha: true }); renderer.setPixelRatio(window.devicePixelRatio); renderer.setSize(window.innerWidth, window.innerHeight); renderer.autoClear = false; canvas = renderer.domElement; document.body.appendChild(canvas); scene = new THREE.Scene(); // Creating the ARView, which is the object that handles // the rendering of the camera stream behind the three.js // scene arView = new THREE.ARView(vrDisplay, renderer); // The ARPerspectiveCamera is very similar to THREE.PerspectiveCamera, // except when using an AR-capable browser, the camera uses // the projection matrix provided from the device, so that the // perspective camera's depth planes and field of view matches // the physical camera on the device. camera = new THREE.ARPerspectiveCamera( vrDisplay, 60, window.innerWidth / window.innerHeight, vrDisplay.depthNear, vrDisplay.depthFar ); // VRControls is a utility from three.js that applies the device's // orientation/position to the perspective camera, keeping our // real world and virtual world in sync. vrControls = new THREE.VRControls(camera); // For shadows to work renderer.shadowMap.enabled = true; renderer.shadowMap.type = THREE.PCFSoftShadowMap; // The materials in Poly models will render as a black mesh // without lights in our scenes. Let's add an ambient light // so our model can be scene, as well as a directional light // for the shadow directionalLight = new THREE.DirectionalLight(); // @TODO in the future, use AR light estimation directionalLight.intensity = 0.3; directionalLight.position.set(10, 15, 10); // We want this light to cast shadow directionalLight.castShadow = true; light = new THREE.AmbientLight(); scene.add(light); scene.add(directionalLight); // Make a large plane to receive our shadows planeGeometry = new THREE.PlaneGeometry(2000, 2000); // Rotate our plane to be parallel to the floor planeGeometry.rotateX(-Math.PI / 2); // Create a mesh with a shadow material, resulting in a mesh // that only renders shadows once we flip the `receiveShadow` property shadowMesh = new THREE.Mesh(planeGeometry, new THREE.ShadowMaterial({ color: 0x111111, opacity: 0.15, })); shadowMesh.receiveShadow = true; scene.add(shadowMesh); THREE.ARUtils.loadModel({ objPath: OBJ_PATH, mtlPath: MTL_PATH, OBJLoader: undefined, // uses window.THREE.OBJLoader by default MTLLoader: undefined, // uses window.THREE.MTLLoader by default }).then(function(group) { model = group; // As OBJ models may contain a group with several meshes, // we want all of them to cast shadow model.children.forEach(function(mesh) { mesh.castShadow = true; }); model.scale.set(SCALE, SCALE, SCALE); // Place the model very far to initialize model.position.set(10000, 10000, 10000); scene.add(model); }); // Bind our event handlers window.addEventListener('resize', onWindowResize, false); canvas.addEventListener('click', onClick, false); // Kick off the render loop! update(); } /** * The render loop, called once per frame. Handles updating * our scene and rendering. */ function update() { // Clears color from the frame before rendering the camera (arView) or scene. renderer.clearColor(); // Render the device's camera stream on screen first of all. // It allows to get the right pose synchronized with the right frame. arView.render(); // Update our camera projection matrix in the event that // the near or far planes have updated camera.updateProjectionMatrix(); // Update our perspective camera's positioning vrControls.update(); // Render our three.js virtual scene renderer.clearDepth(); renderer.render(scene, camera); // Kick off the requestAnimationFrame to call this function // when a new VRDisplay frame is rendered vrDisplay.requestAnimationFrame(update); } /** * On window resize, update the perspective camera's aspect ratio, * and call `updateProjectionMatrix` so that we can get the latest * projection matrix provided from the device */ function onWindowResize () { camera.aspect = window.innerWidth / window.innerHeight; camera.updateProjectionMatrix(); renderer.setSize(window.innerWidth, window.innerHeight); } /** * When clicking on the screen, fire a ray from where the user clicked * on the screen and if a hit is found, place a cube there. */ function onClick (e) { // Inspect the event object and generate normalize screen coordinates // (between 0 and 1) for the screen position. var x = e.clientX / window.innerWidth; var y = e.clientY / window.innerHeight; // Send a ray from the point of click to the real world surface // and attempt to find a hit. `hitTest` returns an array of potential // hits. var hits = vrDisplay.hitTest(x, y); if (!model) { console.warn('Model not yet loaded'); return; } // If a hit is found, just use the first one if (hits && hits.length) { var hit = hits[0]; // Turn the model matrix from the VRHit into a // THREE.Matrix4 so we can extract the position // elements out so we can position the shadow mesh // to be directly under our model. This is a complicated // way to go about it to illustrate the process, and could // be done by manually extracting the "Y" value from the // hit matrix via `hit.modelMatrix[13]` var matrix = new THREE.Matrix4(); var position = new THREE.Vector3(); matrix.fromArray(hit.modelMatrix); position.setFromMatrixPosition(matrix); // Set our shadow mesh to be at the same Y value // as our hit where we're placing our model // @TODO use the rotation from hit.modelMatrix shadowMesh.position.y = position.y; // Use the `placeObjectAtHit` utility to position // the cube where the hit occurred THREE.ARUtils.placeObjectAtHit(model, // The object to place hit, // The VRHit object to move the cube to 1, // Easing value from 0 to 1; we want to move // the cube directly to the hit position true); // Whether or not we also apply orientation // Rotate the model to be facing the user var angle = Math.atan2( camera.position.x - model.position.x, camera.position.z - model.position.z ); model.rotation.set(0, angle, 0); } } </script> </body> </html>