OBJLoader displays strange texture mapping with cube made in Blender (problem and solution)
See original GitHub issueHi. Thank you for this amazing book !
In the Loading more complex models chapter, I struggled to understand why my cube’s texture was not displayed correctly. The cube was made with Blender and with correct UV. image
After a quick search I have found #59 issue and understood the problem.
Maybe a little reword could help to not fall in this trap for further readers. IMO the sentence “And we will get our familiar textured cube.” can be misunderstood in the way that “the obj loader correctly works with a cube and texture”.
BTW here is my kotlin code (straight forward I hope) that supports both index by position and index by texture coords. It is inspired from code in #59 but cleaner and refactored to not have code duplication.
Anyone can copy/paste and use it (no external function used) and of course you can links this issue/code in your book
data class Face(
val indexGroups: List<IndexesGroup>,
)
data class IndexesGroup(val idxPos: Int = NO_VALUE, val idxTextCoords: Int = NO_VALUE, val idxVecNormal: Int = NO_VALUE) {
companion object {
const val NO_VALUE = -1
}
}
@OptIn(ExperimentalTime::class)
object ObjLoader {
fun loadMesh(fileName: String): Mesh {
val positionsData = mutableListOf<Vector3f>()
val texCoordsData = mutableListOf<Vector2f>()
val normalsData = mutableListOf<Vector3f>()
val facesData = mutableListOf<Face>()
File(fileName).forEachLine { line ->
val tokens = line.split(" ")
when (tokens.first()) {
"v" -> positionsData += Vector3f(tokens[1].toFloat(), tokens[2].toFloat(), tokens[3].toFloat())
"vt" -> texCoordsData += Vector2f(tokens[1].toFloat(), tokens[2].toFloat())
"vn" -> normalsData += Vector3f(tokens[1].toFloat(), tokens[2].toFloat(), tokens[3].toFloat())
"f" -> {
val indexGroups = mutableListOf<IndexesGroup>()
tokens.drop(1).forEach { groupString ->
val group = groupString.split("/").map { it.toInt() - 1 }
indexGroups += IndexesGroup(group[0], group[1], group[2])
}
facesData += Face(indexGroups)
}
}
}
val usePosForIdx = positionsData.size >= texCoordsData.size
return reorderLists(positionsData, texCoordsData, normalsData, facesData, usePosForIdx)
}
private fun reorderLists(
positionsData: MutableList<Vector3f>,
texCoordsData: MutableList<Vector2f>,
normalsData: MutableList<Vector3f>,
facesData: MutableList<Face>,
usePosForIdx: Boolean = true
): Mesh {
println("Mesh reordering with positions: $usePosForIdx")
val arraySize = if (usePosForIdx) positionsData.size else texCoordsData.size
val positions = FloatArray(arraySize * 3)
val texCoords = FloatArray(arraySize * 2)
val normals = FloatArray(arraySize * 3)
val indices = mutableListOf<Int>()
facesData.forEach { face ->
face.indexGroups.forEach { groupId ->
val index = if (usePosForIdx) groupId.idxPos else groupId.idxTextCoords
val position = positionsData[groupId.idxPos]
positions[index * 3] = position.x
positions[index * 3 + 1] = position.y
positions[index * 3 + 2] = position.z
val coords = texCoordsData[groupId.idxTextCoords]
texCoords[index * 2] = coords.x
texCoords[index * 2 + 1] = 1 - coords.y
val normal = normalsData[groupId.idxVecNormal]
normals[index * 3] = normal.x
normals[index * 3 + 1] = normal.y
normals[index * 3 + 2] = normal.z
indices += index
}
}
return Mesh(positions, texCoords, normals, indices.toIntArray())
}
}
Issue Analytics
- State:
- Created 2 years ago
- Comments:5 (3 by maintainers)
Top GitHub Comments
Done!
Thank you very much for the example.
Hi,
You are right, I forgot this sentence in chapter 9. I have a public repository with the code, but with 2 methods used from outside the OBJLoader in repo class (
measureAndLog
andLog.debug
) and I could move file/package in the future.So I have created an OBJLoader gits gits with no external function. Anyone can copy/paste and use it in seconds.
Of course you can link that in your book, if I can bring my little contribution for future readers, I’m happy with that 😃