Augments the real world with computer graphics
There are two main AR systems:
Uses a visual marker to unproject the image and obtain world coordinates for the camera
Uses unique features between frames to detect camera location and mapping (SLAM)
The WebXR Device API provides the following key capabilities:
ARjs - link
import Scenes from './scenes';
class Engine {
constructor() {
this.times = [];
this.fps;
this.markerFound;
}
init() {
this.scene = new THREE.Scene();
this.clock = new THREE.Clock();
this.camera = new THREE.Camera();
this.scene.add(this.camera);
let ambientLight = new THREE.AmbientLight( 0x333333, 0.5 );
this.scene.add( ambientLight );
this.sunlight = new THREE.PointLight(0xffffff, 1, 100);
this.sunlight.position.set(5, 5, 5);
this.scene.add(this.sunlight);
this.renderer = new THREE.WebGLRenderer({
antialias : true,
alpha: true
});
this.renderer.setClearColor(new THREE.Color('lightgrey'), 0)
this.renderer.setSize( 640, 480 );
document.getElementById('container').appendChild(this.renderer.domElement);
this.addEvents();
this.createARProfile();
this.createARSource();
this.createARContext();
this.createARScene();
this.render();
}
addEvents() {
window.addEventListener('resize', () => this.onResize());
window.addEventListener('orientationchange', () => this.onResize());
}
onResize() {
this.arToolkitSource.onResizeElement()
this.arToolkitSource.copyElementSizeTo(this.renderer.domElement)
if ( this.arToolkitContext.arController !== null ){
this.arToolkitSource.copyElementSizeTo(this.arToolkitContext.arController.canvas)
}
}
createARProfile() {
this.arProfile = new THREEx.ArToolkitProfile();
this.arProfile.sourceWebcam();
}
createARSource() {
this.arToolkitSource = new THREEx.ArToolkitSource(this.arProfile.sourceParameters);
this.arToolkitSource.init(() => this.onResize());
}
createARContext() {
this.arToolkitContext = new THREEx.ArToolkitContext({
...this.arProfile.contextParameters,
cameraParametersUrl: '/data/camera_para.dat',
detectionMode: 'mono',
patternRatio: .75
});
this.arToolkitContext.init(() => this.camera.projectionMatrix.copy( this.arToolkitContext.getProjectionMatrix() ));
}
createARScene() {
this.sceneRoot = new THREE.Group();
this.scene.add(this.sceneRoot);
this.marker = new THREEx.ArMarkerControls(this.arToolkitContext, this.camera, {
...this.arProfile.defaultMarkerParameters,
patternUrl: '/data/pattern-ar-marker.patt',
changeMatrixMode: 'cameraTransformMatrix',
});
window.addEventListener('markerFound', () => this.markerFound = true);
window.addEventListener('markerLost', () => this.markerFound = false);
switch (window.scene) {
case 2:
this.demoScene = new Scenes.Demo2(this);
break;
default:
this.demoScene = new Scenes.Demo1(this);
}
}
update() {
// update artoolkit on every frame
if (this.arToolkitSource.ready !== false) {
this.arToolkitContext.update(this.arToolkitSource.domElement);
this.onResize();
this.sunlight.position.set(this.camera.position.x, this.camera.position.y, this.camera.position.z);
if (this.markerFound) {
this.sceneRoot.visible = true;
} else {
this.sceneRoot.visible = false;
}
this.demoScene.update();
}
}
render() {
const now = performance.now();
while (this.times.length > 0 && this.times[0] <= now - 1000) {
this.times.shift();
}
this.times.push(now);
this.fps = this.times.length;
if (document.getElementById('frameRate')) document.getElementById('frameRate').innerText = `${this.fps} FPS`;
this.update();
this.renderer.render(this.scene, this.camera);
window.requestAnimationFrame(() => this.render());
}
}
export default Engine;
export default class Demo1 {
constructor(engine) {
// load the assets
const gltfLoader = new THREE.GLTFLoader();
gltfLoader.load('/data/head.glb', gltf => {
gltf.scene.scale.set(.4, .4, .4);
engine.sceneRoot.add(gltf.scene);
});
}
update() {
// update the scene
}
}
export default class Demo2 {
constructor(engine) {
this.engine = engine;
this.touchStart;
this.touchEnd;
this.paper = [];
this.loadAssets();
this.addEvents();
}
loadAssets() {
const cylinderGeometry = new THREE.CylinderGeometry( .2, .2, .1, 16 );
const cylinderMaterial = new THREE.MeshPhongMaterial( {color: 0x00000, side: THREE.BackSide} );
this.cylinder = new THREE.Mesh( cylinderGeometry, cylinderMaterial );
this.engine.sceneRoot.add(this.cylinder);
const paperGeometry = new THREE.SphereGeometry( .1, 6, 6 );
const paperMaterial = new THREE.MeshPhongMaterial( {color: 0xFFFFFF} );
this.paperMesh = new THREE.Mesh( paperGeometry, paperMaterial );
}
addEvents() {
this.engine.renderer.domElement.addEventListener('touchstart', e => this.handleStart(e), false);
this.engine.renderer.domElement.addEventListener('touchend', e => this.handleEnd(e), false);
this.engine.renderer.domElement.addEventListener('touchmove', e => this.handleMove(e), false);
}
handleStart(e) {
e.preventDefault();
this.touchStart = {
touch: e.changedTouches[0],
time: this.engine.clock.getElapsedTime(),
}
console.log(this.touchStart);
}
handleEnd(e) {
e.preventDefault();
this.touchEnd = {
touch: e.changedTouches[0],
time: this.engine.clock.getElapsedTime(),
}
console.log(this.touchEnd);
this.paper.push(new Paper(this));
}
handleMove(e) {
e.preventDefault();
}
update() {
// update the scene
this.paper.forEach(paper => paper.update(this.engine));
}
}
class Paper {
constructor(parent) {
this.parent = parent;
this.mesh = this.parent.paperMesh.clone();
this.mesh.position.set(this.parent.engine.camera.position.x, this.parent.engine.camera.position.y, this.parent.engine.camera.position.z);
this.direction = this.parent.engine.camera.getWorldDirection().divide(new THREE.Vector3(4, 4, 4));
this.gravity = 0;
this.parent.engine.sceneRoot.add(this.mesh);
}
destroy(engine) {
this.dead = true;
engine.sceneRoot.remove(this.mesh);
}
collision(object1, object2) {
object1.geometry.computeBoundingBox(); //not needed if its already calculated
object2.geometry.computeBoundingBox();
object1.updateMatrixWorld();
object2.updateMatrixWorld();
var box1 = object1.geometry.boundingBox.clone();
box1.applyMatrix4(object1.matrixWorld);
var box2 = object2.geometry.boundingBox.clone();
box2.applyMatrix4(object2.matrixWorld);
return box1.intersectsBox(box2);
}
kill(engine) {
this.destroy(engine);
}
update(engine) {
if (this.dead) return;
this.mesh.position.add(this.direction);
this.mesh.position.z += this.gravity / 50;
this.gravity = Math.min(this.gravity + .1, 3.2);
if (this.collision(this.mesh, this.parent.cylinder)) {
console.log('collided !!');
this.kill(engine);
} else if (this.mesh.position.distanceTo(engine.camera.position) > 30) {
console.log('dying');
this.kill(engine);
}
}
}
![]() |
@beclamide |
![]() |
@beclamide |
![]() |
@johnmbower |