SceneKit SCNPhysicsVehicle wheels point toward centre of vehicle - swift

I'm trying to get the SceneKit Physics Vehicle demonstration from WWDC working using Swift 2 and also the Scene Editor and .scnassets. I'm adapting this port of the demo to Swift 1.
I've also been experimenting with some of the Model I/O functions, such as SkyBox and normal map generation.
Everything works fine, except that the wheels are rotated 180 degrees around their steering axes, so that the hubcaps point in towards the centre of the car:
Hubcaps pointing inwards
The wheels spin in the right direction, and the car can be driven and steered fine, but it just looks very weird (and the left-right wheelbase is narrower than it should be).
If I comment out the SCNPhysicsVehicle and SCNPhysicsVehicleWheel code, so that it's just geometry with a physics body for the chassis, the geometry displays correctly, with the wheel hubs pointing outward:
Hubcaps pointing outwards
I've tried rotating the wheelnodes using wheelnode0.rotation before defining the SCNPhysicsVehicleWheel, but it has no effect.
I tried reversing the wheel axles with wheel0.axle = SCNVector3(x: 1,y: 0,z: 0) and that makes the wheels face the right way, hubcap outwards, (and they still spin the right way), but the car moves backwards (ie the opposite direction to how the wheels are spinning).
It feels like something weird is going on with how axes are being translated, but I can't figure what it is. I've tried it with the original .dae model from the Apple demo, as well as converted to an .scn file. I've tried it inside and outside an .scnassets folder (in case it was something to do with scnassets auto correction of up axis). I tried inverting the wheel steering axes, but that just resulted in the wheels pointing up in the air.
Here's my vehicle creation function, with all the stuff I've tried commented out. Full repo is here: https://github.com/Utsira/SCNPhysicsVehicle-test. Can anyone see what's going wrong here? Thanks in advance.
func setupCar() -> SCNNode {
let carScene = SCNScene(named: "art.scnassets/rc_car.scn") //rc_car.dae
let chassisNode = carScene!.rootNode.childNodeWithName("rccarBody", recursively: true)!
chassisNode.position = SCNVector3Make(0, 10, 30)
//chassisNode.rotation = SCNVector4(0, 1, 0, CGFloat(M_PI))
let body = SCNPhysicsBody.dynamicBody()
body.allowsResting = false
body.mass = 80
body.restitution = 0.1
body.friction = 0.5
body.rollingFriction = 0
chassisNode.physicsBody = body
scnScene.rootNode.addChildNode(chassisNode)
//getNode("rccarBody", fromDaePath: "rc_car.dae")
let wheelnode0 = chassisNode
.childNodeWithName("wheelLocator_FL", recursively: true)!
let wheelnode1 = chassisNode
.childNodeWithName("wheelLocator_FR", recursively: true)!
let wheelnode2 = chassisNode
.childNodeWithName("wheelLocator_RL", recursively: true)!
let wheelnode3 = chassisNode
.childNodeWithName("wheelLocator_RR", recursively: true)!
//wheelnode0.geometry!.firstMaterial!.emission.contents = UIColor.blueColor()
// SCNTransaction.begin()
// wheelnode0.rotation = SCNVector4(x: 0, y: 1, z: 0, w: Float(M_PI)) //CGFloat(M_PI)
// wheelnode1.rotation = SCNVector4(x: 0, y: 1, z: 0, w: Float(M_PI))
// wheelnode2.rotation = SCNVector4(x: 0, y: 1, z: 0, w: Float(M_PI))
// wheelnode3.rotation = SCNVector4(x: 0, y: 1, z: 0, w: Float(M_PI))
// SCNTransaction.commit()
// wheelnode0.eulerAngles = SCNVector3Make(0, Float(M_PI), 0 )
let wheel0 = SCNPhysicsVehicleWheel(node: wheelnode0)
let wheel1 = SCNPhysicsVehicleWheel(node: wheelnode1)
let wheel2 = SCNPhysicsVehicleWheel(node: wheelnode2)
let wheel3 = SCNPhysicsVehicleWheel(node: wheelnode3)
// wheel0.steeringAxis = SCNVector3Make(0, -1, 1) //wheels point up in the air with 0,1,0
// wheel1.steeringAxis = SCNVector3Make(0, -1, 1)
// wheel2.steeringAxis = SCNVector3Make(0, -1, 1)
// wheel3.steeringAxis = SCNVector3Make(0, -1, 1)
//
// wheel0.axle = SCNVector3(x: 1,y: 0,z: 0) //wheels face and spin the right way, but car moves in opposite direction with 1,0,0
// wheel1.axle = SCNVector3(x: 1,y: 0,z: 0)
// wheel2.axle = SCNVector3(x: 1,y: 0,z: 0)
// wheel3.axle = SCNVector3(x: 1,y: 0,z: 0)
// wheel0.steeringAxis = SCNVector3()
// var min = SCNVector3(x: 0, y: 0, z: 0)
// var max = SCNVector3(x: 0, y: 0, z: 0)
// wheelnode0.getBoundingBoxMin(&min, max: &max)
// let wheelHalfWidth = Float(0.5 * (max.x - min.x))
// var w0 = wheelnode0.convertPosition(SCNVector3Zero, toNode: chassisNode)
// w0 = w0 + SCNVector3Make(wheelHalfWidth, 0, 0)
// wheel0.connectionPosition = w0
// var w1 = wheelnode1.convertPosition(SCNVector3Zero, toNode: chassisNode)
// w1 = w1 - SCNVector3Make(wheelHalfWidth, 0, 0)
// wheel1.connectionPosition = w1
// var w2 = wheelnode2.convertPosition(SCNVector3Zero, toNode: chassisNode)
// w2 = w2 + SCNVector3Make(wheelHalfWidth, 0, 0)
// wheel2.connectionPosition = w2
// var w3 = wheelnode3.convertPosition(SCNVector3Zero, toNode: chassisNode)
// w3 = w3 - SCNVector3Make(wheelHalfWidth, 0, 0)
// wheel3.connectionPosition = w3
vehicle = SCNPhysicsVehicle(chassisBody: chassisNode.physicsBody!,
wheels: [wheel0, wheel1, wheel2, wheel3])
scnScene.physicsWorld.addBehavior(vehicle)
return chassisNode
}

