Skip to content
Permalink
master
Switch branches/tags
Go to file
 
 
Cannot retrieve contributors at this time
<html>
<!-- Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/
-->
<head>
<title>Spinners example</title>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, user-scalable=no, minimum-scale=1.0, maximum-scale=1.0">
<link rel="stylesheet" href="../common.css"/>
<script src="../libs/three-r86/three.min.js"></script>
<script src="../libs/three-r86/loaders/BinaryLoader.js"></script>
<script src="../libs/three-r86/postprocessing/EffectComposer.js"></script>
<script src="../libs/three-r86/shaders/ConvolutionShader.js"></script>
<script src="../libs/three-r86/postprocessing/ShaderPass.js"></script>
<script src="../libs/three-r86/postprocessing/MaskPass.js"></script>
<script src="../libs/three-r86/shaders/CopyShader.js"></script>
<script src="../libs/three-r86/shaders/FilmShader.js"></script>
<script src="../libs/three-r86/postprocessing/RenderPass.js"></script>
<script src="../libs/three-r86/postprocessing/BloomPass.js"></script>
<script src="../libs/three-r86/postprocessing/FilmPass.js"></script>
</head>
<body>
<div id="description">
<h2>Peoples</h2>
<p>Place animated people on surfaces by tapping.</p>
</div>
<button type=button id=go-button>Go</button>
<script type=module>
/*
This is based on the THREE.js example from https://threejs.org/examples/webgl_points_dynamic.html ported to WebXR
*/
// some dependencies and utilities
import * as mat4 from '../libs/gl-matrix/mat4.js';
import * as vec3 from '../libs/gl-matrix/vec3.js';
import XREngine from '../XREngine.js';
let session = null;
let localReferenceSpace = null;
let viewerReferenceSpace = null;
let engine = null;
let canvas = null;
let hitTestSource = null;
let inputSource = null;
let hitTestSourceFrameNum = 0;
let isSelecting = false;
// temporary working variables
const workingMatrix = mat4.create();
const workingVec3 = vec3.create();
const clock = new THREE.Clock();
const meshes = [];
const clonemeshes = [];
const geometries = [];
let femaleGeometry = null;
let maleGeometry = null;
let composer = null;
const colors = [
0xff4422,
0xff9955,
0xff77dd,
0xff7744,
0xff5522,
0xff9922,
0xff99ff
];
const goButton = document.getElementById('go-button');
const initXR = () => {
if (navigator.xr) {
navigator.xr.isSessionSupported('immersive-ar').then(supported => {
if (supported) {
goButton.disabled = false;
goButton.addEventListener('click', onButtonClick);
} else {
goButton.initText = 'No WebXR AR support';
}
});
} else {
goButton.initText = 'No WebXR support';
}
};
const onButtonClick = event => {
if (!session) {
navigator.xr.requestSession('immersive-ar', {requiredFeatures: ['hit-test']})
.then(xrSession => {
initSession(xrSession);
goButton.innerText = 'End';
}).catch(err => {
console.error('Session setup error', err);
});
} else {
session.end();
}
};
const initSession = async xrSession => {
session = xrSession;
session.addEventListener('end', onSessionEnd);
session.addEventListener('select', onSelect);
session.addEventListener('inputsourceschange', onInputSourcesChange);
localReferenceSpace = await session.requestReferenceSpace('local');
viewerReferenceSpace = await session.requestReferenceSpace('viewer');
// Create the context where we will render our 3D scene
canvas = document.createElement('canvas');
const context = canvas.getContext('webgl', {
xrCompatible: true
});
if (!context) throw new Error('Could not create a webgl context');
// Set up the base layer
session.updateRenderState({baseLayer: new XRWebGLLayer(session, context)});
// Create a simple test scene and renderer
// The engine's scene is in the eye-level coordinate system
engine = new XREngine(canvas, context);
// get the location of the device, and use it to create an
// anchor with the identity orientation
session.requestAnimationFrame(async (t, frame) => {
mat4.copy(workingMatrix, frame.getPose(localReferenceSpace, viewerReferenceSpace).transform.matrix);
mat4.getTranslation(workingVec3, workingMatrix);
mat4.fromTranslation(workingMatrix, workingVec3);
const anchor = await frame.addAnchor(workingMatrix, localReferenceSpace);
engine.addAnchoredNode(anchor, engine.root);
// Kick off rendering
session.requestAnimationFrame(handleAnimationFrame);
});
// initialize scene
engine.addAmbientLight();
engine.addDirectionalLight();
// Add a box and axis at the origin of the eye-level coordinate system
// for debugging by uncommenting these lines
// engine.addBox([0, 0, 0], [0.025, 0.025, 0.025], 0x44ff44)
// engine.addAxesHelper([0,0,0], [0.2,0.2,0.2])
const loader = new THREE.BinaryLoader();
loader.load('./female02/Female02_bin.js', geometry => {
femaleGeometry = geometry;
geometries.push(femaleGeometry);
});
loader.load('./male02/Male02_bin.js', geometry => {
maleGeometry = geometry;
geometries.push(maleGeometry);
});
composer = new THREE.EffectComposer(engine.renderer);
composer.addPass(new THREE.RenderPass(engine.scene, engine.camera));
composer.addPass(new THREE.BloomPass(0.75));
const filmPass = new THREE.FilmPass(0.5, 0.5, 1448, false);
filmPass.renderToScreen = true;
composer.addPass(filmPass);
};
const onSessionEnd = event => {
clearHitTestSource();
session = null;
inputSource = null;
viewerReferenceSpace = null;
localReferenceSpace = null;
goButton.innerText = 'Go';
};
const onInputSourcesChange = event => {
if (inputSource && event.removed.includes(inputSource)) {
inputSource = null;
}
if (!inputSource && event.added.length > 0) {
inputSource = event.added[0];
}
};
const onSelect = event => {
clearHitTestSource();
isSelecting = true;
};
const clearHitTestSource = () => {
if (hitTestSource) {
hitTestSource.cancel();
}
hitTestSource = null;
hitTestSourceFrameNum = 0;
};
//////////////////////////////
// create and update the "peoples"
const createSceneGraphNode = () => {
const group = new THREE.Group();
group.add(createMesh(
geometries[Math.floor(geometries.length * Math.random())],
0.006,
0,0,0,
0.005,
colors[Math.floor(colors.length * Math.random())],
true
));
return group;
};
const updateScene = frame => {
let delta = 10 * clock.getDelta();
delta = delta < 2 ? delta : 2;
let data = null;
let vertices = null;
let vertices_tmp = null;
let vl = null;
let d = null;
let vt = null;
let mesh = null;
let p = null;
for (let j = 0, jl = meshes.length; j < jl; j ++) {
data = meshes[j];
mesh = data.mesh;
vertices = data.vertices;
vertices_tmp = data.vertices_tmp;
vl = data.vl;
if (!data.dynamic) continue;
if (data.start > 0) {
data.start -= 1;
} else {
if (!data.started) {
data.direction = -1;
data.started = true;
}
}
for (let i = 0; i < vl; i ++) {
p = vertices[i];
vt = vertices_tmp[i];
// falling down
if (data.direction < 0) {
if (p.y > 0) {
p.x += 1.5 * (0.50 - Math.random()) * data.speed * delta;
p.y += 3.0 * (0.25 - Math.random()) * data.speed * delta;
p.z += 1.5 * (0.50 - Math.random()) * data.speed * delta;
} else {
if (!vt[3]) {
vt[3] = 1;
data.down += 1;
}
}
}
// rising up
if (data.direction > 0) {
d = Math.abs(p.x - vt[0]) + Math.abs(p.y - vt[1]) + Math.abs(p.z - vt[2]);
if (d > 1) {
p.x += - (p.x - vt[0]) / d * data.speed * delta * (0.85 - Math.random());
p.y += - (p.y - vt[1]) / d * data.speed * delta * (1 + Math.random());
p.z += - (p.z - vt[2]) / d * data.speed * delta * (0.85 - Math.random());
} else {
if (!vt[4]) {
vt[4] = 1;
data.up += 1;
}
}
}
}
// all down
if (data.down === vl) {
if (data.delay === 0) {
data.direction = 1;
data.speed = 10;
data.down = 0;
data.delay = 320;
for (let i = 0; i < vl; i ++) {
vertices_tmp[i][3] = 0;
}
} else {
data.delay -= 1;
}
}
// all up
if (data.up === vl) {
if (data.delay === 0) {
data.direction = -1;
data.speed = 35;
data.up = 0;
data.delay = 120;
for (let i = 0; i < vl; i ++) {
vertices_tmp[i][4] = 0;
}
} else {
data.delay -= 1;
}
}
mesh.geometry.verticesNeedUpdate = true;
}
};
const createMesh = (originalGeometry, scale, x, y, z, pointSize, color, dynamic) => {
let i, c, mesh, p;
let vertices = originalGeometry.vertices;
let vl = vertices.length;
let geometry = new THREE.Geometry();
let vertices_tmp = [];
for (i = 0; i < vl; i ++) {
p = vertices[i];
geometry.vertices[i] = p.clone();
vertices_tmp[i] = [p.x, p.y, p.z, 0, 0];
}
if (dynamic) {
c = color;
mesh = new THREE.Points(geometry, new THREE.PointsMaterial({size: pointSize, color: c}));
clonemeshes.push({mesh: mesh, speed: 0.5 + Math.random()});
} else {
mesh = new THREE.Points(geometry, new THREE.PointsMaterial({size: pointSize, color: color}));
}
mesh.scale.x = mesh.scale.y = mesh.scale.z = scale;
mesh.position.x = x;
mesh.position.y = y;
mesh.position.z = z;
mesh.quaternion.setFromEuler(new THREE.Euler(0, (Math.PI * 2) * Math.random(), 0));
meshes.push({
mesh: mesh,
vertices: geometry.vertices,
vertices_tmp: vertices_tmp,
vl: vl,
down: 0,
up: 0,
direction: 0,
speed: 35,
delay: Math.floor(10 * Math.random()),
started: false,
start: Math.floor(100 * Math.random()),
dynamic: dynamic
});
mesh.name = 'prettyperson: ' + Math.random();
return mesh;
};
// Create offset ray for hit test from the relative transform
// between viewerPose and inputPose. There may be a room to optimize.
const createOffsetRay = (viewerPose, inputPose) => {
const offsetMatrix = mat4.multiply(mat4.create(), viewerPose.transform.matrix, inputPose.transform.matrix);
const direction = vec3.fromValues(0.0, 0.0, -0.2);
vec3.transformMat4(direction, direction, offsetMatrix);
vec3.normalize(direction, direction);
const offsetDirection = {
x: direction[0],
y: direction[1],
z: direction[2],
w: 0.0
};
const offsetOrigin = {x: 0, y: 0, z: 0, w: 1.0};
return new XRRay(offsetOrigin, offsetDirection);
};
////////////////////////////////
// custom render loop to use the compositor instead of the vanilla renderer
const handleAnimationFrame = (t, frame) => {
if (!session || session.ended) return;
session.requestAnimationFrame(handleAnimationFrame);
const viewerPose = frame.getViewerPose(localReferenceSpace);
if (!viewerPose) {
console.log('No viewer pose');
return;
}
// Create HitTest Source. Calculating offset ray from the relative transform
// between viewerPose and inputPose so we need to do in animation frame.
if (isSelecting && inputSource) {
const inputPose = frame.getPose(inputSource.targetRaySpace, localReferenceSpace);
const offsetRay = createOffsetRay(viewerPose, inputPose);
session.requestHitTestSource({space: viewerReferenceSpace, offsetRay: offsetRay}).then(xrHitTestSource => {
hitTestSource = xrHitTestSource;
hitTestSourceFrameNum = 0;
});
isSelecting = false;
}
if (hitTestSource) {
const results = frame.getHitTestResults(hitTestSource);
if (results.length > 0) {
const result = results[0];
const pose = result.getPose(localReferenceSpace);
if (pose) {
frame.addAnchor(pose.transform.matrix, localReferenceSpace).then(anchor => {
engine.addAnchoredNode(anchor, createSceneGraphNode());
}).catch(err => {
console.error('Error adding anchor', err);
});
}
clearHitTestSource();
} else {
hitTestSourceFrameNum++;
// Dispose hit test source if we don't get any hit test result
// in arbitary frame nums
if (hitTestSourceFrameNum > 2) {
clearHitTestSource();
}
}
}
updateScene(frame);
engine.startFrame();
for (const view of viewerPose.views) {
engine.camera.matrix.fromArray(view.transform.matrix);
engine.camera.updateMatrixWorld();
engine.camera.projectionMatrix.fromArray(view.projectionMatrix);
engine.renderer.setSize(canvas.offsetWidth, canvas.offsetHeight, false)
const viewport = session.renderState.baseLayer.getViewport(view);
//engine.renderer.setViewport(viewport.x, viewport.y, viewport.width, viewport.height);
engine.renderer.setViewport(viewport.x / window.devicePixelRatio, viewport.y / window.devicePixelRatio, viewport.width / window.devicePixelRatio, viewport.height / window.devicePixelRatio)
composer.render(0.01);
}
engine.endFrame();
};
initXR();
</script>
</body>
</html>