I would like to copy a SCNNode multiple times, have different materials for each node and different positions. However keeping the same scale. So, if I change the scale for the node I copy, all copied nodes should change.
In the code below, when I run changeScale(), the copied node scale does not change.
Is there a way I can change the scale of all copied Nodes or size of geometry together. Without enumerating or changing them individually
let mainNode = SCNNode()
let mainGeo = SCNPlane(width: CGFloat(4), height: CGFloat(4))
mainNode.geometry = mainGeo
for var i = 1; i <= 10; i += 1 {
let thisNode = mainNode.copy() as! SCNNode
thisNode.position = SCNVector3Make( Float(rx), Float(ry), Float(rz) )
thisNode.geometry = thisNode.geometry!.copy() as? SCNGeometry
thisNode.geometry?.firstMaterial = thisNode.geometry?.firstMaterial!.copy() as? SCNMaterial
if i == 0 {
thisNode.geometry?.firstMaterial?.diffuse.contents = UIColor.blueColor()
} else {
thisNode.geometry?.firstMaterial?.diffuse.contents = UIColor.redColor()
}
scene.rootNode.addChildNode(thisNode)
}
func changeScale() {
mainNode.scale = SCNVector3Make(7, 7, 7)
}
I am not sure whether this is the right answer or not. As I am not an expert in Swift or ios.
It seems like when a node is copied or cloned, the node carries no properties apart from the assigned information on geometries etc.
I had initially thought some properties could be kept the same for all copied nodes, like scale or position.
What I wanted was to have different positions and materials for every node at creation, with a changeable scale of all the nodes or size of geometries together.
Now, as the nodes and geometry's are created multiple times, they are all different and cannot be sized or scaled together
So what I did:
Copied the main node multiple times, gave it different positions
Created one main geometry (outside), set it to the main node with a
particular size. So all the main nodes use this geometry
Added a subnode to the main node, with different material properties
Added a SCNTransformConstraint to the subnode, to transform according
to the size of the main material
So now whenever I edit the geometry size the subnode size changes all together.
I am not sure how this method is with speed/performance. But it seems better than enumerating through every node
You may try to use clone instead of copy:
let thisNode = mainNode.clone() as! SCNNode
Related
In a Swift iOS app, I have a main SCNNode containing thousands of nodes which all contain the SAME geometry. I am cloning the nodes using .copy():
let firstNode = SCNNode(geometry: myGeo)
for i in 0...10000
{
let newNode=firstNode.copy()
rootNode.addChildNode(newNode)
// Change position
...
}
scene.rootNode.addChildNode(rootNode)
All the nodes are properly displayed but performance extremely slow. I am hence using flattenedNode to hope for having an optimized single node using efficiently the fact I am only using 1 geometry:
// Removing of previous "scene.rootNode.addChildNode(rootNode)"
let clo=rootNode.flattenedClone()
scene.rootNode.addChildNode(clo)
However the app crashes with the following error:
-[MTLDebugDevice newBufferWithBytes:length:options:], line 644: error 'Buffer Validation newBufferWith*:length 0x120ba300 must not exceed
256 MB.
As I am only using 1 geometry, is it normal that flattenedNode generates such a huge buffer ?
I'm working on determining which node is tapped in an SKScene. I have a line that is created from a CGMutablePath and added to an SKShapeNode. However, when trying to accurately select this line, it is included in the array returned from nodesAtPoint whenever i tap anywhere within it's accumulated rectangle frame. See the image for more detail. I'm able to tap anywhere within those blue or red squares and still get the line from nodesAtPoint. I'm trying to figure out a way to only return the node from nodesAtPoint if the actual path was tapped (or maybe a threshold of +- 20 points to help). What method am i looking for?
Here is some relevant code that i've tried based on some articles i've found.
let tempActualTouchedNodes = self.nodes(at: positionInScene)
for n in tempActualTouchedNodes {
if let tempLine = n as? LineNode {
if tempLine.contains(touch.location(in: tempLine)){
print("yes")
}
}
}
However, "yes" is never printed.
Edit: I have been able to solve this problem by using PhysicsEditor to make a polygonal physicsbody instead of using SKPhysicsBody(... alphaThreshold: ... )
--
For some reason I'm having trouble with what I'm assuming is SKPhysicBodies being slightly off-place. While using showPhysics my stationary obstacle nodes appear to have their physicbodies in the correct position, however I am able to trigger collisions without actually touching the obstacle. If you look at the image below it shows where I have found the physicsbodies to be off centre, despite showPhysics telling me otherwise. (Note, the player node travels in the middle of these obstacle nodes).
I also thought it would be worth noting that while the player is travelling, its physicbody appears to travel slightly ahead but I figured this is probably normal.
I also use SKPhysicsBody(... alphaThreshold: ... ) to create the physicbodies from .png images.
Cheers.
Edit: Here's how I create the obstacle physicbodies. Once they're added into the worldNode they are left alone until they need to be removed. Apart from that I don't change them in any way.
let obstacleNode = SKSpriteNode(imageNamed: ... )
obstacleNode.position = CGPoint(x: ..., y: ...)
obstacleNode.name = "obstacle"
obstacleNode.physicsBody = SKPhysicsBody(texture: obstacleNode.texture!, alphaThreshold: 0.1, size: CGSize(width: obstacleNode.texture!.size().width, height: obstacleNode.texture!.size().height))
obstacleNode.physicsBody?.affectedByGravity = false
obstacleNode.physicsBody?.isDynamic = false
obstacleNode.physicsBody!.categoryBitMask = CC.wall.rawValue
obstacleNode.physicsBody!.collisionBitMask = CC.player.rawValue
obstacleNode.physicsBody!.contactTestBitMask = CC.player.rawValue
worldNode.addChild(obstacleNode)
The player node is treated the same way, here is how the player moves.
playerNode.physicsBody?.velocity = CGVector(dx: dx, dy: dy)
I'm assuming you aren't showing the exact images that you used to create your SKSpriteNode and SKPhysicsBody instances. Since you are using a texture to define the shape of your SKPhysicsBody you are likely running up against this:
SKPhysicsBody documentation
If you do not want to create your own shapes, you can use SpriteKit to create a shape for you based on the sprite’s texture.
This is easy and convenient but it can sometimes give unexpected results depending on the textures you are using for your sprite. Perhaps try making an explicit mask or using a simple shape to represent your physics body. There are very good examples and guidelines in that documentation.
I would also follow this pattern when you set the properties on your objects:
// safely unwrap and handle failure if it fails
guard let texture = obstacleNode.texture else { return }
// create the physics body
let physicsBody = SKPhysicsBody(texture: texture,
alphaThreshold: 0.1,
size: CGSize(width: texture.size().width,
height: texture.size().height))
// safely set its properties without the need to unwrap an Optional
physicsBody.affectedByGravity = false
// set the rest of the properties
// set the physics body property on the node
obstacleNode.physicsBody = physicsBody
By setting the properties on a concrete instance of SKPhysicsBody and fully unwrapping and testing Optionals you minimize the chances for a run-time crash that may be difficult to debug.
Hello I'm trying to spawn bullets at the bottom of my screen to travel upwards but the current code that I have spawns the bullets at the top of the screen. I've tried making the height negative and nothing happened. Here's the code I'm working with, thanks.
let randomBulletPosition = GKRandomDistribution(lowestValue: -300, highestValue: 300)
let position = CGFloat(randomBulletPosition.nextInt())
bullet.position = CGPoint(x: position, y: self.frame.size.height + bullet.size.height)
Some nice conversions will help you.
Now, do not do this all the time, this should be a one and done type deal, like in a lazy property.
First, we want to get the bottom of our view
let viewBottom = CGPoint(x:scene!.view!.midX,y:scene!.view!.frame.maxY) //In a UIView, 0,0 is the top left corner, so we look to bottom middle
Second, we want to convert the position to the scene
let sceneBottom = scene!.view!.convert(viewBottom, to:scene!)
Finally we want to convert to whatever node you need it to be a part of. (This is optional if you want to place it on the scene)
let nodeBottom = scene!.convert(sceneBottom,to:node)
Code should look like this:
let viewBottom = CGPoint(x:scene!.view!.midX,y:scene!.view!.frame.maxY)
let sceneBottom = scene!.view!.convert(viewBottom!, to:scene!)
let nodeBottom = scene!.convert(sceneBottom,to:node)
Of course, this is a little ugly.
Thankfully we have convertPoint and convert(_from:) to clean things up a little bit
let sceneBottom = scene.convertPoint(from:viewBottom)
Which means we can clean up the code to look like this:
let sceneBottom = scene.convertPoint(from:CGPoint(x:scene!.view!.midX,y:scene!.view!.frame.maxY))
let nodeBottom = node.convert(sceneBottom,from:scene!)
Then we can make it 1 line as:
let nodeBottom = node.convert(scene.convertPoint(from:CGPoint(x:scene!.view!.midX,y:scene!.view!.frame.maxY),from:scene!)
As long as the node is available to the class, we can make it lazy:
lazy var nodeBottom = self.node.convert(self.scene!.convertPoint(CGPoint(x:self.scene!.view!.midX,y:self.scene!.view!.frame.maxY),from:self.scene!)
This means the first time you call nodeBottom, it will do these calculations for you and store it into memory. Everytime after that, the number is preserved.
Now that you know where the bottom of the screen is in the coordinate system you want to use, you can assign the x value to whatever your random is producing, and you can subtract the (node.height * (1 - node.anchorPoint.y)) to fully hide your node from the scene.
Now keep in mind, if your node moves between various parents, this lazy will not update.
Also note, I unwrapped all optionals with !, you may want to be using ? and checking if it exists first.
This is the first time I'm trying to use Stencil Test but I have seen some examples using OpenGL and a few on Metal but focused on the Depth test instead. I understand the theory behind the Stencil test but I don't know how to set it up on Metal.
I want to draw irregular shapes. For the sake of simplicity lets consider the following 2D polygon:
I want the stencil to pass where the number of overlapping triangles is odd, so that I can reach something like this, where the white area is the area to be ignored:
I'm doing the following steps in the exact order:
Setting the depthStencilPixelFormat:
mtkView.depthStencilPixelFormat = .stencil8
mtkView.clearStencil = .allZeros
Stencil attachment:
let textureDescriptor = MTLTextureDescriptor.texture2DDescriptor(pixelFormat: .stencil8, width: drawable.texture.width, height: drawable.texture.height, mipmapped: true)
textureDescriptor.textureType = .type2D
textureDescriptor.storageMode = .private
textureDescriptor.usage = [.renderTarget, .shaderRead, .shaderWrite]
mainPassStencilTexture = device.makeTexture(descriptor: textureDescriptor)
let stencilAttachment = MTLRenderPassStencilAttachmentDescriptor()
stencilAttachment.texture = mainPassStencilTexture
stencilAttachment.clearStencil = 0
stencilAttachment.loadAction = .clear
stencilAttachment.storeAction = .store
renderPassDescriptor.stencilAttachment = stencilAttachment
Stencil descriptor:
stencilDescriptor.depthCompareFunction = MTLCompareFunction.always
stencilDescriptor.isDepthWriteEnabled = true
stencilDescriptor.frontFaceStencil.stencilCompareFunction = MTLCompareFunction.equal
stencilDescriptor.frontFaceStencil.stencilFailureOperation = MTLStencilOperation.keep
stencilDescriptor.frontFaceStencil.depthFailureOperation = MTLStencilOperation.keep
stencilDescriptor.frontFaceStencil.depthStencilPassOperation = MTLStencilOperation.invert
stencilDescriptor.frontFaceStencil.readMask = 0x1
stencilDescriptor.frontFaceStencil.writeMask = 0x1
stencilDescriptor.backFaceStencil = nil
depthStencilState = device.makeDepthStencilState(descriptor: stencilDescriptor)
and lastly, Im setting the reference value and the stencil state in the main pass:
renderEncoder.setStencilReferenceValue(0x1)
renderEncoder.setDepthStencilState(self.depthStencilState)
Am I missing something because the result I got is just like there is no stencil at all. I can see some differences when changing the settings of the depth test but nothing happens when changing the settings of the stencil ...
Any clue?
Thank you in advance
You're clearing the stencil texture to 0. The reference value is 1. The comparison function is "equal". So, the comparison will fail (1 does not equal 0). The operation for when the stencil comparison fails is "keep", so the stencil texture remains 0. Nothing changes for subsequent fragments.
I would expect that you'd get no rendering, although depending on the order of your vertexes and the front-face winding mode, you may be looking at the back faces of your triangles, in which case the stencil test is effectively disabled. If you don't otherwise care about front vs. back, just set both stencil descriptors the same way.
I think you need to do two passes: first, a stencil-only render; second, the color render governed by the stencil buffer. For the stencil only, you would make the compare function .always. This will toggle (invert) the low bit for each triangle that's drawn over a given pixel, giving you an indication of even or odd count. Because neither the compare function nor the operation involve the reference value, it doesn't matter what it is.
For the second pass, you'd set the compare function to .equal and the reference value to 1. The operations should all be .keep. Also, make sure to set the stencil attachment load action to .load (not .clear).