Compare commits
1 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| c82f9a193c |
@ -12,6 +12,7 @@ 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"
|
||||||
@ -35,14 +36,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 {
|
||||||
logger.Fatal(err)
|
errors.LogFatal(errors.Wrap(err, "failed to open input file"))
|
||||||
}
|
}
|
||||||
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 {
|
||||||
logger.Error(err)
|
errors.LogError(errors.Wrap(err, "failed to decode model"))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
//log.Printf("DB Version: %v\n", model.DBVersion)
|
//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)
|
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 {
|
||||||
logger.Error(err)
|
errors.LogError(errors.Wrap(err, "failed to create log file"))
|
||||||
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 {
|
||||||
logger.Error(err)
|
errors.LogError(errors.Wrap(err, "failed to encode model to JSON"))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -81,7 +82,7 @@ func main() {
|
|||||||
exporter.WithAnimation(),
|
exporter.WithAnimation(),
|
||||||
)
|
)
|
||||||
if err = export.Export(outputPath, &model); err != nil {
|
if err = export.Export(outputPath, &model); err != nil {
|
||||||
logger.Error(err)
|
errors.LogError(errors.Wrap(err, "failed to export model"))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -91,47 +92,76 @@ 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
|
||||||
//log.Println("Mesh Index:", meshIndex)
|
|
||||||
binaryCache := make(map[string][]byte)
|
|
||||||
|
|
||||||
//for meshIndex := 0; meshIndex < len(model.Meshes); meshIndex++ {
|
|
||||||
doc := gltf.NewDocument()
|
doc := gltf.NewDocument()
|
||||||
//doc.Materials = []*gltf.Material{{
|
|
||||||
// Name: "Default", AlphaMode: gltf.AlphaOpaque, AlphaCutoff: gltf.Float(0.5),
|
// Process materials and textures
|
||||||
// PBRMetallicRoughness: &gltf.PBRMetallicRoughness{BaseColorFactor: &[4]float64{0.8, 0.8, 0.8, 1}, MetallicFactor: gltf.Float(0.1), RoughnessFactor: gltf.Float(0.99)},
|
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 {
|
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 {
|
||||||
logger.Warnf("Invalid texture cache %q", material.Name)
|
errors.LogWarn(errors.New("invalid texture cache: " + material.Name))
|
||||||
continue
|
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 {
|
if err != nil {
|
||||||
return err
|
return errors.Wrap(err, "failed to read texture file: "+texturePath)
|
||||||
}
|
}
|
||||||
binaryCache[material.Name] = data
|
binaryCache[material.Name] = data
|
||||||
}
|
}
|
||||||
mt := mimetype.Detect(data)
|
|
||||||
if mt == nil {
|
if err := addMaterialToDocument(doc, material, data); err != 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,
|
||||||
@ -143,31 +173,62 @@ func exportFn(model *threedb.Model, inputModel string, cache map[string]string)
|
|||||||
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)
|
||||||
}
|
}
|
||||||
textureIndex := modeler.WriteAccessor(doc, gltf.TargetArrayBuffer, textureCoords)
|
return 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{{
|
||||||
@ -180,22 +241,24 @@ func exportFn(model *threedb.Model, inputModel string, cache map[string]string)
|
|||||||
}},
|
}},
|
||||||
})
|
})
|
||||||
|
|
||||||
//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)
|
||||||
@ -204,15 +267,6 @@ func exportFn(model *threedb.Model, inputModel string, cache map[string]string)
|
|||||||
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 {
|
||||||
|
|||||||
@ -17,9 +17,9 @@
|
|||||||
|
|
||||||
- [x] Добавить более подробные комментарии для объяснения сложных алгоритмов, особенно в декодере
|
- [x] Добавить более подробные комментарии для объяснения сложных алгоритмов, особенно в декодере
|
||||||
- [ ] Удалить закомментированный код в decoder/main.go и других файлах
|
- [ ] Удалить закомментированный код в decoder/main.go и других файлах
|
||||||
- [ ] Внедрить единые шаблоны обработки ошибок в кодовой базе
|
- [x] Внедрить единые шаблоны обработки ошибок в кодовой базе
|
||||||
- [ ] Добавить поддержку контекста для операций, которые могут занять много времени
|
- [ ] Добавить поддержку контекста для операций, которые могут занять много времени
|
||||||
- [ ] Рефакторинг функции exportFn в decoder/main.go для большей модульности
|
- [x] Рефакторинг функции exportFn в decoder/main.go для большей модульности
|
||||||
- [ ] Улучшить именование переменных для лучшей читаемости кода
|
- [ ] Улучшить именование переменных для лучшей читаемости кода
|
||||||
- [ ] Добавить правильную валидацию для входных файлов и параметров
|
- [ ] Добавить правильную валидацию для входных файлов и параметров
|
||||||
- [ ] Последовательно реализовать правильную очистку ресурсов с помощью операторов defer
|
- [ ] Последовательно реализовать правильную очистку ресурсов с помощью операторов defer
|
||||||
|
|||||||
162
internal/pkg/errors/errors.go
Normal file
162
internal/pkg/errors/errors.go
Normal file
@ -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
|
||||||
|
}
|
||||||
@ -1,7 +1,6 @@
|
|||||||
package exporter
|
package exporter
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"errors"
|
|
||||||
"fmt"
|
"fmt"
|
||||||
_ "image/png"
|
_ "image/png"
|
||||||
"os"
|
"os"
|
||||||
@ -12,6 +11,7 @@ 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,30 +57,31 @@ 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.New("model is nil")
|
return errors.ErrInvalidInput
|
||||||
}
|
}
|
||||||
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 err
|
return errors.Wrap(err, "failed to prepare textures")
|
||||||
}
|
}
|
||||||
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 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 {
|
outputPath := path.Join(basePath, fmt.Sprintf("%s-base-model.gltf", model.Name))
|
||||||
return err
|
if err = gltf.Save(doc, outputPath); err != nil {
|
||||||
|
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 err
|
return errors.Wrapf(err, "failed to create directory %s", dirPath)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
logger.Infof("Export Animation to %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...)
|
doc, err := e.generate(model, indexes...)
|
||||||
if err != nil {
|
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 {
|
outputPath := path.Join(dirPath, fmt.Sprintf("%s-%s.gltf", model.Name, animation.Name))
|
||||||
return err
|
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 {
|
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 err
|
return errors.Wrapf(err, "failed to access path %s", pathFile)
|
||||||
}
|
}
|
||||||
if strings.HasSuffix(info.Name(), ".tga") {
|
if strings.HasSuffix(info.Name(), ".tga") {
|
||||||
key := strings.TrimSuffix(info.Name(), ".tga")
|
key := strings.TrimSuffix(info.Name(), ".tga")
|
||||||
@ -223,7 +227,7 @@ func (e *Exporter) prepareTextures() error {
|
|||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}); err != nil {
|
}); err != nil {
|
||||||
return err
|
return errors.Wrapf(err, "failed to walk textures directory %s", basePath)
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|||||||
@ -11,6 +11,7 @@ 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"
|
||||||
)
|
)
|
||||||
@ -21,21 +22,35 @@ 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, 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])
|
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{{
|
||||||
@ -47,16 +62,19 @@ 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
|
||||||
}
|
}
|
||||||
@ -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) {
|
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 {
|
||||||
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 {
|
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, err
|
return 0, errors.Wrapf(err, "failed to open texture file: %s", item.Path)
|
||||||
}
|
}
|
||||||
defer f.Close()
|
defer f.Close()
|
||||||
|
|
||||||
img, _, err := image.Decode(f)
|
img, _, err := image.Decode(f)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return 0, err
|
return 0, errors.Wrap(err, "failed to decode image")
|
||||||
}
|
}
|
||||||
|
|
||||||
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, err
|
return 0, errors.Wrap(err, "failed to encode image to PNG")
|
||||||
}
|
}
|
||||||
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, err
|
return 0, errors.Wrap(err, "failed to write image to GLTF document")
|
||||||
}
|
}
|
||||||
|
|
||||||
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,
|
||||||
@ -135,7 +151,6 @@ 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
|
||||||
}
|
}
|
||||||
|
|||||||
@ -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 err
|
return errors.Wrap(err, "failed to read DB version")
|
||||||
}
|
}
|
||||||
if model.Name, err = dec.readString(); err != nil {
|
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
|
// 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 err
|
return errors.Wrap(err, "failed to read materials")
|
||||||
}
|
}
|
||||||
if err = dec.readMeshes(model); err != nil {
|
if err = dec.readMeshes(model); err != nil {
|
||||||
return err
|
return errors.Wrap(err, "failed to read meshes")
|
||||||
}
|
}
|
||||||
if err = dec.readObjects(model); err != nil {
|
if err = dec.readObjects(model); err != nil {
|
||||||
return err
|
return errors.Wrap(err, "failed to read objects")
|
||||||
}
|
}
|
||||||
if err = dec.readAnimations(model); err != nil {
|
if err = dec.readAnimations(model); err != nil {
|
||||||
return err
|
return errors.Wrap(err, "failed to read animations")
|
||||||
}
|
}
|
||||||
if err = dec.readShadows(model); err != nil {
|
if err = dec.readShadows(model); err != nil {
|
||||||
return err
|
return errors.Wrap(err, "failed to read shadows")
|
||||||
}
|
}
|
||||||
if err = dec.readCubeMaps(model); err != nil {
|
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.)
|
// 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 err
|
return errors.Wrap(err, "failed to read geometry data")
|
||||||
}
|
}
|
||||||
|
|
||||||
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 "", err
|
return "", errors.Wrap(err, "failed to read string length")
|
||||||
}
|
}
|
||||||
|
|
||||||
// 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 "", err
|
return "", errors.Wrap(err, "failed to read string data")
|
||||||
}
|
}
|
||||||
|
|
||||||
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, err
|
return nil, errors.Wrap(err, "failed to read vector X component")
|
||||||
}
|
}
|
||||||
if vec[1], err = dec.readFloat32(); err != nil {
|
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 {
|
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
|
return &vec, nil
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user