Entity disappears at the end of the animation - swift

I'm trying to animate the movement of an entity but at the end of the animation the entity disappears. It occurs if you animate the scale or translation but not rotation. I'm not sure if it's a bug or expected behaviour but I would like to find a way to stop it.
let transform = Transform(scale: simd_float3.one,
rotation: simd_quatf(),
translation: [0.05, 0, 0])
let animationDefinition = FromToByAnimation<Transform>(by: transform,
duration: 1.0,
bindTarget: .transform)
if let animationResource = try? AnimationResource.generate(with: animationDefinition) {
entity.playAnimation(animationResource)
}
I know you can use entity.move() and that works fine but I want to explore other ways to animate entities.

This transform animation works as expected. Fix the opacity of your model, if it has translucent materials (for that use USDZ Python Tools commands fixOpacity and usdARKitChecker). Also, check if any transformation ​​are applied to the entity on which you are running the transform animation.
let boxScene = try! Experience.loadBox()
boxScene.children[0].scale *= 3
arView.scene.anchors.append(boxScene)
let entity = boxScene.children[0].children[0]
let transform = Transform(scale: simd_float3.init(2, 2, 2),
rotation: simd_quatf.init(angle: .pi, axis: [1, 1, 1]),
translation: [0.4, 0, 0])
let animationDefinition = FromToByAnimation<Transform>(by: transform,
duration: 2.0,
bindTarget: .transform)
if let anime = try? AnimationResource.generate(with: animationDefinition) {
entity.playAnimation(anime)
}
As you can see from this example, after applying the animation, the entity does not disappear.

Related

How to get true Orientation/Rotation value after a Transform applied?

In my code, I am currently loading an Entity using the .loadAsync(named: String) method, then adding to my existing AnchorEntity. As a test, I am then rotating my Entity 90°, and would like to determine how to then get the current angle of rotation.
The long-term intent is that I am going to allow users to rotate a model, but want to limit the rotation to a certain degree (I.E., the user can rotate the pitch of the model to 90° or -90°, but no further than that). Without being able to know the current angle of rotation for the Entity, I am unsure what logic I could use to limit this.
Entity.loadAsync(named: "myModel.usdz")
.receive(on: RunLoop.main)
.sink { completion in
// ...
} receiveValue: { [weak self] entity in
guard let self = self else { return }
self.objectAnchor.addChild(entity)
scene.addAnchor(objectAnchor)
let transform = Transform(pitch: .pi / 2,
yaw: .zero,
roll: .zero)
entity.setOrientation(transform.rotation,
relativeTo: nil)
print(entity.orientation)
// Sample Output: simd_quatf(real: 0.7071069,
// imag: SIMD3<Float>(0.7071067, 0.0, 0.0))
}
.store(in: &subscriptions)
I would have expected entity.orientation to give me something like 90.0 or 1.57 (.pi / 2), but unsure how I can get the current rotation of the Entity in a form that would align with expected angles.
simd_quatf
In RealityKit 2.0, when retrieving simd_quatf structure's values from .orientation and .transform.rotation instance properties, the default initializer brings real (scalar) and imaginary (vector) parts of a Quaternion:
public init(real: Float, imag: SIMD3<Float>)
Hamilton's quaternion expression looks like this:
In your case, the value 0.707 corresponds to a rotation angle of 45 degrees. If you set both real (a) and imag.x (bi) to 0.707, you'll get a total rotation angle of 90 degrees for X axis.
It's easy to check:
let quaternion = simd_quatf(real: 0.707, imag: [0.707, 0, 0]) // 90 degrees
entity.orientation = quaternion
To check what "readable" model's orientation is, use regular parameters from another initializer:
public init(angle: Float, axis: SIMD3<Float>)
print(entity.orientation.angle) // 1.5707964
print(entity.orientation.axis) // SIMD3<Float>(0.99999994, 0.0, 0.0)

Within RealityKit, how can I make the world without friction?

