From 9d15d0958d454b36fae7c4da461fb096939c077d Mon Sep 17 00:00:00 2001 From: Kieran Farr Date: Sat, 23 Dec 2023 21:32:42 -0800 Subject: [PATCH 1/4] Revert "delete blogs not ready for publishing" This reverts commit 04ef93d48acfa8045e8f6c4facfbc31a85623745. --- ...w-mixed-reality-devices-impact-on-webxr.md | 51 ++++++ blog/2023-12-15-city-builder-jr.md | 34 ++++ blog/2023-12-15-mozilla-hubs-and-3dstreet.md | 167 ++++++++++++++++++ 3 files changed, 252 insertions(+) create mode 100644 blog/2023-12-14-new-mixed-reality-devices-impact-on-webxr.md create mode 100644 blog/2023-12-15-city-builder-jr.md create mode 100644 blog/2023-12-15-mozilla-hubs-and-3dstreet.md diff --git a/blog/2023-12-14-new-mixed-reality-devices-impact-on-webxr.md b/blog/2023-12-14-new-mixed-reality-devices-impact-on-webxr.md new file mode 100644 index 000000000..c8037bc6e --- /dev/null +++ b/blog/2023-12-14-new-mixed-reality-devices-impact-on-webxr.md @@ -0,0 +1,51 @@ +--- +title: Impact on WebXR Application Design from New Mixed Reality Headsets +description: What changes should WebXR developers consider to their application given new devices coming to market? +authors: kfarr +tags: [webxr, headset, mixed-reality, ar, vr] +image: https://i.imgur.com/mErPwqL.png +hide_table_of_contents: false +--- + +# A *Gaze* into the Future of WebXR +Last week I attended a "developer day" at a local electronics company down the road featuring their new mixed reality VR headset. I spent the entire day testing many different WebXR applications with a heavy focus on evaluating which user interaction elements will be relevant for developers. + +## Gaze *just works* +Gaze interaction *"just works"* for native applications in this headset, however it's a privacy nightmare as gaze movement can be use to fingerprint (identify) individuals for tracking purposes, so that information isn't even given to native application. + +Instead, applications need to provide . Hinting on that for 2D DOM. however there's no clear way to do this in WebXR "3d dom" + +Open ticket + +## Hands free is the true MVP +While controllers offer high precision, they aren't always the most accessible option. Imagine a world where WebXR experiences are 100% hands-free—where a simple ray from the wrist or a pitch of the hand can activate functions. This is not just a possibility but a necessity that must be informed by the gap in current technologies. + +## Diorama vs. Full Room Scale +Design standards +focus on a Volume for miniaturized scenes + +https://developer.apple.com/documentation/visionos/diorama + +When we dive deeper into the design aspect of WebXR, we're met with choices: Diorama versus Life Size, Immersive versus Mixed Reality. Each of these choices presents a unique way of experiencing content that's worth exploring. + +## Immersive +Immersive at room scale is not offered, the scene fades out at the edges. + at "Room Scale" + +## Future exploration +* The integration of WebXR in webview, especially when embedded in apps, opens up a new horizon for developers and users alike. It enables a seamless transition from 3D scenes to real-world applications, enhancing the composer workflow. +* Reality Composer +* Lastly, the use of USDZ output—just like glb—suggests a promising direction for compatibility and ease of use in the creation of immersive content. The potential for these technologies is immense, and their exploration could lead to groundbreaking developments in the field of virtual and augmented reality. + +## Asset Pipeline Step-By-Step + + +## General useability for end-users +Hidden behind multiple Safari flags, and then facing 3 separate approval popups on the web page itself, means WebXR is still a practical use case only for dedicated users. It may be possible to "pull" users into using WebXR on these devices with instruction on feature flag setting, but it will be impossible to use WebXR on this platform to "push" more people to your WebXR experience in its present form. + + + +This is my first post on Docusaurus 2. + +A whole bunch of exploration to follow. + diff --git a/blog/2023-12-15-city-builder-jr.md b/blog/2023-12-15-city-builder-jr.md new file mode 100644 index 000000000..14e33056b --- /dev/null +++ b/blog/2023-12-15-city-builder-jr.md @@ -0,0 +1,34 @@ +--- +title: City Builder Jr +description: End-to-end glTF to polished WebXR app workflow. +slug: city-builder-jr-gltf-to-webxr-pipeline +authors: kfarr +tags: [webxr, gltf, citybuilderjr] +image: https://i.imgur.com/mErPwqL.png +hide_table_of_contents: false +--- + +This is an awesome blog post about City Builder Jr. +- It is a cool app +- There was a problem -- it's hard to create an end-to-end pipeline from creating models in Blender, converting those source assets for use on the web, hosting and then making those assets available to your application through an asset catalog. As your project grows this admin work becomes larger and larger until it's no longer maintainable. +- I reached out to Don McCurdy who has been working on gltf transform to provide command-line tools to modify and process gltf files. We created a workflow +- Used off-the-shelf assets from a library. +- Generated thumbnails +- Generate catalog of assets + +## Asset Pipeline Step-By-Step +- Asset pipeline + +## Making Models available to the application +- catalog.json, parsing + +## State component + +## Spatial anchors + + + +This is my first post on Docusaurus 2. + +A whole bunch of exploration to follow. + diff --git a/blog/2023-12-15-mozilla-hubs-and-3dstreet.md b/blog/2023-12-15-mozilla-hubs-and-3dstreet.md new file mode 100644 index 000000000..6febd740b --- /dev/null +++ b/blog/2023-12-15-mozilla-hubs-and-3dstreet.md @@ -0,0 +1,167 @@ +--- +title: Using Mozilla Hubs & 3DStreet for virtual Safe Streets collaboration +description: A guide to adding multi-user interactivity for 3D web applications using glTF files as intermediary. +slug: mozilla-hubs-and-3dstreet-virtual-safe-street-collaboration +authors: kfarr +tags: [webxr, gltf, hubs] +image: https://i.imgur.com/mErPwqL.png +hide_table_of_contents: false +--- + +Our WebXR app 3DStreet launched this year to empower anyone to visualize a safer, greener world, one street at a time. 3DStreet users have asked for collaboration features, but our team is small and integrating multi-user presence and conferencing into 3DStreet would be difficult and time consuming. How can we offer remote, virtual collaboration tools without major rewrites of our 3DStreet application? + +Answer: use the power of glTF as an intermediary format combined with the power of Mozilla Hubs self-hosted on Community Edition! In this post we'll outline the strategy we used to implement a proof of concept for "3DStreet Club" -- our branded experiment for a hosted collaboration experience using 3DStreet designs as the foundation of the virtual space. + + + +## What is Mozilla Hubs? +Hubs is an [open-source 3D conferencing cloud software](https://labs.mozilla.org/projects/hubs/) that works across every browser and VR, AR headsets like the Oculus Quest. Users create "rooms" and invite other users to join with live video and audio streaming like Zoom or Google Meets but for users with XR headsets they can choose live-animated 3D avatars connected to the XR headset and controllers for an immersive experience. + +I’ve been a long time user of Hubs, but never customized it. During the pandemic our small but mighty marketing team at Bitmovin got Oculus Quest headsets and used Hubs for internal meetings. We had the most fun when interacting in a custom virtual space that a 3D artist helped us create, bespoke for a virtual conference that we eventually hosted open to the public. + +## Project Strategy +During user testing we hear that a primary value of VR or AR headset visualization is gaining an instant spatial understanding of a scene and proposed civil infrastructure projects. When collaborating remotely on project designs, it can be time consuming for engineers and planning professionals to take 2D screen grabs, make markups, send back via email, and then still expect users to maintain that spatial reference to make use of the feedback. What if both users could be in the same 3D reference space and communicate with each other in real-time? + +We’d like to provide the users an option of exporting their 3DStreet scene to have a live conversation with other users they invite. They may not have VR headsets, so supporting a wide range of devices is key. + +We considered a few options for this. One option is to pay for a hosted pro plan, the other is to use CE. For this trial we are helping Mozilla test out their new workflow and chose to use the CE. + +## Hubs Community Edition Setup and Deployment on GCP +My favorite reference document for setup was [the CE Quick Start on GCP](https://hubs.mozilla.com/labs/community-edition-case-study-quick-start-on-gcp-w-aws-services/), and there's no sense in copying all the setup steps here, so instead in this post I'll share a condensed set of instructions on just what you need to get started and what I learned deploying these things myself, but check out that link for more background on the Hubs architecture, Kubernetes and configuration options. + +### Project Structure in Google Cloud Platform +GCP use of Projects is a very handy way to structure cloud services, I strongly recommend creating a dedicated project for your Hubs CE deployment. In our case we already have a project for 3DStreet Cloud production, and another project for 3DStreet Dev Server which is a copy of production for testing against, so adding another for Hubs is simple and fits in this existing structure. You can get other features by "leaning in" to this structure such as distinct user rights management and billing settings for each project. + +### Domain and DNS Setup +I used an old domain lying around `3dstreet.club` which was registered with GoDaddy, and used [GCP for DNS hosting](https://cloud.google.com/dns) for the project. Using GCP to [setup Cloud DNS hosting zones was straightforward](https://cloud.google.com/dns/docs/set-up-dns-records-domain-name), once I [updated the GoDaddy domain to use custom nameservers](https://www.godaddy.com/help/edit-my-domain-nameservers-664) pointing to the new GCP Cloud DNS zone. + +### Email SMTP Server and Forwarding +You'll need an SMTP server for Hubs to send emails for authentication. Thanks to some recommendations from others on the Hubs Discord, I tried out [Elastic Email's SMTP free tier]( https://elasticemail.com/referral-reward?r=2d26b9c5-2367-4c1a-a658-b9eaba965057) (you read that right, $0 / month) and it worked great. I also use [ImprovMX for email forwarding](https://improvmx.com) to receive email from various addresses across multiple domains. + +### CLI Cheat Sheet +Here are the most critical command lines for deploying your Hubs scene -- you'll be using these over and over again if you're testing and changing deployment settings. This assumes you have everything else setup. Reference [the Mozilla Quick Start guide](https://hubs.mozilla.com/labs/community-edition-case-study-quick-start-on-gcp-w-aws-services/) for step-by-step guide on how to get your local dev environment setup. + +#### Create cluster in GCP +`gcloud container clusters create --zone=` + +For this example project I'm using zone: "us-central1" and namespace: "hubs-test-cluster" + +`gcloud container clusters create hubs-test-cluster --zone us-central1` + +#### Deploy to newly created cluster with custom settings +After editing render_hcce.sh to place appropriate environment variables like your Hub domain, email and Namespace: + +`bash render_hcce.sh && kubectl apply -f hcce.yaml` + +And wait until all 11 pods are ready from this command: + +`kubectl get deployment -n hubs-test-cluster` + +Then wait until you get an "External IP" from this command: + +`kubectl -n hubs-test-cluster get svc lb` + +Use the External IP to set as A record for all 4 domains in DNS Zone: domain, assets.domain, stream.domain, cors.domain. + +Then run the script to setup certificates for the domains after placing appropriate environment variables: + +`bash cbb.sh` + +You should be able to access your Hubs server on a web browser and see no certificate warnings at this stage. Now you can begin customizing your admin settings, the Hubs client, and other exciting ideas that come to mind. + +#### "Pausing" the cluster +Evidently K8S doesn't have the concept of "pausing" a cluster, but instead you can scale it down with this command: + +`kubectl scale --replicas=0 -f hcce.yaml` + +`kubectl scale --replicas=1 -f hcce.yaml` + +#### Deleting the cluster +`gcloud container clusters delete hubs-test-cluster --region=us-central1` + +### Quota Limits +You will hit quota limits -- the defaults in GCP are too low for IP address limits and Persistent Disk SSD total storage capacity (GB). You will need to go into the [GCP Console > IAM & Admin > Quotas](https://console.cloud.google.com/apis/api/compute.googleapis.com/quotas) to make a quota request to increase these. Persistent disk storage upgrade from 500gb to 1000gb. IP addresses raise limit to 16. + +### When things go wrong +Automated server deployment with Kubernetes engine on GCP magically works -- until it doesn't. Unfortunately, when it doesn't work you will need to dig through a lot of layers of clusters, nodes, and pods (oh my!) to see what's going wrong. In addition to using [Mirantis Lens](https://k8slens.dev/) to poke around at your cluster, I found the #community-edition channel on the Hubs discord to be super helpful. + +### Billing and cost considerations +For a given GCP Project we can check out billing history. I've seen about $10 per day cost for compute and Kubernetes engine which was a bit more than I expected, and considerably more expensive (forecasted at $300/mo) compared to the $79 per month plan with the fully managed Hubs Professional plan. + +## Modifying Hubs for 3DStreet Scene Collaboration + +Our project goal is to allow a user to click a button inside of a 3DStreet.app scene and launch a Hubs room using that scene. + +We do this by using a glTF file as an intermediary -- the 3DStreet.app will create a glb (all-in-one compressed binary glTF file representing the 3DStreet scene) and then storing it on the 3DStreet server. Our custom Hubs client will then reference this glb file to present in the user's scene when they create a new Hubs Room. + +Here is an architecture diagram / flow chart of how this sequence might work: + +```mermaid +flowchart TD + A(User Loads or Creates 3DStreet.app Scene) --> B + B(User Clicks 'Collaborate Live with Hubs') --> D + D{Is user logged in to \n3DStreet Cloud to save GLB?} --> |Yes| E + D --> |No|F(3DStreet Cloud Sign In) --> E + E(3DStreet creates GLB file and saves to Firebase Cloud Storage) --> G + G(Send to CE Hubs Server: 3DStreet.Club\nPass 3DStreet GLB URL as Hash, such as:\nhttps://3dstreet.club/#https://URL-for-3DStreet-GLB-file) --> H + H(Custom Hubs Client Stores this GLB URL path to Local Storage) --> I + I(User Creates Room) --> J + J(Custom Hubs Client adds 3DStreet GLB to Scene from Local Storage Path) +``` + +### Hubs Client Customization + +To support the above workflow, we need to modify the Hubs client in 2 places: +* First, we need to capture the path of the 3DStreet Scene GLB when the user clicks on the link to leave 3DStreet.app and load the custom CE Hubs instance at 3DStreet.club. +* Then, after the user creates a new Hubs Room on the server, we need to add that GLB to the scene that we saved when the user first loaded the custom CE Hubs instance. + +#### Modifying Hubs index.html +The first file we'll modify is `index.html` -- displayed to a user when they first land on our custom Hubs CE homepage. I'm going to cheat and insert a script at the bottom of the page, after all the important stuff is done, to save to local storage the GLB path passed from 3DStreet application. + +```index.html + +``` + +#### Modifying Hubs hub.js + +The second file we'll modify is `hub.js`. This is a critical file used in the core instantiation of Hubs logic such as voice communication, user interface, physics, etc. We are doing our best to "tread lightly" and only run our code after the entire scene is instantiated. To do this, we piggy back off of an existing `onSceneLoaded` function [in line 780](https://github.com/mozilla/hubs/blob/master/src/hub.js#L780). + +Here is how we modified `hub.js` to add the 3DStreet glb from the path saved in local storage: +```hub.js + const onSceneLoaded = () => { + // existing physics setup here + + // Load 3DStreet glb path from local storage + var streetEl = document.createElement('a-entity'); + const gltfPath = localStorage.getItem('gltf-path'); + streetEl.setAttribute("media-loader", { src: gltfPath, fitToBox: true, resolve: true }) + streetEl.setAttribute("networked", { template: "#interactable-media" } ); + streetEl.id = 'streetEl'; + streetEl.setAttribute('scale', '100 100 100'); + streetEl.setAttribute('position', '0 1 0'); + document.getElementById('objects-scene').append(streetEl); +}; +``` + +### Deploying Hubs Custom Client to Community Edition +(...) + + +## User testing and prep for launch +Initial user feedback +Preparing for launch + +## Summary +Learnings +Next steps +CTA + +### Technical Notes + From 1b0f1ebe03d00c3319de153da8ae2519d88acc66 Mon Sep 17 00:00:00 2001 From: Kieran Farr Date: Fri, 29 Dec 2023 10:34:27 -0800 Subject: [PATCH 2/4] update vision pro blog post --- ...w-mixed-reality-devices-impact-on-webxr.md | 51 ----------------- ...w-mixed-reality-devices-impact-on-webxr.md | 53 ++++++++++++++++++ blog/images/visionOS-platform-compressed.jpg | Bin 0 -> 58897 bytes 3 files changed, 53 insertions(+), 51 deletions(-) delete mode 100644 blog/2023-12-14-new-mixed-reality-devices-impact-on-webxr.md create mode 100644 blog/2023-12-29-new-mixed-reality-devices-impact-on-webxr.md create mode 100644 blog/images/visionOS-platform-compressed.jpg diff --git a/blog/2023-12-14-new-mixed-reality-devices-impact-on-webxr.md b/blog/2023-12-14-new-mixed-reality-devices-impact-on-webxr.md deleted file mode 100644 index c8037bc6e..000000000 --- a/blog/2023-12-14-new-mixed-reality-devices-impact-on-webxr.md +++ /dev/null @@ -1,51 +0,0 @@ ---- -title: Impact on WebXR Application Design from New Mixed Reality Headsets -description: What changes should WebXR developers consider to their application given new devices coming to market? -authors: kfarr -tags: [webxr, headset, mixed-reality, ar, vr] -image: https://i.imgur.com/mErPwqL.png -hide_table_of_contents: false ---- - -# A *Gaze* into the Future of WebXR -Last week I attended a "developer day" at a local electronics company down the road featuring their new mixed reality VR headset. I spent the entire day testing many different WebXR applications with a heavy focus on evaluating which user interaction elements will be relevant for developers. - -## Gaze *just works* -Gaze interaction *"just works"* for native applications in this headset, however it's a privacy nightmare as gaze movement can be use to fingerprint (identify) individuals for tracking purposes, so that information isn't even given to native application. - -Instead, applications need to provide . Hinting on that for 2D DOM. however there's no clear way to do this in WebXR "3d dom" - -Open ticket - -## Hands free is the true MVP -While controllers offer high precision, they aren't always the most accessible option. Imagine a world where WebXR experiences are 100% hands-free—where a simple ray from the wrist or a pitch of the hand can activate functions. This is not just a possibility but a necessity that must be informed by the gap in current technologies. - -## Diorama vs. Full Room Scale -Design standards -focus on a Volume for miniaturized scenes - -https://developer.apple.com/documentation/visionos/diorama - -When we dive deeper into the design aspect of WebXR, we're met with choices: Diorama versus Life Size, Immersive versus Mixed Reality. Each of these choices presents a unique way of experiencing content that's worth exploring. - -## Immersive -Immersive at room scale is not offered, the scene fades out at the edges. - at "Room Scale" - -## Future exploration -* The integration of WebXR in webview, especially when embedded in apps, opens up a new horizon for developers and users alike. It enables a seamless transition from 3D scenes to real-world applications, enhancing the composer workflow. -* Reality Composer -* Lastly, the use of USDZ output—just like glb—suggests a promising direction for compatibility and ease of use in the creation of immersive content. The potential for these technologies is immense, and their exploration could lead to groundbreaking developments in the field of virtual and augmented reality. - -## Asset Pipeline Step-By-Step - - -## General useability for end-users -Hidden behind multiple Safari flags, and then facing 3 separate approval popups on the web page itself, means WebXR is still a practical use case only for dedicated users. It may be possible to "pull" users into using WebXR on these devices with instruction on feature flag setting, but it will be impossible to use WebXR on this platform to "push" more people to your WebXR experience in its present form. - - - -This is my first post on Docusaurus 2. - -A whole bunch of exploration to follow. - diff --git a/blog/2023-12-29-new-mixed-reality-devices-impact-on-webxr.md b/blog/2023-12-29-new-mixed-reality-devices-impact-on-webxr.md new file mode 100644 index 000000000..cd787ef4b --- /dev/null +++ b/blog/2023-12-29-new-mixed-reality-devices-impact-on-webxr.md @@ -0,0 +1,53 @@ +--- +title: Impact on WebXR Application Design from New Mixed Reality Headsets +description: What changes should WebXR developers consider to their application given new devices coming to market? +authors: kfarr +tags: [webxr, headset, mixed-reality, ar, vr] +image: ./images/visionOS-platform-compressed.jpg +hide_table_of_contents: false +--- + +# A *Gaze* into the Future of WebXR +A few weeks ago I attended a "developer day" at a local electronics company down the road featuring their new mixed reality VR headset. I spent the entire day testing many different WebXR applications with a heavy focus on evaluating which user interaction elements will be relevant for developers. + +Below are some notes intended for other 3DStreet code contributors to reference as we work on supporting new WebXR compatible devices. + + + +## Gaze works very well as a primary user input mechanism +Gaze interaction *"just works"* for native applications in this headset, however it's a privacy nightmare as gaze movement can be use to fingerprint (identify) individuals for tracking purposes, so that information isn't even given to native application. + +To enable gaze user interface while not exposing gaze to the application, apps need to provide "hot spots" or hints as to areas that can be hovered. [There is a great video guide on how to adapt 2D DOM CSS website for gaze highlight interaction](https://developer.apple.com/videos/play/wwdc2023/10279/), but there's no clear equivalent to this CSS hinting that can be done in the WebXR "3D DOM" or 3D space. The need has been recognized however, [there is a new ticket opened in the WebXR `immersive-web` GitHub repository that I recently added to.](https://github.com/immersive-web/proposals/issues/86) + +## Hand tracking is most accessible input, not controllers +Imagine a world where WebXR experiences are 100% hands-free, where a simple ray from the wrist and a pitch of the hand can activate functions. This is not just a possibility but now a necessity -- going forward, all WebXR experiences must be accessible via hands first. + +We should now regard hand tracking as the "lowest common denominator" of user input for headset experiences. Controllers will still offer higher precision, off-camera movement tracking, more button trigger inputs, etc. but they are no longer the most accessible "default" option. + +## Launching WebXR applications +Getting WebXR working on this device is cumbersome. WebXR must be activated by searching through multiple Safari flags (System Settings > Apps > Safari > Blah blah blah). Then, on the web page itself a user must accept 2 separate approval popups when entering WebXR mode -- one for enabling immersive mode, the other for enabling hand tracking. + +It may be possible to "pull" some advanced users into using WebXR on these devices with instruction on feature flag setting, but it will be impossible to use WebXR on this platform to "push" more people to your WebXR experience in its present form. + +## Space, Immersion and Passthrough with WebXR +In WebXR mode on the device, a scene is rendered in _Full Space_ meaning no other application windows are present, unlike native apps that can choose to run in _Shared Space_ amongst other applications. An additional limitation is that the device does not currently support WebXR with _Passthrough_ mode, therefore you cannot run WebAR apps that make use of external cameras. + +*Therefore only your WebXR scene will appear in _Full Space_ immersion mode with no _Passthrough_.* This may be desirable for traditional VR applications but not as useful for next generation mixed reality applications. + +An additional limitation is that WebXR Immersive mode is limited to a 1.5m radius from the user-specified origin, usually where the user was located when entering XR mode. Beyond this radius the immersive scene _Full Space_ begins fading into _Passthrough_ without the scene persisting. If a user continues moving away from the origin the will see a round Safari icon at the origin point, presumably indicating that the user has an open WebXR Safari session with an origin at that point. + +## Diorama vs. Full Room Scale +The [visionOS developer documentation](https://developer.apple.com/documentation/visionos/) is excellent and signals clear directions for the market. + +The Diorama appears to be major application interface concept, [featured as a sample application](https://developer.apple.com/documentation/visionos/diorama) and a prime example of output from the new Reality Composer Pro authoring tool. + +This is a useful concept to adopt with 3DStreet. With 3DStreet there are cases for both Diorama and Full Scale modes: some users may wish to view a scene in Full Scale life-size to get an accurate viewpoint of dimension, while other users may wish to shrink a scene down to a Diorama view to easily manipulate a large scene from a smaller workspace. + +## Combining WebXR with native applications +* Native applications can ask for rights such as _Passthrough_ or _Shared Space_ that are not currently accessible via Safari WebXR. It is definitely worth exploring the use of an embedded webkit-based webview in a visionOS application which may expand the ability for WebXR application to offer augmented reality experiences with a native-like experience. +* Reality Composer Pro looks like a powerful tool for non-developers. It is worth exploring a pipeline of 3DStreet > Reality Composer Pro > App Publishing. Therefore it is likely worth exploring tools to convert glTF exporting to USDZ format, or directly exporting the three.js / A-Frame scene into USDZ format. + +## Summary +* WebXR appears to be purposefully limited on this device with a degraded user experience. Consider webview or another mechanism to adapt WebXR application into a native app. +* Hand tracking is now a minimum requirement for WebXR apps, controllers are optional. +* Gaze interface is very effective but needs standardization work to enable in WebXR mode. \ No newline at end of file diff --git a/blog/images/visionOS-platform-compressed.jpg b/blog/images/visionOS-platform-compressed.jpg new file mode 100644 index 0000000000000000000000000000000000000000..f5bd60daa50803a1cef4d61d1057219cac4a68ec GIT binary patch literal 58897 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 dK2G1 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

