Skip to Content
Course content

193: Spatial Computing Concepts in visionOS

Click on the "Edit" button in the top corner of the screen to edit your slide content.

I remember the first time I jumped into visionOS development. I spent about an hour staring at a blank screen, convinced my code was broken because my 3D model simply wouldn't appear. I had written the RealityView perfectly, the asset was loaded, and there were no crashes in the console. I just... saw nothing.

struct SpatialView: View {
    var body: some View {
        RealityView { content in
            if let model = try? await Entity.load(named: "Robot") {
                // I just want it to appear right in front of me!
                model.position = [0, 0, 0] 
                content.add(model)
            }
        }
    }
}

The Invisible Model Mystery

The bug here is a classic "spatial" mistake. In a traditional 2D app, (0,0) is the top-left corner of your screen. In visionOS, the origin (0,0,0) is the center of the user's head. By setting the position to zero, I had effectively spawned my robot inside my own skull. Because the camera (the user's eyes) cannot see geometry that is clipping through the near-plane of the view, the model was invisible.

When you're working in spatial computing, you have to stop thinking in pixels and start thinking in meters. In RealityKit, 1 unit equals 1 meter. If you put something at [0, 0, 0], you're putting it exactly where the user is standing.

Pushing Content into the Living Room

To fix this, we need to move the entity along the Z-axis. In the visionOS coordinate system, negative Z is forward. If you want an object to appear in front of the user, you move it away from the origin in the negative direction.

struct SpatialView: View {
    var body: some View {
        RealityView { content in
            if let model = try? await Entity.load(named: "Robot") {
                // Move it 1.5 meters away from the user
                // X: 0 (center), Y: 0 (eye level), Z: -1.5 (forward)
                model.position = [0, 0, -1.5] 
                content.add(model)
            }
        }
    }
}

Now the robot is sitting comfortably in the room. I usually recommend starting with a distance of 1.5 to 2.0 meters. Anything closer than 0.5 meters feels claustrophobic and can cause visual discomfort for the user; anything further than 5 meters starts to feel disconnected from the interaction.

Managing Windows vs. Immersive Spaces

It's also important to remember that where you place your RealityView matters. If you put it inside a standard WindowGroup, the "world" is relative to that window's position in space. The window itself is a 2D plane floating in a 3D world. When you add a 3D entity to a window's RealityView, the (0,0,0) origin is the center of that window, not the user's head.

However, once you move into an ImmersiveSpace, you're dealing with the "World Space." Here, the user is the center of the universe. This is where coordinate precision becomes critical. If you're building a game or a simulation, you'll likely spend more time managing these transforms than actually writing logic. I've found that creating a small "Debug Sphere" (a simple 10cm sphere) and placing it at known coordinates is the fastest way to orient yourself when your main assets are acting up.

The Logic of Spatial Transforms

One thing that trips people up is the difference between position and transform. While position is a convenient shorthand for moving something, the Transform object handles rotation and scale as well. In spatial computing, rotation is handled via quaternions (which are a headache to calculate manually), so I highly suggest using the simd_quatf helpers or the Transform convenience methods to rotate your objects to face the user.




📋 Practical Task

Exercise: Building a Spatial Floating Dashboard

Your goal is to create a RealityView that simulates a basic dashboard. Instead of one object, you need to place three different 3D primitives (a sphere, a box, and a cylinder) in a semi-circle in front of the user.

  • The Layout:
    • Place the Sphere 1.5 meters directly in front of the user (Center).
    • Place the Box 1.5 meters in front, but 0.7 meters to the left.
    • Place the Cylinder 1.5 meters in front, but 0.7 meters to the right.
  • The Constraints: All objects must be at a Y-height of 0 (eye level) to ensure they don't appear on the floor or ceiling.
  • The Challenge: Ensure you are using the correct Z-axis polarity so that the objects appear in the room, not behind the user's head.

Hint: Use MeshResource.generateSphere(radius:) and ModelEntity to create the shapes since you don't have external assets.

Rating
0 0

There are no comments for now.

to be the first to leave a comment.