1
\$\begingroup\$

I'm creating a minecraft clone (for practice), in scala, using largely functional programming. When a chunk doesn't have a mesh loaded into VRAM, it create a Future for the vertex and index arrays, and gives it to a low priority ExecutionContext (so as not to freeze the main game loop). The render loop (in the OpenGL thread) checks if the Future is completed, and if it is, uploads the data to VRAM, after which the chunk can be rendered. Here's the ChunkRenderer class, I'm providing the whole thing for context, but I'm only really looking for a review of the meshData future. I would appreciate reviews as to how I can make this process faster, as well as the code in general.

case class ChunkRenderer(
                          chunk: Chunk,
                          texturePack: TexturePack,
                          world: World,
                          previous: Option[ChunkRenderer]
                        ) extends RenderableFactory {

  val meshData: Future[(Array[Float], Array[Short])] = Future {
    // first, compute the exposed surfaces
    type SurfaceMap = Map[Direction, List[V3I]]

    // compute the given surface of the given block
    def surface(m: SurfaceMap, v: V3I, s: Direction): SurfaceMap =
      (world.blockAt(v), world.blockAt(v + s)) match {
        // if the target is non-existent, the face is invisible
        case (None, _) => m
        // if the target is translucent, the face is invisible
        case (Some(t), _) if t isTranslucent => m
        // if the cover is opaque, the face is invisible
        case (_, Some(c)) if c isOpaque => m
        // if the cover is translucent (and the target is opaque), the face is visible
        case (_, Some(c)) if c isTranslucent => m.updated(s, v :: m(s))
        // if the cover is non-existent (and the target is opaque), the face is visible
        case (_, None) => m.updated(s, v :: m(s))
        // in all other cases, the face is invisible
        case _ => m
      }

    // compute all surfaces of a block
    def block(m: SurfaceMap, v: V3I): SurfaceMap =
      (Stream.iterate(v)(identity) zip Directions()).foldLeft(m)({ case (m, (v, s)) => surface(m, v, s) })

    // do the computation
    val empty: SurfaceMap = Directions() zip Stream.iterate(Nil)(identity) toMap
    val blocks: Seq[V3I] = (Origin until V3I(chunk.size, chunk.size, chunk.size)) map (_ + (chunk.pos * chunk.size))
    val exposed: SurfaceMap = blocks.foldLeft(empty)(block) //.mapValues(_.map(_ - (chunk.pos * chunk.size)))

    // fold the exposure sets into vertex data and indices
    type VertDatum = (V3F, Color, V2F)
    val vertSize = 6
    // convert from size in bytes to size in floats
    val p = 1
    val n = 0
    val offset = chunk.pos * chunk.size

    def addSquareIndices(verts: List[VertDatum], indices: List[Short]): List[Short] =
      indices
        .::((verts.length + 0).toShort)
        .::((verts.length + 1).toShort)
        .::((verts.length + 2).toShort)
        .::((verts.length + 0).toShort)
        .::((verts.length + 2).toShort)
        .::((verts.length + 3).toShort)

    var data: (List[VertDatum], List[Short]) = (Nil, Nil)

    data = exposed(Up).foldLeft(data)(
      (data, b) => data match {
        case (verts, indices) =>
          val r = texturePack(world.blockAt(b).get.tid)
          (
            verts
              .::((b + V3F(n, p, p), Color.WHITE, V2F(r.getU, r.getV)))
              .::((b + V3F(p, p, p), Color.WHITE, V2F(r.getU2, r.getV)))
              .::((b + V3F(p, p, n), Color.WHITE, V2F(r.getU2, r.getV2)))
              .::((b + V3F(n, p, n), Color.WHITE, V2F(r.getU, r.getV2))),
            addSquareIndices(verts, indices)
          )
      }
    )
    data = exposed(West).foldLeft(data)(
      (data, b) => data match {
        case (verts, indices) =>
          val r = texturePack(world.blockAt(b).get.tid)
          (
            verts
              .::((b + V3F(n, p, p), new Color(0.85f, 0.85f, 0.85f, 1f), V2F(r.getU, r.getV)))
              .::((b + V3F(n, p, n), new Color(0.85f, 0.85f, 0.85f, 1f), V2F(r.getU2, r.getV)))
              .::((b + V3F(n, n, n), new Color(0.85f, 0.85f, 0.85f, 1f), V2F(r.getU2, r.getV2)))
              .::((b + V3F(n, n, p), new Color(0.85f, 0.85f, 0.85f, 1f), V2F(r.getU, r.getV2))),
            addSquareIndices(verts, indices)
          )
      }
    )
    data = exposed(East).foldLeft(data)(
      (data, b) => data match {
        case (verts, indices) =>
          val r = texturePack(world.blockAt(b).get.tid)
          (
            verts
              .::((b + V3F(p, p, n), new Color(0.8f, 0.8f, 0.8f, 1f), V2F(r.getU, r.getV)))
              .::((b + V3F(p, p, p), new Color(0.8f, 0.8f, 0.8f, 1f), V2F(r.getU2, r.getV)))
              .::((b + V3F(p, n, p), new Color(0.8f, 0.8f, 0.8f, 1f), V2F(r.getU2, r.getV2)))
              .::((b + V3F(p, n, n), new Color(0.8f, 0.8f, 0.8f, 1f), V2F(r.getU, r.getV2))),
            addSquareIndices(verts, indices)
          )
      }
    )
    data = exposed(South).foldLeft(data)(
      (data, b) => data match {
        case (verts, indices) =>
          val r = texturePack(world.blockAt(b).get.tid)
          (
            verts
              .::((b + V3F(n, n, n), new Color(0.85f, 0.85f, 0.85f, 1f), V2F(r.getU, r.getV)))
              .::((b + V3F(n, p, n), new Color(0.85f, 0.85f, 0.85f, 1f), V2F(r.getU2, r.getV)))
              .::((b + V3F(p, p, n), new Color(0.85f, 0.85f, 0.85f, 1f), V2F(r.getU2, r.getV2)))
              .::((b + V3F(p, n, n), new Color(0.85f, 0.85f, 0.85f, 1f), V2F(r.getU, r.getV2))),
            addSquareIndices(verts, indices)
          )
      }
    )
    data = exposed(North).foldLeft(data)(
      (data, b) => data match {
        case (verts, indices) =>
          val r = texturePack(world.blockAt(b).get.tid)
          (
            verts
              .::((b + V3F(n, n, p), new Color(0.8f, 0.8f, 0.8f, 1f), V2F(r.getU, r.getV)))
              .::((b + V3F(p, n, p), new Color(0.8f, 0.8f, 0.8f, 1f), V2F(r.getU2, r.getV)))
              .::((b + V3F(p, p, p), new Color(0.8f, 0.8f, 0.8f, 1f), V2F(r.getU2, r.getV2)))
              .::((b + V3F(n, p, p), new Color(0.8f, 0.8f, 0.8f, 1f), V2F(r.getU, r.getV2))),
            addSquareIndices(verts, indices)
          )
      }
    )
    data = exposed(Down).foldLeft(data)(
      (data, b) => data match {
        case (verts, indices) =>
          val r = texturePack(world.blockAt(b).get.tid)
          (
            verts
              .::((b + V3F(n, n, p), new Color(0.75f, 0.75f, 0.75f, 1f), V2F(r.getU, r.getV)))
              .::((b + V3F(n, n, n), new Color(0.75f, 0.75f, 0.75f, 1f), V2F(r.getU2, r.getV)))
              .::((b + V3F(p, n, n), new Color(0.75f, 0.75f, 0.75f, 1f), V2F(r.getU2, r.getV2)))
              .::((b + V3F(p, n, p), new Color(0.75f, 0.75f, 0.75f, 1f), V2F(r.getU, r.getV2))),
            addSquareIndices(verts, indices)
          )
      }
    )

    val (vertices: List[VertDatum], indices: List[Short]) = data

    // reverse and serialize the vertex data into floats
    val vertexSerial = vertices.reverse.flatMap({
      case (v, color, t) => List(
        v.x, v.y, v.z,
        color toFloatBits,
        t.x, t.y
      )
    })


    // compile the vertices into an array (they were already reversed during serialization)
    val vertArr = new Array[Float](vertexSerial.size)
    var i = 0
    for (f <- vertexSerial) {
      vertArr.update(i, f)
      i += 1
    }
    // reverse and compile the indices into an array
    val indexArr = new Array[Short](indices.size)
    i = 0
    for (s <- indices.reverseIterator) {
      indexArr.update(i, s)
      i += 1
    }

    (vertArr, indexArr)
  } (PriorityExecContext(if (previous isDefined) Thread.MAX_PRIORITY else Thread.MIN_PRIORITY))

  var renderable = new DisposableCache[Renderable]({
    // create a mesh
    val mesh = new Mesh(true, 4 * 6 * chunk.blocks.length, 6 * 6 * chunk.blocks.length,
      new VertexAttribute(Usage.Position, 3, "a_position"),
      new VertexAttribute(Usage.ColorPacked, 4, "a_color"),
      new VertexAttribute(Usage.TextureCoordinates, 2, "a_texCoord0")
    )

    // get the arrays
    val (vertArr, indexArr) = Await.result(meshData, Duration.Inf)

    // plug the arrays into the mesh (this uploads them to VRAM)
    mesh.setVertices(vertArr)
    mesh.setIndices(indexArr)

    // create the material
    val material = new Material
    material.set(TextureAttribute.createDiffuse(texturePack.texture))

    // create the renderable
    val renderable = new Renderable()
    renderable.meshPart.mesh = mesh
    renderable.material = material
    renderable.meshPart.offset = 0
    renderable.meshPart.size = indexArr.length
    renderable.meshPart.primitiveType = GL20.GL_TRIANGLES
    renderable
  }, _.meshPart.mesh.dispose())

  /**
    * Bring this object into an active state, generating resources, and return the renderables.
    */
  override def apply(): Seq[Renderable] =
    if (meshData.isCompleted) Seq(renderable())
    else previous match {
      case Some(renderer) => renderer()
      case None => Nil
    }

  /**
    * Return the sequence of factories that this factory depends on for being in an activate state.
    */
  override def dependencies: Seq[RenderableFactory] =
    if (meshData.isCompleted) Nil
    else previous match {
      case Some(previous) => Seq(previous)
      case None => Nil
    }

  /**
    * Bring this object into an unactive state, and dispose of resources.
    */
  override def dispose(): Unit = renderable.invalidate
}

Some context:

  • V3F is a vector of 3 floats
  • V3I is a vector of 3 ints, and it subclasses V3F
  • Direction is a sealed abstract class that represents V3I and represents a unit vector along an axis
  • There are 6 direction objects: Up=<0,1,0>, Down=<0,-1,0>, North=<0,0,1>, South=<0,0,-1>, East=<1,0,0>, West=<-1,0,0>
  • Directions() returns a sequence of all directions
  • Ones=<1,1,1>
  • TexturePack.apply(tid: TextureID) returns a TextureRegion

And finally, since a gif is worth 1000^2 words

enter image description here

\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Browse other questions tagged or ask your own question.