Skip to content

Commit 8093829

Browse files
authored
add self-hosted vps examples
1 parent 38a6eb7 commit 8093829

12 files changed

+517
-0
lines changed

examples/aframe/vps/README.md

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
Use the VPS Blank Project to bootstrap VPS development. Building location-based AR can be done in three simple steps:
2+
3+
1. Add a test scan or a public VPS Location to the project via the Geospatial Browser (https://www.8thwall.com/docs/web/#managing-wayspots)
4+
2. Copy the `name` of the project VPS Location and replace `my-location` with the name of the project VPS Location on line 48 of `index.html` (the `named-location` `name` parameter)
5+
3. Download the selected mesh as a glb from the Geospatial Browser, upload to the project and set the `src` of the `vps-mesh` asset (line 39 of `index.html`) to a relative path to the glb
6+
7+
If you use the A-Frame inspector to build your bespoke experience, you can speed up development by adding the `desktop-development` component to the `<a-scene>`. The `desktop-development` component automatically removes all 8th Wall components predefined in the scene, removes the occluder material, and optionally opens the A-Frame inspector. You can even test at the real world location on your mobile device without changing a single line of code.
8+
9+
To test localization, you can render the scanned mesh over the real-world location. Simply remove (or add a z to the beginning of) the `hider-material` component on line 50 of `index.html`.
10+
11+
To add content or animations placed relative to the mesh in 3D modeling software, simply upload the model and update the `vps-anim` `src` path on line 41 of `index.html`
Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
/* globals XR8 AFRAME */
2+
AFRAME.registerComponent('desktop-development', {
3+
schema: {
4+
inspector: {type: 'boolean', default: true},
5+
},
6+
init() {
7+
const onAttach = ({sessionAttributes}) => {
8+
const s = sessionAttributes
9+
const isDesktop = !s.cameraLinkedToViewer && !s.controlsCamera && !s.fillsCameraTexture &&
10+
!s.supportsHtmlEmbedded && s.supportsHtmlOverlay && !s.usesMediaDevices && !s.usesWebXr
11+
12+
const namedWayspot = document.querySelector('[named-wayspot]')
13+
const occluder = document.querySelector('[hider-material]')
14+
15+
const scene = this.el
16+
const removeXRandExtras = () => {
17+
scene.removeAttribute('landing-page')
18+
scene.removeAttribute('xrextras-loading')
19+
scene.removeAttribute('xrextras-gesture-detector')
20+
scene.removeAttribute('xrextras-runtime-error')
21+
scene.removeAttribute('vps-coaching-overlay')
22+
23+
namedWayspot.removeAttribute('named-wayspot')
24+
occluder.removeAttribute('hider-material')
25+
26+
scene.removeAttribute('xrweb')
27+
28+
if (this.data.inspector) {
29+
scene.components.inspector.openInspector()
30+
scene.renderer.autoClearColor = true
31+
}
32+
}
33+
34+
if (isDesktop) {
35+
removeXRandExtras()
36+
}
37+
}
38+
const onxrloaded = () => {
39+
XR8.addCameraPipelineModules([{'name': 'desktopDevelopment', onAttach}])
40+
}
41+
if (window.XR8) onxrloaded()
42+
else window.addEventListener('xrloaded', onxrloaded)
43+
},
44+
})

examples/aframe/vps/hider-material.js

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
/* globals AFRAME THREE */
2+
AFRAME.registerComponent('hider-material', {
3+
init() {
4+
const hiderMaterial = new THREE.MeshStandardMaterial()
5+
hiderMaterial.colorWrite = false
6+
7+
const applyHiderMaterial = (mesh) => {
8+
if (!mesh) {
9+
return
10+
}
11+
if (mesh.material) {
12+
mesh.material = hiderMaterial
13+
}
14+
mesh.traverse((node) => {
15+
if (node.isMesh) {
16+
this.mat = node.material
17+
node.material = hiderMaterial
18+
}
19+
})
20+
}
21+
22+
applyHiderMaterial(this.el.getObject3D('mesh'))
23+
this.el.addEventListener(
24+
'model-loaded', () => applyHiderMaterial(this.el.getObject3D('mesh'))
25+
)
26+
},
27+
remove() {
28+
const hiderMaterial = new THREE.MeshStandardMaterial()
29+
hiderMaterial.colorWrite = true
30+
31+
const applyHiderMaterial = (mesh) => {
32+
if (!mesh) {
33+
return
34+
}
35+
if (mesh.material) {
36+
mesh.material = hiderMaterial
37+
}
38+
mesh.traverse((node) => {
39+
if (node.isMesh) {
40+
node.material = this.mat
41+
}
42+
})
43+
}
44+
45+
applyHiderMaterial(this.el.getObject3D('mesh'))
46+
this.el.addEventListener(
47+
'model-loaded', () => applyHiderMaterial(this.el.getObject3D('mesh'))
48+
)
49+
},
50+
})

examples/aframe/vps/index.html

Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
<!DOCTYPE html>
2+
<html>
3+
4+
<head>
5+
<meta charset="utf-8">
6+
<title>8th Wall Web: VPS</title>
7+
8+
<!-- We've included a slightly modified version of A-Frame, which fixes some polish concerns -->
9+
<script src="//cdn.8thwall.com/web/aframe/8frame-1.5.0.min.js"></script>
10+
11+
<!-- XR Extras - provides utilities like load screen, almost there, and error handling.
12+
See github.com/8thwall/web/tree/master/xrextras -->
13+
<script src="//cdn.8thwall.com/web/xrextras/xrextras.js"></script>
14+
15+
<!-- Landing Pages - see https://www.8thwall.com/docs/web/#landing-pages -->
16+
<script src='//cdn.8thwall.com/web/landing-page/landing-page.js'></script>
17+
18+
<!-- VPS Coaching Overlay -->
19+
<script src='https://cdn.8thwall.com/web/coaching-overlay/coaching-overlay.js'></script>
20+
21+
<!-- 8thWall Web - Replace the app key here with your own app key -->
22+
<script async src="//apps.8thwall.com/xrweb?appKey=XXXXXXX"></script>
23+
24+
<script src="./index.js"></script>
25+
<script src="./named-location.js"></script>
26+
<script src="./vps-animation.js"></script>
27+
<script src="./shadow-shader.js"></script>
28+
<script src="./hider-material.js"></script>
29+
<script src="./desktop-development.js"></script>
30+
31+
</head>
32+
33+
<body>
34+
<a-scene landing-page vps-coaching-overlay xrextras-loading xrextras-runtime-error xrextras-gesture-detector
35+
renderer="colorManagement: true" xrweb="enableVps: true; allowedDevices: any">
36+
37+
<a-assets>
38+
<!-- download the mesh from the geospatial browser and add it here -->
39+
<a-asset-item id="vps-mesh" src=""></a-asset-item>
40+
<!-- if you have a prebaked animation that interacts with the mesh, add it here -->
41+
<a-asset-item id="vps-anim" src=""></a-asset-item>
42+
</a-assets>
43+
44+
<a-camera position="0 2 2" raycaster="objects: .cantap" cursor="fuse: false; rayOrigin: mouse;"></a-camera>
45+
46+
<!-- add a location to the project from the geospatial browser
47+
and replace the name with the 'name' of the location -->
48+
<a-entity named-location="name: location-name">
49+
<!-- apply hider material to the mesh to realistically occlude content -->
50+
<a-entity gltf-model="#vps-mesh" hider-material></a-entity>
51+
<!-- apply shadow shader to the mesh to realistically receive shadows -->
52+
<a-entity gltf-model="#vps-mesh" shadow-shader shadow="cast: false"></a-entity>
53+
<!-- add content here; either prebaked or realtime animations -->
54+
<!-- play-vps-animation waits for successful localization to begin the animation -->
55+
<a-entity gltf-model="#vps-anim" play-vps-animation shadow="receive: false"></a-entity>
56+
</a-entity>
57+
58+
<!-- lights -->
59+
<a-entity light="
60+
type: directional;
61+
intensity: 0.8;
62+
castShadow: true;
63+
shadowMapHeight:2048;
64+
shadowMapWidth:2048;
65+
shadowCameraTop: 10;
66+
shadowCameraBottom: -10;
67+
shadowCameraRight: 10;
68+
shadowCameraLeft: -10;" position="0 10 -6" shadow>
69+
</a-entity>
70+
<a-light type="ambient" intensity="0.5"></a-light>
71+
72+
</a-scene>
73+
</body>
74+
75+
</html>

examples/aframe/vps/index.js

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
/* eslint-disable no-alert */
2+
// Check Location Permissions at beginning of session
3+
const errorCallback = (error) => {
4+
if (error.code === error.PERMISSION_DENIED) {
5+
alert('LOCATION PERMISSIONS DENIED. PLEASE ALLOW AND TRY AGAIN.')
6+
}
7+
}
8+
navigator.geolocation.getCurrentPosition(() => {}, errorCallback)

examples/aframe/vps/named-location.js

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
/* globals AFRAME */
2+
// Updates a single a-entity to track a VPS Location with the given name.
3+
AFRAME.registerComponent('named-location', {
4+
schema: {
5+
name: {type: 'string'},
6+
},
7+
init() {
8+
const {object3D} = this.el
9+
const {name} = this.data
10+
this.el.sceneEl.addEventListener('realityready', () => {
11+
object3D.visible = false
12+
})
13+
14+
const foundLocation = ({detail}) => {
15+
if (name !== detail.name) {
16+
return
17+
}
18+
object3D.position.copy(detail.position)
19+
object3D.quaternion.copy(detail.rotation)
20+
object3D.visible = true
21+
}
22+
23+
const lostLocation = ({detail}) => {
24+
if (name !== detail.name) {
25+
return
26+
}
27+
object3D.visible = false
28+
}
29+
30+
this.el.sceneEl.addEventListener('xrprojectwayspotfound', foundLocation)
31+
this.el.sceneEl.addEventListener('xrprojectwayspotlost', lostLocation)
32+
},
33+
})

examples/aframe/vps/shadow-shader.js

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
/* globals AFRAME THREE */
2+
AFRAME.registerComponent('shadow-shader', {
3+
schema: {
4+
'opacity': {default: 0.4},
5+
},
6+
update() {
7+
if (typeof AFRAME === 'undefined') {
8+
throw new Error('Component attempted to register before AFRAME was available.')
9+
}
10+
11+
const shadowMaterial = new THREE.ShadowMaterial()
12+
shadowMaterial.opacity = this.data.opacity
13+
shadowMaterial.transparent = true
14+
shadowMaterial.polygonOffset = true
15+
shadowMaterial.polygonOffsetFactor = -4
16+
17+
const applyShadowMaterial = (mesh) => {
18+
if (!mesh) {
19+
return
20+
}
21+
if (mesh.material) {
22+
mesh.material = shadowMaterial
23+
mesh.material.needsUpdate = true
24+
}
25+
mesh.traverse((node) => {
26+
if (node.isMesh) {
27+
node.material = shadowMaterial
28+
}
29+
})
30+
}
31+
32+
if (this.el.getObject3D('mesh')) {
33+
applyShadowMaterial(this.el.getObject3D('mesh'))
34+
} else {
35+
this.el.addEventListener('model-loaded', () => {
36+
applyShadowMaterial(this.el.getObject3D('mesh'))
37+
this.el.object3D.traverse((obj) => {
38+
obj.frustumCulled = false
39+
})
40+
})
41+
}
42+
},
43+
})

examples/aframe/vps/vps-animation.js

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
/* globals AFRAME */
2+
AFRAME.registerComponent('play-vps-animation', {
3+
init() {
4+
const overlayHidden = () => {
5+
this.el.setAttribute('animation-mixer', 'clip: *')
6+
}
7+
8+
const overlayVisible = () => {
9+
this.el.removeAttribute('animation-mixer')
10+
}
11+
12+
window.XR8.addCameraPipelineModule({
13+
name: 'vps-coaching-overlay-listen',
14+
listeners: [
15+
{event: 'vps-coaching-overlay.hide', process: overlayHidden},
16+
{event: 'vps-coaching-overlay.show', process: overlayVisible},
17+
],
18+
})
19+
},
20+
})

examples/threejs/vps/README.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
three.js: VPS Template
2+
3+
Quick Start:
4+
1. [Create a self-hosted project in the 8th Wall console and obtain an App Key](https://www.8thwall.com/docs/guides/projects/project-settings/#app-key)
5+
2. Replace "XXXXXXXX" in the 8th Wall Web script tag with your app key (in index.html)
6+
3. Add a Public Location or Test Scan to the project from the 8th Wall console (**make sure you add it to the self-hosted project you obtained the app key from**)

examples/threejs/vps/index.css

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
body {
2+
overflow: hidden;
3+
width: 100%;
4+
height: 100%;
5+
margin: -1px 0px 0px 0px !important;
6+
padding: 0;
7+
}

examples/threejs/vps/index.html

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
<!doctype html>
2+
<html>
3+
4+
<head>
5+
<meta name="viewport" content="width=device-width, initial-scale=1.0, user-scalable=no">
6+
<title>three.js: VPS Template</title>
7+
<link rel="stylesheet" type="text/css" href="index.css">
8+
9+
<!-- THREE.js must be supplied -->
10+
<script src="//cdn.jsdelivr.net/npm/[email protected]/build/three.min.js"></script>
11+
12+
<!-- Landing pages -->
13+
<script src='//cdn.8thwall.com/web/landing-page/landing-page.js'></script>
14+
15+
<!-- VPS Coaching Overlay -->
16+
<script src='//cdn.8thwall.com/web/coaching-overlay/coaching-overlay.js'></script>
17+
18+
<!-- Required to load glTF (.gltf or .glb) models -->
19+
<script src="//cdn.jsdelivr.net/gh/mrdoob/three.js@r146/examples/js/loaders/GLTFLoader.js"></script>
20+
21+
<!-- XR Extras - provides utilities like load screen, almost there, and error handling.
22+
See github.com/8thwall/web/tree/master/xrextras -->
23+
<script src="//cdn.8thwall.com/web/xrextras/xrextras.js"></script>
24+
25+
<!-- 8thWall Web - Replace the app key here with your own app key -->
26+
<script async src="//apps.8thwall.com/xrweb?appKey=XXXXXXXX"></script>
27+
28+
<!-- client code -->
29+
<script src="index.js"></script>
30+
</head>
31+
32+
<body>
33+
<canvas id="camerafeed"></canvas>
34+
</body>
35+
36+
</html>

0 commit comments

Comments
 (0)