Space Rocks—WebXR tech deep dive

Atari’s Asteroids

Three.js

Object.assign( M.three, { renderer, camera, scene, world, render })

WebXR hand controllers for Three.js

Attaching visuals to the controllers

/*       LEFT                              RIGHT
lives score
♥ ♥ ♥ 12345
╭───────╮ cannon ╭───────╮
│ ▪ │ cannon pointlight │ ▪ │
├───────┤ ├───────┤
│ │ │ │
│ │ hull │ │
│ │ │ │
│ │ │ │
├───────┤ engine ├───────┤
│ │ │ │
╲_____╱ ╲_____╱
↓ engine exhaust ↓
*/
const hull = new THREE.Mesh(

new THREE.CylinderGeometry(
player.arms.radii,
player.arms.radii,
0.2,
7
),
new THREE.MeshPhongMaterial({
color: 0x999999,
specular: 0xCCCCCC,
shininess: 70
})
addEventListener( 'vr controller connected', function( event ){    const controller = event.detail
controller.standingMatrix =
M.three.renderer.vr.getStandingMatrix()
controller.head = M.three.camera
M.three.scene.add( controller )
let side = controller.getHandedness()
if( side === 'left' || side === 'right' ) attachArm( side )
controller.addEventListener( 'hand changed', function( event ){
side = event.hand
attachArm( side )
})
const attachArm = function( side ){    controller.add( player.arms[ side ])
}

Handling buttons

myUpdateLoop(){    if( gamepad.buttons[ 1 ].pressed ) fire()
}
myUpdateLoop(){    if( controller.getButton( 1 ).isPressed ) fire()
}
myUpdateLoop(){    if( controller.getButton( 'trigger' ).isPressed ) fire()
}
controller.addEventListener( 'trigger press began', fire )
controller.addEventListener( 'primary press began', fire )

VRController in Space Rocks

controller.addEventListener( 'grip touch began', function(){    engine.rotationVelocity = 0.15
})
controller.updateCallback = function(){    if( controller.getButton( 'grip' ).isTouched ) 
engineThrust()
})

Multi-channel haptic feedback

if( gamepad.hapticActuators && 
gamepad.hapticActuators[ 0 ]){
gamepad.hapticActuators[ 0 ].pulse( intensity, duration )
}

Set and Wait

controller.setVibe( 'engine rumble' ).set( 0.2 )
controller.setVibe( 'engine rumble' ).set( 0 )
controller.setVibe( 'engine rumble' )
.set( 0.8 )
.wait( 1500 )
.set( 0.2 )
controller.setVibe( 'engine rumble' ).wait( 250 ).set( 0 )

Multiple haptic channels

controller.setVibe( 'cannon recoil' )
.set( 0.8 )
.wait( 20 )
.set( 0.0 )
controller.setVibe( 'cannon rotation' )
.set( 0.2 )
controller.setVibe( 'cannon rotation' )
.wait( 500 ).set( 0.10 )
.wait( 500 ).set( 0.05 )
.wait( 500 ).set( 0.00 )

Haptic channels in Space Rocks

Tasks—and task lists

const
a = function(){ console.log( 'Apple' )},
b = function(){ console.log( 'Banana' )},
c = function(){ console.log( 'Carrot' )},
tasks = new TaskList()
tasks
.add( a )
.add( b ).before( a )
.add( c )
.run()
console.log( tasks.find( c ))
console.log( tasks.find( 2 ))
if( this.M === undefined ) this.M = {}
M.tasks = {
setups: new TaskList(),
setup: function(){
M.tasks.setups.run().clear()
M.tasks.update()
},
updates: new TaskList(),
update: function( t ){
M.tasks.updates.run( t )
}
}
document.addEventListener( 'DOMContentLoaded', M.tasks.setup )
let timePrevious
const render = function( timeNow ){
if( timePrevious === undefined ) timePrevious = timeNow
const timeDelta = ( timeNow - timePrevious ) / 1000
timePrevious = timeNow
M.tasks.update( timeDelta )
renderer.render( scene, camera )
}
renderer.animate( render )

Modes

new Mode({    name:    'Apple',
setup: function(){ console.log( 'Hey', Mode.current )},
update: function(){ Mode.switchTo( 'Banana' )}
teardown: function(){ console.log( 'Bye', Mode.current )}
})
new Mode({
name: 'Banana',
setup: function(){ console.log( 'Howdy', Mode.current )}
update: function(){ console.log( 'Ok' )}
})
> Hey Apple
> Bye Apple
> Howdy Banana
> Ok
> Ok
> Ok

Tasks AND modes?

new Mode({    name: 'game play',
setup: function(){

Rock.all.destroy()
level.number = 0
player.reset()
player.enableEngines()
},
update: function(){

if( Rock.all.length === 0 ) level.create()
if( player.lives < 1 ) Mode.switchTo( 'game over' )

},
teardown: function(){
  1. If there are no rocks left, make a new level.
  2. If there are no lives left, switch to “game over.”

Wrapping space


radiusFogBegins: 250,
radiusFogEnds: 295,
radiusWrap: 300,
radiusStarsBegin: 300,
radiusStarsEnd: 400,
function wrap(){    const worldPosition = this.getWorldPosition()
if( worldPosition.distanceTo( player.position ) >= settings.radiusWrap ){

this.position.copy( this.parent.worldToLocal(
worldPosition
.sub( player.position )
.normalize()
.multiplyScalar( settings.radiusWrap * -1 )
.add( player.position )
))
return true
}
return false
}
Rock.prototype.wrap = wrap

To be continued?

BASE GEOMETRY      EXTENDED GEOMETRY
R0 R1 R2 R3 R4 …… R200
─────────··········─────────────────────
╱ ╱ ╲ ╱ ╱ ╱ ╱ ╲
│ │ │ │ │ │ │ │
╲ ╲ ╱ ╲ ╲ ╲ ╲ ╱
─────────··········─────────────────────

--

--

--

Hi, I’m Stewart Smith — a creative polymath in Brooklyn NY. I’m excited about spatial computing, quantum computing, and more. https://stewartsmith.io

Love podcasts or audiobooks? Learn on the go with our new app.

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store
Stewart Smith

Stewart Smith

Hi, I’m Stewart Smith — a creative polymath in Brooklyn NY. I’m excited about spatial computing, quantum computing, and more. https://stewartsmith.io

More from Medium

Workflow Webinar : Shaver / Trimmer

Chatbot integrations with Oswald

Chatbots using the Facebook Messenger UI integration

Deckbuilding — Market Analysis for Lovecrafting

Making a generative recreation of paintings by Vera Molnar and Josef Albers in p5.js

Vera Molnár in front of one of her artworks