A little practical magic

This week, we made a lot of progress on MR.js, adaptive UI & physics-based interactions.

By Michael Gutensohn

Hello!

Michael writing this week. We decided to delay last week’s newsletter until our Quest 3s arrived, so there’s a lot to update you on.

Quest 3

Laurent and I received our Quest 3s this week. I immediately unboxed mine, and I have to say I’m both impressed and frustrated. The pass-through, hand tracking, and scene understanding are quite impressive given the price point, but it’s frustrating that I spent $1500 on a Quest Pro less than a year ago, and it’s already rendered obsolete by a “non-pro” product. It isn’t the worst problem to have, but still.

I’ve moved my entire workflow over to the Quest 3, and I’m excited to share some demos with y’all.

MR.js

I’ve made a lot of progress with mr.js. This week’s update will focus on adaptive UI & physics-based interactions, but here are some other updates:

With that said, let’s dive in.

Adaptive UI

I’m a firm believer in “show then tell,” and my partner had asked me to order a whiteboard for the apartment. So naturally, I opted to build one instead, knocking out two tasks at once.

Clip of a hand writing ‘Hello’ on a whiteboard

Wow! An MR whiteboard, already very cool. You can checkout the full demo here!

…but what if you don’t have access to a headset?

Obviously, this is a Spatial Web company, but it wouldn’t be small-minded to totally exclude the rest of the web. So what would happen if I were to pull up this same demo on an iPad?

Clip of a user using the same whiteboard app to write on an iPad

One code base, all platforms

A responsive interface isn’t a new concept. Pioneered by web developers in the early days of iOS/Android, it filled the need to serve different devices with a single code-base. As XR headsets become more ubiquitous, this need will resurface.

Mr.js is designed from the ground up to fulfill this need. Developers can write their spatial app once and expect it to fit any screen with minimal overhead.

<mr-app camera="camera: orthographic">
    <mr-surface>
        <mr-container>
            <mr-column>
                <mr-whiteboard id="board"></mr-whiteboard>
                <!--markers-->
                <mr-row height ="0.2" width="3">
                    <mr-button color="black"></mr-button>
                    <mr-button color="red"></mr-button>
                    <mr-button color="blue"></mr-button>
                    <mr-button color="green"></mr-button>
                    <mr-button color="yellow"></mr-button>
                    <mr-button color="purple"></mr-button>
                    <mr-button color="brown"></mr-button>
                    <mr-button color="orange"></mr-button>
                    <mr-button color="magenta"></mr-button>
                    <mr-button color="darkred"></mr-button>
                    <mr-button id="eraser" color="white"></mr-button>
                </mr-row>
            </mr-column>
            
        </mr-container>
    </mr-surface> 
</mr-app>

This enables developers to comfortably build for the spatial web without alienating the planar web.

Physics-based events and interactions

The interactions for the above demo were achieved using Rapier.js. Rapier is a powerful physics’ engine, written in Rust and compiled to WASM, enabling performant physics simulations in real time.

Clip of a hand drawing a smiling face on a whiteboard. Debugging lines are cast out from the hand.

This allows us to accurately detect collisions and generate events that can be captured using standard event listeners.

this.addEventListener('touch-start', (event) => {
    this.context.beginPath();
    this.drawStartPosition.copy(event.detail.position)
    this.getCanvasPosition(this.drawStartPosition)
})

this.addEventListener('touch', (event) => {
    console.log('touch');
    this.drawPosition.copy(event.detail.position)
    this.getCanvasPosition(this.drawPosition)
    this.draw()
})

Some practical magic

Another update they may not have been entirely obvious from the above demo is our UI clipping implementation. Here’s a more practical example, and a bad magic trick for fun.

Clip of a 3D virtual object attached to a scroll view. As I scroll the object off the view with my left hand, my right hand appears to catch the virtual object falling. As I open my hand, I reveal a physical object with the same shape.

Clipping prevents content from flowing out of the UI window/panel, keeping things clean and organized.

til next time!

The code for the whiteboard demo can be found here, and the rest of mrjs can be found here. Star the repo and follow me on mastodon for more frequent updates (and magic).

michael.