0
votes

In SceneKit I'm trying to create a simple piece of custom geometry with a texture on it. I'm just trying to make a cube but have each face oriented properly so an SCNBox won't work.

The shape comes out perfectly fine, but the texture is mapped totally wrong.

This is what the geometry comes out looking like:

enter image description here

Based on what I understand, the texture coordinates for every face should be the same. But when I add the points for each face in the same order, it draws similarly incorrectly. So I played around with the order of the coordinates and got the front, back, and one side face to draw mostly correctly. I haven't been able to figure out a pattern or logic to what caused this though.

The front is how every face should look.

Here's how I'm making it:

public struct face {
     var topLeft: SCNVector3
     var topRight: SCNVector3
     var bottomLeft: SCNVector3
     var bottomRight: SCNVector3
}

 func createCube(startFace: face, endFace: face) -> SCNGeometry {
    var vertices: [SCNVector3] = []
    var indices: [Int32] = []
    var textCords: [vector_float2] = []

    // Add table of contents to indices array because we're using polygons
    let polygons = 6 // cube so 6 faces
    indices.append(4) // front face
    indices.append(4) // left face
    indices.append(4) // right face
    indices.append(4) // top face
    indices.append(4) // bottom face
    indices.append(4) // back face

    //  - Convenience Values -

    // Indices offsets
    let startFaceVertex: Int32 = 0
    let endFaceVertex: Int32 = 4

    // Vertex indices
    let bottomLeftPos: Int32 = 0
    let bottomRightPos: Int32 = 1
    let topRightPos: Int32 = 2
    let topLeftPos: Int32 = 3

    // Texture Coordinates
    let topLeft = CGPoint(x: 0, y: 1)
    let topRight = CGPoint(x: 1, y: 1)
    let bottomLeft = CGPoint(x: 0, y: 0)
    let bottomRight = CGPoint(x: 1, y: 0)


    // Add vertices

    vertices.append(startFace.bottomLeft)
    vertices.append(startFace.bottomRight)
    vertices.append(startFace.topRight)
    vertices.append(startFace.topLeft)

    vertices.append(endFace.bottomLeft)
    vertices.append(endFace.bottomRight)
    vertices.append(endFace.topRight)
    vertices.append(endFace.topLeft)


    // Front Face

    indices.append(startFaceVertex + bottomLeftPos)
    indices.append(startFaceVertex + bottomRightPos)
    indices.append(startFaceVertex + topRightPos)
    indices.append(startFaceVertex +  topLeftPos)

    textCords.append(vector_float2(Float(topLeft.x), Float(topLeft.y)))
    textCords.append(vector_float2(Float(topRight.x), Float(topRight.y)))
    textCords.append(vector_float2(Float(bottomRight.x), Float(bottomRight.y)))
    textCords.append(vector_float2(Float(bottomLeft.x), Float(bottomLeft.y)))

    // Left Face

    indices.append(startFaceVertex + bottomLeftPos)
    indices.append(endFaceVertex + bottomLeftPos)
    indices.append(endFaceVertex + topLeftPos)
    indices.append(startFaceVertex + topLeftPos)

    textCords.append(vector_float2(Float(bottomRight.x), Float(bottomRight.y)))
    textCords.append(vector_float2(Float(topLeft.x), Float(topLeft.y)))
    textCords.append(vector_float2(Float(bottomLeft.x), Float(bottomLeft.y)))
    textCords.append(vector_float2(Float(topRight.x), Float(topRight.y)))

    // Top Face

    indices.append(endFaceVertex + topLeftPos)
    indices.append(endFaceVertex + topRightPos)
    indices.append(startFaceVertex + topRightPos)
    indices.append(startFaceVertex + topLeftPos)

    textCords.append(vector_float2(Float(topLeft.x), Float(topLeft.y)))
    textCords.append(vector_float2(Float(topRight.x), Float(topRight.y)))
    textCords.append(vector_float2(Float(bottomRight.x), Float(bottomRight.y)))
    textCords.append(vector_float2(Float(bottomLeft.x), Float(bottomLeft.y)))

    // Right Face

    indices.append(endFaceVertex + bottomRightPos)
    indices.append(startFaceVertex + bottomRightPos)
    indices.append(startFaceVertex + topRightPos)
    indices.append(endFaceVertex + topRightPos)

    textCords.append(vector_float2(Float(bottomRight.x), Float(bottomRight.y)))
    textCords.append(vector_float2(Float(topLeft.x), Float(topLeft.y)))
    textCords.append(vector_float2(Float(bottomLeft.x), Float(bottomLeft.y)))
    textCords.append(vector_float2(Float(topRight.x), Float(topRight.y)))

    // Bottom Face

    indices.append(startFaceVertex + bottomLeftPos)
    indices.append(startFaceVertex + bottomRightPos)
    indices.append(endFaceVertex + bottomRightPos)
    indices.append(endFaceVertex + bottomLeftPos)

    textCords.append(vector_float2(Float(topLeft.x), Float(topLeft.y)))
    textCords.append(vector_float2(Float(topRight.x), Float(topRight.y)))
    textCords.append(vector_float2(Float(bottomRight.x), Float(bottomRight.y)))
    textCords.append(vector_float2(Float(bottomLeft.x), Float(bottomLeft.y)))


    // Back Face

    indices.append(endFaceVertex + bottomLeftPos)
    indices.append(endFaceVertex + bottomRightPos)
    indices.append(endFaceVertex + topRightPos)
    indices.append(endFaceVertex + topLeftPos)

    textCords.append(vector_float2(Float(topLeft.x), Float(topLeft.y)))
    textCords.append(vector_float2(Float(topRight.x), Float(topRight.y)))
    textCords.append(vector_float2(Float(bottomRight.x), Float(bottomRight.y)))
    textCords.append(vector_float2(Float(bottomLeft.x), Float(bottomLeft.y)))


    // Create geometry

    let verticesSource = SCNGeometrySource(vertices: vertices)

    let uvData = Data(bytes: textCords, count: textCords.count * MemoryLayout<vector_float2>.size)
    let textureSource = SCNGeometrySource(data: uvData,
                                          semantic: .texcoord,
                                          vectorCount: textCords.count,
                                          usesFloatComponents: true,
                                          componentsPerVector: 2,
                                          bytesPerComponent: MemoryLayout<Float>.size,
                                          dataOffset: 0,
                                          dataStride: MemoryLayout<vector_float2>.size)

    let indexData = Data(bytes: indices,
                         count: indices.count * MemoryLayout<Int32>.size)
    let elements = SCNGeometryElement(data: indexData,
                                      primitiveType: .polygon,
                                      primitiveCount: polygons,
                                      bytesPerIndex: MemoryLayout<Int32>.size)

    return SCNGeometry(sources: [verticesSource, textureSource], elements: [elements])
}

