Add curve to SCNGeometry, and calculate normals - ios

I've created a polygon using SCNGeometry and I'm trying to add a curve to my shape, My code is:
let vertices: [SCNVector3] = [
SCNVector3Make(-0.1304485, 0.551937, 0.8236193),
SCNVector3Make(0.01393811, 0.601815, 0.7985139),
SCNVector3Make(0.2971005, 0.5591929, 0.7739732),
SCNVector3Make(0.4516893, 0.5150381, 0.7285002),
SCNVector3Make(0.4629132, 0.4383712, 0.7704169),
SCNVector3Make(0.1333823, 0.5224985, 0.8421428),
SCNVector3Make(-0.1684743, 0.4694716, 0.8667254)]
let indices: [Int32] = [Int32(vertices.count), 0, 1, 2, 3, 4, 5, 6]
let vertexSource = SCNGeometrySource(vertices: vertices)
let indexData = Data(bytes: indices, count: indices.count * MemoryLayout<Int32>.size)
let element = SCNGeometryElement(data: indexData, primitiveType: .polygon, primitiveCount: 1, bytesPerIndex: MemoryLayout<Int32>.size)
let geometry = SCNGeometry(sources: [vertexSource], elements: [element])
let material = SCNMaterial()
material.isDoubleSided = true
geometry.materials = [material]
let node = SCNNode(geometry: geometry)
The outcome looks like this:
Desired shape, adding 0.4 curve factor is:
Another question is how do I properly calculate normals for the surface given an array of vertices.
****** Update ******
When assigning geometry.subdivisionLevel = 10 I get a smoother round shape
But I still I can't figure out a way to add curve factor to the polygon shape.

Related

How to improve the accuracy of SCNNode created from array of positions?