I Found A Solution:
(but not the solution I hoped for)
My fix was to adjust the connectionPosition of "FrontLeftWheel", so much to the right, that it took the role of "FrontRightWheel".
And "FrontRightWheel", so much to the left, that it took the role of "FrontLeftWheel".
I also did this for the back.
Basically, where you have:
SCNVector3Make(wheelHalfWidth, 0, 0)
I replaced it with:
SCNVector3Make(wheelHalfWidth * 5.2, 0, 0)
However, you may have to fool around with the number "5.2" to match up with your COLLADA (.dae).
A Note:
Do Not forget to replace:
vehicle = SCNPhysicsVehicle(chassisBody: chassisNode.physicsBody!,
wheels: [wheel0, wheel1, wheel2, wheel3])
With:
vehicle = SCNPhysicsVehicle(chassisBody: chassisNode.physicsBody!,
wheels: [wheel1, wheel0, wheel3, wheel2])
This way, when you use car.wheels[0].You get the wheel you are expecting

I think your problem is that you are not setting the connection positions for each wheel. Apple's default ObjectiveC code is tricky to translate to swift.
wheel0.connectionPosition = SCNVector3FromFloat3(SCNVector3ToFloat3([wheel0Node convertPosition:SCNVector3Zero toNode:chassisNode]) + (vector_float3){wheelHalfWidth, 0.0, 0.0});
wheel1.connectionPosition = SCNVector3FromFloat3(SCNVector3ToFloat3([wheel1Node convertPosition:SCNVector3Zero toNode:chassisNode]) - (vector_float3){wheelHalfWidth, 0.0, 0.0});
wheel2.connectionPosition = SCNVector3FromFloat3(SCNVector3ToFloat3([wheel2Node convertPosition:SCNVector3Zero toNode:chassisNode]) + (vector_float3){wheelHalfWidth, 0.0, 0.0});
wheel3.connectionPosition = SCNVector3FromFloat3(SCNVector3ToFloat3([wheel3Node convertPosition:SCNVector3Zero toNode:chassisNode]) - (vector_float3){wheelHalfWidth, 0.0, 0.0});
These four lines translate (ignoring the repetition)
let wheel0Position = wheel0Node.convertPosition(SCNVector3Zero, to: chassisNode)
let vector0Float3 = vector_float3(wheel0Position.x, wheel0Position.y, wheel0Position.z) + vector_float3(wheelHalfWidth, 0,0)
wheel0.connectionPosition = SCNVector3(vector0Float3.x, vector0Float3.y, vector0Float3.z)
let wheel1Position = wheel1Node.convertPosition(SCNVector3Zero, to: chassisNode)
let vector1Float3 = vector_float3(wheel1Position.x, wheel1Position.y, wheel1Position.z) - vector_float3(wheelHalfWidth, 0,0)
wheel1.connectionPosition = SCNVector3(vector1Float3.x, vector1Float3.y, vector1Float3.z)
let wheel2Position = wheel2Node.convertPosition(SCNVector3Zero, to: chassisNode)
let vector2Float3 = vector_float3(wheel2Position.x, wheel2Position.y, wheel2Position.z) + vector_float3(wheelHalfWidth, 0,0)
wheel2.connectionPosition = SCNVector3(vector2Float3.x, vector2Float3.y, vector2Float3.z)
let wheel3Position = wheel3Node.convertPosition(SCNVector3Zero, to: chassisNode)
let vector3Float3 = vector_float3(wheel3Position.x, wheel3Position.y, wheel3Position.z) - vector_float3(wheelHalfWidth, 0,0)
wheel3.connectionPosition = SCNVector3(vector3Float3.x, vector3Float3.y, vector3Float3.z)
By adding or substracting vector_float3, the wheels are positioned in the right place.
Also, bear in mind that you can't scale the node in the scene or weird things may happen.