Solution

Got it working, here's my code for a working version:

public struct face {
    var topLeft: SCNVector3
    var topRight: SCNVector3
    var bottomLeft: SCNVector3
    var bottomRight: SCNVector3
}

let topLeft = CGPoint(x: 0, y: 1)
let topRight = CGPoint(x: 1, y: 1)
let bottomLeft = CGPoint(x: 0, y: 0)
let bottomRight = CGPoint(x: 1, y: 0)

func createCube(startFace: face, endFace: face) -> SCNGeometry {
    var vertices: [SCNVector3] = []
    var indexTable: [Int32] = []
    var indices: [Int32] = []
    var textCords: [vector_float2] = []

    // Front Face
    addFace(face: startFace, textureOffset: CGPoint.zero, textureSize: CGSize(width: 1, height: 1), toVertices: &vertices, indexTable: &indexTable, indices: &indices, textCords: &textCords)

    // Left Face

    let leftFace = face(topLeft: endFace.topLeft, topRight: startFace.topLeft, bottomLeft: endFace.bottomLeft, bottomRight: startFace.bottomLeft, center: SCNVector3Zero, originOffset: startFace.originOffset)
    addFace(face: leftFace, textureOffset: CGPoint.zero, textureSize: CGSize(width: 1, height: 1), toVertices: &vertices, indexTable: &indexTable, indices: &indices, textCords: &textCords)

    // Top Face

    //let topFace = face(topLeft: startFace.topLeft, topRight: endFace.topLeft, bottomLeft: startFace.topRight, bottomRight: endFace.topRight, center: SCNVector3Zero, originOffset: startFace.originOffset)
    let topFace = face(topLeft: startFace.topLeft, topRight: endFace.topLeft, bottomLeft: startFace.topRight, bottomRight: endFace.topRight, center: SCNVector3Zero, originOffset: startFace.originOffset)
    addFace(face: topFace, textureOffset: CGPoint.zero, textureSize: CGSize(width: 1, height: 1), toVertices: &vertices, indexTable: &indexTable, indices: &indices, textCords: &textCords)

    // Right Face

    let rightFace = face(topLeft: startFace.topRight, topRight: endFace.topRight, bottomLeft: startFace.bottomRight, bottomRight: endFace.bottomRight, center: SCNVector3Zero, originOffset: startFace.originOffset)
    addFace(face: rightFace, textureOffset: CGPoint.zero, textureSize: CGSize(width: 1, height: 1), toVertices: &vertices, indexTable: &indexTable, indices: &indices, textCords: &textCords)

    // Bottom Face

    let bottomFace = face(topLeft: endFace.bottomLeft, topRight: startFace.bottomLeft, bottomLeft: endFace.bottomRight, bottomRight: startFace.bottomRight, center: SCNVector3Zero, originOffset: startFace.originOffset)
    addFace(face: bottomFace, textureOffset: CGPoint.zero, textureSize: CGSize(width: 1, height: 1), toVertices: &vertices, indexTable: &indexTable, indices: &indices, textCords: &textCords)

    // Back Face

    addFace(face: endFace, textureOffset: CGPoint.zero, textureSize: CGSize(width: 1, height: 1), toVertices: &vertices, indexTable: &indexTable, indices: &indices, textCords: &textCords)

    // Create geometry

    let verticesSource = SCNGeometrySource(vertices: vertices)

    let uvData = Data(bytes: textCords, count: textCords.count * MemoryLayout<vector_float2>.size)
    let textureSource = SCNGeometrySource(data: uvData,
                                          semantic: .texcoord,
                                          vectorCount: textCords.count,
                                          usesFloatComponents: true,
                                          componentsPerVector: 2,
                                          bytesPerComponent: MemoryLayout<Float>.size,
                                          dataOffset: 0,
                                          dataStride: MemoryLayout<vector_float2>.size)

    var finalIndices: [Int32] = []
    finalIndices.append(contentsOf: indexTable)
    finalIndices.append(contentsOf: indices)

    let indexData = Data(bytes: finalIndices,
                         count: finalIndices.count * MemoryLayout<Int32>.size)
    let elements = SCNGeometryElement(data: indexData,
                                      primitiveType: .polygon,
                                      primitiveCount: indexTable.count,
                                      bytesPerIndex: MemoryLayout<Int32>.size)

    return SCNGeometry(sources: [verticesSource, textureSource], elements: [elements])
}
fileprivate func addFace(face: face, textureOffset: CGPoint, textureSize: CGSize, toVertices: inout [SCNVector3], indexTable: inout [Int32], indices: inout [Int32], textCords: inout [vector_float2]) {
    toVertices.append(face.topRight)
    toVertices.append(face.topLeft)
    toVertices.append(face.bottomLeft)
    toVertices.append(face.bottomRight)

    let polygonPointCount: Int32 = 4
    indexTable.append(polygonPointCount)
    for _ in 0..<polygonPointCount {
        indices.append(Int32(indices.count))
    }

    textCords.append(vector_float2(Float(bottomRight.x + textureOffset.x + textureSize.width), Float(bottomRight.y + textureOffset.y)))
    textCords.append(vector_float2(Float(bottomLeft.x + textureOffset.x + textureSize.width), Float(bottomLeft.y + textureOffset.y)))
    textCords.append(vector_float2(Float(topLeft.x + textureOffset.x + textureSize.width), Float(topLeft.y + textureOffset.y)))
    textCords.append(vector_float2(Float(topRight.x + textureOffset.x + textureSize.width), Float(topRight.y + textureOffset.y)))
}
1

