Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

visionOS sample code: add view model, get Mesh construction off the main thread #116

Draft
wants to merge 7 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
217 changes: 203 additions & 14 deletions Euclid.xcodeproj/project.pbxproj

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
{
"images" : [
{
"idiom" : "vision",
"scale" : "2x"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
{
"info" : {
"author" : "xcode",
"version" : 1
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
{
"info" : {
"author" : "xcode",
"version" : 1
},
"layers" : [
{
"filename" : "Front.solidimagestacklayer"
},
{
"filename" : "Middle.solidimagestacklayer"
},
{
"filename" : "Back.solidimagestacklayer"
}
]
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
{
"images" : [
{
"idiom" : "vision",
"scale" : "2x"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
{
"info" : {
"author" : "xcode",
"version" : 1
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
{
"images" : [
{
"idiom" : "vision",
"scale" : "2x"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
{
"info" : {
"author" : "xcode",
"version" : 1
}
}
6 changes: 6 additions & 0 deletions ExampleVisionOS/Assets.xcassets/Contents.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
{
"info" : {
"author" : "xcode",
"version" : 1
}
}
22 changes: 22 additions & 0 deletions ExampleVisionOS/ContentView.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
//
// ContentView.swift
// ExampleVisionOS
//
// Created by Hal Mueller on 3/5/24.
// Copyright © 2024 Nick Lockwood. All rights reserved.
//

import RealityKit
import SwiftUI

struct ContentView: View {
@State private var enlarge = false

var body: some View {
VolumetricView()
}
}

#Preview(windowStyle: .volumetric) {
ContentView()
}
20 changes: 20 additions & 0 deletions ExampleVisionOS/ExampleVisionOSApp.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
//
// ExampleVisionOSApp.swift
// ExampleVisionOS
//
// Created by Hal Mueller on 3/5/24.
// Copyright © 2024 Nick Lockwood. All rights reserved.
//

import SwiftUI

@main
struct ExampleVisionOSApp: App {
var body: some Scene {
WindowGroup {
ContentView()
}
.windowStyle(.volumetric)
.defaultSize(width: 1.5, height: 1.5, depth: 1.5, in: .meters)
}
}
15 changes: 15 additions & 0 deletions ExampleVisionOS/Info.plist
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>UIApplicationSceneManifest</key>
<dict>
<key>UIApplicationPreferredDefaultSceneSessionRole</key>
<string>UIWindowSceneSessionRoleVolumetricApplication</string>
<key>UIApplicationSupportsMultipleScenes</key>
<true/>
<key>UISceneConfigurations</key>
<dict/>
</dict>
</dict>
</plist>
65 changes: 65 additions & 0 deletions ExampleVisionOS/MeshViewModel.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
//
// MeshViewModel.swift
// Euclid
//
// Created by Hal Mueller on 3/11/24.
// Copyright © 2024 Nick Lockwood. All rights reserved.
//

import CoreGraphics
import Euclid
import RealityKit

/// Holds an Entity built from a Euclid Mesh, plus state for the VolumetricView.
@Observable
class MeshViewModel: ObservableObject {
/// The Euclid mesh's Entity lands here.
public var entity: Entity? = nil
/// Euclid has finished preparing the content.
public var contentReady = false
/// The RealityView instance has added the content.
public var contentAdded = false

/// A demonstration Mesh generated by Euclid
private let euclidMesh: Mesh = {
let start = CFAbsoluteTimeGetCurrent()

// create some geometry using Euclid
let cube = Mesh.cube(size: 0.8, material: Color.red)
let sphere = Mesh.sphere(slices: 120, material: CGImage.checkerboard())
let mesh = cube.subtracting(sphere).makeWatertight()

print("Time:", CFAbsoluteTimeGetCurrent() - start)
print("Polygons:", mesh.polygons.count)
print("Triangles:", mesh.triangulate().polygons.count)
print("Watertight:", mesh.isWatertight)

// Sleep here to simulate a long-running mesh construction.
sleep(5)

return mesh
}()

func prepareContent() {
Task.init {
do {
let demoBoxEntity = try await ModelEntity(euclidMesh.scaled(by: 0.5))
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Probably not a huge deal, but what about moving euclidMesh into this class? (I always look askance at global functions.)


// for more realism, add a shadow
await demoBoxEntity.components.set(GroundingShadowComponent(castsShadow: true))

// needed for tap detection/response
await demoBoxEntity.generateCollisionShapes(recursive: true)

// for gesture targeting
await demoBoxEntity.components.set(InputTargetComponent())

print(#function, "complete")
entity = demoBoxEntity
contentReady = true
} catch {
print(#function, "failed to update content")
}
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
{
"info" : {
"author" : "xcode",
"version" : 1
}
}
60 changes: 60 additions & 0 deletions ExampleVisionOS/VolumetricView.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
//
// VolumetricView.swift
// ExampleVisionOS
//
// Created by Hal Mueller on 3/6/24.
// Copyright © 2024 Nick Lockwood. All rights reserved.
//

import RealityKit
import SwiftUI

struct VolumetricView: View {
@State private var spinX = 0.0
@State private var spinY = 0.0

private var viewModel = MeshViewModel()

var body: some View {
RealityView { content in
// In this demo we'll skip adding content at init time. But if you have other content
// that you want visible while the EuclidMesh is being built, add it in this closure.
// A simulated expensive, long-running Mesh generation happens on its own Task.
debugPrint(content)
} update: { content in
if viewModel.contentReady,
!viewModel.contentAdded,
let box = viewModel.entity
{
content.add(box)
viewModel.contentAdded = true
}
guard let entity = content.entities.first else {
return
}

let pitch = Transform(pitch: Float(spinX * -1)).matrix
let yaw = Transform(yaw: Float(spinY)).matrix
entity.transform.matrix = pitch * yaw
}
.gesture(
DragGesture(minimumDistance: 0)
.targetedToAnyEntity()
.onChanged { value in
let startLocation = value.convert(value.startLocation3D, from: .local, to: .scene)
let currentLocation = value.convert(value.location3D, from: .local, to: .scene)
let delta = currentLocation - startLocation
spinX = Double(delta.y) * 5
spinY = Double(delta.x) * 5
}
)
.task {
viewModel.prepareContent()
print("content preparation launched...")
}
}
}

#Preview {
VolumetricView()
}
5 changes: 3 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -58,9 +58,10 @@ Feel free to open an issue in Github if you have questions about how to use the
If you wish to contribute improvements to the documentation or the code itself, that's great! But please read the [CONTRIBUTING.md](CONTRIBUTING.md) file before submitting a pull request.


# Example
# Example and ExampleVisionOS

See the included project for an example of how Euclid can be used in conjunction with SceneKit or RealityKit to generate and render a nontrivial 3D shape on iOS.
See the included projects for examples of how Euclid can be used in conjunction with SceneKit or RealityKit to generate and render a nontrivial 3D shape. `Example` uses storyboards, is built for iOS, and runs in "Designed for iPad" mode on macOS and visionOS.
`ExampleVisionPro` uses SwiftUI and a RealityView in a volumetric window, and runs only on visionOS.


# Documentation
Expand Down
Loading