I am creating an SCNNode using an array of x, y, and z positions. The following code generates the desired node shape; However, the shape is partially filled in at areas that aren't supposed to be filled in.
I think this could be improved with a better version generateIndices() method, but I'm not exactly sure the specifics of what I could do to improve this. I would really appreciate any suggestions.
Example:
Points generated from equation: "z = x^2 + y^2"
What I see:
What the shape is supposed to look like:
class CustomShapeNode: SCNNode {
var indices = [Int32]()
var vertices = [SCNVector3]()
init(positions: [(Double,Double,Double)]) {
super.init()
// Create an array of SCNVector3 from the positions array
for position in positions {
vertices.append(SCNVector3(Float(position.0), Float(position.1), Float(position.2)))
}
// Create the indices array
indices = generateIndices(positions)
let positionSource = SCNGeometrySource(vertices: vertices)
let element = SCNGeometryElement(indices: indices, primitiveType: .triangles)
let geometry = SCNGeometry(sources: [positionSource], elements: [element])
self.geometry = geometry
// Set the color of the node geometry
self.geometry?.firstMaterial?.diffuse.contents = UIColor(hexString: UIColor.systemOrange.toHexString(), alpha: 0.2)
self.geometry?.firstMaterial?.lightingModel = .constant
self.geometry?.firstMaterial?.isDoubleSided = true
// Set the physics body for the node (object will fall if alone since gravity exists)
//let shape = SCNPhysicsShape(geometry: geometry, options: [SCNPhysicsShape.Option.type: SCNPhysicsShape.ShapeType.convexHull])
//self.physicsBody = SCNPhysicsBody(type: .dynamic, shape: shape)
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
func generateIndices(_ positions: [(Double, Double, Double)]) -> [Int32] {
var indices: [Int32] = []
for i in 0..<positions.count {
let current = positions[i]
for j in i+1..<positions.count {
let next = positions[j]
indices.append(Int32(i))
indices.append(Int32(j))
indices.append(Int32((j+1) % positions.count))
}
}
return indices
}
}

Wrong vertex data when getting sources from SCNNode geometry

I'm using the code below inside a SCNGeometry extension to get the vertex data, and a similar one for .normals, my objective is to create a cloned mesh from the original node to colorize each vertex, but when I try to create another node with those vertices and normals, the resulting mesh has some triangles messed up, I have a small mesh to test and this is what I got, Do any of you have some guidance on what I could be doing wrong?
In the example, this function is getting me an array of 50 vertices, while the mesh actually has 18 faces, hence the result should be an array of 54 vertices, am I right?
Original mesh
Cloned mesh
Extension:
func extGetVertices() -> [SCNVector3]? {
let sources = self.sources(for: .vertex)
guard let source = sources.first else{return nil}
let stride = source.dataStride / source.bytesPerComponent
let offset = source.dataOffset / source.bytesPerComponent
let vectorCount = source.vectorCount
return source.data.withUnsafeBytes { (buffer : UnsafePointer<Float>) -> [SCNVector3] in
var result = Array<SCNVector3>()
for i in 0...vectorCount - 1 {
let start = i * stride + offset
result.append(SCNVector3(buffer[start], buffer[start+ 1], buffer[start+ 2]))
}
return result
}
}
obj:
#
# object obj_12853878
#
v 1097 957 36
v 779.26361083984375 992 0
v 707.26361083984375 590.5828857421875 91
v 1076 334.41595458984375 0
v 748.26361083984375 326.41595458984375 0
v 732.01263427734375 22.33051872253417968 0
v 1110.4652099609375 639.2049560546875 0
v 335.71615600585937504 680.5828857421875 39
v 314.88912963867187504 369.207000732421875 9.023892608628001E-14
v 350.4644775390625 926.65570068359375 -33
v 36 358.41595458984375 0
v 0 0 -33
v 0 680.5828857421875 -27
v 0 957 19
v 335.71615600585937504 22 0
v 1076 0 30
# 16 vertices
vn -0.08388713002204895 0.23459480702877044 0.9684669375419616
vn 0 0 1
vn 0.24344816803932188 -0.28190669417381288 0.92804181575775152
vn -0.1642393171787262 -0.11176854372024536 0.98006796836853024
vn -0.11669350415468216 0.28533965349197388 0.9512959122657776
vn 0.00356122362427413 -0.0920381024479866 0.99574911594390864
vn -0.19254806637763976 0.06056648120284081 0.9794166088104248
vn 0.13100945949554444 -0.1627427488565445 0.97793215513229376
vn -0.0974447876214981 -0.0058451765216887 0.9952237606048584
vn -0.03258795291185379 -0.3300407826900482 0.9434040188789368
vn 0.23050078749656676 -0.09988142549991608 0.967932403087616
vn -0.07897967845201492 0.233848974108696 0.96905976533889776
vn 0.00482096569612622 -0.1245955303311348 0.99219590425491328
vn -0.18483471870422364 0.28617173433303832 0.940181851387024
vn -0.08079835772514343 0.08905769884586334 0.99274384975433344
vn 1.8364935581471252E-16 -2.4888339972415545E-16 1
# 16 vertex normals
g obj_12853878
s 1
f 1//1 2//1 3//1
f 4//2 5//2 6//2
f 7//3 3//3 5//3
f 3//4 8//4 9//4
f 2//5 10//5 8//5
f 9//6 11//6 12//6
f 8//7 13//7 11//7
f 10//8 14//8 13//8
f 15//9 9//9 12//9
f 5//10 3//10 9//10
f 7//11 1//11 3//11
f 2//12 8//12 3//12
f 8//13 11//13 9//13
f 10//14 13//14 8//14
f 4//15 6//15 16//15
f 7//2 5//2 4//2
f 5//2 15//2 6//2
f 5//16 9//16 15//16
#18 polygons
Clone code:
guard let let vertices = node.geometry?.extGetVertices() else {return nil}
guard let let normals = node.geometry?.extGetNormals() else {return nil}
guard let let indices = (0..<vertices.count).indices.map {Int32($0)}
let vertexSource = SCNGeometrySource(vertices: vertices)
let indexElement = SCNGeometryElement(indices: indices, primitiveType: SCNGeometryPrimitiveType.triangles)
let normalSource = SCNGeometrySource(normals: normals)
let voxelGeometry = SCNGeometry(sources: [vertexSource, normalSource], elements: [indexElement])
let voxelMaterial = SCNMaterial()
voxelMaterial.diffuse.contents = UIColor.white
voxelGeometry.materials = [voxelMaterial]
let clonedNode = SCNNode(geometry: voxelGeometry)
Ok, I found my original problem, I was creating the array of indexes as a series of consecutive numbers, but they could be obtained from the original mesh too
func extGetIndices() -> [Int32]? {
guard let dataCount = self.elements.first?.data.count else { return nil }
let faces = self.elements.first?.data.withUnsafeBytes {(ptr: UnsafeRawBufferPointer) -> [Int32] in
guard let boundPtr = ptr.baseAddress?.assumingMemoryBound(to: Int32.self) else {return []}
let buffer = UnsafeBufferPointer(start: boundPtr, count: dataCount / 4)
return Array<Int32>(buffer)
}
return faces
}
so, the updated code for cloning the node is:
guard let let vertices = node.geometry?.extGetVertices() else {return nil}
guard let let normals = node.geometry?.extGetNormals() else {return nil}
guard let let indices = node.geometry?.extGetIndices() else {return nil}
let vertexSource = SCNGeometrySource(vertices: vertices)
let indexElement = SCNGeometryElement(indices: indices, primitiveType: SCNGeometryPrimitiveType.triangles)
let normalSource = SCNGeometrySource(normals: normals)
let voxelGeometry = SCNGeometry(sources: [vertexSource, normalSource], elements: [indexElement])
let voxelMaterial = SCNMaterial()
voxelMaterial.diffuse.contents = UIColor.white
voxelGeometry.materials = [voxelMaterial]
let clonedNode = SCNNode(geometry: voxelGeometry)
Additionally, the node could be colored having a SCNGeometrySource of color type and adding colors for each vertex:
let colors = getRandomColors(vertices.count)
let colorData = NSData(bytes: colors , length: MemoryLayout<SCNVector3>.stride * colors.count)
let colorSource = SCNGeometrySource(data: colorData as Data, semantic: .color, vectorCount: colors.count, usesFloatComponents: true, componentsPerVector: 3, bytesPerComponent: MemoryLayout<Float>.size, dataOffset: 0, dataStride: MemoryLayout<SCNVector3>.stride)
let voxelGeometry = SCNGeometry(sources: [vertexSource, normalSource, colorSource ], elements: [indexElement])
//random colors function
func getRandomColors(count: Int) -> [SCNVector3] {
var colors: [SCNVector3] = []
for _ in 0..<count {
let red = Double.random(in: 0...1)
let green = Double.random(in: 0...1)
let blue = Double.random(in: 0...1)
colors.append(SCNVector3(red, green, blue))
}
return colors
}
Here an example :
func cloneNode(node: SCNNode) -> SCNNode? {
guard let vertices = node.geometry?.extGetVertices() else {return nil}
guard let normals = node.geometry?.extGetNormals() else {return nil}
let indices = (0..<vertices.count).indices.map {Int32($0)}
// initialise color source
struct RGBColor {
let r,g,b : Float
}
var colors : [RGBColor] = []
// to have contiguous memory space
colors.reserveCapacity(vertices.count)
// convert to color geometry source
let colorsAsData = NSData(bytes: colors, length: MemoryLayout<RGBColor>.size * colors.count) as Data
// fill colors arrays
let colorSource = SCNGeometrySource(data: colorsAsData,
semantic: .color,
vectorCount: colors.count,
usesFloatComponents: true,
componentsPerVector: 3,
bytesPerComponent: MemoryLayout<Float>.size,
dataOffset: MemoryLayout.offset(of: \RGBColor.r)!,
dataStride: MemoryLayout<RGBColor>.stride)
let vertexSource = SCNGeometrySource(vertices: vertices)
let indexElement = SCNGeometryElement(indices: indices, primitiveType: SCNGeometryPrimitiveType.triangles)
let normalSource = SCNGeometrySource(normals: normals)
// Add the colors source in the list
let voxelGeometry = SCNGeometry(sources: [vertexSource, normalSource, colorSource], elements: [indexElement])
let voxelMaterial = SCNMaterial()
voxelMaterial.diffuse.contents = UIColor.white
voxelGeometry.materials = [voxelMaterial]
let clonedNode = SCNNode(geometry: voxelGeometry)
return clonedNode
}

How to generating a concave or convex polygon plane in SceneKit from SCNVector3

I'm trying to create a custom SCNGeometry in the form of a plane with custom shape, which could be placed in an ARKit session. I'm using the option SCNGeometryPrimitiveTypePolygon in the following method which seems to work fine:
extension SCNGeometry {
class func polygonfrom(vectices: [SCNVector3]) -> SCNGeometry {
let indices: [Int32] = getIndices(count: vectices.count)
let indexData = Data(bytes: indices, count: indices.count * MemoryLayout<Int32>.size)
let source = SCNGeometrySource(vertices: vectices)
let element = SCNGeometryElement(data: indexData, primitiveType: .polygon, primitiveCount: 1, bytesPerIndex: MemoryLayout<Int32>.size)
return SCNGeometry(sources: [source], elements: [element])
}
class private func getIndices(count: Int) -> [Int32] {
var indices: [Int32] = []
indices.append(Int32(count))
for i in 0..<count{
indices.append(Int32(i))
}
return indices
}
}
Unfortunately, It doesn't fit for a concave polygon:
results
The SCNShape class creates a suited triangulation on your behalf.

Swift 4 - How to create Custom SCNGeometry as polygon

I've been trying to create a custom SCNGeometry polygon from a set of SCNVector3 positions with the following code:
private func polygonGeometry (vertices: [SCNVector3]) -> SCNGeometry {
var indices: [Int32] = [Int32(vertices.count)]
indices.append(contentsOf: generateIndices(max: vertices.count))
let vertexSource = SCNGeometrySource(vertices: vertices )
let indexData = Data(bytes: indices,
count: indices.count * MemoryLayout<Int32>.size)
let element = SCNGeometryElement(data: indexData,
primitiveType: .polygon,
primitiveCount: 1,
bytesPerIndex: MemoryLayout<Int32>.size)
return SCNGeometry(sources: [vertexSource], elements: [element])
}
With this helperMethod for generate the correct amount of indices:
private func generateIndices(max maxIndexValue: Int) -> [Int32]{
var counter: Int = 0
var output: [Int32] = []
while counter < maxIndexValue {
output.append(Int32(counter))
counter += 1
}
return output
}
The code works fine for some polygons. However, sometimes I get the following error in console when trying to create a custom geometry, and the program crashes.
[MTLDebugDevice validateNewBufferArgs:options:]:467: failed assertion Cannot create buffer of zero length.
Does anyone have a clue why this happens? Is there any flaws in the code?
[EDIT]: I believe problem occurs when first position and last position has the same x,y,z values. Simply remove the the last position from the array of positions and the polygon should render as expected!

Drawing a line on SceneKit won't work on device

Following this solution: Custom SceneKit Geometry and converted to Swift 3, the code became:
func drawLine() {
var verts = [SCNVector3(x: 0,y: 0,z: 0),SCNVector3(x: 1,y: 0,z: 0),SCNVector3(x: 0,y: 1,z: 0)]
let src = SCNGeometrySource(vertices: &verts, count: 3)
let indexes: [CInt] = [0, 1, 2]
let dat = NSData(
bytes: indexes,
length: MemoryLayout<CInt>.size * indexes.count
)
let ele = SCNGeometryElement(
data: dat as Data,
primitiveType: .line,
primitiveCount: 2,
bytesPerIndex: MemoryLayout<CInt>.size
)
let geo = SCNGeometry(sources: [src], elements: [ele])
let nd = SCNNode(geometry: geo)
geo.materials.first?.lightingModel = .blinn
geo.materials.first?.diffuse.contents = UIColor.red
scene.rootNode.addChildNode(nd)
}
It work on simulator:
But I got error on device:
/BuildRoot/Library/Caches/com.apple.xbs/Sources/Metal/Metal-85.83/ToolsLayers/Debug/MTLDebugRenderCommandEncoder.mm:130: failed assertion `indexBufferOffset(0) + (indexCount(4) * 4) must be <= [indexBuffer length](12).'
What is happening?
The entire code is here: Source code
I'm answering my own question because I found a solution that can help others.
The problem was on "indexes", 3 indexes won't draw 2 vertices. Must set 2 indexes for each vertice you want to draw.
This is the final function:
func drawLine(_ verts : [SCNVector3], color : UIColor) -> SCNNode? {
if verts.count < 2 { return nil }
let src = SCNGeometrySource(vertices: verts, count: verts.count )
var indexes: [CInt] = []
for i in 0...verts.count - 1 {
indexes.append(contentsOf: [CInt(i), CInt(i + 1)])
}
let dat = NSData(
bytes: indexes,
length: MemoryLayout<CInt>.size * indexes.count
)
let ele = SCNGeometryElement(
data: dat as Data,
primitiveType: .line,
primitiveCount: verts.count - 1,
bytesPerIndex: MemoryLayout<CInt>.size
)
let line = SCNGeometry(sources: [src], elements: [ele])
let node = SCNNode(geometry: line)
line.materials.first?.lightingModel = .blinn
line.materials.first?.diffuse.contents = color
return node
}
Calling:
scene.rootNode.addChildNode(
drawLine(
[SCNVector3(x: -1,y: 0,z: 0),
SCNVector3(x: 1,y: 0.5,z: 1),
SCNVector3(x: 0,y: 1.5,z: 0)] , color: UIColor.red
)!
)
Will draw:

Resources