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

WIP: Vertex reflection along a plane #129

Closed
wants to merge 33 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
33 commits
Select commit Hold shift + click to select a range
caea2cb
Add SwiftUI visionOS example. (#115)
halmueller Mar 9, 2024
5f6ffe2
Tweak build settings
nicklockwood Feb 18, 2024
6a680f3
Rename [Vertex].mapVertxColors() to mapColors()
nicklockwood Aug 1, 2024
fa90bab
Add Polygon.mapVertices()
nicklockwood Aug 1, 2024
6d3d87a
Fix documentation typo
nicklockwood Aug 1, 2024
f6e2f7f
Add subdivide() functions
nicklockwood Aug 1, 2024
646ab0d
Add Mesh.icosphere() primitive
nicklockwood Aug 1, 2024
4f6f17b
Add Polygon.inset(by:)
nicklockwood Nov 12, 2022
4d9ebab
Path CSG WIP
nicklockwood Aug 7, 2024
d12c0b8
fix
nicklockwood Aug 22, 2023
f34fb65
Add Mesh.stlString() export options
nicklockwood Aug 20, 2023
63bbbcd
Add Mesh.stlData() export options
nicklockwood Aug 20, 2023
af5bbad
Add XYZRepresentable and RGBARepresentable protocols
nicklockwood Nov 28, 2022
4ac7fba
Add Direction type
nicklockwood Jan 21, 2023
a126aa4
Add Direction-based alternative APIs
nicklockwood Jan 22, 2023
191379f
Add Position type (WIP)
nicklockwood Jan 22, 2023
467f673
Polygon CSG WIP
nicklockwood Jan 16, 2023
0ef0310
Add Path.inset(by:)
nicklockwood Feb 21, 2022
9361eaf
Add Mesh.inset(by:)
nicklockwood Aug 9, 2022
6505ef5
Improve Mesh.inset(by:) performance
nicklockwood Feb 22, 2022
d0447bb
Add BSP.isInverted
nicklockwood Aug 14, 2022
0dddc1f
Correctly handle containsPoint() for inverted meshes
nicklockwood Aug 14, 2022
379a0f7
tmp
nicklockwood Mar 18, 2023
81667a5
sugar (TMP)
nicklockwood Nov 28, 2022
2862265
WIP
nicklockwood Jan 15, 2023
71efd5f
WIP
nicklockwood Dec 22, 2023
375515d
Remove Quaternion
nicklockwood Jan 31, 2024
9643e62
Remove deprecated methods
nicklockwood Jan 31, 2024
eee79b3
Do not automatically strip flat-shaded vertex normals (#127)
zilmarinen Aug 11, 2024
0100e5e
Add methods to reflect mesh polygons along a plane
zilmarinen Aug 30, 2024
c004f95
Add quad reflection unit test
zilmarinen Aug 30, 2024
1d5ba3a
Add method to reflect vertex along a plane
zilmarinen Aug 30, 2024
4b86e6e
Fix incorrect inversion of polygon vertices
zilmarinen Aug 31, 2024
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
4 changes: 2 additions & 2 deletions Euclid.docc/Extensions/Color.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,11 @@

### Creating a Color

- ``Color/init(r:g:b:a:)``
- ``Color/init(_:_:_:_:)``
- ``Color/init(_:_:)``
- ``Color/init(_:)-25eby``
- ``Color/init(_:)-53lhy``
- ``Color/init(_:)-7d8un``
- ``Color/init(_:)-9bvpm``

### Default Colors

Expand Down
8 changes: 3 additions & 5 deletions Euclid.docc/Extensions/Vector.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,14 +4,12 @@

### Creating Vectors

- ``Vector/init(_:)-228p6``
- ``Vector/init(_:)-4eop9``
- ``Vector/init(_:)-5n3j``
- ``Vector/init(_:)-63ct7``
- ``Vector/init(_:)-6nlm``
- ``Vector/init(x:y:z:)``
- ``Vector/init(_:_:_:)``
- ``Vector/init(size:)-8b34m``
- ``Vector/init(size:)-nkyk``
- ``Vector/init(_:)-63ct7``
- ``Vector/init(_:)-602vn``

### Default Vectors

Expand Down
258 changes: 238 additions & 20 deletions Euclid.xcodeproj/project.pbxproj

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion Euclid.xcodeproj/xcshareddata/xcschemes/Euclid.xcscheme
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@
</Testables>
</TestAction>
<LaunchAction
buildConfiguration = "Release"
buildConfiguration = "Debug"
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
launchStyle = "0"
Expand Down
25 changes: 23 additions & 2 deletions Example/SceneKitViewController.swift
Original file line number Diff line number Diff line change
Expand Up @@ -25,15 +25,36 @@ class SceneKitViewController: UIViewController {
// place the camera
cameraNode.position = SCNVector3(x: 0, y: 0, z: 2)

// create some geometry using Euclid
let start = CFAbsoluteTimeGetCurrent()
// let cube = Mesh.cube(size: 0.8, material: UIColor.red)
// let sphere = Mesh.sphere(slices: 120, material: UIColor.blue)
// let mesh = cube.subtracting(sphere).makeWatertight()

// let cube = Mesh.cube().inverted()
// let cube2 = Mesh.cube().inverted().translated(by: Vector(0.5, 0.5, 0.5))
// let mesh = cube.union(cube2).inverted()

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

let polygon1 = Path.square(color: .red)
let polygon2 = Path.square(color: .green).translated(by: Vector(0.5, 0.5, 0)) // .rotated(by: .yaw(.pi / 5))
let polygon3 = Path.square(color: .blue).translated(by: Vector(0.25, -0.25, 0)) // .rotated(by: .yaw(.pi / 5))
let result = Path.difference([polygon1, polygon2, polygon3])
let mesh = Mesh.fill(result)

// create SCNNode
let geometry = SCNGeometry(euclidMesh)
let geometry = SCNGeometry(mesh)
let node = SCNNode(geometry: geometry)
scene.rootNode.addChildNode(node)

// configure the SCNView
let scnView = view as! SCNView
scnView.scene = scene
scnView.autoenablesDefaultLighting = true
// scnView.autoenablesDefaultLighting = true
scnView.allowsCameraControl = true
scnView.backgroundColor = .white
}
Expand Down
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()

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
8 changes: 4 additions & 4 deletions Sources/Angle.swift
Original file line number Diff line number Diff line change
Expand Up @@ -98,13 +98,13 @@ public func tan(_ angle: Angle) -> Double {

public extension Angle {
/// Angle representing a zero (identity) rotation.
static let zero = Angle.radians(0)
static let zero: Angle = .radians(0)
/// Angle representing a quarter rotation.
static let halfPi = Angle.radians(.pi / 2)
static let halfPi: Angle = .radians(.pi / 2)
/// Angle representing a half-rotation.
static let pi = Angle.radians(.pi)
static let pi: Angle = .radians(.pi)
/// Angle representing a full rotation.
static let twoPi = Angle.radians(.pi * 2)
static let twoPi: Angle = .radians(.pi * 2)

/// The angle in degrees.
var degrees: Double {
Expand Down
21 changes: 15 additions & 6 deletions Sources/BSP.swift
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@
struct BSP {
private var nodes: [BSPNode]
private(set) var isConvex: Bool
private(set) var isInverted: Bool
}

extension BSP {
Expand All @@ -45,9 +46,14 @@ extension BSP {
}

init(_ mesh: Mesh, _ isCancelled: CancellationHandler) {
self.init(mesh.polygons, isConvex: mesh.isKnownConvex, isCancelled)
}

init(_ polygons: [Polygon], isConvex: Bool, _ isCancelled: CancellationHandler) {
self.nodes = [BSPNode]()
self.isConvex = mesh.isKnownConvex
initialize(mesh.polygons, isCancelled)
self.isConvex = isConvex
self.isInverted = false
initialize(polygons, isCancelled)
}

func clip(
Expand Down Expand Up @@ -136,17 +142,20 @@ private extension BSP {
}

mutating func initialize(_ polygons: [Polygon], _ isCancelled: CancellationHandler) {
guard !polygons.isEmpty else {
return
}

var rng = DeterministicRNG()

guard isConvex else {
guard !polygons.isEmpty else {
return
}
let startPlane = polygons[0].plane
// Randomly shuffle polygons to reduce average number of splits
let polygons = polygons.shuffled(using: &rng)
nodes.reserveCapacity(polygons.count)
nodes.append(BSPNode(plane: polygons[0].plane))
nodes.append(BSPNode(plane: startPlane))
insert(polygons, isCancelled)
isInverted = containsPoint(startPlane.normal * .greatestFiniteMagnitude)
return
}

Expand Down
Loading
Loading