Compare commits

..

No commits in common. "ai-junie" and "main" have entirely different histories.

6 changed files with 137 additions and 372 deletions

View File

@ -12,7 +12,6 @@ import (
"github.com/qmuntal/gltf" "github.com/qmuntal/gltf"
"github.com/qmuntal/gltf/modeler" "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/pkg/logger"
"git.influ.su/artmares/digglestool/internal/services/exporter" "git.influ.su/artmares/digglestool/internal/services/exporter"
"git.influ.su/artmares/digglestool/pkg/threedb" "git.influ.su/artmares/digglestool/pkg/threedb"
@ -36,14 +35,14 @@ func main() {
f, err := os.Open(path.Join(inputPath, inputModel+".3db")) f, err := os.Open(path.Join(inputPath, inputModel+".3db"))
if err != nil { if err != nil {
errors.LogFatal(errors.Wrap(err, "failed to open input file")) logger.Fatal(err)
} }
defer f.Close() defer f.Close()
model := threedb.Model{} model := threedb.Model{}
err = threedb.NewDecoder(f).Decode(&model) err = threedb.NewDecoder(f).Decode(&model)
if err != nil { if err != nil {
errors.LogError(errors.Wrap(err, "failed to decode model")) logger.Error(err)
return return
} }
//log.Printf("DB Version: %v\n", model.DBVersion) //log.Printf("DB Version: %v\n", model.DBVersion)
@ -65,14 +64,14 @@ func main() {
logFile, err := os.OpenFile(fmt.Sprintf("./%s.json", inputModel), os.O_TRUNC|os.O_CREATE|os.O_WRONLY, 0644) logFile, err := os.OpenFile(fmt.Sprintf("./%s.json", inputModel), os.O_TRUNC|os.O_CREATE|os.O_WRONLY, 0644)
if err != nil { if err != nil {
errors.LogError(errors.Wrap(err, "failed to create log file")) logger.Error(err)
return return
} }
defer logFile.Close() defer logFile.Close()
enc := json.NewEncoder(logFile) enc := json.NewEncoder(logFile)
enc.SetIndent("", " ") enc.SetIndent("", " ")
if err = enc.Encode(model); err != nil { if err = enc.Encode(model); err != nil {
errors.LogError(errors.Wrap(err, "failed to encode model to JSON")) logger.Error(err)
return return
} }
@ -82,7 +81,7 @@ func main() {
exporter.WithAnimation(), exporter.WithAnimation(),
) )
if err = export.Export(outputPath, &model); err != nil { if err = export.Export(outputPath, &model); err != nil {
errors.LogError(errors.Wrap(err, "failed to export model")) logger.Error(err)
return return
} }
@ -92,76 +91,47 @@ func main() {
//} //}
} }
// exportFn exports a 3DB model to GLTF format
func exportFn(model *threedb.Model, inputModel string, cache map[string]string) error { func exportFn(model *threedb.Model, inputModel string, cache map[string]string) error {
//maxMeshIndex := 133
//meshIndex := randRange(0, len(model.Meshes)-1)
meshIndex := 0 meshIndex := 0
doc := gltf.NewDocument() //log.Println("Mesh Index:", meshIndex)
// Process materials and textures
binaryCache := make(map[string][]byte) binaryCache := make(map[string][]byte)
if err := processMaterials(doc, model, cache, binaryCache); err != nil {
return err
}
// Process mesh data //for meshIndex := 0; meshIndex < len(model.Meshes); meshIndex++ {
mesh := model.Meshes[meshIndex] doc := gltf.NewDocument()
if err := processMesh(doc, model, mesh); err != nil { //doc.Materials = []*gltf.Material{{
return err // Name: "Default", 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)},
//}}
// 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 { for _, material := range model.Materials {
data, ok := binaryCache[material.Name] data, ok := binaryCache[material.Name]
if !ok { if !ok {
texturePath, ok := cache[material.Name] texturePath, ok := cache[material.Name]
if !ok { if !ok {
errors.LogWarn(errors.New("invalid texture cache: " + material.Name)) logger.Warnf("Invalid texture cache %q", material.Name)
continue continue
return fmt.Errorf("invalid texture cache %q", material.Name)
} }
var err error data, err := os.ReadFile(texturePath)
data, err = os.ReadFile(texturePath)
if err != nil { if err != nil {
return errors.Wrap(err, "failed to read texture file: "+texturePath) return err
} }
binaryCache[material.Name] = data binaryCache[material.Name] = data
} }
mt := mimetype.Detect(data)
if err := addMaterialToDocument(doc, material, data); err != nil { if mt == nil {
return fmt.Errorf("can't geting mimetype %q", material.Name)
}
imageIndex, err := modeler.WriteImage(doc, material.Name+".tga", mt.String(), bytes.NewBuffer(data))
if err != nil {
return err 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{ doc.Textures = append(doc.Textures, &gltf.Texture{
Name: material.Name, Name: material.Name,
//Sampler: gltf.Index(0),
Source: gltf.Index(imageIndex), Source: gltf.Index(imageIndex),
}) })
doc.Materials = append(doc.Materials, &gltf.Material{ doc.Materials = append(doc.Materials, &gltf.Material{
Name: material.Name, Name: material.Name,
AlphaMode: gltf.AlphaOpaque, AlphaMode: gltf.AlphaOpaque,
@ -173,62 +143,31 @@ func addMaterialToDocument(doc *gltf.Document, material threedb.Material, textur
RoughnessFactor: gltf.Float(0.99), RoughnessFactor: gltf.Float(0.99),
}, },
}) })
}
return nil mesh := model.Meshes[meshIndex]
}
// 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 { for _, meshLink := range mesh.Links {
triangles := model.Triangles[meshLink.Triangles] triangles := model.Triangles[meshLink.Triangles]
points := model.Points[meshLink.Points] points := model.Points[meshLink.Points]
textureCoordinates := model.TextureCoordinates[meshLink.TextureCoordinates] textureCoordinates := model.TextureCoordinates[meshLink.TextureCoordinates]
material := model.Materials[meshLink.Material] 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 var vertices [][3]float32
var mins []float64
var maxs []float64
for _, point := range points { for _, point := range points {
vec := point.Transform(100) vec := point.Transform(100)
vec = vec.Normalize() vec = vec.Normalize()
vertices = append(vertices, vec) vertices = append(vertices, vec)
mins = append(mins, float64(vec.Min()))
maxs = append(maxs, float64(vec.Max()))
} }
verticesIndex := modeler.WriteAccessor(doc, gltf.TargetElementArrayBuffer, vertices)
return vertices
}
// processTextureCoordinates processes the texture coordinates of a mesh
func processTextureCoordinates(textureCoordinates []threedb.Coordinate) [][2]float32 {
var textureCoords [][2]float32 var textureCoords [][2]float32
for _, txc := range textureCoordinates { for _, txc := range textureCoordinates {
textureCoords = append(textureCoords, txc) textureCoords = append(textureCoords, txc)
} }
return textureCoords textureIndex := modeler.WriteAccessor(doc, gltf.TargetArrayBuffer, textureCoords)
} trianglesIndex := modeler.WriteAccessor(doc, gltf.TargetArrayBuffer, triangles)
// 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) materialIndex := len(doc.Materials)
doc.Meshes = append(doc.Meshes, &gltf.Mesh{ doc.Meshes = append(doc.Meshes, &gltf.Mesh{
Name: fmt.Sprintf("%s-%d", model.Name, len(doc.Meshes)), Name: fmt.Sprintf("%s-%d", model.Name, len(doc.Meshes)),
Primitives: []*gltf.Primitive{{ Primitives: []*gltf.Primitive{{
@ -241,24 +180,22 @@ func addMeshToDocument(doc *gltf.Document, model *threedb.Model, verticesIndex,
}}, }},
}) })
//imageIndex, err := modeler.WriteImage(doc)
doc.Materials = append(doc.Materials, &gltf.Material{ doc.Materials = append(doc.Materials, &gltf.Material{
Name: material.Name, Name: material.Name,
AlphaMode: gltf.AlphaOpaque, AlphaMode: gltf.AlphaOpaque,
AlphaCutoff: gltf.Float(0.5), AlphaCutoff: gltf.Float(0.5),
PBRMetallicRoughness: &gltf.PBRMetallicRoughness{ PBRMetallicRoughness: &gltf.PBRMetallicRoughness{
//BaseColorTexture: &gltf.TextureInfo{Index: imageIndex},
BaseColorFactor: &[4]float64{0.8, 0.8, 0.8, 1}, BaseColorFactor: &[4]float64{0.8, 0.8, 0.8, 1},
MetallicFactor: gltf.Float(0.1), MetallicFactor: gltf.Float(0.1),
RoughnessFactor: gltf.Float(0.99), RoughnessFactor: gltf.Float(0.99),
}, },
}) })
doc.Nodes = append(doc.Nodes, &gltf.Node{ doc.Nodes = append(doc.Nodes, &gltf.Node{
Mesh: gltf.Index(len(doc.Meshes) - 1), Mesh: gltf.Index(len(doc.Meshes) - 1),
}) })
} }
// setupScene sets up the scene in the GLTF document
func setupScene(doc *gltf.Document) {
var nodes []int var nodes []int
for index := range doc.Nodes { for index := range doc.Nodes {
nodes = append(nodes, index) nodes = append(nodes, index)
@ -267,6 +204,15 @@ func setupScene(doc *gltf.Document) {
doc.Scenes = []*gltf.Scene{ doc.Scenes = []*gltf.Scene{
{Name: "Root Scene", Nodes: nodes}, {Name: "Root Scene", Nodes: nodes},
} }
if err := gltf.Save(doc, path.Join(outputPath, fmt.Sprintf("%s-%d.gltf", inputModel, meshIndex))); err != nil {
return err
}
//if err := gltf.SaveBinary(doc, fmt.Sprintf("./%s.glb", inputModel)); err != nil {
// return err
//}
//}
return nil
} }
func randRange(min, max int) int { func randRange(min, max int) int {

View File

@ -17,9 +17,9 @@
- [x] Добавить более подробные комментарии для объяснения сложных алгоритмов, особенно в декодере - [x] Добавить более подробные комментарии для объяснения сложных алгоритмов, особенно в декодере
- [ ] Удалить закомментированный код в decoder/main.go и других файлах - [ ] Удалить закомментированный код в decoder/main.go и других файлах
- [x] Внедрить единые шаблоны обработки ошибок в кодовой базе - [ ] Внедрить единые шаблоны обработки ошибок в кодовой базе
- [ ] Добавить поддержку контекста для операций, которые могут занять много времени - [ ] Добавить поддержку контекста для операций, которые могут занять много времени
- [x] Рефакторинг функции exportFn в decoder/main.go для большей модульности - [ ] Рефакторинг функции exportFn в decoder/main.go для большей модульности
- [ ] Улучшить именование переменных для лучшей читаемости кода - [ ] Улучшить именование переменных для лучшей читаемости кода
- [ ] Добавить правильную валидацию для входных файлов и параметров - [ ] Добавить правильную валидацию для входных файлов и параметров
- [ ] Последовательно реализовать правильную очистку ресурсов с помощью операторов defer - [ ] Последовательно реализовать правильную очистку ресурсов с помощью операторов defer

View File

@ -1,162 +0,0 @@
// Package errors provides a unified error handling strategy for the application.
// It extends the standard Go errors package with additional functionality for
// error wrapping, error types, and error logging.
package errors
import (
"errors"
"fmt"
"runtime"
"strings"
"git.influ.su/artmares/digglestool/internal/pkg/logger"
)
// Standard errors that can be used throughout the application.
var (
ErrNotFound = errors.New("not found")
ErrInvalidInput = errors.New("invalid input")
ErrInternal = errors.New("internal error")
ErrNotImplemented = errors.New("not implemented")
)
// Error represents an error with additional context.
type Error struct {
// Original is the original error.
Original error
// Message is an additional message to provide context.
Message string
// File is the file where the error occurred.
File string
// Line is the line where the error occurred.
Line int
}
// Error returns the error message.
func (e *Error) Error() string {
if e.Original == nil {
return e.Message
}
if e.Message == "" {
return e.Original.Error()
}
return fmt.Sprintf("%s: %s", e.Message, e.Original.Error())
}
// Unwrap returns the original error.
func (e *Error) Unwrap() error {
return e.Original
}
// New creates a new error with the given message.
func New(message string) error {
return &Error{
Message: message,
}
}
// Wrap wraps an error with additional context.
// If err is nil, Wrap returns nil.
func Wrap(err error, message string) error {
if err == nil {
return nil
}
_, file, line, _ := runtime.Caller(1)
file = trimFilePath(file)
return &Error{
Original: err,
Message: message,
File: file,
Line: line,
}
}
// Wrapf wraps an error with a formatted message.
// If err is nil, Wrapf returns nil.
func Wrapf(err error, format string, args ...interface{}) error {
if err == nil {
return nil
}
_, file, line, _ := runtime.Caller(1)
file = trimFilePath(file)
return &Error{
Original: err,
Message: fmt.Sprintf(format, args...),
File: file,
Line: line,
}
}
// Is reports whether any error in err's chain matches target.
func Is(err, target error) bool {
return errors.Is(err, target)
}
// As finds the first error in err's chain that matches target, and if so, sets
// target to that error value and returns true. Otherwise, it returns false.
func As(err error, target interface{}) bool {
return errors.As(err, target)
}
// LogError logs an error with the appropriate log level and returns it.
// This is useful for logging an error while still returning it up the call stack.
func LogError(err error) error {
if err == nil {
return nil
}
var e *Error
if errors.As(err, &e) {
logger.Errorf("[%s:%d] %s", e.File, e.Line, err.Error())
} else {
_, file, line, _ := runtime.Caller(1)
file = trimFilePath(file)
logger.Errorf("[%s:%d] %s", file, line, err.Error())
}
return err
}
// LogWarn logs an error as a warning and returns it.
func LogWarn(err error) error {
if err == nil {
return nil
}
var e *Error
if errors.As(err, &e) {
logger.Warnf("[%s:%d] %s", e.File, e.Line, err.Error())
} else {
_, file, line, _ := runtime.Caller(1)
file = trimFilePath(file)
logger.Warnf("[%s:%d] %s", file, line, err.Error())
}
return err
}
// LogFatal logs an error as fatal and exits the program.
func LogFatal(err error) {
if err == nil {
return
}
var e *Error
if errors.As(err, &e) {
logger.Fatalf("[%s:%d] %s", e.File, e.Line, err.Error())
} else {
_, file, line, _ := runtime.Caller(1)
file = trimFilePath(file)
logger.Fatalf("[%s:%d] %s", file, line, err.Error())
}
}
// trimFilePath trims the file path to make it more readable.
func trimFilePath(file string) string {
// Find the last occurrence of "git.influ.su/artmares/digglestool"
idx := strings.LastIndex(file, "git.influ.su\\artmares\\digglestool")
if idx >= 0 {
return file[idx+len("git.influ.su\\artmares\\digglestool"):]
}
return file
}

View File

@ -1,6 +1,7 @@
package exporter package exporter
import ( import (
"errors"
"fmt" "fmt"
_ "image/png" _ "image/png"
"os" "os"
@ -11,7 +12,6 @@ import (
_ "github.com/ftrvxmtrx/tga" _ "github.com/ftrvxmtrx/tga"
"github.com/qmuntal/gltf" "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/internal/pkg/logger"
"git.influ.su/artmares/digglestool/pkg/threedb" "git.influ.su/artmares/digglestool/pkg/threedb"
) )
@ -57,31 +57,30 @@ func NewExporter(options ...Option) *Exporter {
func (e *Exporter) Export(basePath string, model *threedb.Model) error { func (e *Exporter) Export(basePath string, model *threedb.Model) error {
if model == nil { if model == nil {
return errors.ErrInvalidInput return errors.New("model is nil")
} }
meshesLen := len(model.Meshes) meshesLen := len(model.Meshes)
if meshesLen == 0 { if meshesLen == 0 {
return errors.New("no meshes to export") return errors.New("no meshes to export")
} }
if err := e.prepareTextures(); err != nil { if err := e.prepareTextures(); err != nil {
return errors.Wrap(err, "failed to prepare textures") return err
} }
if e.onlyBaseMesh { if e.onlyBaseMesh {
logger.Infof("Export Base Model: %s", model.Name) logger.Infof("Export Base Model: %s", model.Name)
doc, err := e.generate(model, 0) doc, err := e.generate(model, 0)
if err != nil { if err != nil {
return errors.Wrap(err, "failed to generate base model") return err
} }
outputPath := path.Join(basePath, fmt.Sprintf("%s-base-model.gltf", model.Name)) if err = gltf.Save(doc, path.Join(basePath, fmt.Sprintf("%s-base-model.gltf", model.Name))); err != nil {
if err = gltf.Save(doc, outputPath); err != nil { return err
return errors.Wrapf(err, "failed to save base model to %s", outputPath)
} }
} }
if e.animation { if e.animation {
dirPath := path.Join(basePath, "animation", model.Name) dirPath := path.Join(basePath, "animation", model.Name)
if _, err := os.Stat(dirPath); os.IsNotExist(err) { if _, err := os.Stat(dirPath); os.IsNotExist(err) {
if err = os.MkdirAll(dirPath, os.ModeDir); err != nil { if err = os.MkdirAll(dirPath, os.ModeDir); err != nil {
return errors.Wrapf(err, "failed to create directory %s", dirPath) return err
} }
} }
logger.Infof("Export Animation to %s", dirPath) logger.Infof("Export Animation to %s", dirPath)
@ -94,11 +93,10 @@ func (e *Exporter) Export(basePath string, model *threedb.Model) error {
} }
doc, err := e.generate(model, indexes...) doc, err := e.generate(model, indexes...)
if err != nil { if err != nil {
return errors.Wrapf(err, "failed to generate animation %s", animation.Name) return err
} }
outputPath := path.Join(dirPath, fmt.Sprintf("%s-%s.gltf", model.Name, animation.Name)) if err = gltf.Save(doc, path.Join(dirPath, fmt.Sprintf("%s-%s.gltf", model.Name, animation.Name))); err != nil {
if err = gltf.Save(doc, outputPath); err != nil { return err
return errors.Wrapf(err, "failed to save animation %s to %s", animation.Name, outputPath)
} }
} }
} }
@ -208,11 +206,9 @@ func (e *Exporter) prepareTextures() error {
if e.texturesBasePath == nil { if e.texturesBasePath == nil {
return nil return nil
} }
if err := filepath.Walk(*e.texturesBasePath, func(pathFile string, info os.FileInfo, err error) error {
basePath := *e.texturesBasePath
if err := filepath.Walk(basePath, func(pathFile string, info os.FileInfo, err error) error {
if err != nil { if err != nil {
return errors.Wrapf(err, "failed to access path %s", pathFile) return err
} }
if strings.HasSuffix(info.Name(), ".tga") { if strings.HasSuffix(info.Name(), ".tga") {
key := strings.TrimSuffix(info.Name(), ".tga") key := strings.TrimSuffix(info.Name(), ".tga")
@ -227,7 +223,7 @@ func (e *Exporter) prepareTextures() error {
} }
return nil return nil
}); err != nil { }); err != nil {
return errors.Wrapf(err, "failed to walk textures directory %s", basePath) return err
} }
return nil return nil
} }

View File

@ -11,7 +11,6 @@ import (
"github.com/qmuntal/gltf" "github.com/qmuntal/gltf"
"github.com/qmuntal/gltf/modeler" "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/pkg/logger"
"git.influ.su/artmares/digglestool/pkg/threedb" "git.influ.su/artmares/digglestool/pkg/threedb"
) )
@ -22,35 +21,21 @@ type Model struct {
} }
func (m *Model) Generate(model *threedb.Model, cache *Cache[*CacheItem], meshIndexes ...int) (*gltf.Document, error) { 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 = gltf.NewDocument()
m.doc.Scenes = []*gltf.Scene{} m.doc.Scenes = []*gltf.Scene{}
m.materialMap = make(map[uint16]int) m.materialMap = make(map[uint16]int)
logger.Info("Generate use mesh indexes", len(meshIndexes)) logger.Info("Generate use mesh indexes", len(meshIndexes))
for i, meshIndex := range 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] mesh := model.Meshes[meshIndex]
var nodeIndexes []int var nodeIndexes []int
for index, link := range mesh.Links { for index, link := range mesh.Links {
materialIndex, err := m.materialIndex(model, cache, link.Material) materialIndex, err := m.materialIndex(model, cache, link.Material)
if err != nil { if err != nil {
return nil, errors.Wrapf(err, "failed to get material index for mesh %d link %d", return nil, err
meshIndex, index)
} }
verticesIndex := m.generateVertices(model.Points[link.Points]) verticesIndex := m.generateVertices(model.Points[link.Points])
textureCoordIndex := m.generateTextureCoordinate(model.TextureCoordinates[link.TextureCoordinates]) textureCoordIndex := m.generateTextureCoordinate(model.TextureCoordinates[link.TextureCoordinates])
trianglesIndex := modeler.WriteAccessor(m.doc, gltf.TargetArrayBuffer, model.Triangles[link.Triangles]) trianglesIndex := modeler.WriteAccessor(m.doc, gltf.TargetArrayBuffer, model.Triangles[link.Triangles])
m.doc.Meshes = append(m.doc.Meshes, &gltf.Mesh{ m.doc.Meshes = append(m.doc.Meshes, &gltf.Mesh{
Name: fmt.Sprintf("%s-%d-%d", model.Name, i, index), Name: fmt.Sprintf("%s-%d-%d", model.Name, i, index),
Primitives: []*gltf.Primitive{{ Primitives: []*gltf.Primitive{{
@ -62,19 +47,16 @@ func (m *Model) Generate(model *threedb.Model, cache *Cache[*CacheItem], meshInd
Material: gltf.Index(materialIndex), Material: gltf.Index(materialIndex),
}}, }},
}) })
nodeIndexes = append(nodeIndexes, len(m.doc.Nodes)) nodeIndexes = append(nodeIndexes, len(m.doc.Nodes))
m.doc.Nodes = append(m.doc.Nodes, &gltf.Node{ m.doc.Nodes = append(m.doc.Nodes, &gltf.Node{
Mesh: gltf.Index(len(m.doc.Meshes) - 1), Mesh: gltf.Index(len(m.doc.Meshes) - 1),
}) })
} }
m.doc.Scenes = append(m.doc.Scenes, &gltf.Scene{ m.doc.Scenes = append(m.doc.Scenes, &gltf.Scene{
Name: fmt.Sprintf("Root Scene-%d", meshIndex), Name: fmt.Sprintf("Root Scene-%d", meshIndex),
Nodes: nodeIndexes, Nodes: nodeIndexes,
}) })
} }
m.doc.Samplers = []*gltf.Sampler{{}} m.doc.Samplers = []*gltf.Sampler{{}}
return m.doc, nil return m.doc, nil
} }
@ -98,45 +80,47 @@ func (m *Model) generateTextureCoordinate(coordinates []threedb.Coordinate) int
} }
func (m *Model) materialIndex(model *threedb.Model, cache *Cache[*CacheItem], meshMaterialIndex uint16) (int, error) { func (m *Model) materialIndex(model *threedb.Model, cache *Cache[*CacheItem], meshMaterialIndex uint16) (int, error) {
//logger, logPutter := helpers.NewBuffer()
//defer func() {
// logPutter(logger)
//}()
materialIndex, ok := m.materialMap[meshMaterialIndex] materialIndex, ok := m.materialMap[meshMaterialIndex]
//logger.WriteString(fmt.Sprintf("Try find material index %d\n", meshMaterialIndex))
if !ok { if !ok {
//logger.WriteString(fmt.Sprintf("Material index %d not found\n", meshMaterialIndex))
material := model.Materials[meshMaterialIndex] material := model.Materials[meshMaterialIndex]
item, ok := cache.Get(material.Name) item, ok := cache.Get(material.Name)
if !ok { if !ok {
// Instead of returning nil, return a more descriptive error return 0, nil
return 0, errors.New("material not found in cache: " + material.Name)
} }
//logger.WriteString(fmt.Sprintf("Try found material name %s\n", material.Name))
if item.Bytes == nil { if item.Bytes == nil {
//logger.WriteString(fmt.Sprintf("Material name %s not found in cache\n", material.Name))
f, err := os.Open(item.Path) f, err := os.Open(item.Path)
if err != nil { if err != nil {
return 0, errors.Wrapf(err, "failed to open texture file: %s", item.Path) return 0, err
} }
defer f.Close() defer f.Close()
img, _, err := image.Decode(f) img, _, err := image.Decode(f)
if err != nil { if err != nil {
return 0, errors.Wrap(err, "failed to decode image") return 0, err
} }
item.Bytes = &bytes.Buffer{} item.Bytes = &bytes.Buffer{}
if err = png.Encode(item.Bytes, img); err != nil { if err = png.Encode(item.Bytes, img); err != nil {
return 0, errors.Wrap(err, "failed to encode image to PNG") return 0, err
} }
cache.Set(material.Name, item) cache.Set(material.Name, item)
} }
//log.Printf("material index: %d, material name: %s, data: %d", meshMaterialIndex, material.Name, len(data))
imageIndex, err := modeler.WriteImage(m.doc, material.Name, "image/png", item.Bytes) imageIndex, err := modeler.WriteImage(m.doc, material.Name, "image/png", item.Bytes)
if err != nil { if err != nil {
return 0, errors.Wrap(err, "failed to write image to GLTF document") return 0, err
} }
m.doc.Textures = append(m.doc.Textures, &gltf.Texture{ m.doc.Textures = append(m.doc.Textures, &gltf.Texture{
Name: material.Name, Name: material.Name,
Sampler: gltf.Index(0), Sampler: gltf.Index(0),
Source: gltf.Index(imageIndex), Source: gltf.Index(imageIndex),
}) })
materialIndex = len(m.doc.Materials) materialIndex = len(m.doc.Materials)
m.doc.Materials = append(m.doc.Materials, &gltf.Material{ m.doc.Materials = append(m.doc.Materials, &gltf.Material{
Name: material.Name, Name: material.Name,
@ -151,6 +135,7 @@ func (m *Model) materialIndex(model *threedb.Model, cache *Cache[*CacheItem], me
}) })
m.materialMap[meshMaterialIndex] = materialIndex m.materialMap[meshMaterialIndex] = materialIndex
} }
//log.Println(logger.String())
return materialIndex, nil return materialIndex, nil
} }

View File

@ -6,9 +6,9 @@ package threedb
import ( import (
"encoding/binary" "encoding/binary"
"errors"
"io" "io"
"git.influ.su/artmares/digglestool/internal/pkg/errors"
"git.influ.su/artmares/digglestool/internal/pkg/logger" "git.influ.su/artmares/digglestool/internal/pkg/logger"
) )
@ -47,35 +47,35 @@ func (dec *Decoder) Decode(model *Model) error {
// Read file header information // Read file header information
if model.DBVersion, err = dec.readString(); err != nil { if model.DBVersion, err = dec.readString(); err != nil {
return errors.Wrap(err, "failed to read DB version") return err
} }
if model.Name, err = dec.readString(); err != nil { if model.Name, err = dec.readString(); err != nil {
return errors.Wrap(err, "failed to read model name") return err
} }
// Read all model components in the order they appear in the file // Read all model components in the order they appear in the file
if err = dec.readMaterials(model); err != nil { if err = dec.readMaterials(model); err != nil {
return errors.Wrap(err, "failed to read materials") return err
} }
if err = dec.readMeshes(model); err != nil { if err = dec.readMeshes(model); err != nil {
return errors.Wrap(err, "failed to read meshes") return err
} }
if err = dec.readObjects(model); err != nil { if err = dec.readObjects(model); err != nil {
return errors.Wrap(err, "failed to read objects") return err
} }
if err = dec.readAnimations(model); err != nil { if err = dec.readAnimations(model); err != nil {
return errors.Wrap(err, "failed to read animations") return err
} }
if err = dec.readShadows(model); err != nil { if err = dec.readShadows(model); err != nil {
return errors.Wrap(err, "failed to read shadows") return err
} }
if err = dec.readCubeMaps(model); err != nil { if err = dec.readCubeMaps(model); err != nil {
return errors.Wrap(err, "failed to read cube maps") return err
} }
// Read the actual geometry data (triangles, texture coordinates, vertices, etc.) // Read the actual geometry data (triangles, texture coordinates, vertices, etc.)
if err = dec.readData(model); err != nil { if err = dec.readData(model); err != nil {
return errors.Wrap(err, "failed to read geometry data") return err
} }
return nil return nil
@ -129,13 +129,13 @@ func (dec *Decoder) readString() (string, error) {
// First read the length of the string // First read the length of the string
length, err := dec.readUInt32() length, err := dec.readUInt32()
if err != nil { if err != nil {
return "", errors.Wrap(err, "failed to read string length") return "", err
} }
// Then read the string data // Then read the string data
buf := make([]byte, length) buf := make([]byte, length)
if err = dec.read(&buf); err != nil { if err = dec.read(&buf); err != nil {
return "", errors.Wrap(err, "failed to read string data") return "", err
} }
return string(buf), nil return string(buf), nil
@ -150,13 +150,13 @@ func (dec *Decoder) readVector() (*Vector, error) {
// Read the x, y, and z components of the vector // Read the x, y, and z components of the vector
if vec[0], err = dec.readFloat32(); err != nil { if vec[0], err = dec.readFloat32(); err != nil {
return nil, errors.Wrap(err, "failed to read vector X component") return nil, err
} }
if vec[1], err = dec.readFloat32(); err != nil { if vec[1], err = dec.readFloat32(); err != nil {
return nil, errors.Wrap(err, "failed to read vector Y component") return nil, err
} }
if vec[2], err = dec.readFloat32(); err != nil { if vec[2], err = dec.readFloat32(); err != nil {
return nil, errors.Wrap(err, "failed to read vector Z component") return nil, err
} }
return &vec, nil return &vec, nil