上一章中,我们发现转换后顶点数据中,有很多重复的点
如上,可以合并成2个面,但实际上占用了8个面
因为每个面都是四边形,可以建一个类来进行后续操作
data class Quad(
val a: Int,
val b: Int,
val c: Int,
val d: Int
)
首先是判断四边形是否可以合并以及得到合并后的四边形
这里可以穷举每种可能的情况
fun canMerge(oth: Quad): Int {
return if (a == oth.c && b == oth.d) 1
else if (b == oth.a && d == oth.c) 2
else if (c == oth.a && d == oth.b) 3
else if (a == oth.b && c == oth.d) 4
else if (d == oth.b && c == oth.a) 5
else 0
}
fun merge(oth: Quad, method: Int): Quad {
return when(method) {
1 -> Quad(oth.a, oth.b, c, d)
2 -> Quad(a, oth.b, c, oth.d)
3 -> Quad(a, b, oth.c, oth.d)
4 -> Quad(oth.a, b, oth.c, d)
5 -> Quad(a, b, oth.c, oth.d)
else -> this
}
}
索引数组中,每6个点构成一个四边形,同时在转换顶点数据中,是按照特定的顺序生成的各个顶点
将索引数组转成Quad数组
fun buildQuad(indices: IntArray, offset: Int): Quad {
return Quad(indices[offset], indices[offset+1], indices[offset+2], indices[offset+4])
}
fun buildQuads(indices: IntArray): MutableList<Quad> {
val quads = ArrayList<Quad>(indices.size / 6)
for(i in indices.indices step 6) {
quads.add(buildQuad(indices, i))
}
return quads
}
遍历每个四边形进行合并
fun merge(quads: MutableList<Quad>) {
var merged = true
while(merged) {
merged = false
var i = 0
while(i<quads.size) {
var qa = quads[i]
var j = i + 1
while(j<quads.size){
val qb = quads[j]
if(qa.canMerge(qb)>0){
merged = true
quads[i] = qa.merge(qb, qa.canMerge(qb))
quads.removeAt(j)
qa = quads[i]
}else{
j++
}
}
i++
}
}
}
剔除合并后,未使用的顶点
data class Vertex(
val x: Float,
val y: Float,
val z: Float,
val nx: Float,
val ny: Float,
val nz: Float,
val r: Float,
val g: Float,
val b: Float,
val a: Float
)
fun buildVertices(vertices: FloatArray): MutableList<Vertex> {
val ret = ArrayList<Vertex>(vertices.size / 10)
for(i in vertices.indices step 10) {
ret.add(Vertex(
vertices[i],vertices[i+1],vertices[i+2],
vertices[i+3],vertices[i+4],vertices[i+5],
vertices[i+6],vertices[i+7],vertices[i+8],vertices[i+9]
))
}
return ret
}
fun toVertices(verts: List<Vertex>): FloatArray {
val arr = FloatArray(verts.size * 10)
for(i in verts.indices) {
arr[10*i] = verts[i].x
arr[10*i+1] = verts[i].y
arr[10*i+2] = verts[i].z
arr[10*i+3] = verts[i].nx
arr[10*i+4] = verts[i].ny
arr[10*i+5] = verts[i].nz
arr[10*i+6] = verts[i].r
arr[10*i+7] = verts[i].g
arr[10*i+8] = verts[i].b
arr[10*i+9] = verts[i].a
}
return arr
}
fun compressVertices(vertices: FloatArray, indices: IntArray): FloatArray {
val verts = buildVertices(vertices)
val min = indices.min()
var max = indices.max()
var i = min + 1
while(i < max) {
if(!indices.contains(i)){
verts.removeAt(i)
for(a in indices.indices) {
if(indices[a]>i) indices[a] = indices[a]-1
}
max--
}else {
i++
}
}
return toVertices(verts)
}
再来看下效果吧
val vc = VoxelLoader.load(Path("scene_aliens.vox").readBytes())
val (v,i) = c.optimization().buildArray()
val quads = buildQuads(i)
merge(quads)
val indices = toIndices(quads)
val vertices = compressVertices(v, indices)
convertPly(vertices,indices)
面数缩减到了5730,文件大小缩减到了360KB
当然这个压缩方法不是最好的,且非常耗时,压缩这个vox用了20秒
完整代码在github中