Encountered the same issue, my solution was to rotate all wheel nodes so the Y axis pointed down. I figured gravity is a downward force.

Related

In scene kit, SCNPhysicsField.noiseField seems to just terminate, end, after a few seconds

Have an SCNNode, add a physics body,
physicsBody = SCNPhysicsBody(type: .dynamic, shape: ..)
physicsBody?.isAffectedByGravity = false
Now add a physics field, for example
physicsField = SCNPhysicsField.noiseField(smoothness: 0.2, animationSpeed: 0.01)
physicsField?.strength = 0.05
It works perfectly. In this case, .noise , the object will jiggle around.
However after a few seconds (often 7 seconds, sometimes a different length of time), the object will simply stop moving.
(The three values, smoothness speed and strength, make no difference if you change them - it will still end after a few seconds.)
What's the solution to this mystery?
Just to be clear, I never used a SCNPhysicsField.noiseField, but I used one of type SCNPhysicsField.linearGravity and another of type SCNPhysicsField.customField and both of them are working correctly and do not stop unexpected as you describe.
here are my examples:
let attractionField = SCNPhysicsField.linearGravity()
attractionField.halfExtent = SCNVector3(250.0, 35.0, 60.0)
attractionField.direction = SCNVector3(-1.0, 0.0, 0.0)
attractionField.strength = 0.2 // 0.15
attractionNode.physicsField = attractionField
and the other one, (which I used to create a tornado):
private func addCustomVortexField() {
// Tornado Particles Field
let worldOrigin = stormNode.presentation.worldPosition
let worldAxis = simd_float3(0.0, 1.0, 0.0)
let customVortexField = SCNPhysicsField.customField(evaluationBlock: { position, velocity, mass, charge, time in
let l = simd_float3(worldOrigin.x - position.x, 1.0, worldOrigin.z - position.z)
let t = simd_cross(worldAxis, l)
let d2: Float = l.x * l.x + l.z * l.z
let vs: Float = 27 / sqrt(d2) // diameter, the bigger the value the wider it becomes
let fy: Float = 1.0 - Float((min(1.0, (position.y / 240.0)))) // rotations, a higher value means more turn arounds (more screwed)
return SCNVector3Make(t.x * vs + l.x * 10 * fy, 0, t.z * vs + l.z * 10 * fy)
})
customVortexField.halfExtent = SCNVector3Make(100, 100, 100)
stormNode.physicsField = customVortexField
stormNode.physicsField?.categoryBitMask = BitMasks.BitmaskTornadoField
}
I hope this is gonna help you in some way. You can also provide me your project, and I will have a look at it.

Im trying to make an animation trigger on roblox studio, can someone tell me how?

