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
HTML
<!--
/*
* 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.
*/
-->
<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>