I figured it out with the help of an Apple engineer. He basically confirmed that my initial (much simpler) idea was right and so I made a small demo immersive view that shows how to construct physical joints from code:
import SwiftUI
import RealityKit
import RealityKitContent
extension Entity {
func makePhysical(
_ mesh: MeshResource,
mode: PhysicsBodyMode,
_ linDamp: Float,
_ angDamp: Float,
_ color: UIColor,
_ position: simd_float3,
relative: Entity? = nil) -> ModelEntity {
let color = SimpleMaterial(color: color, isMetallic: true)
let entity = ModelEntity(mesh: mesh, materials: [color])
entity.setPosition(position, relativeTo: relative)
self.addChild(entity)
let shape = ShapeResource.generateConvex(from: mesh)
let material = PhysicsMaterialResource.generate(staticFriction: 100000, dynamicFriction: 100000, restitution: 1)
var physicsBody = PhysicsBodyComponent(shapes: [shape], mass: 0.1, material: material, mode: mode)
physicsBody.linearDamping = linDamp
physicsBody.angularDamping = angDamp
entity.components.set(physicsBody)
entity.components.set(CollisionComponent(shapes: [shape], isStatic: false))
return entity
}
}
extension PhysicsCustomJoint {
static func addFixedJoint(_ entity1: Entity, _ entity2: Entity, _ pos1: simd_float3, _ pos2: simd_float3) {
let joint = PhysicsFixedJoint(
pin0: entity1.pins.set(named: UUID().uuidString, position: pos1),
pin1: entity2.pins.set(named: UUID().uuidString, position: pos2)
)
try! joint.addToSimulation()
}
static func addBallJoint(
_ entity1: Entity,
_ entity2: Entity,
_ pos1: simd_float3,
_ pos2: simd_float3) {
var joint = PhysicsCustomJoint(
pin0: entity1.pins.set(named: UUID().uuidString, position: pos1),
pin1: entity2.pins.set(named: UUID().uuidString, position: pos2)
)
joint.angularMotionAroundX = .unlimited
joint.angularMotionAroundY = .unlimited
joint.angularMotionAroundZ = .unlimited
joint.linearMotionAlongX = .fixed
joint.linearMotionAlongY = .fixed
joint.linearMotionAlongZ = .fixed
try! joint.addToSimulation()
}
}
struct ImmersiveView: View {
var body: some View {
RealityView { content in
let root = Entity()
content.add(root)
var simulation = PhysicsSimulationComponent()
// simulation.gravity = simd_float3(0, -9.8, 0)
simulation.solverIterations = .init(positionIterations: 2, velocityIterations: 2)
root.components.set(simulation)
let ball1 = root.makePhysical(.generateSphere(radius: 0.05), mode: .kinematic, 0, 0, .red, [0, 1.75, -0.75])
let rod1 = root.makePhysical(.generateCylinder(height: 0.4, radius: 0.025), mode: .dynamic, 0.2, 0, .green, [0, -0.2, 0], relative: ball1)
let ball2 = root.makePhysical(.generateSphere(radius: 0.05), mode: .dynamic, 0.2, 0, .blue, [0, 0.2, 0], relative: rod1)
let rod2 = root.makePhysical(.generateCylinder(height: 0.4, radius: 0.025), mode: .dynamic, 0.2, 0, .magenta, [0, -0.2, 0], relative: ball2)
let ball3 = root.makePhysical(.generateSphere(radius: 0.05), mode: .dynamic, 0.2, 0, .yellow, [0, 0.2, 0], relative: rod2)
let rod3 = root.makePhysical(.generateCylinder(height: 0.4, radius: 0.025), mode: .dynamic, 0.2, 0, .cyan, [0, -0.2, 0], relative: ball3)
let ball4 = root.makePhysical(.generateSphere(radius: 0.05), mode: .dynamic, 0.2, 0, .black, [0, 0.2, 0], relative: rod3)
let block = root.makePhysical(.generateCylinder(height: 2, radius: 0.025), mode: .kinematic, 0, 0, .white, .zero, relative: nil)
PhysicsCustomJoint.addBallJoint(ball1, rod1, .zero, [0, 0.2, 0])
PhysicsCustomJoint.addFixedJoint(rod1, ball2, [0, -0.2, 0], .zero)
PhysicsCustomJoint.addBallJoint(ball2, rod2, .zero, [0, 0.2, 0])
PhysicsCustomJoint.addFixedJoint(rod2, ball3, [0, -0.2, 0], .zero)
PhysicsCustomJoint.addBallJoint(ball3, rod3, .zero, [0, 0.2, 0])
PhysicsCustomJoint.addFixedJoint(rod3, ball4, [0, -0.2, 0], .zero)
Timer.scheduledTimer(withTimeInterval: 0.01, repeats: true) { _ in
Task {
let f = Date().timeIntervalSince1970 * 4
let y = await ball1.position.y
var x = Float(sin(f) / 5.0)
var z = Float(cos(f) / 5.0)
await ball1.setPosition([x, y, z - 0.75], relativeTo: nil)
x = Float(sin(-f * 0.97341) / 3.0)
z = Float(cos(-f * 0.97341) / 3.0)
await block.setPosition([x, y - 0.3, z - 0.75], relativeTo: nil)
}
}
}
}
}