2025-08-06 16:52:37 +03:00

157 lines
4.5 KiB
Go

package exporter
import (
"bytes"
"fmt"
"image"
"image/png"
"os"
_ "github.com/ftrvxmtrx/tga"
"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/pkg/threedb"
)
type Model struct {
doc *gltf.Document
materialMap map[uint16]int
}
func (m *Model) Generate(model *threedb.Model, cache *Cache[*CacheItem], meshIndexes ...int) (*gltf.Document, error) {
if model == nil {
return nil, errors.ErrInvalidInput
}
m.doc = gltf.NewDocument()
m.doc.Scenes = []*gltf.Scene{}
m.materialMap = make(map[uint16]int)
logger.Info("Generate use mesh indexes", len(meshIndexes))
for i, meshIndex := range meshIndexes {
if meshIndex >= len(model.Meshes) {
return nil, errors.Wrapf(errors.ErrInvalidInput, "mesh index %d out of range (max: %d)",
meshIndex, len(model.Meshes)-1)
}
mesh := model.Meshes[meshIndex]
var nodeIndexes []int
for index, link := range mesh.Links {
materialIndex, err := m.materialIndex(model, cache, link.Material)
if err != nil {
return nil, errors.Wrapf(err, "failed to get material index for mesh %d link %d",
meshIndex, index)
}
verticesIndex := m.generateVertices(model.Points[link.Points])
textureCoordIndex := m.generateTextureCoordinate(model.TextureCoordinates[link.TextureCoordinates])
trianglesIndex := modeler.WriteAccessor(m.doc, gltf.TargetArrayBuffer, model.Triangles[link.Triangles])
m.doc.Meshes = append(m.doc.Meshes, &gltf.Mesh{
Name: fmt.Sprintf("%s-%d-%d", model.Name, i, index),
Primitives: []*gltf.Primitive{{
Attributes: gltf.PrimitiveAttributes{
gltf.POSITION: verticesIndex,
gltf.TEXCOORD_0: textureCoordIndex,
},
Indices: gltf.Index(trianglesIndex),
Material: gltf.Index(materialIndex),
}},
})
nodeIndexes = append(nodeIndexes, len(m.doc.Nodes))
m.doc.Nodes = append(m.doc.Nodes, &gltf.Node{
Mesh: gltf.Index(len(m.doc.Meshes) - 1),
})
}
m.doc.Scenes = append(m.doc.Scenes, &gltf.Scene{
Name: fmt.Sprintf("Root Scene-%d", meshIndex),
Nodes: nodeIndexes,
})
}
m.doc.Samplers = []*gltf.Sampler{{}}
return m.doc, nil
}
func (m *Model) generateVertices(vectors []threedb.Vector) int {
var vertices [][3]float32
for _, vector := range vectors {
vector = vector.Transform(100)
vertices = append(vertices, vector.Normalize())
}
return modeler.WriteAccessor(m.doc, gltf.TargetElementArrayBuffer, vertices)
}
func (m *Model) generateTextureCoordinate(coordinates []threedb.Coordinate) int {
var coords [][2]float32
for _, coordinate := range coordinates {
coords = append(coords, coordinate)
}
return modeler.WriteAccessor(m.doc, gltf.TargetArrayBuffer, coords)
}
func (m *Model) materialIndex(model *threedb.Model, cache *Cache[*CacheItem], meshMaterialIndex uint16) (int, error) {
materialIndex, ok := m.materialMap[meshMaterialIndex]
if !ok {
material := model.Materials[meshMaterialIndex]
item, ok := cache.Get(material.Name)
if !ok {
// Instead of returning nil, return a more descriptive error
return 0, errors.New("material not found in cache: " + material.Name)
}
if item.Bytes == nil {
f, err := os.Open(item.Path)
if err != nil {
return 0, errors.Wrapf(err, "failed to open texture file: %s", item.Path)
}
defer f.Close()
img, _, err := image.Decode(f)
if err != nil {
return 0, errors.Wrap(err, "failed to decode image")
}
item.Bytes = &bytes.Buffer{}
if err = png.Encode(item.Bytes, img); err != nil {
return 0, errors.Wrap(err, "failed to encode image to PNG")
}
cache.Set(material.Name, item)
}
imageIndex, err := modeler.WriteImage(m.doc, material.Name, "image/png", item.Bytes)
if err != nil {
return 0, errors.Wrap(err, "failed to write image to GLTF document")
}
m.doc.Textures = append(m.doc.Textures, &gltf.Texture{
Name: material.Name,
Sampler: gltf.Index(0),
Source: gltf.Index(imageIndex),
})
materialIndex = len(m.doc.Materials)
m.doc.Materials = append(m.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),
},
})
m.materialMap[meshMaterialIndex] = materialIndex
}
return materialIndex, nil
}