I want to make the physics world without friction and damping.
I tried to make the scene's gravity to (0,0,0), and make a square and ball, give force when tapping. I want to make the ball move eternally, but it just stop in some time.
How can I make the entities friction to zero?
Apply a new Physics Material to your model entity.
For this use generate(friction:restitution:) type method:
static func generate(friction: Float = 0,
restitution: Float = 0) -> PhysicsMaterialResource
where
/* the coefficient of friction is in the range [0, infinity] */
/* and the coefficient of restitution is in the range [0, 1] */
Here's a code:
arView.environment.background = .color(.darkGray)
let mesh = MeshResource.generateSphere(radius: 0.5)
let material = SimpleMaterial()
let model = ModelEntity(mesh: mesh,
materials: [material]) as (ModelEntity & HasPhysics)
let physicsResource: PhysicsMaterialResource = .generate(friction: 0,
restitution: 0)
model.components[PhysicsBodyComponent] = PhysicsBodyComponent(
shapes: [.generateSphere(radius: 0.51)],
mass: 20, // in kilograms
material: physicsResource,
mode: .dynamic)
model.generateCollisionShapes(recursive: true)
let anchor = AnchorEntity()
anchor.addChild(model)
arView.scene.anchors.append(anchor)
P.S. Due to some imperfectness of physics engine in RealityKit, I suppose there's no possibility to create an eternal bouncing. Seemingly next RealityKit's update will fix physics engine imperfectness.

I've tried to give a physicsBodyComponet to modelEntity, and It just falls deep down

On reality kit, I've tried to give a physicsBodyComponent to a modelEntity.
But as I put to modelEntity to real world, It just fall down.
Is there anyway to fix this?
You need to create a floor mesh with a PhysicsBodyComponent:
let floor = ModelEntity(mesh: .generateBox(size: [1000, 0, 1000]), materials: [SimpleMaterial()])
floor.generateCollisionShapes(recursive: true)
if let collisionComponent = floor.components[CollisionComponent] as? CollisionComponent {
floor.components[PhysicsBodyComponent] = PhysicsBodyComponent(shapes: collisionComponent.shapes, mass: 0, material: nil, mode: .static)
floor.components[ModelComponent] = nil // make the floor invisible
}
scene?.addChild(floor)
Then, when you load your entities, you also give them a PhysicsBodyComponent (and they need a non-zero mass, otherwise they will anyways fall through, which is what eluded me for a long time):
var loadModelCancellable: AnyCancellable? = nil
loadModelCancellable = Entity.loadModelAsync(named: modelUri)
.sink(receiveCompletion: { _ in
loadModelCancellable?.cancel()
}, receiveValue: { entity in
entity.generateCollisionShapes(recursive: true)
if let collisionComponent = entity.components[CollisionComponent] as? CollisionComponent {
entity.components[PhysicsBodyComponent] = PhysicsBodyComponent(shapes: collisionComponent.shapes, mass: 1, material: nil, mode: .dynamic)
}
scene.addChild(entity)
loadModelCancellable?.cancel()
})
In the end, adding physics to my project had too many unintended consequences for what I was trying to do (just preventing models to overlap), like models pushing each other, and movements needing to be redone completely, ... So I didn't get further than this, but at least this should let you add physics to your models without them falling indefinitely from gravity.

SKAction with Key changes Sprite Behavior

