Canvatorium Visio Labs 5027 and 5028
I’d like your feedback on two transformation modes I’m working on for Project Graveyard.
Lab 5027: Modify Transform with System Gestures
Testing multiple system gestures in one Lab: (1) Sequenced long-press and drag gestures (Lab 5026). (2) Combined rotation and scale gesture (Lab 5025).
Lab 5028: Modify Transform with Indirect Mode and a single Drag Gesture.
Use a toolbar to select a transformation mode. All transformations are done with a drag gesture.
I’m leaning towards the approach from Lab 5027 for Project Graveyard, but I’d like to hear your thoughts.
Lab 5027 Code
struct Lab5027: View {
@State private var entityTransformAtStartOfGesture: Transform?
var body: some View {
RealityView { content in
if let root = try? await Entity(named: "GestureLab", in: realityKitContentBundle) {
root.position = [0, -0.45, 0]
root.scale = [2,2,2]
if let subject = root.findEntity(named: "Cube") {
subject.components.set(HoverEffectComponent())
subject.components.set(GroundingShadowComponent(castsShadow: false))
}
content.add(root)
}
}
.gesture(longPress.sequenced(before: dragGesture))
.gesture(rotateScaleGesture)
}
var longPress: some Gesture {
LongPressGesture(minimumDuration: 0.25)
.targetedToAnyEntity()
.onEnded { value in
value.entity.position.y = value.entity.position.y + 0.01
if var shadow = value.entity.components[GroundingShadowComponent.self] {
shadow.castsShadow = true
value.entity.components.set(shadow)
}
if let model = value.entity.components[ModelComponent.self] {
print("material", model)
if let mat = model.materials.first {
print("material", mat)
// I have a material here but I can't set any properties?
// mat.diffuseColor does not exist
// PhysicallyBasedMaterial
}
}
}
}
var dragGesture: some Gesture {
DragGesture()
.targetedToAnyEntity()
.onChanged { value in
let newPostion = value.convert(value.location3D, from: .global, to: value.entity.parent!)
let limit: Float = 0.175
value.entity.position.x = min(max(newPostion.x, -limit), limit)
value.entity.position.z = min(max(newPostion.z, -limit), limit)
}
.onEnded { value in
value.entity.position.y = value.entity.position.y - 0.01
if var shadow = value.entity.components[GroundingShadowComponent.self] {
shadow.castsShadow = false
value.entity.components.set(shadow)
}
}
}
var rotateScaleGesture: some Gesture {
RotateGesture3D(constrainedToAxis: .y)
.simultaneously(with: MagnifyGesture())
.targetedToAnyEntity()
.onChanged { value in
if entityTransformAtStartOfGesture == nil {
entityTransformAtStartOfGesture = value.entity.transform
}
if let rotation = value.first?.rotation, let magnification = value.second?.magnification, let initialScale = entityTransformAtStartOfGesture?.scale.x {
// Rotation
let rotationTransform = Transform(AffineTransform3D(rotation: rotation))
value.entity.transform.rotation = entityTransformAtStartOfGesture!.rotation * rotationTransform.rotation
// Scaling
let scaler = Float(magnification) * initialScale
let minScale: Float = 0.1
let maxScale: Float = 1
let scaled = min(Float(max(Float(scaler), minScale)), maxScale)
let newScale = SIMD3(x: scaled, y: scaled, z: scaled)
value.entity.setScale(newScale, relativeTo: value.entity.parent!)
value.entity.position.y = scaled / 10
}
}
.onEnded { value in
entityTransformAtStartOfGesture = nil
}
}
}
Lab 5028 Code
struct Lab5028: View {
@State private var entityTransformAtStartOfGesture: Transform?
@State var rotation: Angle = .zero
@State private var transformMode: IndirectTransformMode = .none
var body: some View {
ZStack(alignment: Alignment(horizontal: .center, vertical: .bottom)) {
RealityView { content in
if let root = try? await Entity(named: "GestureLab", in: realityKitContentBundle) {
root.position = [0, -0.45, 0]
root.scale = [2,2,2]
if let subject = root.findEntity(named: "Cube") {
subject.components.set(HoverEffectComponent())
subject.components.set(GroundingShadowComponent(castsShadow: false))
}
content.add(root)
}
}
.gesture(dragToTransform)
// Fake toolbar
...
}
}
var dragToTransform: some Gesture {
DragGesture()
.targetedToAnyEntity()
.onChanged { value in
if entityTransformAtStartOfGesture == nil {
entityTransformAtStartOfGesture = value.entity.transform
}
if(transformMode == .move) {
let newPostion = value.convert(value.location3D, from: .global, to: value.entity.parent!)
let limit: Float = 0.175
value.entity.position.x = min(max(newPostion.x, -limit), limit)
value.entity.position.z = min(max(newPostion.z, -limit), limit)
} else if (transformMode == .rotate) {
// Just a hack to rotate by *something* from the drag gesture. I'm sure there is a better way.
rotation.degrees += 0.001 * (value.velocity.width)
let rotationTransform = Transform(yaw: Float(rotation.radians))
value.entity.transform.rotation = entityTransformAtStartOfGesture!.rotation * rotationTransform.rotation
} else if (transformMode == .scale) {
if let initialScale = entityTransformAtStartOfGesture?.scale.x {
// Just a hack to scale by *something* from the drag gesture. I'm sure there is a better way.
let length = value.translation3D.length.magnitude
let scaler = Float(length / 500) * initialScale
let minScale: Float = 0.1
let maxScale: Float = 1
let scaled = min(Float(max(Float(scaler), minScale)), maxScale)
let newScale = SIMD3(x: scaled, y: scaled, z: scaled)
value.entity.setScale(newScale, relativeTo: value.entity.parent!)
value.entity.position.y = scaled / 10
}
}
}
.onEnded { value in
entityTransformAtStartOfGesture = nil
rotation.degrees = 0.0
}
}
}