package exporter import ( "errors" "fmt" _ "image/png" "os" "path" "path/filepath" "strings" _ "github.com/ftrvxmtrx/tga" "github.com/qmuntal/gltf" "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.New("model is nil") } meshesLen := len(model.Meshes) if meshesLen == 0 { return errors.New("no meshes to export") } if err := e.prepareTextures(); err != nil { return err } if e.onlyBaseMesh { logger.Infof("Export Base Model: %s", model.Name) doc, err := e.generate(model, 0) if err != nil { return err } if err = gltf.Save(doc, path.Join(basePath, fmt.Sprintf("%s-base-model.gltf", model.Name))); err != nil { return err } } 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 err } } 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 err } if err = gltf.Save(doc, path.Join(dirPath, fmt.Sprintf("%s-%s.gltf", model.Name, animation.Name))); err != nil { return err } } } 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 } if err := filepath.Walk(*e.texturesBasePath, func(pathFile string, info os.FileInfo, err error) error { if err != nil { return err } 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 err } return nil }