Skip to content

Commit 7985557

Browse files
authored
Assistant APIs (#100)
1 parent 90b747e commit 7985557

File tree

12 files changed

+135
-46
lines changed

12 files changed

+135
-46
lines changed

Sources/VimKit/Geometry+Mesh.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ import VimKitShaders
1010
extension Instance {
1111

1212
/// Convenience var that returns the bounding box.
13-
var boundingBox: MDLAxisAlignedBoundingBox {
13+
public var boundingBox: MDLAxisAlignedBoundingBox {
1414
.init(maxBounds: maxBounds, minBounds: minBounds)
1515
}
1616

Sources/VimKit/Geometry.swift

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -916,6 +916,14 @@ extension Geometry {
916916

917917
extension Geometry {
918918

919+
/// Returns the instance with the specified id.
920+
/// - Parameter id: the instance id
921+
/// - Returns: the instance with the specified id or nil
922+
public func instance(id: Int) -> Instance? {
923+
guard let index = instanceOffsets.firstIndex(of: id) else { return nil }
924+
return instances[index]
925+
}
926+
919927
/// Toggles the instance hidden state to `.selected` or
920928
/// - Parameters:
921929
/// - id: the index of the instances to select or deselect
@@ -935,6 +943,15 @@ extension Geometry {
935943
}
936944
}
937945

946+
/// Toggles the instance hidden state of all instances from `.selected` to `.default`
947+
public func deselectAll() {
948+
for (i, value) in instances.enumerated() {
949+
if value.state == .selected {
950+
instances[i].state = .default
951+
}
952+
}
953+
}
954+
938955
/// Convenience var that returns a count of the selected instances.
939956
public var selectedCount: Int {
940957
instances.filter{ $0.state == .selected }.count

Sources/VimKit/Renderer/RenderPass+Indirect.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -53,7 +53,7 @@ class RenderPassIndirect: RenderPass {
5353

5454
/// Boolean flag indicating if indirect command buffers should perform depth occlusion testing or not.
5555
/// Frustum testing will always happen
56-
open var enableDepthTesting: Bool {
56+
var enableDepthTesting: Bool {
5757
context.vim.options.enableDepthTesting
5858
}
5959

Sources/VimKit/Renderer/RenderPass+Shapes.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,7 @@ class RenderPassShapes: RenderPass {
5050
encode(descriptor: descriptor, renderEncoder: renderEncoder)
5151

5252
// Draw the clip planes
53-
drawClipPlanes(descriptor: descriptor, renderEncoder: renderEncoder)
53+
//drawClipPlanes(descriptor: descriptor, renderEncoder: renderEncoder)
5454
}
5555

5656
/// Encodes the buffer data into the render encoder.

Sources/VimKit/Renderer/Renderer+Drawing.swift

Lines changed: 3 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -8,14 +8,10 @@
88
import MetalKit
99
import VimKitShaders
1010

11-
/// The render encoder label.
12-
private let renderEncoderLabel = "VimRenderEncoder"
13-
/// The render encoder debug group.
14-
private let renderEncoderDebugGroupName = "VimDrawGroup"
15-
private let labelOnScreenCommandBuffer = "OnScreenCommandBuffer"
11+
/// The label for the off-screen commands to execute. Used by indirect rendering compute encoder to dispatch threads.
1612
private let labelOffScreenCommandBuffer = "OffScreenCommandBuffer"
17-
/// The minimum amount of instanced meshes to implement frustum culling.
18-
private let minFrustumCullingThreshold = 1024
13+
/// The label for the on-screen commands to execute.
14+
private let labelOnScreenCommandBuffer = "OnScreenCommandBuffer"
1915

2016
#if !os(visionOS)
2117

Sources/VimKit/Renderer/Renderer.swift

Lines changed: 13 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -188,22 +188,26 @@ extension Renderer {
188188
/// Informs the renderer that the model was tapped at the specified point.
189189
/// Creates a region at the specified point the size of a single pixel and
190190
/// grabs the byte encoded at that pixel on the instance index texture.
191-
/// - Parameter point: the screen point
192-
public func didTap(at point: SIMD2<Float>) {
191+
/// - Parameters:
192+
/// - pixelLocation: the screen pixel location (unscaled)
193+
/// - displayScale: the screen display scale
194+
public func didTap(at pixelLocation: SIMD2<Float>, _ displayScale: Float = 2.0) {
193195
guard let geometry, let texture = instancePickingTexture else { return }
194-
let region = MTLRegionMake2D(Int(point.x), Int(point.y), 1, 1)
196+
197+
let displayLocation = pixelLocation * displayScale
198+
let region = MTLRegionMake2D(Int(displayLocation.x), Int(displayLocation.y), 1, 1)
195199
let bytesPerRow = MemoryLayout<Int32>.stride * texture.width
196-
var pixel: Int32 = .empty
197-
texture.getBytes(&pixel, bytesPerRow: bytesPerRow, from: region, mipmapLevel: 0)
198-
guard pixel != .empty else {
200+
var pixelBytes: Int32 = .empty
201+
texture.getBytes(&pixelBytes, bytesPerRow: bytesPerRow, from: region, mipmapLevel: 0)
202+
guard pixelBytes != .empty else {
199203
context.vim.erase()
200204
return
201205
}
202206

203-
let id = Int(pixel)
207+
let id = Int(pixelBytes)
204208
guard let index = geometry.instanceOffsets.firstIndex(of: id) else { return }
205209

206-
let query = camera.unprojectPoint(point)
210+
let query = camera.unprojectPoint(displayLocation)
207211
var point3D: SIMD3<Float> = .zero
208212

209213
// Raycast into the instance
@@ -213,7 +217,7 @@ extension Renderer {
213217
}
214218

215219
// Select the instance so the event gets published.
216-
context.vim.select(id: id, point: point3D)
220+
context.vim.select(id: id, pixel: pixelLocation, point: point3D)
217221
}
218222
}
219223

Sources/VimKit/Views/RendererContainerViewCoordinator.swift

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -69,9 +69,10 @@ extension RendererContainerViewCoordinator {
6969
// where UIView 0,0 is in the top left with positive values of Y going down
7070
let frame = view.frame
7171
let location = gesture.location(in: view)
72+
7273
let y = frame.height - location.y // Flip the Y coordinate
73-
let point: SIMD2<Float> = [Float(location.x), Float(y)] * contentScaleFactor
74-
renderer.didTap(at: point)
74+
let point: SIMD2<Float> = [Float(location.x), Float(y)]
75+
renderer.didTap(at: point, contentScaleFactor)
7576
default:
7677
break
7778
}
@@ -84,9 +85,10 @@ extension RendererContainerViewCoordinator {
8485
guard let view = gesture.view else { return }
8586
switch gesture.state {
8687
case .recognized:
87-
let location = gesture.location(in: view) * view.contentScaleFactor
88+
let contentScaleFactor = Float(view.contentScaleFactor)
89+
let location = gesture.location(in: view)
8890
let point: SIMD2<Float> = [Float(location.x), Float(location.y)]
89-
renderer.didTap(at: point)
91+
renderer.didTap(at: point, contentScaleFactor)
9092
default:
9193
break
9294
}

Sources/VimKit/Vim+Camera.swift

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -213,6 +213,22 @@ extension Vim {
213213
position = p
214214
}
215215

216+
/// Frames the bounding box by zooming to it's extents and applies clip planes around the box if specified.
217+
/// - Parameters:
218+
/// - box: the box to frame
219+
/// - clip: if true, the camera will add clip planes around to the box.
220+
public func zoom(to box: MDLAxisAlignedBoundingBox, clip: Bool = false) {
221+
if clip {
222+
clipPlanes = box.planes
223+
}
224+
let center = box.center
225+
let radius = box.radius
226+
let fovRadians = fovDegrees.radians
227+
let distance = radius / sin(fovRadians / 2)
228+
let eye = center - distance * forward
229+
look(at: box.center, from: eye)
230+
}
231+
216232
/// Projects a point from the 3D world coordinate system of the scene to the 2D pixel coordinate system.
217233
/// - Parameters:
218234
/// - point: A point in the world coordinate system of the scene.

Sources/VimKit/Vim.swift

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -30,9 +30,9 @@ public class Vim: NSObject, ObservableObject, @unchecked Sendable {
3030
/// An instance has been selected or deselected.
3131
/// - Parameter Int: the instance identifer
3232
/// - Parameter Bool: true if the instance has been selected, false if de-selected.
33-
/// - Parameter Int: the total count of selected instances.
33+
/// - Parameter SIMD2<Float>: the 2D pixel point on the screen that was selected
3434
/// - Parameter SIMD3<Float>: the 3D positon of the selected object.
35-
case selected(Int, Bool, Int, SIMD3<Float>)
35+
case selected(Int, Bool, SIMD2<Float>, SIMD3<Float>)
3636

3737
/// An instance has been hidden or shown.
3838
/// - Parameter Int: the total count of hidden instances.
@@ -290,18 +290,20 @@ extension Vim {
290290

291291
/// Erases a previous published event to downstream event subscribers.
292292
public func erase() {
293+
geometry?.deselectAll()
293294
eventPublisher.send(.empty)
294295
}
295296

296297
/// Toggles an instance selection state for the instance with the specified id.
297298
/// - Parameters:
298299
/// - id: the id of the instance to select (or deselect if already selected).
300+
/// - pixel: the pixel location of the selection point inside the 2D viewport
299301
/// - point: the point in 3D space where the object was selected
300302
@MainActor
301-
public func select(id: Int, point: SIMD3<Float> = .zero) {
303+
public func select(id: Int, pixel: SIMD2<Float> = .zero, point: SIMD3<Float> = .zero) {
302304
guard let geometry else { return }
303305
let selected = geometry.select(id: id)
304-
eventPublisher.send(.selected(id, selected, 1, point))
306+
eventPublisher.send(.selected(id, selected, pixel, point))
305307
}
306308

307309
/// Toggles an instance hidden state for the instance with the specified id

Sources/VimKitShaders/Resources/Indirect.metal

Lines changed: 10 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -54,18 +54,19 @@ static bool isInsideViewFrustumAndClipPlanes(const Camera camera,
5454
}
5555

5656
const float4 clipPlane = camera.clipPlanes[i];
57-
57+
const float3 planeNormal = normalize(clipPlane.xyz);
58+
5859
// Skip the plane if it's not valid
5960
if (isinf(clipPlane.w)) { continue; }
6061

61-
if (-dot(clipPlane.xyz, corners[0].xyz) + clipPlane.w < 0 &&
62-
-dot(clipPlane.xyz, corners[1].xyz) + clipPlane.w < 0 &&
63-
-dot(clipPlane.xyz, corners[2].xyz) + clipPlane.w < 0 &&
64-
-dot(clipPlane.xyz, corners[3].xyz) + clipPlane.w < 0 &&
65-
-dot(clipPlane.xyz, corners[4].xyz) + clipPlane.w < 0 &&
66-
-dot(clipPlane.xyz, corners[5].xyz) + clipPlane.w < 0 &&
67-
-dot(clipPlane.xyz, corners[6].xyz) + clipPlane.w < 0 &&
68-
-dot(clipPlane.xyz, corners[7].xyz) + clipPlane.w < 0) {
62+
if (-dot(planeNormal, corners[0].xyz) + clipPlane.w < 0 &&
63+
-dot(planeNormal, corners[1].xyz) + clipPlane.w < 0 &&
64+
-dot(planeNormal, corners[2].xyz) + clipPlane.w < 0 &&
65+
-dot(planeNormal, corners[3].xyz) + clipPlane.w < 0 &&
66+
-dot(planeNormal, corners[4].xyz) + clipPlane.w < 0 &&
67+
-dot(planeNormal, corners[5].xyz) + clipPlane.w < 0 &&
68+
-dot(planeNormal, corners[6].xyz) + clipPlane.w < 0 &&
69+
-dot(planeNormal, corners[7].xyz) + clipPlane.w < 0) {
6970
// Not visible - all corners returned negative
7071
return false;
7172
}

0 commit comments

Comments
 (0)