Skip to content

Commit

Permalink
Add Spatial UI example
Browse files Browse the repository at this point in the history
  • Loading branch information
dmarcos committed Jul 15, 2023
1 parent b74b974 commit 7ea40c1
Show file tree
Hide file tree
Showing 13 changed files with 1,051 additions and 0 deletions.
1 change: 1 addition & 0 deletions examples/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -143,6 +143,7 @@ <h2>Examples</h2>
<li><a href="showcase/ui/">User Interface</a></li>
<li><a href="showcase/link-traversal/">Link Traversal</a></li>
<li><a href="showcase/model-viewer/">Model Viewer</a></li>
<li><a href="showcase/spatial-ui/">Spatial UI</a></li>
<li><a href="showcase/tracked-controls/">Tracked Controls</a></li>
<li><a href="showcase/shopping/">Shopping</a></li>
<li><a href="showcase/spheres-and-fog/">Spheres and Fog</a></li>
Expand Down
34 changes: 34 additions & 0 deletions examples/showcase/spatial-ui/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
# Spatial UI widgets for A-Frame

Explore spatial design as described in Apple guidelines presented at WWDC 2023.

For quick prototyping and experimentation of Spatial Applications in your browser: desktop or any headset.

##

## TO DO

- Buttons drop shadows.
- Polish hover effect.
- Reflection / border of windows.
- Implement disolve effect when modal shows / hides.
- Replace MeshPhysicalMaterial with custom glass shader for window. It will allow for finer control of look (reflections, alpha blending to animate opacity...) [Meet SwiftUI for spatial computing
8:19](https://developer.apple.com/videos/play/wwdc2023/10109/)
- Vibrancy
- Lazy follow
- Display icons
- Scale and reposition windows.
- Embed models in the view. [WWDC 2023 Get started with building apps for spatial computing 23:00](https://developer.apple.com/videos/play/wwdc2023/10260/)
- Gestures (Zoom, Rotate): [WWDC 2023 Design For Spatial Design 13:01](https://link-url-here.org)
https://developer.apple.com/videos/play/wwdc2023/10073/)
- Direct input: (Zoom, Rotate): [WWDC 2023 Design For Spatial Design 17:00](https://link-url-here.org)
https://developer.apple.com/videos/play/wwdc2023/10073/)
- Tab Views and ornaments. [Meet SwiftUI for spatial computing
7:50](https://developer.apple.com/videos/play/wwdc2023/10109/)
- Text rendering / rasterization [Explore rendering for spatial computing
14:00](https://developer.apple.com/videos/play/wwdc2023/10095/)
- Passthrough tinting for videos [Enhance your spatial computing app with RealityKit 10:05](https://developer.apple.com/videos/play/wwdc2023/10081/]

dynamic scale
tab, toolbox, ornaments

Binary file added examples/showcase/spatial-ui/close-button.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
144 changes: 144 additions & 0 deletions examples/showcase/spatial-ui/index.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,144 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>Spatial UI</title>
<meta name="description" content="Spatial UI • A-Frame">
<script src="https://cdn.jsdelivr.net/gh/aframevr/aframe@309a1b73786ad79bfe64065f1decac741151524d/dist/aframe-master.min.js"></script>

<script src="ui-controller.js"></script>
<script src="spatial-utils.js"></script>
<script src="spatial-window.js"></script>
<script src="spatial-modal.js"></script>
<script src="spatial-modal-image.js"></script>
<script src="spatial-button.js"></script>
<script src="spatial-close-button.js"></script>
<script src="spatial-hero-image.js"></script>
<script src="tile.js"></script>
</head>
<body>
<a-scene cursor="rayOrigin: mouse;" raycaster="objects: .tile, .start-button, .close-button" ui-controller>
<a-assets>
<a-mixin
id="tile" tile="width: 0.400; height: 0.285"
animation__scale="property: scale; to: 1.02 1.02 1.02; dur: 100; startEvents: mouseenter"
animation__scale_reverse="property: scale; to: 1 1 1; dur: 100; startEvents: mouseleave">
</a-mixin>
<a-mixin
id="focus-animation"
animation__scale="property: object3D.position.z; to: -0.05; dur: 100; startEvents: unfocused"
animation__scale_reverse="property: object3D.position.z; to: 0; dur: 100; startEvents: focused">
</a-mixin>
<img id="leaving-room" src="https://cdn.aframe.io/examples/spatial-input/leaving-room.jpg" crossorigin="anonymous">
<img id="hero-image" src="https://cdn.aframe.io/examples/spatial-input/hero-image.jpg" crossorigin="anonymous">
</a-assets>
<a-sky src="#leaving-room" rotation="0 -90 0"></a-sky>

<a-entity position="0 1.7 -1">
<a-entity spatial-modal="width: 1.2; height: 0.72" visible="false">
<a-entity spatial-modal-image="width: 0.6; height: 0.72" position="-0.3 0 0.001"></a-entity>
<a-entity spatial-close-button="" position="0.55 0.31 0.001" class="close-button"></a-entity>
<a-text font="kelsonsans" class="movie-title" value="Hayao Miyazaki" align="left" width="0.75"position="0.03 0.2 0.001"></a-text>
<a-text font="kelsonsans" height="1" class="movie-synopsis" value="Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum." align="left" width="0.5" position="0.03 0.08 0.001"></a-text>
</a-entity>

<a-entity mixin="focus-animation" spatial-window="width: 1.775; height: 1">
<a-entity spatial-hero-image="src: #hero-image; width: 1.775; height: 1" position="0 0 0.001">
<a-text font="kelsonsans" value="Hayao Miyazaki" align="center" width="3" position="0 0.29 0.001"></a-text>
<a-entity spatial-button position="0 0.06 0.001" class="start-button"></a-entity>
</a-entity>

<a-entity id="image-grid" visible="false">
<!-- row -->
<a-entity
mixin="tile"
title="The Secret World of Arrietty (2010)"
synopsis="Arrietty, a tiny teenager, lives with her parents in the recesses of a suburban home, unbeknown to the homeowner and housekeeper. Like others of her kind, Arrietty remains hidden from her human hosts."
tile="src: https://cdn.aframe.io/examples/ui/karigurashi.jpg"
position="-0.6435 0.318 0.01" opacity="0.5"></a-entity>
<a-entity
mixin="tile"
title="Ponyo (2008)"
synopsis="During a forbidden excursion to see the surface world, a goldfish princess encounters a human boy named Sosuke, who gives her the name Ponyo. Ponyo longs to become human, and as her friendship with Sosuke grows, she becomes more humanlike."
tile="src: https://cdn.aframe.io/examples/ui/ponyo.jpg"
position="-0.2150 0.320 0.01"></a-entity>
<a-entity
mixin="tile"
title="The Wind Rises (2013)"
synopsis="A lifelong love of flight inspires Japanese aviation engineer Jiro Horikoshi (Hideaki Anno), whose storied career includes the creation of the A6M World War II fighter plane."
tile="src: https://cdn.aframe.io/examples/ui/wind-raises1.jpg"
position="0.2135 0.320 0.01"></a-entity>
<a-entity
mixin="tile"
title="The Wind Rises (2013)"
synopsis="A lifelong love of flight inspires Japanese aviation engineer Jiro Horikoshi (Hideaki Anno), whose storied career includes the creation of the A6M World War II fighter plane."
tile="src: https://cdn.aframe.io/examples/ui/wind-raises2.jpg"
position="0.6420 0.320 0.01"></a-entity>
<!-- row -->
<a-entity
mixin="tile"
title="The Wind Rises (2013)"
synopsis="A lifelong love of flight inspires Japanese aviation engineer Jiro Horikoshi (Hideaki Anno), whose storied career includes the creation of the A6M World War II fighter plane."
tile="src: https://cdn.aframe.io/examples/ui/kazetachinu.jpg"
position="-0.6435 0 0.01"></a-entity>
<a-entity
mixin="tile"
title="Spirited Away (2001)"
synopsis="Miyazaki, 10-year-old Chihiro (Rumi Hiiragi) and her parents (Takashi Naitô, Yasuko Sawaguchi) stumble upon a seemingly abandoned amusement park. After her mother and father are turned into giant pigs, Chihiro meets the mysterious Haku."
tile="src: https://cdn.aframe.io/examples/ui/spirited-away1.jpg"
position="-0.2150 0 0.01"></a-entity>
<a-entity
mixin="tile"
title="Spirited Away (2001)"
synopsis="Miyazaki, 10-year-old Chihiro (Rumi Hiiragi) and her parents (Takashi Naitô, Yasuko Sawaguchi) stumble upon a seemingly abandoned amusement park. After her mother and father are turned into giant pigs, Chihiro meets the mysterious Haku."
tile="src: https://cdn.aframe.io/examples/ui/spirited-away2.jpg"
position="0.2135 0 0.01"></a-entity>
<a-entity
mixin="tile"
title="Spirited Away (2001)"
synopsis="Miyazaki, 10-year-old Chihiro (Rumi Hiiragi) and her parents (Takashi Naitô, Yasuko Sawaguchi) stumble upon a seemingly abandoned amusement park. After her mother and father are turned into giant pigs, Chihiro meets the mysterious Haku."
tile="src: https://cdn.aframe.io/examples/ui/spirited-away3.jpg"
position="0.6420 0 0.01"></a-entity>

<!-- row -->
<a-entity
mixin="tile"
title="Tales from Earthsea (2006)"
synopsis="Something bizarre has come over the land. The kingdom is deteriorating. People are beginning to act strange... What's even more strange is that people are beginning to see dragons, which shouldn't enter the world of humans."
tile="src: https://cdn.aframe.io/examples/ui/earth-sea1.jpg"
position="-0.6435 -0.320 0.01"></a-entity>
<a-entity
mixin="tile"
title="Tales from Earthsea (2006)"
synopsis="Something bizarre has come over the land. The kingdom is deteriorating. People are beginning to act strange... What's even more strange is that people are beginning to see dragons, which shouldn't enter the world of humans."
tile="src: https://cdn.aframe.io/examples/ui/earth-sea2.jpg"
position="-0.2150 -0.320 0.01"></a-entity>
<a-entity
mixin="tile"
title="Tales from Earthsea (2006)"
synopsis="Something bizarre has come over the land. The kingdom is deteriorating. People are beginning to act strange... What's even more strange is that people are beginning to see dragons, which shouldn't enter the world of humans."
tile="src: https://cdn.aframe.io/examples/ui/earth-sea3.jpg"
position="0.2135 -0.320 0.01"></a-entity>
<a-entity
mixin="tile"
title="From Up on Poppy Hill (2011)"
synopsis="It's 1963 and Japan is in the midst of swift modernisation, leaving behind the Second World War's painful memories and focusing on a brighter future, symbolised by the coming year's Tokyo Olympics."
tile="src: https://cdn.aframe.io/examples/ui/poppy-hill1.jpg"
position="0.6420 -0.320 0.01"></a-entity>
</a-entity>
</a-entity>
</a-entity>

<a-entity position="0 1.6 0" look-controls camera>
<a-entity class="camera-cursor"
raycaster="objects: .tile, .start-button, .close-button"
cursor
geometry="primitive: circle; radius: 0.005"
material="color: #283644; shader: flat"
position="0 0 -0.75"></a-entity>
</a-entity>
<a-entity hand-tracking-controls="hand: left"></a-entity>
<a-entity hand-tracking-controls="hand: right"></a-entity>
</a-scene>
</body>
</html>
44 changes: 44 additions & 0 deletions examples/showcase/spatial-ui/spatial-button.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
/* global AFRAME, THREE, SPATIAL */

if (typeof AFRAME === 'undefined') {
throw new Error('Component attempted to register before AFRAME was available.');
}

/**
* Spatial Window component for A-Frame.
*/
AFRAME.registerComponent('spatial-button', {
schema: {
color: {type: 'color', default: '#60ff7e'},
width: {default: 0.35, min: 0},
height: {default: 0.08, min: 0},
focused: {default: true},
text: {default: 'start'}
},

init: function () {
var data = this.data;
var geometry = this.geometry = SPATIAL.utils.generatePlaneGeometryIndexed(data.width, data.height, 0.035, 22);
var material = this.material = new THREE.MeshPhysicalMaterial({
roughness: 0.6,
transmission: 1,
color: new THREE.Color(data.color)
});

var textEl = this.textEl = document.createElement('a-entity');
textEl.setAttribute('text', {
value: this.data.text,
width: 0.75,
align: 'center',
font: 'kelsonsans'
});
textEl.setAttribute('position', '0 0 0.005');

this.el.appendChild(textEl);
this.plane = new THREE.Mesh(geometry, material);
this.el.setObject3D('mesh', this.plane);

this.el.addEventListener('mouseenter', function () { material.roughness = 0.9; });
this.el.addEventListener('mouseleave', function () { material.roughness = 0.8; });
}
});
61 changes: 61 additions & 0 deletions examples/showcase/spatial-ui/spatial-close-button.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
/* global AFRAME, THREE */

if (typeof AFRAME === 'undefined') {
throw new Error('Component attempted to register before AFRAME was available.');
}

/**
* Spatial Window component for A-Frame.
*/
AFRAME.registerComponent('spatial-close-button', {
schema: {
color: {type: 'color', default: '#ffffff'},
width: {default: 1, min: 0},
height: {default: 1, min: 0},
focused: {default: true}
},

init: function () {
var data = this.data;
var geometry = this.geometry = new THREE.CircleGeometry(0.025, 32);

var material = this.material = new THREE.MeshPhysicalMaterial({
roughness: 0.8,
transmission: 1,
color: new THREE.Color(data.color)
});

var planeGeometry = this.planeGeometry = new THREE.PlaneGeometry(0.05, 0.05);
var planeMaterial = this.planeMaterial = new THREE.MeshBasicMaterial({
color: new THREE.Color(data.color),
transparent: true
});

var texture = new THREE.TextureLoader().load('./close-button.png', function () {
// material.needsUpdate = true;
});
planeMaterial.map = texture;
texture.colorSpace = THREE.SRGBColorSpace;

this.circle = new THREE.Mesh(geometry, material);
this.el.setObject3D('mesh', this.circle);

this.plane = new THREE.Mesh(planeGeometry, planeMaterial);
this.plane.position.set(0, 0, 0.001);
this.el.setObject3D('plane', this.plane);

var targetPlaneEl = this.targetPlaneEl = document.createElement('a-entity');
targetPlaneEl.setAttribute('geometry', {primitive: 'plane', width: 0.1, height: 0.1});
targetPlaneEl.setAttribute('position', '0 0 0.002');
targetPlaneEl.setAttribute('visible', false);
targetPlaneEl.classList.add('close-button');

this.el.appendChild(targetPlaneEl);

targetPlaneEl.addEventListener('mouseenter', function () { material.roughness = 0.9; });
targetPlaneEl.addEventListener('mouseleave', function () { material.roughness = 0.8; });
},

update: function () {
}
});
30 changes: 30 additions & 0 deletions examples/showcase/spatial-ui/spatial-hero-image.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
/* global AFRAME, THREE, SPATIAL */

if (typeof AFRAME === 'undefined') {
throw new Error('Component attempted to register before AFRAME was available.');
}

/**
* Spatial Window component for A-Frame.
*/
AFRAME.registerComponent('spatial-hero-image', {
schema: {
color: {type: 'color', default: '#fff'},
width: {default: 1, min: 0},
height: {default: 1, min: 0},
focused: {default: true},
src: {type: 'map'}
},

init: function () {
var data = this.data;
var geometry = this.geometry = SPATIAL.utils.generatePlaneGeometryIndexed(data.width, data.height, 0.05, 22);
var material = this.material = new THREE.MeshBasicMaterial({color: new THREE.Color(data.color)});
this.el.sceneEl.systems.material.loadTexture(data.src, {src: data.src}, function textureLoaded (texture) {
material.map = texture;
texture.colorSpace = THREE.SRGBColorSpace;
});
this.plane = new THREE.Mesh(geometry, material);
this.el.setObject3D('mesh', this.plane);
}
});
40 changes: 40 additions & 0 deletions examples/showcase/spatial-ui/spatial-modal-image.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
/* global AFRAME, THREE, SPATIAL */

if (typeof AFRAME === 'undefined') {
throw new Error('Component attempted to register before AFRAME was available.');
}

/**
* Spatial Window component for A-Frame.
*/
AFRAME.registerComponent('spatial-modal-image', {
schema: {
color: {type: 'color', default: '#fff'},
width: {default: 1, min: 0},
height: {default: 1, min: 0},
src: {type: 'map', default: 'https://cdn.aframe.io/examples/ui/kazetachinu.jpg'}
},

init: function () {
var data = this.data;
var geometry = this.geometry = SPATIAL.utils.generatePlaneGeometryTwoCorners(data.width, data.height, 0.05, 22);
var material = this.material = new THREE.MeshBasicMaterial({ color: new THREE.Color(data.color) });

this.plane = new THREE.Mesh(geometry, material);
this.el.setObject3D('mesh', this.plane);
},

update: function (oldData) {
var data = this.data;
var material = this.material;

if (data.src !== oldData.src) {
var texture = new THREE.TextureLoader().load(data.src, function () {
// material.needsUpdate = true;
});
material.map = texture;
material.needsUpdate = true;
texture.colorSpace = THREE.SRGBColorSpace;
}
}
});
31 changes: 31 additions & 0 deletions examples/showcase/spatial-ui/spatial-modal.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
/* global AFRAME, THREE, SPATIAL */

if (typeof AFRAME === 'undefined') {
throw new Error('Component attempted to register before AFRAME was available.');
}

/**
* Spatial Window component for A-Frame.
*/
AFRAME.registerComponent('spatial-modal', {
schema: {
color: {type: 'color', default: '#fff'},
width: {default: 1, min: 0},
height: {default: 1, min: 0},
src: {type: 'map'}
},

init: function () {
var data = this.data;
var geometry = this.geometry = SPATIAL.utils.generatePlaneGeometryIndexed(data.width, data.height, 0.05, 22);
var material = this.material = new THREE.MeshPhysicalMaterial({
roughness: 0.6,
transmission: 1,
transparent: true,
color: new THREE.Color(data.color)
});

this.plane = new THREE.Mesh(geometry, material);
this.el.setObject3D('mesh', this.plane);
}
});
Loading

0 comments on commit 7ea40c1

Please sign in to comment.