I am trying to understand visionOS/RealityKit.
As a test, I created a box and wanted to apply a texture image only to the top of the box.
To achieve this, I have apparently to construct the box of 6 faces, e.g. planes (code see below).
As a first test, I assigned a green material to the top face, and a brown material to all other faces. This is the result:
Then, I wanted to replace the top face material by a texture image. I read this tutorial how to do this, implemented the tutorial code, and the texture image is displayed correctly:
Eventually, I wanted to apply the same texture image to the top face of my box. I used the following code:
let texture = try! TextureResource.load(named: "tomatoes")
var material = UnlitMaterial()
material.color = UnlitMaterial.BaseColor(texture: .init(texture))
Here is the result:
Apparently, only part of the bottom row of the image is copied to all depth values of the top face.
I don't have experience in computer graphics, and thus don't know how mapping a texture to a plane works.
Obviously, something is wrong with my code, but what?
Here is the code that creates the anchor entity of the box:
let halfWidth = 1.0
let halfHeight = 1.0
let halfThickness = 0.04
// Define the 8 corners of a box
let frontBottomLeft = SIMD3(-halfWidth, -halfHeight, halfThickness) // 0
let frontBottomRight = SIMD3( halfWidth, -halfHeight, halfThickness) // 1
let frontTopRight = SIMD3( halfWidth, halfHeight, halfThickness) // 2
let frontTopLeft = SIMD3(-halfWidth, halfHeight, halfThickness) // 3
let backBottomLeft = SIMD3(-halfWidth, -halfHeight, -halfThickness) // 4
let backBottomRight = SIMD3( halfWidth, -halfHeight, -halfThickness) // 5
let backTopRight = SIMD3( halfWidth, halfHeight, -halfThickness) // 6
let backTopLeft = SIMD3(-halfWidth, halfHeight, -halfThickness) // 7
// Define the 8 vertices of the box
let vertices: [SIMD3<Float>] = [
frontBottomLeft,
frontBottomRight,
frontTopRight,
frontTopLeft,
backBottomLeft,
backBottomRight,
backTopRight,
backTopLeft
]
// Define a polygon for every face. THere are 6 faces, and every face has 4 polygons.
let polygons: ([UInt8], [UInt32]) = ([4, 4, 4, 4, 4, 4], // The nr of vertices in each polygon
[0, 1, 2, 3, // Front face
4, 5, 6, 7, // Back face
4, 0, 3, 7, // Left face
1, 5, 6, 2, // Right face
3, 2, 6, 7, // Top face
4, 5, 1, 0]) // Bottom face
// Define materials
var defaultMaterial = UnlitMaterial(applyPostProcessToneMap: false)
defaultMaterial.color.tint = .brown
var topMaterial = material
// Loop over the polygons. Here, primitives are polygons. They are processed sequentially.
let boardEntity = Entity()
var indexPointer = 0 // Point to the 1st index of the next polygon
for i in 0 ..< polygons.0.count { // There are 6 polygons for a box
var meshDescriptor = MeshDescriptor()
meshDescriptor.name = "Board mesh face \(i)"
let nrVerticesOfPolygon = Int(polygons.0[i]) // Here, all 6 polygons have 4 vertices
var indices: [UInt32] = [] // The indices say which vertex is connected which other one for the current polygon
for j in indexPointer ..< indexPointer + nrVerticesOfPolygon { // Use the next 4 indexes
indices.append(polygons.1[j]) // Append next vertex index
}
meshDescriptor.positions = MeshBuffer(vertices)
meshDescriptor.primitives = .polygons([polygons.0[i]], indices)
let mesh = try! MeshResource.generate(from: [meshDescriptor])
let material = i == 4 ? topMaterial : defaultMaterial
let modelEntity = ModelEntity(mesh: mesh, materials: [material])
boardEntity.addChild(modelEntity)
indexPointer += nrVerticesOfPolygon
}
let anchorEntity = AnchorEntity(world: position)
anchorEntity.addChild(boardEntity)
My code was much too complicated. I found the solution to my problem here.
It is possible by a built-in function to generate a box and to assign different materials to its faces, see the docs.
Using this, the solution is trivial:
var defaultMaterial = UnlitMaterial(applyPostProcessToneMap: false)
defaultMaterial.color.tint = .brown
let materials = [defaultMaterial, topMaterial, defaultMaterial, defaultMaterial, defaultMaterial, defaultMaterial]
let mesh = MeshResource.generateBox(width: 2, height: 0.04, depth: 2, splitFaces: true)
let boardEntity = ModelEntity(mesh: mesh, materials: materials)
let anchorEntity = AnchorEntity(world: position)
anchorEntity.addChild(boardEntity)