So when I try touch the trigger, the thing I'm trying to animate doesn't so the animation, I tried the animation id, anything else, can someone send me a model that has this, it will be nice if you can.
I made this with the gui elements already done u fill in what u need.
local library = {}
local function onClicked(frame, gui)
gui:SetVisible(false)
gui:ClearAllChildren()
end
function library:Create(parent)
local frame = Instance.new("Frame")
frame.Size = UDim2.new(1, 0, 1, 0)
frame.BackgroundColor3 = Color3.new(1, 1, 1)
frame.BorderSizePixel = 0
local gui = Instance.new("TextLabel")
gui.Size = UDim2.new(0, 200, 0, 50)
gui.BackgroundColor3 = Color3.new(0.5, 0.5, 0.5)
gui.Position = UDim2.new(0.5, -100, 0.5, -25)
gui.Text = "Click the Button"
gui.TextColor3 = Color3.new(1, 1, 1)
gui.TextXAlignment = Enum.TextXAlignment.Center
gui.TextYAlignment = Enum.TextYAlignment.Center
gui.Font = Enum.Font.SourceSans
gui.TextSize = 24
gui.Parent = frame
local button = Instance.new("TextButton")
button.Size = UDim2.new(0, 50, 0, 25)
button.BackgroundColor3 = Color3.new(1, 0, 0)
button.Position = UDim2.new(0.5, -25, 0.85, 0)
button.Text = "X"
button.TextColor3 = Color3.new(1, 1, 1)
button.Parent = frame
button.MouseButton1Click:Connect(function()
onClicked(frame, gui)
end)
frame.Parent = parent
return frame
end

How to use SceneKit vortex field to create a tornato effect

In the SceneKit WWDC 2014, they have an example of a vortex field with this effect:
The particle system looks much like a tornato, as it spins inward with a hollow center.
However, the documentation for vortex fields have no information on how to achieve this effect. Right now, I have this:
// create the particle system
let exp = SCNParticleSystem()
exp.loops = true
exp.particleMass = 5
exp.birthRate = 10000
exp.emissionDuration = 10
exp.emitterShape = SCNTorus(ringRadius: 5, pipeRadius: 1)
exp.particleLifeSpan = 15
exp.particleVelocity = 2
exp.particleColor = UIColor.white
exp.isAffectedByPhysicsFields = true
scene.addParticleSystem(exp, transform: SCNMatrix4MakeRotation(0, 0, 0, 0))
// create the field
let field = SCNPhysicsField.vortex()
field.strength = -5
field.direction = SCNVector3(x: 0, y: 1, z: 0)
let fieldNode = SCNNode()
fieldNode.physicsField = field
scene.rootNode.addChildNode(fieldNode)
This creates this effect:
Where I am looking down at the particles rotating clockwise with a really big radius outwards. It looks nothing like a tornato effect. How can I create this effect?
You say tornato, I say tornado, let’s call the whole thing off...
The SceneKit WWDC 2014 demo/slides is a sample code project, so you can see for yourself how they made any of the effects you see therein. In this case, it looks like the “vortex” demo isn’t actually using the vortexField API, but instead the custom field API that lets you supply your own math in an evaluator block. (See the link for the code in that block.)
You might be able to get similar behavior without a custom field by combining a vortex (causes rotation only) with radial gravity (attracts inward) with linear gravity (attracts downward), or some other combination (possibly something involving electric charge). But you’d probably have to experiment with tweaking the parameters quite a bit.
If anyone is still interested in this topic - here is a Swift 5 implementation of that legendary tornado effect.
Here is an example function that will create your tornado.
func addTornadoPhysicsField() {
// Tornado Particles Field Example
guard let tornadoSystem = SCNParticleSystem(named: "tornado.scnp", inDirectory: nil) else { return }
let emitterGeometry = SCNTorus(ringRadius: 1.0, pipeRadius: 0.2)
emitterGeometry.firstMaterial?.transparency = 0.0
let fieldAndParticleNode = SCNNode(geometry: emitterGeometry)
fieldAndParticleNode.position = SCNVector3(0.0, 0.0, -20.0)
tornadoSystem.emitterShape = emitterGeometry
fieldAndParticleNode.addParticleSystem(tornadoSystem)
yourScene.rootNode.addChildNode(fieldAndParticleNode)
// Tornado
let worldOrigin = SCNVector3Make(fieldAndParticleNode.worldTransform.m41,
fieldAndParticleNode.worldTransform.m42,
fieldAndParticleNode.worldTransform.m43)
let worldAxis = simd_float3(0.0, 1.0, 0.0) // i.Ex. the Y axis
// Custom Field (Tornado)
let customVortexField = SCNPhysicsField.customField(evaluationBlock: { position, velocity, mass, charge, time in
let l = simd_float3(worldOrigin.x - position.x, 1.0, worldOrigin.z - position.z)
let t = simd_cross(worldAxis, l)
let d2: Float = l.x * l.x + l.z * l.z
let vs: Float = 27 / sqrt(d2) // diameter, the bigger the value the wider it becomes (Apple Default = 20)
let fy: Float = 1.0 - Float((min(1.0, (position.y / 240.0)))) // rotations, a higher value means more turn arounds (more screwed, Apple Default = 15.0))
return SCNVector3Make(t.x * vs + l.x * 10 * fy, 0, t.z * vs + l.z * 10 * fy)
})
customVortexField.halfExtent = SCNVector3Make(100, 100, 100)
fieldAndParticleNode.physicsField = customVortexField // Attach the Field
}
Additional Configuration Options:
Finally all this can result in something like that:
Note: if you would like to move your static tornado almost like a real tornado, you will have to find a way to re-apply the physics field for each rendererd frame. If you don't, the world origin used in the evaluation block will not move and it will distort your tornado.
Note: You can also split the particle/field node into two different nodes that moves independently from each other. Constrain the field node to the position of the particle node and play around with the influence factor (still need to re-apply the field each frame)
For more information on Custom Fields check out here.

