diff --git a/cmd/decoder/main.go b/cmd/decoder/main.go index 1a1ac73..31e1986 100644 --- a/cmd/decoder/main.go +++ b/cmd/decoder/main.go @@ -12,6 +12,7 @@ import ( "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" @@ -35,14 +36,14 @@ func main() { f, err := os.Open(path.Join(inputPath, inputModel+".3db")) if err != nil { - logger.Fatal(err) + 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 { - logger.Error(err) + errors.LogError(errors.Wrap(err, "failed to decode model")) return } //log.Printf("DB Version: %v\n", model.DBVersion) @@ -64,14 +65,14 @@ func main() { logFile, err := os.OpenFile(fmt.Sprintf("./%s.json", inputModel), os.O_TRUNC|os.O_CREATE|os.O_WRONLY, 0644) if err != nil { - logger.Error(err) + 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 { - logger.Error(err) + errors.LogError(errors.Wrap(err, "failed to encode model to JSON")) return } @@ -81,7 +82,7 @@ func main() { exporter.WithAnimation(), ) if err = export.Export(outputPath, &model); err != nil { - logger.Error(err) + errors.LogError(errors.Wrap(err, "failed to export model")) return } @@ -91,111 +92,173 @@ func main() { //} } +// exportFn exports a 3DB model to GLTF format func exportFn(model *threedb.Model, inputModel string, cache map[string]string) error { - //maxMeshIndex := 133 - //meshIndex := randRange(0, len(model.Meshes)-1) meshIndex := 0 - //log.Println("Mesh Index:", meshIndex) - binaryCache := make(map[string][]byte) - - //for meshIndex := 0; meshIndex < len(model.Meshes); meshIndex++ { doc := gltf.NewDocument() - //doc.Materials = []*gltf.Material{{ - // 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)}, - //}} + + // 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 { - logger.Warnf("Invalid texture cache %q", material.Name) + errors.LogWarn(errors.New("invalid texture cache: " + material.Name)) continue - return fmt.Errorf("invalid texture cache %q", material.Name) } - data, err := os.ReadFile(texturePath) + var err error + data, err = os.ReadFile(texturePath) if err != nil { - return err + return errors.Wrap(err, "failed to read texture file: "+texturePath) } binaryCache[material.Name] = data } - mt := mimetype.Detect(data) - 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 { + + if err := addMaterialToDocument(doc, material, data); err != nil { return err } - doc.Textures = append(doc.Textures, &gltf.Texture{ - Name: material.Name, - //Sampler: gltf.Index(0), - 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), - }, - }) } - mesh := model.Meshes[meshIndex] + 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] - var vertices [][3]float32 - var mins []float64 - var maxs []float64 - for _, point := range points { - vec := point.Transform(100) - vec = vec.Normalize() - vertices = append(vertices, vec) - mins = append(mins, float64(vec.Min())) - maxs = append(maxs, float64(vec.Max())) - } - verticesIndex := modeler.WriteAccessor(doc, gltf.TargetElementArrayBuffer, vertices) - var textureCoords [][2]float32 - for _, txc := range textureCoordinates { - textureCoords = append(textureCoords, txc) - } - textureIndex := modeler.WriteAccessor(doc, gltf.TargetArrayBuffer, textureCoords) - trianglesIndex := modeler.WriteAccessor(doc, gltf.TargetArrayBuffer, triangles) - 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), - }}, - }) - //imageIndex, err := modeler.WriteImage(doc) - 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), - }, - }) - doc.Nodes = append(doc.Nodes, &gltf.Node{ - Mesh: gltf.Index(len(doc.Meshes) - 1), - }) + // 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) @@ -204,15 +267,6 @@ func exportFn(model *threedb.Model, inputModel string, cache map[string]string) doc.Scenes = []*gltf.Scene{ {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 { diff --git a/docs/tasks.md b/docs/tasks.md index 3c4a435..75affeb 100644 --- a/docs/tasks.md +++ b/docs/tasks.md @@ -17,9 +17,9 @@ - [x] Добавить более подробные комментарии для объяснения сложных алгоритмов, особенно в декодере - [ ] Удалить закомментированный код в decoder/main.go и других файлах -- [ ] Внедрить единые шаблоны обработки ошибок в кодовой базе +- [x] Внедрить единые шаблоны обработки ошибок в кодовой базе - [ ] Добавить поддержку контекста для операций, которые могут занять много времени -- [ ] Рефакторинг функции exportFn в decoder/main.go для большей модульности +- [x] Рефакторинг функции exportFn в decoder/main.go для большей модульности - [ ] Улучшить именование переменных для лучшей читаемости кода - [ ] Добавить правильную валидацию для входных файлов и параметров - [ ] Последовательно реализовать правильную очистку ресурсов с помощью операторов defer diff --git a/internal/pkg/errors/errors.go b/internal/pkg/errors/errors.go new file mode 100644 index 0000000..2b64730 --- /dev/null +++ b/internal/pkg/errors/errors.go @@ -0,0 +1,162 @@ +// 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 +} diff --git a/internal/services/exporter/exporter.go b/internal/services/exporter/exporter.go index aa59bfc..7689e0b 100644 --- a/internal/services/exporter/exporter.go +++ b/internal/services/exporter/exporter.go @@ -1,7 +1,6 @@ package exporter import ( - "errors" "fmt" _ "image/png" "os" @@ -12,6 +11,7 @@ import ( _ "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" ) @@ -57,30 +57,31 @@ func NewExporter(options ...Option) *Exporter { func (e *Exporter) Export(basePath string, model *threedb.Model) error { if model == nil { - return errors.New("model is 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 err + 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 err + return errors.Wrap(err, "failed to generate base model") } - if err = gltf.Save(doc, path.Join(basePath, fmt.Sprintf("%s-base-model.gltf", model.Name))); err != nil { - return err + 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 err + return errors.Wrapf(err, "failed to create directory %s", dirPath) } } logger.Infof("Export Animation to %s", dirPath) @@ -93,10 +94,11 @@ func (e *Exporter) Export(basePath string, model *threedb.Model) error { } doc, err := e.generate(model, indexes...) if err != nil { - return err + return errors.Wrapf(err, "failed to generate animation %s", animation.Name) } - if err = gltf.Save(doc, path.Join(dirPath, fmt.Sprintf("%s-%s.gltf", model.Name, animation.Name))); err != nil { - return err + 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) } } } @@ -206,9 +208,11 @@ 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 { + + basePath := *e.texturesBasePath + if err := filepath.Walk(basePath, func(pathFile string, info os.FileInfo, err error) error { if err != nil { - return err + return errors.Wrapf(err, "failed to access path %s", pathFile) } if strings.HasSuffix(info.Name(), ".tga") { key := strings.TrimSuffix(info.Name(), ".tga") @@ -223,7 +227,7 @@ func (e *Exporter) prepareTextures() error { } return nil }); err != nil { - return err + return errors.Wrapf(err, "failed to walk textures directory %s", basePath) } return nil } diff --git a/internal/services/exporter/model.go b/internal/services/exporter/model.go index 8278846..20c2d29 100644 --- a/internal/services/exporter/model.go +++ b/internal/services/exporter/model.go @@ -11,6 +11,7 @@ import ( "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" ) @@ -21,21 +22,35 @@ type Model struct { } 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, err + 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{{ @@ -47,16 +62,19 @@ func (m *Model) Generate(model *threedb.Model, cache *Cache[*CacheItem], meshInd 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 } @@ -80,47 +98,45 @@ func (m *Model) generateTextureCoordinate(coordinates []threedb.Coordinate) int } 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] - //logger.WriteString(fmt.Sprintf("Try find material index %d\n", meshMaterialIndex)) if !ok { - //logger.WriteString(fmt.Sprintf("Material index %d not found\n", meshMaterialIndex)) material := model.Materials[meshMaterialIndex] item, ok := cache.Get(material.Name) if !ok { - return 0, nil + // Instead of returning nil, return a more descriptive error + 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 { - //logger.WriteString(fmt.Sprintf("Material name %s not found in cache\n", material.Name)) f, err := os.Open(item.Path) if err != nil { - return 0, err + 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, err + return 0, errors.Wrap(err, "failed to decode image") } + item.Bytes = &bytes.Buffer{} if err = png.Encode(item.Bytes, img); err != nil { - return 0, err + return 0, errors.Wrap(err, "failed to encode image to PNG") } 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) if err != nil { - return 0, err + 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, @@ -135,7 +151,6 @@ func (m *Model) materialIndex(model *threedb.Model, cache *Cache[*CacheItem], me }) m.materialMap[meshMaterialIndex] = materialIndex } - //log.Println(logger.String()) return materialIndex, nil } diff --git a/pkg/threedb/decoder.go b/pkg/threedb/decoder.go index 10d1e83..2032fce 100644 --- a/pkg/threedb/decoder.go +++ b/pkg/threedb/decoder.go @@ -6,9 +6,9 @@ package threedb import ( "encoding/binary" - "errors" "io" + "git.influ.su/artmares/digglestool/internal/pkg/errors" "git.influ.su/artmares/digglestool/internal/pkg/logger" ) @@ -47,35 +47,35 @@ func (dec *Decoder) Decode(model *Model) error { // Read file header information if model.DBVersion, err = dec.readString(); err != nil { - return err + return errors.Wrap(err, "failed to read DB version") } if model.Name, err = dec.readString(); err != nil { - return err + return errors.Wrap(err, "failed to read model name") } // Read all model components in the order they appear in the file if err = dec.readMaterials(model); err != nil { - return err + return errors.Wrap(err, "failed to read materials") } if err = dec.readMeshes(model); err != nil { - return err + return errors.Wrap(err, "failed to read meshes") } if err = dec.readObjects(model); err != nil { - return err + return errors.Wrap(err, "failed to read objects") } if err = dec.readAnimations(model); err != nil { - return err + return errors.Wrap(err, "failed to read animations") } if err = dec.readShadows(model); err != nil { - return err + return errors.Wrap(err, "failed to read shadows") } if err = dec.readCubeMaps(model); err != nil { - return err + return errors.Wrap(err, "failed to read cube maps") } // Read the actual geometry data (triangles, texture coordinates, vertices, etc.) if err = dec.readData(model); err != nil { - return err + return errors.Wrap(err, "failed to read geometry data") } return nil @@ -129,13 +129,13 @@ func (dec *Decoder) readString() (string, error) { // First read the length of the string length, err := dec.readUInt32() if err != nil { - return "", err + return "", errors.Wrap(err, "failed to read string length") } // Then read the string data buf := make([]byte, length) if err = dec.read(&buf); err != nil { - return "", err + return "", errors.Wrap(err, "failed to read string data") } return string(buf), nil @@ -150,13 +150,13 @@ func (dec *Decoder) readVector() (*Vector, error) { // Read the x, y, and z components of the vector if vec[0], err = dec.readFloat32(); err != nil { - return nil, err + return nil, errors.Wrap(err, "failed to read vector X component") } if vec[1], err = dec.readFloat32(); err != nil { - return nil, err + return nil, errors.Wrap(err, "failed to read vector Y component") } if vec[2], err = dec.readFloat32(); err != nil { - return nil, err + return nil, errors.Wrap(err, "failed to read vector Z component") } return &vec, nil