1 Answers

1
votes

A vertex position and all related attributes form a record. This means, if a vertex position has to be used more than once, with different texture coordinates, the the vertex position has to be add to the buffer once for each texture coordinate. It is not possible to associate the element array with an texture coordinate.
You have to create a vertex position buffer with 4 vertices for each side of the cube and a texture coordinate buffer with 4 texture coordinates for each side of the cube:

texureCoordsSide =[
    vector_float2(Float(bottomLeft.x), Float(bottomLeft.y)),
    vector_float2(Float(bottomRight.x), Float(bottomRight.y)),
    vector_float2(Float(topRight.x), Float(topRight.y)),
    vector_float2(Float(topLeft.x), Float(topLeft.y))]

// Front Face
vertices += [startFace.bottomLeft, startFace.bottomRight, startFace.topRight, startFace.topLeft];
textCords += texureCoordsSide

// Left Face
vertices += [endFace.bottomLeft, startFace.bottomLeft, startFace.topLeft, endFace.topLeft];
textCords += texureCoordsSide

// Top Face
vertices += [startFace.topLeft, startFace.topRight, endFace.topRight, endFace.topLeft];
textCords += texureCoordsSide

// Right Face
vertices += [startFace.bottomRight, endFace.bottomRight, endFace.topRight, startFace.topRight];
textCords += texureCoordsSide

// Bottom Face
vertices += [endFace.bottomRight, endFace.bottomLeft, startFace.bottomLeft, startFace.bottomRight];
textCords += texureCoordsSide

// Back Face
vertices += [endFace.bottomRight, endFace.bottomLeft, endFace.topLeft, endFace.topRight];
textCords += texureCoordsSide

The element array (indices) has to contain the 24 indices (4 for each of the 6 sides of the cube) in conscutive order from 0 to 23.