I've been learning Swift while laid up from back surgery and any help with this is greatly appreciated.
Issue Description:
I have a function that pushes a sprite backwards when hit. When I use
self.runAction(jumpBackSequence)
I get the desired behavior. (The sprite is pushed back a good distance.)
Now when I update this to use "withKey" (because I need to be able to stop the forward action again on the next contact the forward movement is greatly dimished.
self.runAction(jumpBackSequence, withKey: "moveStraightLine")
My function creating this action is below:
func pushBack(){
self.removeActionForKey("moveStraightLine")
let height:CGFloat = 5
let jumpDistanceA:CGFloat = 20
let jumpDistanceB:CGFloat = 20
let duration:NSTimeInterval = 0.15
let jumpUp = SKAction.moveByX(0, y:height, duration:duration)
let moveBack = SKAction.moveByX(jumpDistanceA, y: 0, duration: duration)
let jStep1 = SKAction.group([jumpUp, moveBack])
let jumpDown = SKAction.moveByX(0, y:(height * -1), duration:duration)
let moveBack2 = SKAction.moveByX(jumpDistanceB, y: 0, duration: duration)
let jStep2 = SKAction.group([jumpDown, moveBack2])
let moveZombie1 = SKAction.moveToX(0, duration: spawnSpeed1() )
let removeZombie1 = SKAction.removeFromParent()
let moveAndRemoveZombie1 = SKAction.sequence([moveZombie1, removeZombie1])
let jumpBackSequence = SKAction.sequence([jStep1, jStep2, moveAndRemoveZombie1])
self.runAction(jumpBackSequence)
// self.runAction(jumpBackSequence, withKey: "moveStraightLine")
}
You are using moveTo, not moveBy, so when you add the action back in to move the zombie, you are now saying starting from the closer spot, take X seconds to move to the end, not take X seconds - the time already travelled. You do not want this due to the effect you see. Use moveBy, and the zombie speed will stay consistant
I'm not sure why this behavior happened, but I realized that the runAction withKey was actually moving the code the specified distance where as the example without the key was not. When I updated the code to use a fraction of screen width it behaves the same with and without a key - which makes me think this might actually be an minor bug in Swift.
To fix the issue I updated the distances to
let jumpDistanceA:CGFloat = (self.scene?.size.width)! * 0.15
let jumpDistanceB:CGFloat = (self.scene?.size.width)! * 0.15
I hope this helps someone else.
Best regards,
mpe

How to combine to two or more SCNGeometry / SCNNode into one

As if I would like to create some custom shape by combining some SCNBox and SCNPyramid etc, I can put them together by setting the right positions and geometries. How ever I just can not find a way to combine them as a single unit which can be modified or reacted in physical world.
The code below that I would like create a simple house shape SCNNode, and I would like the node attached each other when affected by any collisions and gravity.
Can anyone give some hints?
let boxGeo = SCNBox(width: 5, height: 5, length: 5, chamferRadius: 0)
boxGeo.firstMaterial?.diffuse.contents = UIColor.blueColor()
let box = SCNNode(geometry: boxGeo)
box.position = SCNVector3Make(0, -2.5, 0)
scene.rootNode.addChildNode(box)
let pyramidGeo = SCNPyramid(width: 7, height: 7, length: 7)
pyramidGeo.firstMaterial?.diffuse.contents = UIColor.greenColor()
let pyramid = SCNNode(geometry: pyramidGeo)
pyramid.position = SCNVector3Make(0, 0, 0)
scene.rootNode.addChildNode(pyramid)
Make a container node, simply an empty node without any geometry. Let's call it "houseNode" since that's what it looks like you're building.
let houseNode = SCNNode()
Now make your other two nodes children of this.
houseNode.addChildNode(pyramid)
houseNode.addChildNode(box)
Now use the container node anytime you want to act on the two combined nodes.
Edit: You can effect changes to the geometry of the objects in your container by enumeration:
houseNode.enumerateChildNodesUsingBlock({
node, stop in
// change the color of all the children
node.geometry?.firstMaterial?.diffuse.contents = UIColor.purpleColor()
// I'm not sure on this next one, I've yet to use "physics".
houseNode.physicsBody?.affectedByGravity = true
})
Thanks bpedit!
With the childNodes method and the following code to set it a physicsShape, I found the solution for it.
houseNode.physicsBody = SCNPhysicsBody(type: .Dynamic,
shape: SCNPhysicsShape(node: houseNode,
options: [SCNPhysicsShapeKeepAsCompoundKey: true]))