l1rCHr6O?lF41u&MjiEr=-VT!DSzH*P2nvp{ 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 WEu3C!#C2PmP+;W+*^~M4@_zs#MlqQH literal 0 HcmV?d00001 From c7c3957f984b6b614c8c51e42a1e8bacdd14e584 Mon Sep 17 00:00:00 2001 From: Kieran Farr Date: Fri, 29 Dec 2023 10:35:17 -0800 Subject: [PATCH 3/4] not ready --- blog/2023-12-15-city-builder-jr.md | 34 ---- blog/2023-12-15-mozilla-hubs-and-3dstreet.md | 167 ------------------- 2 files changed, 201 deletions(-) delete mode 100644 blog/2023-12-15-city-builder-jr.md delete mode 100644 blog/2023-12-15-mozilla-hubs-and-3dstreet.md diff --git a/blog/2023-12-15-city-builder-jr.md b/blog/2023-12-15-city-builder-jr.md deleted file mode 100644 index 14e33056b..000000000 --- a/blog/2023-12-15-city-builder-jr.md +++ /dev/null @@ -1,34 +0,0 @@ ---- -title: City Builder Jr -description: End-to-end glTF to polished WebXR app workflow. -slug: city-builder-jr-gltf-to-webxr-pipeline -authors: kfarr -tags: [webxr, gltf, citybuilderjr] -image: https://i.imgur.com/mErPwqL.png -hide_table_of_contents: false ---- - -This is an awesome blog post about City Builder Jr. -- It is a cool app -- There was a problem -- it's hard to create an end-to-end pipeline from creating models in Blender, converting those source assets for use on the web, hosting and then making those assets available to your application through an asset catalog. As your project grows this admin work becomes larger and larger until it's no longer maintainable. -- I reached out to Don McCurdy who has been working on gltf transform to provide command-line tools to modify and process gltf files. We created a workflow -- Used off-the-shelf assets from a library. -- Generated thumbnails -- Generate catalog of assets - -## Asset Pipeline Step-By-Step -- Asset pipeline - -## Making Models available to the application -- catalog.json, parsing - -## State component - -## Spatial anchors - - - -This is my first post on Docusaurus 2. - -A whole bunch of exploration to follow. - diff --git a/blog/2023-12-15-mozilla-hubs-and-3dstreet.md b/blog/2023-12-15-mozilla-hubs-and-3dstreet.md deleted file mode 100644 index 6febd740b..000000000 --- a/blog/2023-12-15-mozilla-hubs-and-3dstreet.md +++ /dev/null @@ -1,167 +0,0 @@ ---- -title: Using Mozilla Hubs & 3DStreet for virtual Safe Streets collaboration -description: A guide to adding multi-user interactivity for 3D web applications using glTF files as intermediary. -slug: mozilla-hubs-and-3dstreet-virtual-safe-street-collaboration -authors: kfarr -tags: [webxr, gltf, hubs] -image: https://i.imgur.com/mErPwqL.png -hide_table_of_contents: false ---- - -Our WebXR app 3DStreet launched this year to empower anyone to visualize a safer, greener world, one street at a time. 3DStreet users have asked for collaboration features, but our team is small and integrating multi-user presence and conferencing into 3DStreet would be difficult and time consuming. How can we offer remote, virtual collaboration tools without major rewrites of our 3DStreet application? - -Answer: use the power of glTF as an intermediary format combined with the power of Mozilla Hubs self-hosted on Community Edition! In this post we'll outline the strategy we used to implement a proof of concept for "3DStreet Club" -- our branded experiment for a hosted collaboration experience using 3DStreet designs as the foundation of the virtual space. - - - -## What is Mozilla Hubs? -Hubs is an [open-source 3D conferencing cloud software](https://labs.mozilla.org/projects/hubs/) that works across every browser and VR, AR headsets like the Oculus Quest. Users create "rooms" and invite other users to join with live video and audio streaming like Zoom or Google Meets but for users with XR headsets they can choose live-animated 3D avatars connected to the XR headset and controllers for an immersive experience. - -I’ve been a long time user of Hubs, but never customized it. During the pandemic our small but mighty marketing team at Bitmovin got Oculus Quest headsets and used Hubs for internal meetings. We had the most fun when interacting in a custom virtual space that a 3D artist helped us create, bespoke for a virtual conference that we eventually hosted open to the public. - -## Project Strategy -During user testing we hear that a primary value of VR or AR headset visualization is gaining an instant spatial understanding of a scene and proposed civil infrastructure projects. When collaborating remotely on project designs, it can be time consuming for engineers and planning professionals to take 2D screen grabs, make markups, send back via email, and then still expect users to maintain that spatial reference to make use of the feedback. What if both users could be in the same 3D reference space and communicate with each other in real-time? - -We’d like to provide the users an option of exporting their 3DStreet scene to have a live conversation with other users they invite. They may not have VR headsets, so supporting a wide range of devices is key. - -We considered a few options for this. One option is to pay for a hosted pro plan, the other is to use CE. For this trial we are helping Mozilla test out their new workflow and chose to use the CE. - -## Hubs Community Edition Setup and Deployment on GCP -My favorite reference document for setup was [the CE Quick Start on GCP](https://hubs.mozilla.com/labs/community-edition-case-study-quick-start-on-gcp-w-aws-services/), and there's no sense in copying all the setup steps here, so instead in this post I'll share a condensed set of instructions on just what you need to get started and what I learned deploying these things myself, but check out that link for more background on the Hubs architecture, Kubernetes and configuration options. - -### Project Structure in Google Cloud Platform -GCP use of Projects is a very handy way to structure cloud services, I strongly recommend creating a dedicated project for your Hubs CE deployment. In our case we already have a project for 3DStreet Cloud production, and another project for 3DStreet Dev Server which is a copy of production for testing against, so adding another for Hubs is simple and fits in this existing structure. You can get other features by "leaning in" to this structure such as distinct user rights management and billing settings for each project. - -### Domain and DNS Setup -I used an old domain lying around `3dstreet.club` which was registered with GoDaddy, and used [GCP for DNS hosting](https://cloud.google.com/dns) for the project. Using GCP to [setup Cloud DNS hosting zones was straightforward](https://cloud.google.com/dns/docs/set-up-dns-records-domain-name), once I [updated the GoDaddy domain to use custom nameservers](https://www.godaddy.com/help/edit-my-domain-nameservers-664) pointing to the new GCP Cloud DNS zone. - -### Email SMTP Server and Forwarding -You'll need an SMTP server for Hubs to send emails for authentication. Thanks to some recommendations from others on the Hubs Discord, I tried out [Elastic Email's SMTP free tier]( https://elasticemail.com/referral-reward?r=2d26b9c5-2367-4c1a-a658-b9eaba965057) (you read that right, $0 / month) and it worked great. I also use [ImprovMX for email forwarding](https://improvmx.com) to receive email from various addresses across multiple domains. - -### CLI Cheat Sheet -Here are the most critical command lines for deploying your Hubs scene -- you'll be using these over and over again if you're testing and changing deployment settings. This assumes you have everything else setup. Reference [the Mozilla Quick Start guide](https://hubs.mozilla.com/labs/community-edition-case-study-quick-start-on-gcp-w-aws-services/) for step-by-step guide on how to get your local dev environment setup. - -#### Create cluster in GCP -`gcloud container clusters create --zone=` - -For this example project I'm using zone: "us-central1" and namespace: "hubs-test-cluster" - -`gcloud container clusters create hubs-test-cluster --zone us-central1` - -#### Deploy to newly created cluster with custom settings -After editing render_hcce.sh to place appropriate environment variables like your Hub domain, email and Namespace: - -`bash render_hcce.sh && kubectl apply -f hcce.yaml` - -And wait until all 11 pods are ready from this command: - -`kubectl get deployment -n hubs-test-cluster` - -Then wait until you get an "External IP" from this command: - -`kubectl -n hubs-test-cluster get svc lb` - -Use the External IP to set as A record for all 4 domains in DNS Zone: domain, assets.domain, stream.domain, cors.domain. - -Then run the script to setup certificates for the domains after placing appropriate environment variables: - -`bash cbb.sh` - -You should be able to access your Hubs server on a web browser and see no certificate warnings at this stage. Now you can begin customizing your admin settings, the Hubs client, and other exciting ideas that come to mind. - -#### "Pausing" the cluster -Evidently K8S doesn't have the concept of "pausing" a cluster, but instead you can scale it down with this command: - -`kubectl scale --replicas=0 -f hcce.yaml` - -`kubectl scale --replicas=1 -f hcce.yaml` - -#### Deleting the cluster -`gcloud container clusters delete hubs-test-cluster --region=us-central1` - -### Quota Limits -You will hit quota limits -- the defaults in GCP are too low for IP address limits and Persistent Disk SSD total storage capacity (GB). You will need to go into the [GCP Console > IAM & Admin > Quotas](https://console.cloud.google.com/apis/api/compute.googleapis.com/quotas) to make a quota request to increase these. Persistent disk storage upgrade from 500gb to 1000gb. IP addresses raise limit to 16. - -### When things go wrong -Automated server deployment with Kubernetes engine on GCP magically works -- until it doesn't. Unfortunately, when it doesn't work you will need to dig through a lot of layers of clusters, nodes, and pods (oh my!) to see what's going wrong. In addition to using [Mirantis Lens](https://k8slens.dev/) to poke around at your cluster, I found the #community-edition channel on the Hubs discord to be super helpful. - -### Billing and cost considerations -For a given GCP Project we can check out billing history. I've seen about $10 per day cost for compute and Kubernetes engine which was a bit more than I expected, and considerably more expensive (forecasted at $300/mo) compared to the $79 per month plan with the fully managed Hubs Professional plan. - -## Modifying Hubs for 3DStreet Scene Collaboration - -Our project goal is to allow a user to click a button inside of a 3DStreet.app scene and launch a Hubs room using that scene. - -We do this by using a glTF file as an intermediary -- the 3DStreet.app will create a glb (all-in-one compressed binary glTF file representing the 3DStreet scene) and then storing it on the 3DStreet server. Our custom Hubs client will then reference this glb file to present in the user's scene when they create a new Hubs Room. - -Here is an architecture diagram / flow chart of how this sequence might work: - -```mermaid -flowchart TD - A(User Loads or Creates 3DStreet.app Scene) --> B - B(User Clicks 'Collaborate Live with Hubs') --> D - D{Is user logged in to \n3DStreet Cloud to save GLB?} --> |Yes| E - D --> |No|F(3DStreet Cloud Sign In) --> E - E(3DStreet creates GLB file and saves to Firebase Cloud Storage) --> G - G(Send to CE Hubs Server: 3DStreet.Club\nPass 3DStreet GLB URL as Hash, such as:\nhttps://3dstreet.club/#https://URL-for-3DStreet-GLB-file) --> H - H(Custom Hubs Client Stores this GLB URL path to Local Storage) --> I - I(User Creates Room) --> J - J(Custom Hubs Client adds 3DStreet GLB to Scene from Local Storage Path) -``` - -### Hubs Client Customization - -To support the above workflow, we need to modify the Hubs client in 2 places: -* First, we need to capture the path of the 3DStreet Scene GLB when the user clicks on the link to leave 3DStreet.app and load the custom CE Hubs instance at 3DStreet.club. -* Then, after the user creates a new Hubs Room on the server, we need to add that GLB to the scene that we saved when the user first loaded the custom CE Hubs instance. - -#### Modifying Hubs index.html -The first file we'll modify is `index.html` -- displayed to a user when they first land on our custom Hubs CE homepage. I'm going to cheat and insert a script at the bottom of the page, after all the important stuff is done, to save to local storage the GLB path passed from 3DStreet application. - -```index.html - -``` - -#### Modifying Hubs hub.js - -The second file we'll modify is `hub.js`. This is a critical file used in the core instantiation of Hubs logic such as voice communication, user interface, physics, etc. We are doing our best to "tread lightly" and only run our code after the entire scene is instantiated. To do this, we piggy back off of an existing `onSceneLoaded` function [in line 780](https://github.com/mozilla/hubs/blob/master/src/hub.js#L780). - -Here is how we modified `hub.js` to add the 3DStreet glb from the path saved in local storage: -```hub.js - const onSceneLoaded = () => { - // existing physics setup here - - // Load 3DStreet glb path from local storage - var streetEl = document.createElement('a-entity'); - const gltfPath = localStorage.getItem('gltf-path'); - streetEl.setAttribute("media-loader", { src: gltfPath, fitToBox: true, resolve: true }) - streetEl.setAttribute("networked", { template: "#interactable-media" } ); - streetEl.id = 'streetEl'; - streetEl.setAttribute('scale', '100 100 100'); - streetEl.setAttribute('position', '0 1 0'); - document.getElementById('objects-scene').append(streetEl); -}; -``` - -### Deploying Hubs Custom Client to Community Edition -(...) - - -## User testing and prep for launch -Initial user feedback -Preparing for launch - -## Summary -Learnings -Next steps -CTA - -### Technical Notes - From e1d0410fb4303ac042cea26f105d7dd78b8b0bf4 Mon Sep 17 00:00:00 2001 From: Kieran Farr Date: Fri, 29 Dec 2023 10:36:36 -0800 Subject: [PATCH 4/4] edit --- blog/2023-12-29-new-mixed-reality-devices-impact-on-webxr.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/blog/2023-12-29-new-mixed-reality-devices-impact-on-webxr.md b/blog/2023-12-29-new-mixed-reality-devices-impact-on-webxr.md index cd787ef4b..915b78b4a 100644 --- a/blog/2023-12-29-new-mixed-reality-devices-impact-on-webxr.md +++ b/blog/2023-12-29-new-mixed-reality-devices-impact-on-webxr.md @@ -19,7 +19,7 @@ Gaze interaction *"just works"* for native applications in this headset, however To enable gaze user interface while not exposing gaze to the application, apps need to provide "hot spots" or hints as to areas that can be hovered. [There is a great video guide on how to adapt 2D DOM CSS website for gaze highlight interaction](https://developer.apple.com/videos/play/wwdc2023/10279/), but there's no clear equivalent to this CSS hinting that can be done in the WebXR "3D DOM" or 3D space. The need has been recognized however, [there is a new ticket opened in the WebXR `immersive-web` GitHub repository that I recently added to.](https://github.com/immersive-web/proposals/issues/86) -## Hand tracking is most accessible input, not controllers +## Hand tracking is the most accessible input, not controllers Imagine a world where WebXR experiences are 100% hands-free, where a simple ray from the wrist and a pitch of the hand can activate functions. This is not just a possibility but now a necessity -- going forward, all WebXR experiences must be accessible via hands first. We should now regard hand tracking as the "lowest common denominator" of user input for headset experiences. Controllers will still offer higher precision, off-camera movement tracking, more button trigger inputs, etc. but they are no longer the most accessible "default" option.