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

Add SwiftUI visionOS example. #115

Merged
merged 7 commits into from
Mar 9, 2024
Merged
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
207 changes: 200 additions & 7 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()
}
26 changes: 26 additions & 0 deletions ExampleVisionOS/EuclidMesh.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
//
// EuclidMesh.swift
// ExampleVisionOS
//
// Created by Hal Mueller on 3/5/24.
// Copyright © 2024 Nick Lockwood. All rights reserved.
//

import CoreGraphics
import Euclid

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()
Copy link

Choose a reason for hiding this comment

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

I know this PR is closed, but (and maybe this is the wrong place to ask this question), I'm curious how important makeWatertight() is here.

When running this on device as/is, the print of Time: was ~4.5 seconds. If I remove the call to makeWatertight() and bump the sphere slices parameter down to 60, it runs in around .5 seconds, and there doesn't seem (to me) to be a discernible difference.

Copy link
Owner

Choose a reason for hiding this comment

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

@mgrider Are you sure it wasn't reducing the slices that had the main impact, not removing makeWatertight()? The latter is usually negligible.

In any case, the answer is that non-watertight shapes can cause weird artifacts when performing CSG operations, but in this case since it's the last step you can skip it.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

A better improvement might be to get the construction off the main thread with a Task.

Copy link
Owner

Choose a reason for hiding this comment

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

I almost forgot, if you are running in debug mode, switching to release mode (or just enabling optimized builds for the Euclid module in debug mode, which will still let you debug the example app) should yield a significant speedup.

Copy link

Choose a reason for hiding this comment

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

You're totally right. Adding makeWatertight() back yields: Time: 0.6943490505218506.

Copy link

Choose a reason for hiding this comment

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

And it's just my opinion, but I think a 4.5 second delay in the example project feels broken (or at least not especially great), whereas .5 gets a 🤷‍♂️. Of course there will be ways to optimize, but this is going to be what people see when they're evaluating Euclid as a dependency.

Copy link

Choose a reason for hiding this comment

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

Love that this got made/merged, BTW. Definitely aiming for (hopefully helpful) constructive criticism!

Copy link
Contributor Author

Choose a reason for hiding this comment

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

There's still a problem with blocking the main thread. What I had missed earlier is that Apple's RK samples all use the try! await Entity(named: "MyEntity") idiom, calling the async method to load from a file. There is no async version of init(_ mesh:...), so we should be calling that in a task and adding the content when it's ready. A real-world user of the framework may have very complicated/expensive meshes to build. Let me poke at that a bit and I'll post a new PR/branch.

Copy link
Owner

Choose a reason for hiding this comment

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

Everything in Euclid should be safe to run on a background thread. Distinct meshes should be safe to create in parallel on multiple threads/tasks as there's no global shared state.

Euclid already makes some use of multithreading internally which may not play nicely with async/await but is probably OK. Search for "batch" in the codebase if you're curious.

I plan to add async interfaces at some point, but it's complicated for various reasons. You're more than welcome to take a stab at it if you like.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

See what we think about #116. And let's continue conversation on that PR, close out this thread.


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

return mesh
}()
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>
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
{
"info" : {
"author" : "xcode",
"version" : 1
}
}
53 changes: 53 additions & 0 deletions ExampleVisionOS/VolumetricView.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
//
// 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

var body: some View {
RealityView { content in
if let demoBoxEntity = try? ModelEntity(euclidMesh.scaled(by: 0.5)) {
// for more realism, add a shadow
demoBoxEntity.components.set(GroundingShadowComponent(castsShadow: true))

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

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

content.add(demoBoxEntity)
}
} update: { content in
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
}
)
}
}

#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