mirror of
https://github.com/DaanVandenBosch/phantasmal-world.git
synced 2025-04-04 06:28:28 +08:00
Fixed bug in NJ bone weight calculation. Improved NJ parser by avoiding reparsing of chunks.
This commit is contained in:
parent
47598a2670
commit
0ff4752949
@ -55,6 +55,8 @@ private fun <Model : NinjaModel, Context> parseSiblingObjects(
|
||||
val zxyRotationOrder = evalFlags.isBitSet(5)
|
||||
val skip = evalFlags.isBitSet(6)
|
||||
val shapeSkip = evalFlags.isBitSet(7)
|
||||
val clip = evalFlags.isBitSet(8)
|
||||
val modifier = evalFlags.isBitSet(9)
|
||||
|
||||
val modelOffset = cursor.int()
|
||||
val pos = cursor.vec3Float()
|
||||
@ -98,6 +100,8 @@ private fun <Model : NinjaModel, Context> parseSiblingObjects(
|
||||
zxyRotationOrder,
|
||||
skip,
|
||||
shapeSkip,
|
||||
clip,
|
||||
modifier,
|
||||
),
|
||||
model,
|
||||
pos,
|
||||
|
@ -63,6 +63,8 @@ class NinjaEvaluationFlags(
|
||||
var zxyRotationOrder: Boolean,
|
||||
var skip: Boolean,
|
||||
var shapeSkip: Boolean,
|
||||
val clip: Boolean,
|
||||
val modifier: Boolean,
|
||||
)
|
||||
|
||||
sealed class NinjaModel
|
||||
@ -83,7 +85,7 @@ class NjModel(
|
||||
class NjVertex(
|
||||
val position: Vec3,
|
||||
val normal: Vec3?,
|
||||
val boneWeight: Float,
|
||||
val boneWeight: Float?,
|
||||
val boneWeightStatus: Int,
|
||||
val calcContinue: Boolean,
|
||||
)
|
||||
@ -111,19 +113,23 @@ class NjMeshVertex(
|
||||
val texCoords: Vec2?,
|
||||
)
|
||||
|
||||
sealed class NjChunk(val typeId: UByte) {
|
||||
class Unknown(typeId: UByte) : NjChunk(typeId)
|
||||
sealed class NjChunk(val typeId: Int) {
|
||||
class Unknown(typeId: Int) : NjChunk(typeId)
|
||||
|
||||
object Null : NjChunk(0u)
|
||||
object Null : NjChunk(0)
|
||||
|
||||
class Bits(typeId: UByte, val srcAlpha: Int, val dstAlpha: Int) : NjChunk(typeId)
|
||||
class BlendAlpha(val srcAlpha: Int, val dstAlpha: Int) : NjChunk(1)
|
||||
|
||||
class CachePolygonList(val cacheIndex: Int, val offset: Int) : NjChunk(4u)
|
||||
class MipmapDAdjust(val adjust: Int) : NjChunk(2)
|
||||
|
||||
class DrawPolygonList(val cacheIndex: Int) : NjChunk(5u)
|
||||
class SpecularExponent(val specular: Int) : NjChunk(3)
|
||||
|
||||
class CachePolygonList(val cacheIndex: Int) : NjChunk(4)
|
||||
|
||||
class DrawPolygonList(val cacheIndex: Int) : NjChunk(5)
|
||||
|
||||
class Tiny(
|
||||
typeId: UByte,
|
||||
typeId: Int,
|
||||
val flipU: Boolean,
|
||||
val flipV: Boolean,
|
||||
val clampU: Boolean,
|
||||
@ -135,7 +141,7 @@ sealed class NjChunk(val typeId: UByte) {
|
||||
) : NjChunk(typeId)
|
||||
|
||||
class Material(
|
||||
typeId: UByte,
|
||||
typeId: Int,
|
||||
val srcAlpha: Int,
|
||||
val dstAlpha: Int,
|
||||
val diffuse: NjArgb?,
|
||||
@ -143,20 +149,20 @@ sealed class NjChunk(val typeId: UByte) {
|
||||
val specular: NjErgb?,
|
||||
) : NjChunk(typeId)
|
||||
|
||||
class Vertex(typeId: UByte, val vertices: List<NjChunkVertex>) : NjChunk(typeId)
|
||||
class Vertex(typeId: Int, val vertices: List<NjChunkVertex>) : NjChunk(typeId)
|
||||
|
||||
class Volume(typeId: UByte) : NjChunk(typeId)
|
||||
class Volume(typeId: Int) : NjChunk(typeId)
|
||||
|
||||
class Strip(typeId: UByte, val triangleStrips: List<NjTriangleStrip>) : NjChunk(typeId)
|
||||
class Strip(typeId: Int, val triangleStrips: List<NjTriangleStrip>) : NjChunk(typeId)
|
||||
|
||||
object End : NjChunk(255u)
|
||||
object End : NjChunk(255)
|
||||
}
|
||||
|
||||
class NjChunkVertex(
|
||||
val index: Int,
|
||||
val position: Vec3,
|
||||
val normal: Vec3?,
|
||||
val boneWeight: Float,
|
||||
val boneWeight: Float?,
|
||||
val boneWeightStatus: Int,
|
||||
val calcContinue: Boolean,
|
||||
)
|
||||
|
@ -16,7 +16,7 @@ private val logger = KotlinLogging.logger {}
|
||||
|
||||
// TODO: Simplify parser by not parsing chunks into vertices and meshes. Do the chunk to vertex/mesh
|
||||
// conversion at a higher level.
|
||||
fun parseNjModel(cursor: Cursor, cachedChunkOffsets: MutableMap<Int, Int>): NjModel {
|
||||
fun parseNjModel(cursor: Cursor, cachedChunks: MutableMap<Int, List<NjChunk>>): NjModel {
|
||||
val vlistOffset = cursor.int() // Vertex list
|
||||
val plistOffset = cursor.int() // Triangle strip index list
|
||||
val collisionSphereCenter = cursor.vec3Float()
|
||||
@ -27,7 +27,7 @@ fun parseNjModel(cursor: Cursor, cachedChunkOffsets: MutableMap<Int, Int>): NjMo
|
||||
if (vlistOffset != 0) {
|
||||
cursor.seekStart(vlistOffset)
|
||||
|
||||
for (chunk in parseChunks(cursor, cachedChunkOffsets, true)) {
|
||||
for (chunk in parseChunks(cursor)) {
|
||||
if (chunk is NjChunk.Vertex) {
|
||||
for (vertex in chunk.vertices) {
|
||||
while (vertices.size <= vertex.index) {
|
||||
@ -46,44 +46,10 @@ fun parseNjModel(cursor: Cursor, cachedChunkOffsets: MutableMap<Int, Int>): NjMo
|
||||
}
|
||||
}
|
||||
|
||||
if (plistOffset != 0) {
|
||||
if (plistOffset > 0) {
|
||||
cursor.seekStart(plistOffset)
|
||||
|
||||
var textureId: Int? = null
|
||||
var srcAlpha: Int? = null
|
||||
var dstAlpha: Int? = null
|
||||
|
||||
for (chunk in parseChunks(cursor, cachedChunkOffsets, false)) {
|
||||
when (chunk) {
|
||||
is NjChunk.Bits -> {
|
||||
srcAlpha = chunk.srcAlpha
|
||||
dstAlpha = chunk.dstAlpha
|
||||
}
|
||||
|
||||
is NjChunk.Tiny -> {
|
||||
textureId = chunk.textureId
|
||||
}
|
||||
|
||||
is NjChunk.Material -> {
|
||||
srcAlpha = chunk.srcAlpha
|
||||
dstAlpha = chunk.dstAlpha
|
||||
}
|
||||
|
||||
is NjChunk.Strip -> {
|
||||
for (strip in chunk.triangleStrips) {
|
||||
strip.textureId = textureId
|
||||
strip.srcAlpha = srcAlpha
|
||||
strip.dstAlpha = dstAlpha
|
||||
}
|
||||
|
||||
meshes.addAll(chunk.triangleStrips)
|
||||
}
|
||||
|
||||
else -> {
|
||||
// Ignore
|
||||
}
|
||||
}
|
||||
}
|
||||
PolygonChunkProcessor(cachedChunks, meshes).process(parseChunks(cursor))
|
||||
}
|
||||
|
||||
return NjModel(
|
||||
@ -94,157 +60,236 @@ fun parseNjModel(cursor: Cursor, cachedChunkOffsets: MutableMap<Int, Int>): NjMo
|
||||
)
|
||||
}
|
||||
|
||||
// TODO: don't reparse when DrawPolygonList chunk is encountered.
|
||||
private fun parseChunks(
|
||||
cursor: Cursor,
|
||||
cachedChunkOffsets: MutableMap<Int, Int>,
|
||||
wideEndChunks: Boolean,
|
||||
): List<NjChunk> {
|
||||
val chunks: MutableList<NjChunk> = mutableListOf()
|
||||
var loop = true
|
||||
private class PolygonChunkProcessor(
|
||||
private val cachedChunks: MutableMap<Int, List<NjChunk>>,
|
||||
private val meshes: MutableList<NjTriangleStrip>,
|
||||
) {
|
||||
private var textureId: Int? = null
|
||||
private var srcAlpha: Int? = null
|
||||
private var dstAlpha: Int? = null
|
||||
|
||||
while (loop) {
|
||||
val typeId = cursor.uByte()
|
||||
val flags = cursor.uByte().toInt()
|
||||
val chunkStartPosition = cursor.position
|
||||
var size = 0
|
||||
/**
|
||||
* When [cacheList] is non-null we are caching chunks.
|
||||
*/
|
||||
private var cacheList: MutableList<NjChunk>? = null
|
||||
|
||||
when (typeId.toInt()) {
|
||||
0 -> {
|
||||
chunks.add(NjChunk.Null)
|
||||
fun process(chunks: List<NjChunk>) {
|
||||
for (chunk in chunks) {
|
||||
if (cacheList == null) {
|
||||
when (chunk) {
|
||||
is NjChunk.BlendAlpha -> {
|
||||
srcAlpha = chunk.srcAlpha
|
||||
dstAlpha = chunk.dstAlpha
|
||||
}
|
||||
|
||||
is NjChunk.CachePolygonList -> {
|
||||
cacheList = mutableListOf()
|
||||
cachedChunks[chunk.cacheIndex] = cacheList!!
|
||||
}
|
||||
|
||||
is NjChunk.DrawPolygonList -> {
|
||||
val cached = cachedChunks[chunk.cacheIndex]
|
||||
|
||||
if (cached == null) {
|
||||
logger.debug {
|
||||
"Draw Polygon List chunk pointed to nonexistent cache index ${chunk.cacheIndex}."
|
||||
}
|
||||
} else {
|
||||
process(cached)
|
||||
}
|
||||
}
|
||||
|
||||
is NjChunk.Tiny -> {
|
||||
textureId = chunk.textureId
|
||||
}
|
||||
|
||||
is NjChunk.Material -> {
|
||||
srcAlpha = chunk.srcAlpha
|
||||
dstAlpha = chunk.dstAlpha
|
||||
}
|
||||
|
||||
is NjChunk.Strip -> {
|
||||
for (strip in chunk.triangleStrips) {
|
||||
strip.textureId = textureId
|
||||
strip.srcAlpha = srcAlpha
|
||||
strip.dstAlpha = dstAlpha
|
||||
}
|
||||
|
||||
meshes.addAll(chunk.triangleStrips)
|
||||
}
|
||||
|
||||
else -> {
|
||||
// Ignore
|
||||
}
|
||||
}
|
||||
} else {
|
||||
cacheList!!.add(chunk)
|
||||
}
|
||||
in 1..3 -> {
|
||||
chunks.add(NjChunk.Bits(
|
||||
typeId,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun parseChunks(cursor: Cursor): List<NjChunk> {
|
||||
val chunks: MutableList<NjChunk> = mutableListOf()
|
||||
|
||||
do {
|
||||
val chunkStartPosition = cursor.position
|
||||
val typeId = cursor.uByte().toInt()
|
||||
val flags = cursor.uByte().toInt()
|
||||
val chunkDataPosition = cursor.position
|
||||
var size = 0
|
||||
val chunk: NjChunk
|
||||
|
||||
when (typeId) {
|
||||
0 -> {
|
||||
chunk = NjChunk.Null
|
||||
}
|
||||
1 -> {
|
||||
chunk = NjChunk.BlendAlpha(
|
||||
srcAlpha = (flags ushr 3) and 0b111,
|
||||
dstAlpha = flags and 0b111,
|
||||
))
|
||||
)
|
||||
}
|
||||
2 -> {
|
||||
chunk = NjChunk.MipmapDAdjust(
|
||||
adjust = flags and 0b1111,
|
||||
)
|
||||
}
|
||||
3 -> {
|
||||
chunk = NjChunk.SpecularExponent(
|
||||
specular = flags and 0b11111,
|
||||
)
|
||||
}
|
||||
4 -> {
|
||||
val offset = cursor.position
|
||||
|
||||
chunks.add(NjChunk.CachePolygonList(
|
||||
chunk = NjChunk.CachePolygonList(
|
||||
cacheIndex = flags,
|
||||
offset,
|
||||
))
|
||||
|
||||
cachedChunkOffsets[flags] = offset
|
||||
loop = false
|
||||
)
|
||||
}
|
||||
5 -> {
|
||||
val cachedOffset = cachedChunkOffsets[flags]
|
||||
|
||||
if (cachedOffset != null) {
|
||||
cursor.seekStart(cachedOffset)
|
||||
chunks.addAll(parseChunks(cursor, cachedChunkOffsets, wideEndChunks))
|
||||
}
|
||||
|
||||
chunks.add(NjChunk.DrawPolygonList(
|
||||
chunk = NjChunk.DrawPolygonList(
|
||||
cacheIndex = flags,
|
||||
))
|
||||
)
|
||||
}
|
||||
in 8..9 -> {
|
||||
size = 2
|
||||
val textureBitsAndId = cursor.uShort().toInt()
|
||||
|
||||
chunks.add(NjChunk.Tiny(
|
||||
chunk = NjChunk.Tiny(
|
||||
typeId,
|
||||
flipU = typeId.isBitSet(7),
|
||||
flipV = typeId.isBitSet(6),
|
||||
clampU = typeId.isBitSet(5),
|
||||
clampV = typeId.isBitSet(4),
|
||||
mipmapDAdjust = typeId.toUInt() and 0b1111u,
|
||||
flipU = flags.isBitSet(7),
|
||||
flipV = flags.isBitSet(6),
|
||||
clampU = flags.isBitSet(5),
|
||||
clampV = flags.isBitSet(4),
|
||||
mipmapDAdjust = flags.toUInt() and 0b1111u,
|
||||
filterMode = textureBitsAndId ushr 14,
|
||||
superSample = (textureBitsAndId and 0x40) != 0,
|
||||
textureId = textureBitsAndId and 0x1fff,
|
||||
))
|
||||
textureId = textureBitsAndId and 0x1FFF,
|
||||
)
|
||||
}
|
||||
in 17..31 -> {
|
||||
size = 2 + 2 * cursor.short()
|
||||
val bodySize = 2 * cursor.short()
|
||||
size = 2 + bodySize
|
||||
|
||||
var diffuse: NjArgb? = null
|
||||
var ambient: NjArgb? = null
|
||||
var specular: NjErgb? = null
|
||||
|
||||
if (flags.isBitSet(0)) {
|
||||
diffuse = NjArgb(
|
||||
b = cursor.uByte().toFloat() / 255f,
|
||||
g = cursor.uByte().toFloat() / 255f,
|
||||
r = cursor.uByte().toFloat() / 255f,
|
||||
a = cursor.uByte().toFloat() / 255f,
|
||||
)
|
||||
if (typeId == 24) {
|
||||
// Skip bump map data.
|
||||
cursor.seek(bodySize)
|
||||
} else {
|
||||
if (typeId.isBitSet(0)) {
|
||||
diffuse = NjArgb(
|
||||
b = cursor.uByte().toFloat() / 255f,
|
||||
g = cursor.uByte().toFloat() / 255f,
|
||||
r = cursor.uByte().toFloat() / 255f,
|
||||
a = cursor.uByte().toFloat() / 255f,
|
||||
)
|
||||
}
|
||||
|
||||
if (typeId.isBitSet(1)) {
|
||||
ambient = NjArgb(
|
||||
b = cursor.uByte().toFloat() / 255f,
|
||||
g = cursor.uByte().toFloat() / 255f,
|
||||
r = cursor.uByte().toFloat() / 255f,
|
||||
a = cursor.uByte().toFloat() / 255f,
|
||||
)
|
||||
}
|
||||
|
||||
if (typeId.isBitSet(2)) {
|
||||
specular = NjErgb(
|
||||
b = cursor.uByte(),
|
||||
g = cursor.uByte(),
|
||||
r = cursor.uByte(),
|
||||
e = cursor.uByte(),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
if (flags.isBitSet(1)) {
|
||||
ambient = NjArgb(
|
||||
b = cursor.uByte().toFloat() / 255f,
|
||||
g = cursor.uByte().toFloat() / 255f,
|
||||
r = cursor.uByte().toFloat() / 255f,
|
||||
a = cursor.uByte().toFloat() / 255f,
|
||||
)
|
||||
}
|
||||
|
||||
if (flags.isBitSet(2)) {
|
||||
specular = NjErgb(
|
||||
b = cursor.uByte(),
|
||||
g = cursor.uByte(),
|
||||
r = cursor.uByte(),
|
||||
e = cursor.uByte(),
|
||||
)
|
||||
}
|
||||
|
||||
chunks.add(NjChunk.Material(
|
||||
chunk = NjChunk.Material(
|
||||
typeId,
|
||||
srcAlpha = (flags ushr 3) and 0b111,
|
||||
dstAlpha = flags and 0b111,
|
||||
diffuse,
|
||||
ambient,
|
||||
specular,
|
||||
))
|
||||
)
|
||||
}
|
||||
in 32..50 -> {
|
||||
size = 2 + 4 * cursor.short()
|
||||
chunks.add(NjChunk.Vertex(
|
||||
chunk = NjChunk.Vertex(
|
||||
typeId,
|
||||
vertices = parseVertexChunk(cursor, typeId, flags),
|
||||
))
|
||||
)
|
||||
}
|
||||
in 56..58 -> {
|
||||
size = 2 + 2 * cursor.short()
|
||||
chunks.add(NjChunk.Volume(
|
||||
chunk = NjChunk.Volume(
|
||||
typeId,
|
||||
))
|
||||
)
|
||||
|
||||
// Skip volume information.
|
||||
cursor.seek(2 * cursor.short())
|
||||
}
|
||||
in 64..75 -> {
|
||||
size = 2 + 2 * cursor.short()
|
||||
chunks.add(NjChunk.Strip(
|
||||
chunk = NjChunk.Strip(
|
||||
typeId,
|
||||
triangleStrips = parseTriangleStripChunk(cursor, typeId, flags),
|
||||
))
|
||||
)
|
||||
}
|
||||
255 -> {
|
||||
size = if (wideEndChunks) 2 else 0
|
||||
chunks.add(NjChunk.End)
|
||||
loop = false
|
||||
chunk = NjChunk.End
|
||||
}
|
||||
else -> {
|
||||
size = 2 + 2 * cursor.short()
|
||||
chunks.add(NjChunk.Unknown(
|
||||
val bodySize = 2 * cursor.short()
|
||||
size = 2 + bodySize
|
||||
chunk = NjChunk.Unknown(
|
||||
typeId,
|
||||
))
|
||||
)
|
||||
// Skip unknown data.
|
||||
cursor.seek(bodySize)
|
||||
logger.warn { "Unknown chunk type $typeId at offset ${chunkStartPosition}." }
|
||||
}
|
||||
}
|
||||
|
||||
cursor.seekStart(chunkStartPosition + size)
|
||||
}
|
||||
chunks.add(chunk)
|
||||
|
||||
val bytesRead = cursor.position - chunkDataPosition
|
||||
|
||||
check(bytesRead <= size) {
|
||||
"Expected to read at most $size bytes, actually read $bytesRead."
|
||||
}
|
||||
|
||||
cursor.seekStart(chunkDataPosition + size)
|
||||
} while (chunk != NjChunk.End)
|
||||
|
||||
return chunks
|
||||
}
|
||||
|
||||
private fun parseVertexChunk(
|
||||
cursor: Cursor,
|
||||
chunkTypeId: UByte,
|
||||
chunkTypeId: Int,
|
||||
flags: Int,
|
||||
): List<NjChunkVertex> {
|
||||
val boneWeightStatus = flags and 0b11
|
||||
@ -259,60 +304,79 @@ private fun parseVertexChunk(
|
||||
var vertexIndex = index + i
|
||||
val position = cursor.vec3Float()
|
||||
var normal: Vec3? = null
|
||||
var boneWeight = 1f
|
||||
var boneWeight: Float? = null
|
||||
|
||||
when (chunkTypeId.toInt()) {
|
||||
when (chunkTypeId) {
|
||||
32 -> {
|
||||
// NJDCVSH
|
||||
// NJD_CV_SH
|
||||
cursor.seek(4) // Always 1.0
|
||||
}
|
||||
33 -> {
|
||||
// NJDCVVNSH
|
||||
// NJD_CV_VN_SH
|
||||
cursor.seek(4) // Always 1.0
|
||||
normal = cursor.vec3Float()
|
||||
cursor.seek(4) // Always 0.0
|
||||
}
|
||||
34 -> {
|
||||
// NJD_CV
|
||||
// Nothing to do.
|
||||
}
|
||||
in 35..40 -> {
|
||||
if (chunkTypeId == (37u).toUByte()) {
|
||||
// NJDCVNF
|
||||
if (chunkTypeId == 37) {
|
||||
// NJD_CV_NF
|
||||
// NinjaFlags32
|
||||
vertexIndex = index + cursor.uShort()
|
||||
boneWeight = cursor.uShort().toFloat() / 255f
|
||||
} else {
|
||||
// NJD_CV_D8
|
||||
// NJD_CV_UF
|
||||
// NJD_CV_S5
|
||||
// NJD_CV_S4
|
||||
// NJD_CV_IN
|
||||
// Skip user flags and material information.
|
||||
cursor.seek(4)
|
||||
}
|
||||
}
|
||||
41 -> {
|
||||
// NJD_CV_VN
|
||||
normal = cursor.vec3Float()
|
||||
}
|
||||
in 42..47 -> {
|
||||
normal = cursor.vec3Float()
|
||||
|
||||
if (chunkTypeId == (44u).toUByte()) {
|
||||
// NJDCVVNNF
|
||||
if (chunkTypeId == 44) {
|
||||
// NJD_CV_VN_NF
|
||||
// NinjaFlags32
|
||||
vertexIndex = index + cursor.uShort()
|
||||
boneWeight = cursor.uShort().toFloat() / 255f
|
||||
} else {
|
||||
// NJD_CV_VN_D8
|
||||
// NJD_CV_VN_UF
|
||||
// NJD_CV_VN_S5
|
||||
// NJD_CV_VN_S4
|
||||
// NJD_CV_VN_IN
|
||||
// Skip user flags and material information.
|
||||
cursor.seek(4)
|
||||
}
|
||||
}
|
||||
in 48..50 -> {
|
||||
// NJD_CV_VNX
|
||||
// 32-Bit vertex normal in format: reserved(2)|x(10)|y(10)|z(10)
|
||||
val n = cursor.uInt()
|
||||
normal = Vec3(
|
||||
((n shr 20) and 0x3ffu).toFloat() / 0x3ff,
|
||||
((n shr 10) and 0x3ffu).toFloat() / 0x3ff,
|
||||
(n and 0x3ffu).toFloat() / 0x3ff,
|
||||
((n shr 20) and 0x3FFu).toFloat() / 0x3FF,
|
||||
((n shr 10) and 0x3FFu).toFloat() / 0x3FF,
|
||||
(n and 0x3FFu).toFloat() / 0x3FF,
|
||||
)
|
||||
|
||||
if (chunkTypeId >= 49u) {
|
||||
if (chunkTypeId >= 49) {
|
||||
// NJD_CV_VNX_D8
|
||||
// NJD_CV_VNX_UF
|
||||
// Skip user flags and material information.
|
||||
cursor.seek(4)
|
||||
}
|
||||
}
|
||||
else -> error("Unexpected chunk type ID ${chunkTypeId}.")
|
||||
}
|
||||
|
||||
vertices.add(NjChunkVertex(
|
||||
@ -330,7 +394,7 @@ private fun parseVertexChunk(
|
||||
|
||||
private fun parseTriangleStripChunk(
|
||||
cursor: Cursor,
|
||||
chunkTypeId: UByte,
|
||||
chunkTypeId: Int,
|
||||
flags: Int,
|
||||
): List<NjTriangleStrip> {
|
||||
val ignoreLight = flags.isBitSet(0)
|
||||
@ -342,7 +406,7 @@ private fun parseTriangleStripChunk(
|
||||
val environmentMapping = flags.isBitSet(6)
|
||||
|
||||
val userOffsetAndStripCount = cursor.short().toInt()
|
||||
val userFlagsSize = (userOffsetAndStripCount ushr 14)
|
||||
val userFlagsSize = 2 * (userOffsetAndStripCount ushr 14)
|
||||
val stripCount = userOffsetAndStripCount and 0x3FFF
|
||||
|
||||
var hasTexCoords = false
|
||||
@ -350,7 +414,7 @@ private fun parseTriangleStripChunk(
|
||||
var hasNormal = false
|
||||
var hasDoubleTexCoords = false
|
||||
|
||||
when (chunkTypeId.toInt()) {
|
||||
when (chunkTypeId) {
|
||||
64 -> {
|
||||
}
|
||||
65, 66 -> {
|
||||
@ -381,9 +445,9 @@ private fun parseTriangleStripChunk(
|
||||
val strips: MutableList<NjTriangleStrip> = mutableListOf()
|
||||
|
||||
repeat(stripCount) {
|
||||
val windingFlagAndIndexCount = cursor.short()
|
||||
val clockwiseWinding = windingFlagAndIndexCount < 1
|
||||
val indexCount = abs(windingFlagAndIndexCount.toInt())
|
||||
val windingFlagAndIndexCount = cursor.short().toInt()
|
||||
val clockwiseWinding = windingFlagAndIndexCount < 0
|
||||
val indexCount = abs(windingFlagAndIndexCount)
|
||||
|
||||
val vertices: MutableList<NjMeshVertex> = mutableListOf()
|
||||
|
||||
@ -414,7 +478,7 @@ private fun parseTriangleStripChunk(
|
||||
|
||||
// User flags start at the third vertex because they are per-triangle.
|
||||
if (j >= 2) {
|
||||
cursor.seek(2 * userFlagsSize)
|
||||
cursor.seek(userFlagsSize)
|
||||
}
|
||||
|
||||
vertices.add(NjMeshVertex(
|
||||
|
@ -22,7 +22,7 @@ fun parseXjModel(cursor: Cursor): XjModel {
|
||||
|
||||
val vertices = mutableListOf<XjVertex>()
|
||||
|
||||
if (vertexInfoCount >= 1) {
|
||||
if (vertexInfoCount > 0) {
|
||||
// TODO: parse all vertex info tables.
|
||||
vertices.addAll(parseVertexInfoTable(cursor, vertexInfoTableOffset))
|
||||
}
|
||||
@ -102,11 +102,11 @@ private fun parseVertexInfoTable(cursor: Cursor, vertexInfoTableOffset: Int): Li
|
||||
|
||||
private fun parseTriangleStripTable(
|
||||
cursor: Cursor,
|
||||
triangle_strip_list_offset: Int,
|
||||
triangle_strip_count: Int,
|
||||
triangleStripListOffset: Int,
|
||||
triangleStripCount: Int,
|
||||
): List<XjMesh> {
|
||||
return (0 until triangle_strip_count).map { i ->
|
||||
cursor.seekStart(triangle_strip_list_offset + i * 20)
|
||||
return (0 until triangleStripCount).map { i ->
|
||||
cursor.seekStart(triangleStripListOffset + i * 20)
|
||||
|
||||
val materialTableOffset = cursor.int()
|
||||
val materialTableSize = cursor.int()
|
||||
@ -124,7 +124,7 @@ private fun parseTriangleStripTable(
|
||||
|
||||
XjMesh(
|
||||
material,
|
||||
indices = List(indexCount) { indices[it].toInt() },
|
||||
indices = indices.map { it.toInt() },
|
||||
)
|
||||
}
|
||||
}
|
||||
|
@ -56,10 +56,29 @@ class MeshBuilder(
|
||||
fun getNormal(index: Int): Vector3 =
|
||||
normals[index]
|
||||
|
||||
fun vertex(position: Vector3, normal: Vector3, uv: Vector2? = null) {
|
||||
fun vertex(
|
||||
position: Vector3,
|
||||
normal: Vector3,
|
||||
uv: Vector2? = null,
|
||||
boneIndices: IntArray? = null,
|
||||
boneWeights: FloatArray? = null,
|
||||
) {
|
||||
positions.add(position)
|
||||
normals.add(normal)
|
||||
uv?.let { uvs.add(uv) }
|
||||
|
||||
if (boneIndices != null && boneWeights != null) {
|
||||
require(boneIndices.size == 4)
|
||||
require(boneWeights.size == 4)
|
||||
|
||||
for (index in boneIndices) {
|
||||
this.boneIndices.add(index.toShort())
|
||||
}
|
||||
|
||||
for (weight in boneWeights) {
|
||||
this.boneWeights.add(weight)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun index(groupIdx: Int, index: Int) {
|
||||
@ -71,19 +90,6 @@ class MeshBuilder(
|
||||
bones.add(bone)
|
||||
}
|
||||
|
||||
fun boneWeights(indices: IntArray, weights: FloatArray) {
|
||||
require(indices.size == 4)
|
||||
require(weights.size == 4)
|
||||
|
||||
for (index in indices) {
|
||||
boneIndices.add(index.toShort())
|
||||
}
|
||||
|
||||
for (weight in weights) {
|
||||
boneWeights.add(weight)
|
||||
}
|
||||
}
|
||||
|
||||
fun defaultMaterial(material: Material) {
|
||||
defaultMaterial = material
|
||||
}
|
||||
|
@ -100,7 +100,9 @@ private class NinjaToMeshConverter(private val builder: MeshBuilder) {
|
||||
}
|
||||
}
|
||||
|
||||
boneIndex++
|
||||
if (!ef.skip) {
|
||||
boneIndex++
|
||||
}
|
||||
|
||||
if (!ef.breakChildTrace) {
|
||||
obj.children.forEach { child ->
|
||||
@ -132,7 +134,6 @@ private class NinjaToMeshConverter(private val builder: MeshBuilder) {
|
||||
normal,
|
||||
vertex.boneWeight,
|
||||
vertex.boneWeightStatus,
|
||||
vertex.calcContinue,
|
||||
)
|
||||
}
|
||||
}
|
||||
@ -143,7 +144,7 @@ private class NinjaToMeshConverter(private val builder: MeshBuilder) {
|
||||
val group = builder.getGroupIndex(
|
||||
mesh.textureId,
|
||||
alpha = mesh.useAlpha,
|
||||
additiveBlending = mesh.srcAlpha != 4 || mesh.dstAlpha != 5
|
||||
additiveBlending = mesh.srcAlpha != 4 || mesh.dstAlpha != 5,
|
||||
)
|
||||
var i = 0
|
||||
|
||||
@ -160,10 +161,35 @@ private class NinjaToMeshConverter(private val builder: MeshBuilder) {
|
||||
vertex.normal ?: meshVertex.normal?.let(::vec3ToThree) ?: DEFAULT_NORMAL
|
||||
val index = builder.vertexCount
|
||||
|
||||
val boneIndices = IntArray(4)
|
||||
val boneWeights = FloatArray(4)
|
||||
|
||||
if (vertex.boneWeight == null) {
|
||||
boneIndices[0] = vertex.boneIndex
|
||||
boneWeights[0] = 1f
|
||||
} else {
|
||||
for (v in vertices) {
|
||||
boneIndices[v.boneWeightStatus] = v.boneIndex
|
||||
boneWeights[v.boneWeightStatus] = v.boneWeight ?: 1f
|
||||
}
|
||||
}
|
||||
|
||||
val totalWeight = boneWeights.sum()
|
||||
|
||||
if (totalWeight > 0f) {
|
||||
val weightFactor = 1f / totalWeight
|
||||
|
||||
for (j in boneWeights.indices) {
|
||||
boneWeights[j] *= weightFactor
|
||||
}
|
||||
}
|
||||
|
||||
builder.vertex(
|
||||
vertex.position,
|
||||
normal,
|
||||
meshVertex.texCoords?.let(::vec2ToThree) ?: DEFAULT_UV
|
||||
meshVertex.texCoords?.let(::vec2ToThree) ?: DEFAULT_UV,
|
||||
boneIndices,
|
||||
boneWeights,
|
||||
)
|
||||
|
||||
if (i >= 2) {
|
||||
@ -178,26 +204,6 @@ private class NinjaToMeshConverter(private val builder: MeshBuilder) {
|
||||
}
|
||||
}
|
||||
|
||||
val boneIndices = IntArray(4)
|
||||
val boneWeights = FloatArray(4)
|
||||
|
||||
for (v in vertices) {
|
||||
boneIndices[v.boneWeightStatus] = v.boneIndex
|
||||
boneWeights[v.boneWeightStatus] = v.boneWeight
|
||||
}
|
||||
|
||||
val totalWeight = boneWeights.sum()
|
||||
|
||||
if (totalWeight > 0f) {
|
||||
val weightFactor = 1f / totalWeight
|
||||
|
||||
for (j in boneWeights.indices) {
|
||||
boneWeights[j] *= weightFactor
|
||||
}
|
||||
}
|
||||
|
||||
builder.boneWeights(boneIndices, boneWeights)
|
||||
|
||||
i++
|
||||
}
|
||||
}
|
||||
@ -290,9 +296,8 @@ private class Vertex(
|
||||
val boneIndex: Int,
|
||||
val position: Vector3,
|
||||
val normal: Vector3?,
|
||||
val boneWeight: Float,
|
||||
val boneWeight: Float?,
|
||||
val boneWeightStatus: Int,
|
||||
val calcContinue: Boolean,
|
||||
)
|
||||
|
||||
private class VertexHolder {
|
||||
|
Loading…
Reference in New Issue
Block a user