pointSize property in SceneKit being Ignored

I'm trying to render a point cloud using SceneKit (ultimately for ARKit). However, I'm finding the pointSize (and minimumPointScreenSpaceRadius) properties are ignored when trying to modify the SCNGeometryElement. There was this solution to call on the underlying OpenGL property, but this seems to work only in simulation.
No matter what pointSize I put in, the points are the same size. This is roughly how my code is laid out. I'm wondering if I'm doing something wrong with my GeometryElement setup?
var index: Int = 0
for x_pos in stride(from: start , to: end, by: increment) {
for y_pos in stride(from: start , to: end, by: increment) {
for z_pos in stride(from: start , to: end, by: increment) {
let pos:SCNVector3 = SCNVector3(x: x_pos, y: y_pos, z: z_pos)
positions.append(pos)
normals.append(normal)
indices.append(index)
index = index + 1
}
}
}
let pt_positions : SCNGeometrySource = SCNGeometrySource.init(vertices: positions)
let n_positions: SCNGeometrySource = SCNGeometrySource.init(normals: normals)
let pointer = UnsafeRawPointer(indices)
let indexData = NSData(bytes: pointer, length: MemoryLayout<Int32>.size * indices.count)
let elements = SCNGeometryElement(data: indexData as Data, primitiveType: .point, primitiveCount: positions.count, bytesPerIndex: MemoryLayout<Int>.size)
elements.pointSize = 10.0 //being ignored
elements.minimumPointScreenSpaceRadius = 10 //also being ignored
let pointCloud = SCNGeometry (sources: [pt_positions, n_positions], elements: [elements])
pointCloud.firstMaterial?.lightingModel = SCNMaterial.LightingModel.constant
let ptNode = SCNNode(geometry: pointCloud)
scene.rootNode.addChildNode(ptNode)
you should also specify both element.minimumPointScreenSpaceRadius and element.maximumPointScreenSpaceRadius. if so your point size work! :)

EXC_BAD_INSTRUCTION when spawning an object

So I'm trying to spawn a falling object in my game and i'm using an array to set all the possible xSpawn points and then I randomise through the array to get an x value but the problem is when It gets to this line in the code:
let SpawnPoint = UInt32(randomX)
It gives me the EXC_BAD_INSTRUCTION error and I can't seem to see why. I'm still new to swift so an explanation as to why it gives me an error at this point would be greatly cherished.
Full Code:
func spawnFallingOjects() {
let xSpawnOptions = [-50, -100, 0, 100, 150]
let randomX = xSpawnOptions[Int(arc4random_uniform(UInt32(xSpawnOptions.count)))]
let Bomb = SKSpriteNode(imageNamed: "YellowFrog")
Bomb.zPosition = 900
let SpawnPoint = UInt32(randomX)
Bomb.position = CGPoint(x: CGFloat(arc4random_uniform(SpawnPoint)), y: self.size.height)
let action = SKAction.moveToY(-350, duration: 2.0)
Bomb.runAction(SKAction.repeatActionForever(action))
self.addChild(Bomb)
}
Two out of five members of xSpawnOptions are negative numbers, which cannot be represented in an unsigned integer. So when you try to convert them to such with:
let SpawnPoint = UInt32(randomX)
it crashes as you'd expect.
It's not clear what you're trying to do with your random number generation, but one way or another you need to change the logic of it to account for this, perhaps by calculating a random number which is always positive, and then adding or subtracting an offset to it, for example:
let SpawnPoint: UInt32 = 50
Bomb.position = CGPoint(x: CGFloat(Int(arc4random_uniform(SpawnPoint)) + randomX), y: self.size.height)