All Downloads are FREE. Search and download functionalities are using the official Maven repository.

ai.dragonfly.mesh.io.OBJ.scala Maven / Gradle / Ivy

The newest version!
/*
 * Copyright 2023 dragonfly.ai
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package ai.dragonfly.mesh.io

import narr.*

import slash.vector.*
import Vec.*

import ai.dragonfly.mesh.*

import java.io.OutputStream
import scala.collection.mutable
import scala.scalajs.js.annotation.{JSExportAll, JSExportTopLevel}

@JSExportTopLevel("OBJ") @JSExportAll
object OBJ {

  private val defaultComment = s"OBJ file generated by the mesh.dragonfly.ai Scala library.  Visit http://dragonfly.ai for more information."
  private val defaultMaterialFileName:String = "default.mtl"

  val vertexLine: StringContext = StringContext("v ", " ", " ", "")

  def fromMesh(mesh:Mesh, name:String, comment:String = defaultComment, materialLibraryFile:String = defaultMaterialFileName, material:MTL = MTL.default, smooth:Boolean = true): String = {
    val sb = new mutable.StringBuilder()
    sb.append(s"#  $comment\n")
    sb.append(s"mtllib $materialLibraryFile")
    sb.append(s"o $name\n")
    var i:Int = 0; while (i < mesh.points.length) {
      val p = mesh.points(i)
      sb.append(s"v ${p.x} ${p.z} ${p.y}\n")
      i += 1
    }

    if (smooth) sb.append(s"s 1\n")

    sb.append(s"usemtl ${material.name}\n")

    for (t <- mesh.triangles) {
      if (t != null) sb.append( s"f ${t.v1 + 1} ${t.v2 + 1} ${t.v3 + 1}\n" )
    }

    sb.toString()
  }
  def objTriangle(t:Triangle, offset: Int = 0): String = s"f ${offset + t.v1 + 1} ${offset + t.v2 + 1} ${offset + t.v3 + 1}"

  /* How to add materials to meshes?  How to combine them without losing materials? */
  def fromMaterialMeshGroup(meshGroup:MaterialMeshGroup, comment:String = defaultComment, materialLibraryFile:String = defaultMaterialFileName): String = {
    var pointCount = 0
    var triangleCount = 0

    for (m <- meshGroup.meshes) {
      pointCount = pointCount + m.points.length
      triangleCount = triangleCount + m.triangles.length
    }

    val pointSB = new mutable.StringBuilder()
    val triangleSB = new mutable.StringBuilder()

    pointSB.append(s"# $comment\n\n")
    pointSB.append(s"mtllib $materialLibraryFile\n\n")

    var pi: Int = 0

    var mi:Int = 0; while (mi < meshGroup.meshes.length) {
      var pj = 0
      val m:Mesh = meshGroup.meshes(mi)
      var i:Int = 0; while (i < m.points.length) {
        val p = m.points(i)
        pointSB.append(s"v ${-p.x} ${p.z} ${p.y}\n")
        pj = pj + 1
        i += 1
      }

      var tj = 0

      triangleSB.append(s"g ${m.name}\n")
      //if (m.smooth) triangleSB.append(s"s 1\n") else
      triangleSB.append(s"s 1\n")
      triangleSB.append(s"usemtl ${meshGroup.material.name}\n")

      for (t <- m.triangles) {
        triangleSB.append(s"${objTriangle(t, pi)}\n")
        tj = tj + 1
      }

      pi += m.points.length
      mi += 1
    }
    pointSB.append(triangleSB).toString()
  }

  // IO
  def writeMesh(mesh:Mesh, out:OutputStream, name:String, comment:String = defaultComment, materialLibraryFileName:String = defaultMaterialFileName, material:MTL = MTL.default): Unit = {
    out.write(OBJ.fromMesh(mesh, name, comment, materialLibraryFileName, material).getBytes)
  }

  def writeMaterialMeshGroup(meshGroup:MaterialMeshGroup, out:OutputStream, comment:String = defaultComment, materialLibraryFileName:String = defaultMaterialFileName): Unit = {
    out.write(OBJ.fromMaterialMeshGroup(meshGroup, comment, materialLibraryFileName).getBytes)
  }

  def parseVertex(line:String):Option[Vec[3]] = {
    vertexLine.s.unapplySeq(line) match {
      case Some(Seq(xS:String, yS:String, zS:String)) =>
        try {
          Some(
            Vec[3](
              java.lang.Double.parseDouble(xS),
              java.lang.Double.parseDouble(yS),
              java.lang.Double.parseDouble(zS)
            )
          )
        } catch {
          case _:Throwable => None
        }
      case _ => None
    }
  }

  /**
   * Only parses triangles and quads.
   * @param line a line of text depicting an OBJ face.
   * @return
   */
  def parseFace(line:String):NArray[Triangle] = {
    val tokens:Array[String] = line.split("\\s")
    if (!tokens.head.equals("f")) new NArray[Triangle](0)
    else {
      tokens.length match {
        case 4 =>
          val o = new NArray[Triangle](1)
          o(0) = Triangle(
            Integer.parseInt(tokens(1)) - 1,
            Integer.parseInt(tokens(2)) - 1,
            Integer.parseInt(tokens(3)) - 1
          )
          o
        case 5 => Triangle.fromQuad(
          Integer.parseInt(tokens(1))-1,
          Integer.parseInt(tokens(2))-1,
          Integer.parseInt(tokens(3))-1,
          Integer.parseInt(tokens(4))-1
        )
        case _ => new NArray[Triangle](0)
      }
    }
  }

//  def read(name:String, is:InputStream):OBJ = {
//    val br: java.io.BufferedReader = new BufferedReader(new InputStreamReader(is, "UTF-8"))
//
//    var line = br.readLine()
//    var points:List[Vec[3]] = List[Vec[3]]()
//    var triangles:List[Triangle] = List[Triangle]()
//
//    while (line != null) {
//      parseVertex(line) match {
//        case Some(p: Vec[3]) =>
//          points = points.appended(p)
//        case None =>
//          parseFace(line) match {
//            case Some(t: Triangle) =>
//              triangles = triangles.appended(t)
//            case _ => // ignore
//          }
//      }
//      line = br.readLine()
//    }
//    br.close()
//    val pointsArr:NArray[Vector3] = NArray.tabulate[Vector3](points.size)((i:Int) => points(i))
//    val triangleArr:NArray[Triangle] = NArray.tabulate[Triangle](triangles.size)((i:Int) => triangles(i))
//
//    MaterialMeshGroup(MTL.default, )
//  }
}

@JSExportTopLevel("MaterialMeshGroup") @JSExportAll
case class MaterialMeshGroup(name:String, material: MTL, meshes: NArray[Mesh])

type OBJ = NArray[MaterialMeshGroup]




© 2015 - 2024 Weber Informatics LLC | Privacy Policy