234 lines
6.8 KiB
Go
234 lines
6.8 KiB
Go
package exporter
|
|
|
|
import (
|
|
"fmt"
|
|
_ "image/png"
|
|
"os"
|
|
"path"
|
|
"path/filepath"
|
|
"strings"
|
|
|
|
_ "github.com/ftrvxmtrx/tga"
|
|
"github.com/qmuntal/gltf"
|
|
|
|
"git.influ.su/artmares/digglestool/internal/pkg/errors"
|
|
"git.influ.su/artmares/digglestool/internal/pkg/logger"
|
|
"git.influ.su/artmares/digglestool/pkg/threedb"
|
|
)
|
|
|
|
type Exporter struct {
|
|
onlyBaseMesh bool
|
|
animation bool
|
|
texturesBasePath *string
|
|
cache *Cache[*CacheItem]
|
|
}
|
|
|
|
type Option func(*Exporter)
|
|
|
|
func WithOnlyBaseMesh() Option {
|
|
return func(e *Exporter) {
|
|
e.onlyBaseMesh = true
|
|
}
|
|
}
|
|
|
|
func WithTexturesBasePath(basePath string) Option {
|
|
return func(e *Exporter) {
|
|
if basePath != "" {
|
|
e.texturesBasePath = &basePath
|
|
}
|
|
}
|
|
}
|
|
|
|
func WithAnimation() Option {
|
|
return func(e *Exporter) {
|
|
e.animation = true
|
|
}
|
|
}
|
|
|
|
func NewExporter(options ...Option) *Exporter {
|
|
e := &Exporter{
|
|
cache: NewCache[*CacheItem](),
|
|
}
|
|
for _, option := range options {
|
|
option(e)
|
|
}
|
|
return e
|
|
}
|
|
|
|
func (e *Exporter) Export(basePath string, model *threedb.Model) error {
|
|
if model == nil {
|
|
return errors.ErrInvalidInput
|
|
}
|
|
meshesLen := len(model.Meshes)
|
|
if meshesLen == 0 {
|
|
return errors.New("no meshes to export")
|
|
}
|
|
if err := e.prepareTextures(); err != nil {
|
|
return errors.Wrap(err, "failed to prepare textures")
|
|
}
|
|
if e.onlyBaseMesh {
|
|
logger.Infof("Export Base Model: %s", model.Name)
|
|
doc, err := e.generate(model, 0)
|
|
if err != nil {
|
|
return errors.Wrap(err, "failed to generate base model")
|
|
}
|
|
outputPath := path.Join(basePath, fmt.Sprintf("%s-base-model.gltf", model.Name))
|
|
if err = gltf.Save(doc, outputPath); err != nil {
|
|
return errors.Wrapf(err, "failed to save base model to %s", outputPath)
|
|
}
|
|
}
|
|
if e.animation {
|
|
dirPath := path.Join(basePath, "animation", model.Name)
|
|
if _, err := os.Stat(dirPath); os.IsNotExist(err) {
|
|
if err = os.MkdirAll(dirPath, os.ModeDir); err != nil {
|
|
return errors.Wrapf(err, "failed to create directory %s", dirPath)
|
|
}
|
|
}
|
|
logger.Infof("Export Animation to %s", dirPath)
|
|
length := len(model.Animations)
|
|
for index, animation := range model.Animations {
|
|
logger.Infof("Export progress: %d/%d\r", index+1, length)
|
|
indexes := make([]int, len(animation.MeshIndexes))
|
|
for _, index := range animation.MeshIndexes {
|
|
indexes = append(indexes, int(index))
|
|
}
|
|
doc, err := e.generate(model, indexes...)
|
|
if err != nil {
|
|
return errors.Wrapf(err, "failed to generate animation %s", animation.Name)
|
|
}
|
|
outputPath := path.Join(dirPath, fmt.Sprintf("%s-%s.gltf", model.Name, animation.Name))
|
|
if err = gltf.Save(doc, outputPath); err != nil {
|
|
return errors.Wrapf(err, "failed to save animation %s to %s", animation.Name, outputPath)
|
|
}
|
|
}
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func (e *Exporter) generate(model *threedb.Model, meshIndexes ...int) (*gltf.Document, error) {
|
|
return new(Model).Generate(model, e.cache, meshIndexes...)
|
|
//var err error
|
|
//doc := gltf.NewDocument()
|
|
//log.Println("Generate", meshIndexes)
|
|
//materialsMap := make(map[uint16]int)
|
|
//for _, meshIndex := range meshIndexes {
|
|
// mesh := model.Meshes[meshIndex]
|
|
// var nodeIndexes []int
|
|
// for index, meshLink := range mesh.Links {
|
|
// points := model.Points[meshLink.Points]
|
|
// var vertices [][3]float32
|
|
// for _, point := range points {
|
|
// vec := point.Transform(100)
|
|
// vec = vec.Normalize()
|
|
// vertices = append(vertices, vec)
|
|
// }
|
|
// verticesIndex := modeler.WriteAccessor(doc, gltf.TargetElementArrayBuffer, vertices)
|
|
// textureCoordinates := model.TextureCoordinates[meshLink.TextureCoordinates]
|
|
// var textureCoord [][2]float32
|
|
// for _, txc := range textureCoordinates {
|
|
// textureCoord = append(textureCoord, txc)
|
|
// }
|
|
// textureCoordIndex := modeler.WriteAccessor(doc, gltf.TargetArrayBuffer, textureCoord)
|
|
// trianglesIndex := modeler.WriteAccessor(doc, gltf.TargetArrayBuffer, model.Triangles[meshLink.Triangles])
|
|
// materialIndex, ok := materialsMap[meshLink.Material]
|
|
// if !ok {
|
|
// material := model.Materials[meshLink.Material]
|
|
// log.Println("Material", material.Name)
|
|
// data := e.cache.Bytes(material.Name)
|
|
// if data == nil {
|
|
// var f *os.File
|
|
// if f, err = os.Open(e.cache.Path(material.Name)); err != nil {
|
|
// return nil, err
|
|
// }
|
|
// img, _, err := image.Decode(f)
|
|
// if err != nil {
|
|
// return nil, err
|
|
// }
|
|
// buff, putter := helpers.NewBuffer()
|
|
// if err = png.Encode(buff, img); err != nil {
|
|
// return nil, err
|
|
// }
|
|
// data = buff.Bytes()
|
|
// log.Println(material.Name, len(data))
|
|
// putter(buff)
|
|
// e.cache.AddBytes(material.Name, data)
|
|
// }
|
|
// log.Println(material.Name, len(data))
|
|
// var imageIndex int
|
|
// if imageIndex, err = modeler.WriteImage(doc, material.Name, "image/png", bytes.NewBuffer(data)); err != nil {
|
|
// return nil, err
|
|
// }
|
|
// doc.Textures = append(doc.Textures, &gltf.Texture{
|
|
// Name: material.Name,
|
|
// Sampler: gltf.Index(0),
|
|
// Source: gltf.Index(imageIndex),
|
|
// })
|
|
// materialIndex = len(doc.Materials)
|
|
// 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),
|
|
// },
|
|
// })
|
|
// materialsMap[meshLink.Material] = materialIndex
|
|
// }
|
|
// log.Println("materialIndex", materialIndex)
|
|
// doc.Meshes = append(doc.Meshes, &gltf.Mesh{
|
|
// Name: fmt.Sprintf("%s-%d", model.Name, 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(doc.Nodes))
|
|
// doc.Nodes = append(doc.Nodes, &gltf.Node{
|
|
// Mesh: gltf.Index(len(doc.Meshes) - 1),
|
|
// })
|
|
// }
|
|
// doc.Scenes = []*gltf.Scene{{
|
|
// Name: "Root Scene",
|
|
// Nodes: nodeIndexes,
|
|
// }}
|
|
// doc.Samplers = []*gltf.Sampler{{}}
|
|
//}
|
|
//return doc, nil
|
|
}
|
|
|
|
func (e *Exporter) prepareTextures() error {
|
|
if e.texturesBasePath == nil {
|
|
return nil
|
|
}
|
|
|
|
basePath := *e.texturesBasePath
|
|
if err := filepath.Walk(basePath, func(pathFile string, info os.FileInfo, err error) error {
|
|
if err != nil {
|
|
return errors.Wrapf(err, "failed to access path %s", pathFile)
|
|
}
|
|
if strings.HasSuffix(info.Name(), ".tga") {
|
|
key := strings.TrimSuffix(info.Name(), ".tga")
|
|
item, ok := e.cache.Get(key)
|
|
if !ok {
|
|
item = &CacheItem{}
|
|
}
|
|
if info.Size() > item.Size {
|
|
item.Path = pathFile
|
|
}
|
|
e.cache.Set(key, item)
|
|
}
|
|
return nil
|
|
}); err != nil {
|
|
return errors.Wrapf(err, "failed to walk textures directory %s", basePath)
|
|
}
|
|
return nil
|
|
}
|