I have a strange problem with using getBoundingBoxSize on SCNText geometry - it sometimes causes a crash - EXC_BAD_ACCESS (code=1). Can't figure out why. I use it on main thread.
This happens on iOS 12. Can someone help me resolve this?
let node = node as! AugmentedRealityView3DObjectNode
let mediaNode = mediaNode as! SCNNode
let fontScalling: Float = 0.5
let marginScalling: Float = 0.9
let planeGeometry = mediaNode.geometry as! SCNPlane
let textNode = mediaNode.childNodes.first!
let mediaTextGeometry = textNode.geometry as! SCNText
mediaTextGeometry.containerFrame = CGRect(withSize: CGSize(fromSize3D: node.augmentedRealityView.sizeForMainNode(node: node)) / CGFloat(fontScalling * marginScalling), centeredInContainerOfSize: .zero)
let centerPoint = SCNVector3(getBoundingCenterPoint: mediaTextGeometry.boundingBox)
textNode.position = SCNVector3(-centerPoint.x, -centerPoint.y, -centerPoint.z) * fontScalling * marginScalling
textNode.scale = SCNVector3(qubicVector: fontScalling * marginScalling)
// TODO: This causes crashes sometines in iOS 12.
let boundingBoxSize = SCNVector3(getBoundingBoxSize: mediaTextGeometry.boundingBox) * fontScalling / marginScalling
planeGeometry.width = CGFloat(boundingBoxSize.x)
planeGeometry.height = CGFloat(boundingBoxSize.y)
This is still broken 2.5 years later in SceneKit in iOS 14.2. The best I can tell, you cannot invoke .boundingBox on a node with SCNText geometry until after the node has been rendered at least once. My guess is that something is not initialized until the render loop and boundingBox fails to check for it being uninitialized.
My workaround is the usual hack of putting the .boundingBox in a DispatchQueue.main.asyc {} block so (hopefully) the node & geometry have been initialized. Depending on your app, this may not be feasible.
You are crashing in the Objective-C memory management system, which usually indicates some kind of heap corruption. I would guess that some object has been prematurely deallocated, while references to it still exist. This is super common, and is often called a dangling pointer.
I'd suggest checking out the Zombies tool in Instruments. It can help find this kinds of errors. Good luck!
Related
I'm trying to use an SCNParticleSystem as a "template" for others. I basically want the exact same properties except for the color animation for the particles. Here's what I've got so far:
if let node = self.findNodeWithName(nodeName),
let copiedParticleSystem: SCNParticleSystem = particleSystemToCopy.copy() as? SCNParticleSystem,
let colorController = copiedParticleSystem.propertyControllers?[SCNParticleSystem.ParticleProperty.color],
let animation: CAKeyframeAnimation = colorController.animation as? CAKeyframeAnimation {
guard animation.values?.count == animationColors.count else {
return nil
}
// Need to copy both the animations and the controllers
let copiedAnimation: CAKeyframeAnimation = animation.copy() as! CAKeyframeAnimation
copiedAnimation.values = animationColors
let copiedController: SCNParticlePropertyController = colorController.copy() as! SCNParticlePropertyController
copiedController.animation = copiedAnimation
// Finally set the new copied controller
copiedParticleSystem.propertyControllers?[SCNParticleSystem.ParticleProperty.color] = copiedController
// Add the particle system to the desired node
node.addParticleSystem(copiedParticleSystem)
// Some other work ...
}
I copy not only the SCNParticleSystem, but also SCNParticlePropertyController and CAKeyframeAnimation just to be safe. I've found that I've had to manually do these "deep" copies "manually" since a .copy() on SCNParticleSystem doesn't copy the animation, etc.
When I turn on the copied particle system on the node it was added to (by setting the birthRate to a positive number), nothing happens.
I don't think the problem is with the node that I've added it to, since I've tried adding the particleSystemToCopy to that node and turning that on, and the original particle system becomes visible in that case. This seems to indicate to me that the the node I've added the copied particle system to is OK in terms of its geometry, rendering order, etc.
Something else perhaps worth mentioning: the scene is loaded from a .scn file and not created programmatically in code. In theory that shouldn't affect anything, but who knows ...
Any ideas on why this copied particle system doesn't do anything when I turn it on?
Do not use copy() method for particle systems !
copy() method does not allow copy particles' color (copied particles will be default white).
You can test it with the following code:
let particleSystem01 = SCNParticleSystem()
particleSystem01.birthRate = 2
particleSystem01.particleSize = 0.5
particleSystem01.particleColor = .systemIndigo // INDIGO
particleSystem01.emitterShape = .some(SCNSphere(radius: 2.0))
let particlesNode01 = SCNNode()
particlesNode01.addParticleSystem(particleSystem01)
particlesNode01.position.y = -3
sceneView.scene.rootNode.addChildNode(particlesNode01)
let particleSystem02 = particleSystem01.copy() // WHITE
let particlesNode02 = SCNNode()
particlesNode02.addParticleSystem(particleSystem02 as! SCNParticleSystem)
particlesNode02.position.y = 3
sceneView.scene.rootNode.addChildNode(particlesNode02)
Use clone() method for nodes instead !
clone() method works more consistently for 3d objects and particle systems and it can help you save particles' color but, of course, doesn't allow save a position for each individual particle.
let particlesNode02 = particlesNode01.clone() // INDIGO
particlesNode02.position.y = 3
sceneView.scene.rootNode.addChildNode(particlesNode02)
I have an iOS 10 SpriteKit project where I'm trying to put actions on particles from a basic particle emitter created from the "snow" particle template in Xcode 8:
let snowPath = Bundle.main.path(forResource: "Snow", ofType: "sks")!
snowEmitter = NSKeyedUnarchiver.unarchiveObject(withFile: snowPath) as! SKEmitterNode
snowEmitter.position = CGPoint(x: 0, y: size.height / 2)
snowEmitter.particlePositionRange = CGVector(dx: size.width, dy: 0)
snowEmitter.particleAction = SKAction.scale(to: 3, duration: 3)
effectLayer.addChild(snowEmitter) // effectLayer is a SKNode on the scene
The emitter works as it should, but no matter what kind of SKAction I set particleAction to it gets ignored. Has anyone else experienced this?
Update: Doesn't work with Xcode 7 and iOS 9 either.
I think this still might be a leftover iOS 9 bug, not 100% sure. I just tried myself and I cannot get it to work as well.
SKEmitterNode particleAction not working iOS9 Beta
Can you not achieve the same effect using the particles settings directly in snow.sks in the inspector on the right?
You are probably looking at †hose two settings and its subsettings.
1) Particle life cycle (start, range)
2) Particle scale (start, range, speed)
This article has a nice description of each setting.
http://www.techotopia.com/index.php/An_iOS_8_Sprite_Kit_Particle_Emitter_Tutorial#Particle_Birthrate
As a general tip
Your code is not very safe in the first 2 lines because you force unwrapped the snow particle.
If you ever change the name and forget about it or the file becomes corrupted than you will crash. You should change it to something like this
guard let snowPath = Bundle.main.path(forResource: "Snow", ofType: "sks") else { return } // or if let snowPath = ...
snowEmitter = NSKeyedUnarchiver.unarchiveObject(withFile: snowPath) as? SKEmitterNode
...
You can also simply this code a lot by simple saying this where you define your snowEmitter property
let snowEmitter = SKEmitterNode(fileNamed: "Snow")
This will return an optional as well, just like your old code. Than in your method where you set up the emitter say something like this (dont use !)
if let snowEmitter = snowEmitter {
snowEmitter.position =
...
}
Hope this helps
There's another way to achieve the goal.
Make two or more particle systems.
Create two or more noise fields.
Bitmask match one each of the particle systems to one of the noise fields.
Put the noise fields down the bottom, where you want the wiggles to happen
Adjust the noise fields to taste.
As a 2021 datapoint, whilst adding particle emitters to Touchgram, I spent a couple of days exploring this. I came to the conclusion that they were broken in iOS9 and never fixed.
Using mapKit, I would like to guide the user on the road by displaying the instruction for the current step. Hence, once the step is completed, the instruction is updated for the corresponding (next) step.
From what I know, mapKit doesn't allow you to do so straight. I have found this post but it is in Obj-C and I am using swift. So far my code looks like this:
for step in self.carRoute!.steps {
var laStep: MKRouteStep = step as! MKRouteStep
var mapPointForEndOfStep: MKMapPoint = laStep.polyline(laStep.polyline.pointCount - 1) as! MKMapPoint
}
But I get this error: '(_) -> _' is not identical to 'MKPolyline'
Anyone knows how to set the last point of each step? Or is there some kind of magic that would tell what step the user is on. Or if he is out of the directions indicated by the app?
I found the solution myself. So if anyone ever encounter the same problem, I translated the Obj-c code found here in swift :
let thisStep: MKRouteStep = (self.carRoute?.steps[self.stepsCounter])!
let pointsArray = thisStep.polyline.points()
let lastPoint = pointsArray[thisStep.polyline.pointCount - 1]
let theLoc: CLLocationCoordinate2D = MKCoordinateForMapPoint(lastPoint)
self.theRegion = CLCircularRegion(center: theLoc, radius: 50, identifier: "turningPoint")
I can't figure out how to access properties from the Scenekit SCNFloor class using Swift, in objective-c they do it like this:
SCNNode*floor = [SCNNode node];
floor.geometry = [SCNFloor floor];
((SCNFloor*)floor.geometry).reflectionFalloffEnd = 10;
This creates the floor, how do i acces "reflectionFalloffEnd"?
let levelFloor = SCNNode()
levelFloor.geometry = SCNFloor()
Thanks
EDIT Thanks for the quick responses it works now :)
Whether you're in ObjC or Swift, for the compiler to be happy with you using SCNFloor properties or methods on a reference, that reference needs to have the type SCNFloor.
You can do that inline with a cast:
// ObjC
((SCNFloor*)floor.geometry).reflectionFalloffEnd = 10;
// Swift
(levelFloor.geometry as SCNFloor).reflectionFalloffEnd = 10
But that can get rather unwieldy, especially if you're going to set a bunch of properties on a geometry. I prefer to just use local variables (or constants, rather, since the references don't need to change):
let floor = SCNFloor()
floor.reflectionFalloffEnd = 10
floor.reflectivity = 0.5
let floorNode = SCNNode(geometry: floor)
floorNode.position = SCNVector3(x: 0, y: -10.0, z: 0)
scene.rootNode.addChildNode(floorNode)
(levelFloor.geometry as SCNFloor).reflectionFalloffEnd = 10 is how you would make that cast in Swift - you were almost there!
as an alternative - if you are going to need to access this property often you may end up with a bunch of as casts in your code :) you may instead prefer to simply declare constants to hold those objects:
let levelFloor = SCNNode()
let levelFloorGeometry = SCNFloor()
levelFloor.geometry = levelFloorGeometry
levelFloorGeometry.reflectionFalloffEnd = 10
// and so on as this property of your SCNNode's geometry needs to change throughout your app
This produces "fatal error: Can't unwrap Optional.None" and I don't seem to get why
var motionManager = CMMotionManager()
motionManager.accelerometerUpdateInterval = 0.2
motionManager.startAccelerometerUpdates()
var accelerationData = motionManager.accelerometerData
var accel = accelerationData.acceleration.x
If anyone can help me out, that would be great.
The issue is accelerationData is nil and you aren't checking for this. From the docs:
If no accelerometer data is available, the value of this property is nil.
You should check to make sure there is actually data before calling methods on it like this
if let accelerationData = motionManager.accelerometerData {
var accel = accelerationData.acceleration.x
}
That will ensure that if there is no data your app won't crash. Now to make sure you get some data.
You aren't getting any data because you're asking for data immediately after you initialize the core motion manager. You can show this by waiting a few seconds before checking. You can add NSThread.sleepForTimeInterval(3) right above the if let and run the project and it will enter the if let. Make sure you are using an actual device though, the simulator won't generate any motion data.