SCNNode's flattenedNode behavior in Swift - swift

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 ?

Related

SceneKit & Swift: How to use SCN object as floor?

I have created a mountain landscape in Blender and imported it into my Xcode project.
https://github.com/QBeukelman/Mars_Curiosity.git
I would like to drive the SCNVehicle on the landscape as if it were the floor of the scene (landscapeMountains.scn).
The vehicle falls through the landscape!
I have tried the following to solve the issue, but without success:
Different combinations of static, kinetic and dynamic physics bodies.
Using different collision margins such as 0.01
Using category and collision masks (see image)
Does anyone know how to use a scn object as a floor using SceneKit and Xcode?
category & collision bit masks
Replace your entire "// add mountain" code block (line 185 to 191 in GameViewController.swift) with this:
func floorPhysBody(type: SCNPhysicsBodyType = .static, shape: SCNGeometry, scale: SCNVector3 = SCNVector3(1.0,1.0,1.0)) -> SCNPhysicsBody {
// Create Physics Body and set Physics Properties
let body = SCNPhysicsBody(type: type, shape: SCNPhysicsShape(geometry: shape, options: [SCNPhysicsShape.Option.type: SCNPhysicsShape.ShapeType.concavePolyhedron, SCNPhysicsShape.Option.scale: scale]))
// Physics Config
body.isAffectedByGravity = false
return body
}
// configure Physics Floor
let mountain = scene!.rootNode.childNode(withName: "mountain", recursively: true)!
mountain.physicsBody = floorPhysBody(type: .static, shape: mountain.geometry!)
In addition: Your Mountain Geometry is very large (file is over 50MB in size for some geometry). I recommend you to reduce this, to avoid memory issues when your game grows. Also: try to avoid texture sizes bigger than 1024x1024. Use textures sizes from a power of 2 if possible - like 128px, 256px, 512px, 1024px squared.
Have fun with your project.

Copying data between metal textures of different shapes

I am converting two trained Keras models to Metal Performance Shaders. I have to reshape the output of the first graph and use it as input to the second graph. The first graph's output is an MPSImage with "shape" (1,1,8192), and the second graph's input is an MPSImage of "shape" (4,4,512).
I cast graph1's output image.texture as a float16 array, and pass it to the following function to copy the data into "midImage", a 4x4x512 MPSImage:
func reshapeTexture(imageArray:[Float16]) -> MPSImage{
let image = imageArray
image.withUnsafeBufferPointer { ptr in
let width = midImage.texture.width
let height = midImage.texture.height
for slice in 0..<128{
for w in 0..<width{
for h in 0..<height{
let region = MTLRegion(origin: MTLOriginMake(w, h, 0),
size: MTLSizeMake(1, 1, 1))
midImage.texture.replace(region: region, mipmapLevel: 0, slice: slice, withBytes: ptr.baseAddress!.advanced(by: ((slice * 4 * width * height) + ((w + h) * 4)), bytesPerRow: MemoryLayout<Float16>.stride * 4, bytesPerImage: 0)
}
}
}
}
return midImage
}
When I pass midImage to graph2, the output of the graph is a square with 3/4 garbled noise, 1/4 black in the bottom right corner. I think I am not understanding something about the MPSImage slice property for storing extra channels. Thanks!
Metal 2d texture arrays are nearly always stored in a Morton or “Z” ordering of some kind. Certainly MPS always allocates them that way, though I suppose on MacOS there may be a means to make a linear 2D texture array and wrap a MPSImage around it. So, without undue care, direct access of a 2d texture array backing store is going to result in sadness and confusion.
The right way to do this is to write a simple Metal copy kernel. This gives you storage order independence and you don’t have to wait for the command buffer to complete before you can do the operation.
A feature request in Radar might also be warranted. Please also look in the latest macOS / iOS seed to see if Apple recently added a reshape filter for you.

Accurately selecting a node based on path from SKShapeNode

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.

How to draw concave shape using Stencil test on Metal

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).

Can I copy a SCNNode and keep the scale

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