275 lines
7.8 KiB
Go
275 lines
7.8 KiB
Go
package main
|
|
|
|
import (
|
|
"bytes"
|
|
"encoding/json"
|
|
"fmt"
|
|
"math/rand/v2"
|
|
"os"
|
|
"path"
|
|
|
|
"github.com/gabriel-vasile/mimetype"
|
|
"github.com/qmuntal/gltf"
|
|
"github.com/qmuntal/gltf/modeler"
|
|
|
|
"git.influ.su/artmares/digglestool/internal/pkg/errors"
|
|
"git.influ.su/artmares/digglestool/internal/pkg/logger"
|
|
"git.influ.su/artmares/digglestool/internal/services/exporter"
|
|
"git.influ.su/artmares/digglestool/pkg/threedb"
|
|
)
|
|
|
|
const (
|
|
outputPath = "./models"
|
|
|
|
texturesCacheFile = "./textures.cache"
|
|
)
|
|
|
|
func main() {
|
|
texturesPath := os.Getenv("TEXTURES_PATH")
|
|
if texturesPath == "" {
|
|
logger.Info("TEXTURES_PATH environment variable not set")
|
|
return
|
|
}
|
|
|
|
inputPath := "./data"
|
|
inputModel := "produktionsstaetten"
|
|
|
|
f, err := os.Open(path.Join(inputPath, inputModel+".3db"))
|
|
if err != nil {
|
|
errors.LogFatal(errors.Wrap(err, "failed to open input file"))
|
|
}
|
|
defer f.Close()
|
|
|
|
model := threedb.Model{}
|
|
err = threedb.NewDecoder(f).Decode(&model)
|
|
if err != nil {
|
|
errors.LogError(errors.Wrap(err, "failed to decode model"))
|
|
return
|
|
}
|
|
//log.Printf("DB Version: %v\n", model.DBVersion)
|
|
//log.Printf("Name: %v\n", model.Name)
|
|
//log.Printf("Materials: %d\n", len(model.Materials))
|
|
//for _, material := range model.Materials {
|
|
// log.Printf("\t: %v\n", material)
|
|
//}
|
|
//log.Printf("Meshes: %d\n", len(model.Meshes))
|
|
//log.Printf("Objects: %+v\n", model.Objects)
|
|
//log.Printf("Animations: %d\n", len(model.Animations))
|
|
//for _, animation := range model.Animations {
|
|
// log.Printf("\t: %+v\n", animation)
|
|
//}
|
|
//log.Printf("Triangles: %d\n", len(model.Triangles))
|
|
//log.Printf("Texture Coordinates: %d\n", len(model.TextureCoordinates))
|
|
//log.Printf("Points: %d\n", len(model.Points))
|
|
//log.Printf("Brightness: %d\n", len(model.Brightness))
|
|
|
|
logFile, err := os.OpenFile(fmt.Sprintf("./%s.json", inputModel), os.O_TRUNC|os.O_CREATE|os.O_WRONLY, 0644)
|
|
if err != nil {
|
|
errors.LogError(errors.Wrap(err, "failed to create log file"))
|
|
return
|
|
}
|
|
defer logFile.Close()
|
|
enc := json.NewEncoder(logFile)
|
|
enc.SetIndent("", " ")
|
|
if err = enc.Encode(model); err != nil {
|
|
errors.LogError(errors.Wrap(err, "failed to encode model to JSON"))
|
|
return
|
|
}
|
|
|
|
export := exporter.NewExporter(
|
|
exporter.WithTexturesBasePath(texturesPath),
|
|
exporter.WithOnlyBaseMesh(),
|
|
exporter.WithAnimation(),
|
|
)
|
|
if err = export.Export(outputPath, &model); err != nil {
|
|
errors.LogError(errors.Wrap(err, "failed to export model"))
|
|
return
|
|
}
|
|
|
|
//if err = export(&model, inputModel, texturesCache); err != nil {
|
|
// log.Println(err)
|
|
// return
|
|
//}
|
|
}
|
|
|
|
// exportFn exports a 3DB model to GLTF format
|
|
func exportFn(model *threedb.Model, inputModel string, cache map[string]string) error {
|
|
meshIndex := 0
|
|
doc := gltf.NewDocument()
|
|
|
|
// Process materials and textures
|
|
binaryCache := make(map[string][]byte)
|
|
if err := processMaterials(doc, model, cache, binaryCache); err != nil {
|
|
return err
|
|
}
|
|
|
|
// Process mesh data
|
|
mesh := model.Meshes[meshIndex]
|
|
if err := processMesh(doc, model, mesh); err != nil {
|
|
return err
|
|
}
|
|
|
|
// Setup scene
|
|
setupScene(doc)
|
|
|
|
// Save the GLTF document
|
|
if err := gltf.Save(doc, path.Join(outputPath, fmt.Sprintf("%s-%d.gltf", inputModel, meshIndex))); err != nil {
|
|
return errors.Wrap(err, "failed to save GLTF document")
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// processMaterials processes all materials in the model and adds them to the GLTF document
|
|
func processMaterials(doc *gltf.Document, model *threedb.Model, cache map[string]string, binaryCache map[string][]byte) error {
|
|
for _, material := range model.Materials {
|
|
data, ok := binaryCache[material.Name]
|
|
if !ok {
|
|
texturePath, ok := cache[material.Name]
|
|
if !ok {
|
|
errors.LogWarn(errors.New("invalid texture cache: " + material.Name))
|
|
continue
|
|
}
|
|
var err error
|
|
data, err = os.ReadFile(texturePath)
|
|
if err != nil {
|
|
return errors.Wrap(err, "failed to read texture file: "+texturePath)
|
|
}
|
|
binaryCache[material.Name] = data
|
|
}
|
|
|
|
if err := addMaterialToDocument(doc, material, data); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// addMaterialToDocument adds a material and its texture to the GLTF document
|
|
func addMaterialToDocument(doc *gltf.Document, material threedb.Material, textureData []byte) error {
|
|
mt := mimetype.Detect(textureData)
|
|
if mt == nil {
|
|
return errors.New("failed to detect mimetype for texture: " + material.Name)
|
|
}
|
|
|
|
imageIndex, err := modeler.WriteImage(doc, material.Name+".tga", mt.String(), bytes.NewBuffer(textureData))
|
|
if err != nil {
|
|
return errors.Wrap(err, "failed to write image to GLTF document")
|
|
}
|
|
|
|
doc.Textures = append(doc.Textures, &gltf.Texture{
|
|
Name: material.Name,
|
|
Source: gltf.Index(imageIndex),
|
|
})
|
|
|
|
doc.Materials = append(doc.Materials, &gltf.Material{
|
|
Name: material.Name,
|
|
AlphaMode: gltf.AlphaOpaque,
|
|
AlphaCutoff: gltf.Float(0.5),
|
|
PBRMetallicRoughness: &gltf.PBRMetallicRoughness{
|
|
BaseColorTexture: &gltf.TextureInfo{Index: imageIndex},
|
|
BaseColorFactor: &[4]float64{0.8, 0.8, 0.8, 1},
|
|
MetallicFactor: gltf.Float(0.1),
|
|
RoughnessFactor: gltf.Float(0.99),
|
|
},
|
|
})
|
|
|
|
return nil
|
|
}
|
|
|
|
// processMesh processes a mesh and adds it to the GLTF document
|
|
func processMesh(doc *gltf.Document, model *threedb.Model, mesh threedb.Mesh) error {
|
|
for _, meshLink := range mesh.Links {
|
|
triangles := model.Triangles[meshLink.Triangles]
|
|
points := model.Points[meshLink.Points]
|
|
textureCoordinates := model.TextureCoordinates[meshLink.TextureCoordinates]
|
|
material := model.Materials[meshLink.Material]
|
|
|
|
// Process vertices
|
|
vertices := processVertices(points)
|
|
verticesIndex := modeler.WriteAccessor(doc, gltf.TargetElementArrayBuffer, vertices)
|
|
|
|
// Process texture coordinates
|
|
textureCoords := processTextureCoordinates(textureCoordinates)
|
|
textureIndex := modeler.WriteAccessor(doc, gltf.TargetArrayBuffer, textureCoords)
|
|
|
|
// Process triangles
|
|
trianglesIndex := modeler.WriteAccessor(doc, gltf.TargetArrayBuffer, triangles)
|
|
|
|
// Add mesh to document
|
|
addMeshToDocument(doc, model, verticesIndex, textureIndex, trianglesIndex, material)
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// processVertices processes the vertices of a mesh
|
|
func processVertices(points []threedb.Vector) [][3]float32 {
|
|
var vertices [][3]float32
|
|
|
|
for _, point := range points {
|
|
vec := point.Transform(100)
|
|
vec = vec.Normalize()
|
|
vertices = append(vertices, vec)
|
|
}
|
|
|
|
return vertices
|
|
}
|
|
|
|
// processTextureCoordinates processes the texture coordinates of a mesh
|
|
func processTextureCoordinates(textureCoordinates []threedb.Coordinate) [][2]float32 {
|
|
var textureCoords [][2]float32
|
|
for _, txc := range textureCoordinates {
|
|
textureCoords = append(textureCoords, txc)
|
|
}
|
|
return textureCoords
|
|
}
|
|
|
|
// addMeshToDocument adds a mesh to the GLTF document
|
|
func addMeshToDocument(doc *gltf.Document, model *threedb.Model, verticesIndex, textureIndex, trianglesIndex int, material threedb.Material) {
|
|
materialIndex := len(doc.Materials)
|
|
|
|
doc.Meshes = append(doc.Meshes, &gltf.Mesh{
|
|
Name: fmt.Sprintf("%s-%d", model.Name, len(doc.Meshes)),
|
|
Primitives: []*gltf.Primitive{{
|
|
Attributes: gltf.PrimitiveAttributes{
|
|
gltf.POSITION: verticesIndex,
|
|
gltf.TEXCOORD_0: textureIndex,
|
|
},
|
|
Indices: gltf.Index(trianglesIndex),
|
|
Material: gltf.Index(materialIndex),
|
|
}},
|
|
})
|
|
|
|
doc.Materials = append(doc.Materials, &gltf.Material{
|
|
Name: material.Name,
|
|
AlphaMode: gltf.AlphaOpaque,
|
|
AlphaCutoff: gltf.Float(0.5),
|
|
PBRMetallicRoughness: &gltf.PBRMetallicRoughness{
|
|
BaseColorFactor: &[4]float64{0.8, 0.8, 0.8, 1},
|
|
MetallicFactor: gltf.Float(0.1),
|
|
RoughnessFactor: gltf.Float(0.99),
|
|
},
|
|
})
|
|
|
|
doc.Nodes = append(doc.Nodes, &gltf.Node{
|
|
Mesh: gltf.Index(len(doc.Meshes) - 1),
|
|
})
|
|
}
|
|
|
|
// setupScene sets up the scene in the GLTF document
|
|
func setupScene(doc *gltf.Document) {
|
|
var nodes []int
|
|
for index := range doc.Nodes {
|
|
nodes = append(nodes, index)
|
|
}
|
|
|
|
doc.Scenes = []*gltf.Scene{
|
|
{Name: "Root Scene", Nodes: nodes},
|
|
}
|
|
}
|
|
|
|
func randRange(min, max int) int {
|
|
return rand.IntN(max-min) + min
|
|
}
|