feat: test tool write

This commit is contained in:
ArtMares 2024-07-19 10:21:26 +03:00
parent 481a1e65ee
commit 0f6262da8f
20 changed files with 1392 additions and 0 deletions

4
.gitignore vendored
View File

@ -154,3 +154,7 @@ Network Trash Folder
Temporary Items
.apdisk
# Ignore models
models/*
!models/.keep

220
cmd/decoder/main.go Normal file
View File

@ -0,0 +1,220 @@
package main
import (
"bytes"
"encoding/json"
"fmt"
"log"
"math/rand/v2"
"os"
"path"
"github.com/gabriel-vasile/mimetype"
"github.com/qmuntal/gltf"
"github.com/qmuntal/gltf/modeler"
"git.influ.su/artmares/digglestool/internal/services/exporter"
"git.influ.su/artmares/digglestool/pkg/threedb"
)
const (
outputPath = "./models"
texturesCacheFile = "./textures.cache"
)
func main() {
texturesPath := os.Getenv("TEXTURES_PATH")
if texturesPath == "" {
log.Println("TEXTURES_PATH environment variable not set")
return
}
inputPath := "./data"
inputModel := "produktionsstaetten"
f, err := os.Open(path.Join(inputPath, inputModel+".3db"))
if err != nil {
log.Fatal(err)
}
defer f.Close()
model := threedb.Model{}
err = threedb.NewDecoder(f).Decode(&model)
if err != nil {
log.Println(err)
return
}
//log.Printf("DB Version: %v\n", model.DBVersion)
//log.Printf("Name: %v\n", model.Name)
//log.Printf("Materials: %d\n", len(model.Materials))
//for _, material := range model.Materials {
// log.Printf("\t: %v\n", material)
//}
//log.Printf("Meshes: %d\n", len(model.Meshes))
//log.Printf("Objects: %+v\n", model.Objects)
//log.Printf("Animations: %d\n", len(model.Animations))
//for _, animation := range model.Animations {
// log.Printf("\t: %+v\n", animation)
//}
//log.Printf("Triangles: %d\n", len(model.Triangles))
//log.Printf("Texture Coordinates: %d\n", len(model.TextureCoordinates))
//log.Printf("Points: %d\n", len(model.Points))
//log.Printf("Brightness: %d\n", len(model.Brightness))
logFile, err := os.OpenFile(fmt.Sprintf("./%s.json", inputModel), os.O_TRUNC|os.O_CREATE|os.O_WRONLY, 0644)
if err != nil {
log.Println(err)
return
}
defer logFile.Close()
enc := json.NewEncoder(logFile)
enc.SetIndent("", " ")
if err = enc.Encode(model); err != nil {
log.Println(err)
return
}
export := exporter.NewExporter(
exporter.WithTexturesBasePath(texturesPath),
exporter.WithOnlyBaseMesh(),
exporter.WithAnimation(),
)
if err = export.Export(outputPath, &model); err != nil {
log.Println(err)
return
}
//if err = export(&model, inputModel, texturesCache); err != nil {
// log.Println(err)
// return
//}
}
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)},
//}}
for _, material := range model.Materials {
data, ok := binaryCache[material.Name]
if !ok {
texturePath, ok := cache[material.Name]
if !ok {
log.Printf("Invalid texture cache %q", material.Name)
continue
return fmt.Errorf("invalid texture cache %q", material.Name)
}
data, err := os.ReadFile(texturePath)
if err != nil {
return err
}
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 {
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]
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),
})
}
var nodes []int
for index := range doc.Nodes {
nodes = append(nodes, index)
}
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 {
return rand.IntN(max-min) + min
}

45
cmd/testgltf/main.go Normal file
View File

@ -0,0 +1,45 @@
package main
import (
"log"
"github.com/qmuntal/gltf"
)
func main() {
doc := &gltf.Document{
Accessors: []*gltf.Accessor{
{BufferView: gltf.Index(0), ComponentType: gltf.ComponentUshort, Count: 36, Type: gltf.AccessorScalar},
{BufferView: gltf.Index(1), ComponentType: gltf.ComponentFloat, Count: 24, Max: []float64{0.5, 0.5, 0.5}, Min: []float64{-0.5, -0.5, -0.5}, Type: gltf.AccessorVec3},
{BufferView: gltf.Index(2), ComponentType: gltf.ComponentFloat, Count: 24, Type: gltf.AccessorVec3},
{BufferView: gltf.Index(3), ComponentType: gltf.ComponentFloat, Count: 24, Type: gltf.AccessorVec4},
{BufferView: gltf.Index(4), ComponentType: gltf.ComponentFloat, Count: 24, Type: gltf.AccessorVec2},
},
Asset: gltf.Asset{Version: "2.0", Generator: "FBX2glTF"},
BufferViews: []*gltf.BufferView{
{Buffer: 0, ByteLength: 72, ByteOffset: 0, Target: gltf.TargetElementArrayBuffer},
{Buffer: 0, ByteLength: 288, ByteOffset: 72, Target: gltf.TargetArrayBuffer},
{Buffer: 0, ByteLength: 288, ByteOffset: 360, Target: gltf.TargetArrayBuffer},
{Buffer: 0, ByteLength: 384, ByteOffset: 648, Target: gltf.TargetArrayBuffer},
{Buffer: 0, ByteLength: 192, ByteOffset: 1032, Target: gltf.TargetArrayBuffer},
},
Buffers: []*gltf.Buffer{{ByteLength: 1224, Data: []byte{97, 110, 121, 32, 99, 97, 114, 110, 97, 108, 32, 112, 108, 101, 97, 115}}},
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)},
}},
Meshes: []*gltf.Mesh{{Name: "Cube", Primitives: []*gltf.Primitive{{Indices: gltf.Index(0), Material: gltf.Index(0), Mode: gltf.PrimitiveTriangles, Attributes: gltf.PrimitiveAttributes{gltf.POSITION: 1, gltf.COLOR_0: 3, gltf.NORMAL: 2, gltf.TEXCOORD_0: 4}}}}},
Nodes: []*gltf.Node{
{Name: "RootNode", Children: []int{1, 2, 3}},
{Name: "Mesh"},
{Name: "Cube", Mesh: gltf.Index(0)},
{Name: "Texture Group"},
},
Samplers: []*gltf.Sampler{{WrapS: gltf.WrapRepeat, WrapT: gltf.WrapRepeat}},
Scene: gltf.Index(0),
Scenes: []*gltf.Scene{{Name: "Root Scene", Nodes: []int{0}}},
}
if err := gltf.Save(doc, "./test.gltf"); err != nil {
log.Println(err)
}
}

12
go.mod
View File

@ -1,3 +1,15 @@
module git.influ.su/artmares/digglestool
go 1.22.3
require github.com/stretchr/testify v1.9.0
require (
github.com/davecgh/go-spew v1.1.1 // indirect
github.com/ftrvxmtrx/tga v0.0.0-20150524081124-bd8e8d5be13a // indirect
github.com/gabriel-vasile/mimetype v1.4.4 // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect
github.com/qmuntal/gltf v0.26.1-0.20240704075444-782e57e021e1 // indirect
golang.org/x/net v0.25.0 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
)

20
go.sum Normal file
View File

@ -0,0 +1,20 @@
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/ftrvxmtrx/tga v0.0.0-20150524081124-bd8e8d5be13a h1:eSqaRmdlZ9JsJ7JuWfDr3ym3monToXRczohBOL+heVQ=
github.com/ftrvxmtrx/tga v0.0.0-20150524081124-bd8e8d5be13a/go.mod h1:US5WvgEHtG+BvWNNs6gk937h0QL2g2x+r7RH8m3g80Y=
github.com/gabriel-vasile/mimetype v1.4.4 h1:QjV6pZ7/XZ7ryI2KuyeEDE8wnh7fHP9YnQy+R0LnH8I=
github.com/gabriel-vasile/mimetype v1.4.4/go.mod h1:JwLei5XPtWdGiMFB5Pjle1oEeoSeEuJfJE+TtfvdB/s=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/qmuntal/gltf v0.26.0 h1:geVxZPBJ43f0ouLyseqMuzgKG6m6DkNH+gI3A63PFas=
github.com/qmuntal/gltf v0.26.0/go.mod h1:YoXZOt0Nc0kIfSKOLZIRoV4FycdC+GzE+3JgiAGYoMs=
github.com/qmuntal/gltf v0.26.1-0.20240704075444-782e57e021e1 h1:E2QnY6J3DP8tSyAE28BiGcbPFpbIDDtvq3zB6KyBVSI=
github.com/qmuntal/gltf v0.26.1-0.20240704075444-782e57e021e1/go.mod h1:YoXZOt0Nc0kIfSKOLZIRoV4FycdC+GzE+3JgiAGYoMs=
github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg=
github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
golang.org/x/net v0.25.0 h1:d/OCCoBEUq33pjydKrGQhw7IlUPI2Oylr+8qLx49kac=
golang.org/x/net v0.25.0/go.mod h1:JkAGAh7GEvH74S6FOH42FLoXpXbE/aqXSrIQjXgsiwM=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=

View File

@ -0,0 +1,21 @@
package helpers
import (
"bytes"
"sync"
)
var pool sync.Pool
func NewBuffer() (*bytes.Buffer, func(*bytes.Buffer)) {
if v := pool.Get(); v != nil {
b, _ := v.(*bytes.Buffer)
b.Reset()
return b, putter
}
return &bytes.Buffer{}, putter
}
func putter(buff *bytes.Buffer) {
pool.Put(buff)
}

View File

@ -0,0 +1,41 @@
package exporter
import (
"bytes"
"strings"
"sync"
)
type Cache[T any] struct {
data map[string]T
mutex sync.RWMutex
}
func NewCache[T any]() *Cache[T] {
return &Cache[T]{
data: make(map[string]T),
}
}
func (c *Cache[T]) Set(key string, value T) {
key = strings.ToLower(key)
c.mutex.Lock()
defer c.mutex.Unlock()
c.data[key] = value
}
func (c *Cache[T]) Get(key string) (T, bool) {
key = strings.ToLower(key)
c.mutex.RLock()
defer c.mutex.RUnlock()
value, ok := c.data[key]
return value, ok
}
type CacheItem struct {
Path string
Size int64
Bytes *bytes.Buffer
}

View File

@ -0,0 +1,229 @@
package exporter
import (
"errors"
"fmt"
_ "image/png"
"log"
"os"
"path"
"path/filepath"
"strings"
_ "github.com/ftrvxmtrx/tga"
"github.com/qmuntal/gltf"
"git.influ.su/artmares/digglestool/pkg/threedb"
)
type Exporter struct {
onlyBaseMesh bool
animation bool
texturesBasePath *string
cache *Cache[*CacheItem]
}
type Option func(*Exporter)
func WithOnlyBaseMesh() Option {
return func(e *Exporter) {
e.onlyBaseMesh = true
}
}
func WithTexturesBasePath(basePath string) Option {
return func(e *Exporter) {
if basePath != "" {
e.texturesBasePath = &basePath
}
}
}
func WithAnimation() Option {
return func(e *Exporter) {
e.animation = true
}
}
func NewExporter(options ...Option) *Exporter {
e := &Exporter{
cache: NewCache[*CacheItem](),
}
for _, option := range options {
option(e)
}
return e
}
func (e *Exporter) Export(basePath string, model *threedb.Model) error {
if model == nil {
return errors.New("model is nil")
}
meshesLen := len(model.Meshes)
if meshesLen == 0 {
return errors.New("no meshes to export")
}
if err := e.prepareTextures(); err != nil {
return err
}
if e.onlyBaseMesh {
log.Printf("Export Base Model: %s", model.Name)
doc, err := e.generate(model, 0)
if err != nil {
return err
}
if err = gltf.Save(doc, path.Join(basePath, fmt.Sprintf("%s-base-model.gltf", model.Name))); err != nil {
return err
}
}
if e.animation {
dirPath := path.Join(basePath, "animation", model.Name)
if _, err := os.Stat(dirPath); os.IsNotExist(err) {
if err = os.MkdirAll(dirPath, os.ModeDir); err != nil {
return err
}
}
log.Printf("Export Animation to %s\n", dirPath)
length := len(model.Animations)
for index, animation := range model.Animations {
log.Printf("Export progress: %d/%d\r", index+1, length)
indexes := make([]int, len(animation.MeshIndexes))
for _, index := range animation.MeshIndexes {
indexes = append(indexes, int(index))
}
doc, err := e.generate(model, indexes...)
if err != nil {
return err
}
if err = gltf.Save(doc, path.Join(dirPath, fmt.Sprintf("%s-%s.gltf", model.Name, animation.Name))); err != nil {
return err
}
}
}
return nil
}
func (e *Exporter) generate(model *threedb.Model, meshIndexes ...int) (*gltf.Document, error) {
return new(Model).Generate(model, e.cache, meshIndexes...)
//var err error
//doc := gltf.NewDocument()
//log.Println("Generate", meshIndexes)
//materialsMap := make(map[uint16]int)
//for _, meshIndex := range meshIndexes {
// mesh := model.Meshes[meshIndex]
// var nodeIndexes []int
// for index, meshLink := range mesh.Links {
// points := model.Points[meshLink.Points]
// var vertices [][3]float32
// for _, point := range points {
// vec := point.Transform(100)
// vec = vec.Normalize()
// vertices = append(vertices, vec)
// }
// verticesIndex := modeler.WriteAccessor(doc, gltf.TargetElementArrayBuffer, vertices)
// textureCoordinates := model.TextureCoordinates[meshLink.TextureCoordinates]
// var textureCoord [][2]float32
// for _, txc := range textureCoordinates {
// textureCoord = append(textureCoord, txc)
// }
// textureCoordIndex := modeler.WriteAccessor(doc, gltf.TargetArrayBuffer, textureCoord)
// trianglesIndex := modeler.WriteAccessor(doc, gltf.TargetArrayBuffer, model.Triangles[meshLink.Triangles])
// materialIndex, ok := materialsMap[meshLink.Material]
// if !ok {
// material := model.Materials[meshLink.Material]
// log.Println("Material", material.Name)
// data := e.cache.Bytes(material.Name)
// if data == nil {
// var f *os.File
// if f, err = os.Open(e.cache.Path(material.Name)); err != nil {
// return nil, err
// }
// img, _, err := image.Decode(f)
// if err != nil {
// return nil, err
// }
// buff, putter := helpers.NewBuffer()
// if err = png.Encode(buff, img); err != nil {
// return nil, err
// }
// data = buff.Bytes()
// log.Println(material.Name, len(data))
// putter(buff)
// e.cache.AddBytes(material.Name, data)
// }
// log.Println(material.Name, len(data))
// var imageIndex int
// if imageIndex, err = modeler.WriteImage(doc, material.Name, "image/png", bytes.NewBuffer(data)); err != nil {
// return nil, err
// }
// doc.Textures = append(doc.Textures, &gltf.Texture{
// Name: material.Name,
// Sampler: gltf.Index(0),
// Source: gltf.Index(imageIndex),
// })
// materialIndex = len(doc.Materials)
// doc.Materials = append(doc.Materials, &gltf.Material{
// Name: material.Name,
// AlphaMode: gltf.AlphaOpaque,
// AlphaCutoff: gltf.Float(0.5),
// PBRMetallicRoughness: &gltf.PBRMetallicRoughness{
// BaseColorTexture: &gltf.TextureInfo{Index: imageIndex},
// BaseColorFactor: &[4]float64{0.8, 0.8, 0.8, 1},
// MetallicFactor: gltf.Float(0.1),
// RoughnessFactor: gltf.Float(0.99),
// },
// })
// materialsMap[meshLink.Material] = materialIndex
// }
// log.Println("materialIndex", materialIndex)
// doc.Meshes = append(doc.Meshes, &gltf.Mesh{
// Name: fmt.Sprintf("%s-%d", model.Name, index),
// Primitives: []*gltf.Primitive{{
// Attributes: gltf.PrimitiveAttributes{
// gltf.POSITION: verticesIndex,
// gltf.TEXCOORD_0: textureCoordIndex,
// },
// Indices: gltf.Index(trianglesIndex),
// Material: gltf.Index(materialIndex),
// }},
// })
// nodeIndexes = append(nodeIndexes, len(doc.Nodes))
// doc.Nodes = append(doc.Nodes, &gltf.Node{
// Mesh: gltf.Index(len(doc.Meshes) - 1),
// })
// }
// doc.Scenes = []*gltf.Scene{{
// Name: "Root Scene",
// Nodes: nodeIndexes,
// }}
// doc.Samplers = []*gltf.Sampler{{}}
//}
//return doc, nil
}
func (e *Exporter) prepareTextures() error {
if e.texturesBasePath == nil {
return nil
}
if err := filepath.Walk(*e.texturesBasePath, func(pathFile string, info os.FileInfo, err error) error {
if err != nil {
return err
}
if strings.HasSuffix(info.Name(), ".tga") {
key := strings.TrimSuffix(info.Name(), ".tga")
item, ok := e.cache.Get(key)
if !ok {
item = &CacheItem{}
}
if info.Size() > item.Size {
item.Path = pathFile
}
e.cache.Set(key, item)
}
return nil
}); err != nil {
return err
}
return nil
}

View File

@ -0,0 +1,141 @@
package exporter
import (
"bytes"
"fmt"
"image"
"image/png"
"log"
"os"
_ "github.com/ftrvxmtrx/tga"
"github.com/qmuntal/gltf"
"github.com/qmuntal/gltf/modeler"
"git.influ.su/artmares/digglestool/pkg/threedb"
)
type Model struct {
doc *gltf.Document
materialMap map[uint16]int
}
func (m *Model) Generate(model *threedb.Model, cache *Cache[*CacheItem], meshIndexes ...int) (*gltf.Document, error) {
m.doc = gltf.NewDocument()
m.doc.Scenes = []*gltf.Scene{}
m.materialMap = make(map[uint16]int)
log.Println("Generate use mesh indexes", len(meshIndexes))
for i, meshIndex := range meshIndexes {
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
}
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{{
Attributes: gltf.PrimitiveAttributes{
gltf.POSITION: verticesIndex,
gltf.TEXCOORD_0: textureCoordIndex,
},
Indices: gltf.Index(trianglesIndex),
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
}
func (m *Model) generateVertices(vectors []threedb.Vector) int {
var vertices [][3]float32
for _, vector := range vectors {
vector = vector.Transform(100)
vertices = append(vertices, vector.Normalize())
}
return modeler.WriteAccessor(m.doc, gltf.TargetElementArrayBuffer, vertices)
}
func (m *Model) generateTextureCoordinate(coordinates []threedb.Coordinate) int {
var coords [][2]float32
for _, coordinate := range coordinates {
coords = append(coords, coordinate)
}
return modeler.WriteAccessor(m.doc, gltf.TargetArrayBuffer, coords)
}
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
}
//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
}
defer f.Close()
img, _, err := image.Decode(f)
if err != nil {
return 0, err
}
item.Bytes = &bytes.Buffer{}
if err = png.Encode(item.Bytes, img); err != nil {
return 0, err
}
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
}
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,
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),
},
})
m.materialMap[meshMaterialIndex] = materialIndex
}
//log.Println(logger.String())
return materialIndex, nil
}

0
models/.keep Normal file
View File

11
pkg/threedb/animation.go Normal file
View File

@ -0,0 +1,11 @@
package threedb
type Animation struct {
Name string
MeshIndexes []uint32
Unknown, Unknown1, Unknown2 uint16
//Unknown1 float32
Unknown3 string
MoveVector *Vector
RotationVector *Vector
}

12
pkg/threedb/coordinate.go Normal file
View File

@ -0,0 +1,12 @@
package threedb
import "fmt"
type Coordinate [2]float32
func (m Coordinate) X() float32 { return m[0] }
func (m Coordinate) Y() float32 { return m[1] }
func (m *Coordinate) Set(x, y float32) { m[0], m[1] = x, y }
func (m *Coordinate) SetX(x float32) { m[0] = x }
func (m *Coordinate) SetY(y float32) { m[1] = y }
func (m Coordinate) String() string { return fmt.Sprintf("x:%f,y:%f", m[0], m[1]) }

View File

@ -0,0 +1,14 @@
package threedb
import (
"testing"
"github.com/stretchr/testify/require"
)
func TestCoordinate_Set(t *testing.T) {
expect := Coordinate{1, 1}
coord := Coordinate{}
coord.Set(1, 1)
require.Equal(t, expect, coord)
}

490
pkg/threedb/decoder.go Normal file
View File

@ -0,0 +1,490 @@
package threedb
import (
"encoding/binary"
"errors"
"io"
"log"
)
type Decoder struct {
reader io.ReadSeeker
}
func NewDecoder(reader io.ReadSeeker) *Decoder {
return &Decoder{reader: reader}
}
func (dec *Decoder) Decode(model *Model) error {
if model == nil {
return errors.New("model is nil")
}
var err error
if model.DBVersion, err = dec.readString(); err != nil {
return err
}
if model.Name, err = dec.readString(); err != nil {
return err
}
if err = dec.readMaterials(model); err != nil {
return err
}
if err = dec.readMeshes(model); err != nil {
return err
}
if err = dec.readObjects(model); err != nil {
return err
}
if err = dec.readAnimations(model); err != nil {
return err
}
if err = dec.readShadows(model); err != nil {
return err
}
if err = dec.readCubeMaps(model); err != nil {
return err
}
if err = dec.readData(model); err != nil {
return err
}
return nil
}
func (dec *Decoder) seek(offset int64) (err error) {
_, err = dec.reader.Seek(offset, io.SeekCurrent)
return
}
func (dec *Decoder) read(dst any) error {
return binary.Read(dec.reader, binary.LittleEndian, dst)
}
func (dec *Decoder) readUInt8() (result uint8, err error) {
err = dec.read(&result)
return
}
func (dec *Decoder) readUInt16() (result uint16, err error) {
err = dec.read(&result)
return
}
func (dec *Decoder) readUInt32() (result uint32, err error) {
err = dec.read(&result)
return
}
func (dec *Decoder) readFloat32() (result float32, err error) {
err = dec.read(&result)
return
}
func (dec *Decoder) readString() (string, error) {
length, err := dec.readUInt32()
if err != nil {
return "", err
}
buf := make([]byte, length)
if err = dec.read(&buf); err != nil {
return "", err
}
return string(buf), nil
}
func (dec *Decoder) readVector() (*Vector, error) {
var vec Vector
var err error
if vec[0], err = dec.readFloat32(); err != nil {
return nil, err
}
if vec[1], err = dec.readFloat32(); err != nil {
return nil, err
}
if vec[2], err = dec.readFloat32(); err != nil {
return nil, err
}
return &vec, nil
}
func (dec *Decoder) readMaterials(model *Model) error {
materialCount, err := dec.readUInt16()
if err != nil {
return err
}
count := int(materialCount)
for i := 0; i < count; i++ {
material := Material{}
if material.Name, err = dec.readString(); err != nil {
return err
}
if material.Path, err = dec.readString(); err != nil {
return err
}
if material.Unknown, err = dec.readUInt32(); err != nil {
return err
}
model.Materials = append(model.Materials, material)
}
return nil
}
func (dec *Decoder) readMeshes(model *Model) error {
meshCount, err := dec.readUInt32()
if err != nil {
return err
}
count := int(meshCount)
for i := 0; i < count; i++ {
mesh := Mesh{}
if err = dec.readMeshLink(&mesh); err != nil {
return err
}
if mesh.Vector1, err = dec.readVector(); err != nil {
return err
}
if mesh.Vector2, err = dec.readVector(); err != nil {
return err
}
_ = dec.seek(0x80)
if mesh.Shadow, err = dec.readUInt16(); err != nil {
return err
} // Mesh Shadow Index ?
_ = dec.seek(0x30)
if mesh.CMap, err = dec.readUInt16(); err != nil {
return err
} // Mesh CMap Index ?
model.Meshes = append(model.Meshes, mesh)
}
return nil
}
func (dec *Decoder) readMeshLink(mesh *Mesh) error {
meshLinkCount, err := dec.readUInt16()
if err != nil {
return err
}
count := int(meshLinkCount)
for i := 0; i < count; i++ {
meshLink := MeshLink{}
if meshLink.Material, err = dec.readUInt16(); err != nil {
return err
}
if meshLink.Unknown, err = dec.readUInt16(); err != nil {
return err
}
if meshLink.Triangles, err = dec.readUInt16(); err != nil {
return err
}
if meshLink.TextureCoordinates, err = dec.readUInt16(); err != nil {
return err
}
if meshLink.Points, err = dec.readUInt16(); err != nil {
return err
}
if meshLink.Brightness, err = dec.readUInt16(); err != nil {
return err
}
mesh.Links = append(mesh.Links, meshLink)
}
return nil
}
func (dec *Decoder) readObjects(model *Model) error {
keyValuePairCount, err := dec.readUInt16()
if err != nil {
return err
}
count := int(keyValuePairCount)
if count > 0 && model.Objects == nil {
model.Objects = make(map[string][]uint32)
}
for i := 0; i < count; i++ {
var key string
if key, err = dec.readString(); err != nil {
return err
}
var objectCount uint16
if objectCount, err = dec.readUInt16(); err != nil {
return err
}
model.Objects[key] = make([]uint32, objectCount)
for j := 0; j < int(objectCount); j++ {
var n uint32
if n, err = dec.readUInt32(); err != nil {
return err
}
model.Objects[key] = append(model.Objects[key], n)
}
}
return nil
}
func (dec *Decoder) readAnimations(model *Model) error {
animationCount, err := dec.readUInt16()
if err != nil {
return err
}
count := int(animationCount)
for i := 0; i < count; i++ {
animation := Animation{}
if animation.Name, err = dec.readString(); err != nil {
return err
}
var meshIndexesCount uint16
if meshIndexesCount, err = dec.readUInt16(); err != nil {
return err
}
animation.MeshIndexes = make([]uint32, meshIndexesCount)
for j := 0; j < int(meshIndexesCount); j++ {
var n uint32
if n, err = dec.readUInt32(); err != nil {
return err
}
animation.MeshIndexes[j] = n
}
if animation.Unknown, err = dec.readUInt16(); err != nil {
return err
}
if animation.Unknown1, err = dec.readUInt16(); err != nil {
return err
}
if animation.Unknown2, err = dec.readUInt16(); err != nil {
return err
}
//if animation.Unknown1, err = dec.readFloat32(); err != nil {
// return err
//}
if animation.Unknown3, err = dec.readString(); err != nil {
return err
}
if animation.MoveVector, err = dec.readVector(); err != nil {
return err
}
if animation.RotationVector, err = dec.readVector(); err != nil {
return err
}
model.Animations = append(model.Animations, animation)
}
return nil
}
func (dec *Decoder) readShadows(_ *Model) error {
shadowCount, err := dec.readUInt16()
if err != nil {
return err
}
count := int(shadowCount)
for i := 0; i < count; i++ {
// Skip // TODO: возможно это картинка 32х32
_ = dec.seek(32 * 32)
}
return nil
}
func (dec *Decoder) readCubeMaps(_ *Model) error {
cubeMapCount, err := dec.readUInt16()
if err != nil {
return err
}
count := int(cubeMapCount)
for i := 0; i < count; i++ {
var width, height uint16
if width, err = dec.readUInt16(); err != nil {
return err
}
if height, err = dec.readUInt16(); err != nil {
return err
}
_, _ = dec.readUInt16()
_, _ = dec.readUInt16()
// Skip pixel data
_ = dec.seek(int64(width * height))
}
return nil
}
func (dec *Decoder) readData(model *Model) error {
var (
triangleCount uint16
trianglesCounts []uint16
textureCoordCount uint16
textureCoordCounts []uint16
pointCount uint16
pointCounts []uint16
brightnessCount uint16
brightnessCounts []uint16
unknownCount uint32
cnt uint16
err error
)
if triangleCount, err = dec.readUInt16(); err != nil {
return err
}
if textureCoordCount, err = dec.readUInt16(); err != nil {
return err
}
if pointCount, err = dec.readUInt16(); err != nil {
return err
}
if brightnessCount, err = dec.readUInt16(); err != nil {
return err
}
if unknownCount, err = dec.readUInt32(); err != nil {
return err
}
log.Println("unknownCount:", unknownCount)
for i := 0; i < int(triangleCount); i++ {
if cnt, err = dec.readUInt16(); err != nil {
return err
}
trianglesCounts = append(trianglesCounts, cnt)
}
for i := 0; i < int(textureCoordCount); i++ {
if cnt, err = dec.readUInt16(); err != nil {
return err
}
textureCoordCounts = append(textureCoordCounts, cnt)
}
for i := 0; i < int(pointCount); i++ {
if cnt, err = dec.readUInt16(); err != nil {
return err
}
pointCounts = append(pointCounts, cnt)
}
for i := 0; i < int(brightnessCount); i++ {
if cnt, err = dec.readUInt16(); err != nil {
return err
}
brightnessCounts = append(brightnessCounts, cnt)
}
for i := 0; i < int(unknownCount); i++ {
//x, err := dec.readFloat32()
//if err != nil {
// return err
//}
//y, err := dec.readFloat32()
//if err != nil {
// return err
//}
//vec, err := dec.readVector()
//if err != nil {
// return err
//}
//log.Println(Coordinate{x, y}, vec)
//if cnt, err = dec.readUInt16(); err != nil {
// return err
//}
//log.Println("cnt", cnt)
//buff := make([]byte, 20)
//err = dec.read(&buff)
//if err != nil {
// return err
//}
//log.Println(buff)
//vec, err := dec.readVector()
//if err != nil {
// return err
//}
//log.Println(vec)
_ = dec.seek(20)
}
if err = dec.readTriangles(model, int(triangleCount), trianglesCounts); err != nil {
return err
}
if err = dec.readTextureCoordinates(model, int(textureCoordCount), textureCoordCounts); err != nil {
return err
}
if err = dec.readPoint(model, int(pointCount), pointCounts); err != nil {
return err
}
if err = dec.readBrightness(model, int(brightnessCount), brightnessCounts); err != nil {
return err
}
return nil
}
func (dec *Decoder) readTriangles(model *Model, count int, counts []uint16) error {
for i := 0; i < count; i++ {
cnt := int(counts[i])
var triangles []uint16
for j := 0; j < cnt; j++ {
n, err := dec.readUInt16()
if err != nil {
return err
}
triangles = append(triangles, n)
}
model.Triangles = append(model.Triangles, triangles)
}
return nil
}
func (dec *Decoder) readTextureCoordinates(model *Model, count int, counts []uint16) error {
for i := 0; i < count; i++ {
cnt := int(counts[i])
var cords []Coordinate
for j := 0; j < cnt; j++ {
cord := Coordinate{}
u, err := dec.readFloat32()
if err != nil {
return err
}
v, err := dec.readFloat32()
if err != nil {
return err
}
cord.Set(u, v)
cords = append(cords, cord)
}
model.TextureCoordinates = append(model.TextureCoordinates, cords)
}
return nil
}
func (dec *Decoder) readPoint(model *Model, count int, counts []uint16) error {
for i := 0; i < count; i++ {
cnt := int(counts[i])
var vectors []Vector
for j := 0; j < cnt; j++ {
vec := Vector{}
ux, err := dec.readUInt16()
if err != nil {
return err
}
uy, err := dec.readUInt16()
if err != nil {
return err
}
uz, err := dec.readUInt16()
if err != nil {
return err
}
vec.Set(float32(ux)/float32(0xffff), float32(uy)/float32(0xffff), float32(uz)/float32(0xffff))
vectors = append(vectors, vec)
}
model.Points = append(model.Points, vectors)
}
return nil
}
func (dec *Decoder) readBrightness(model *Model, count int, counts []uint16) error {
for i := 0; i < count; i++ {
cnt := int(counts[i])
var brightness []byte
for j := 0; j < cnt; j++ {
b, err := dec.readUInt8()
if err != nil {
return err
}
brightness = append(brightness, b)
}
model.Brightness = append(model.Brightness, brightness)
}
return nil
}

6
pkg/threedb/material.go Normal file
View File

@ -0,0 +1,6 @@
package threedb
type Material struct {
Name, Path string
Unknown uint32
}

9
pkg/threedb/mesh.go Normal file
View File

@ -0,0 +1,9 @@
package threedb
type Mesh struct {
Links []MeshLink
Vector1 *Vector
Vector2 *Vector
Shadow uint16
CMap uint16
}

10
pkg/threedb/meshlink.go Normal file
View File

@ -0,0 +1,10 @@
package threedb
type MeshLink struct {
Material uint16
Triangles uint16
TextureCoordinates uint16
Points uint16
Brightness uint16
Unknown uint16
}

58
pkg/threedb/model.go Normal file
View File

@ -0,0 +1,58 @@
package threedb
import "encoding/json"
type Model struct {
DBVersion, Name string
Materials []Material
Meshes []Mesh
Objects map[string][]uint32
Animations []Animation
Triangles [][]uint16
TextureCoordinates [][]Coordinate
Points [][]Vector
Brightness [][]byte
view ModelView
}
type ModelView interface {
Prepare(*Model)
}
func (m Model) MarshalJSON() ([]byte, error) {
if m.view == nil {
m.view = &DefaultModelView{}
}
m.view.Prepare(&m)
return json.Marshal(m.view)
}
type DefaultModelView struct {
DBVersion, Name string
MeshesCount int
Meshes []int
FirstMesh Mesh
Materials []Material
Animations []Animation
Triangles int
TextureCoordinates int
Points int
Brightness int
}
func (v *DefaultModelView) Prepare(m *Model) {
v.DBVersion = m.DBVersion
v.Name = m.Name
v.MeshesCount = len(m.Meshes)
for _, mesh := range m.Meshes {
v.Meshes = append(v.Meshes, len(mesh.Links))
}
v.FirstMesh = m.Meshes[0]
v.Materials = m.Materials
v.Animations = m.Animations
v.Triangles = len(m.Triangles)
v.TextureCoordinates = len(m.TextureCoordinates)
v.Points = len(m.Points)
v.Brightness = len(m.Brightness)
}

32
pkg/threedb/vector.go Normal file
View File

@ -0,0 +1,32 @@
package threedb
import (
"fmt"
"slices"
)
type Vector [3]float32
func (m Vector) X() float32 { return m[0] }
func (m Vector) Y() float32 { return m[1] }
func (m Vector) Z() float32 { return m[2] }
func (m *Vector) Set(x, y, z float32) { m[0], m[1], m[2] = x, y, z }
func (m *Vector) SetX(x float32) { m[0] = x }
func (m *Vector) SetY(y float32) { m[1] = y }
func (m *Vector) SetZ(z float32) { m[2] = z }
func (m Vector) String() string { return fmt.Sprintf("x:%f,y:%f,z:%f", m[0], m[1], m[2]) }
func (m Vector) Transform(scale float32) Vector {
return Vector{(m[0] - .5) * scale, (m[1] - .5) * scale, (m[2] - .5) * scale}
}
func (m Vector) Normalize() Vector {
return Vector{m[0] * -1, m[1] * -1, m[2]}
}
func (m Vector) Min() float32 { return slices.Min([]float32{m[0], m[1], m[2]}) }
func (m Vector) Max() float32 { return slices.Max([]float32{m[0], m[1], m[2]}) }
func (m Vector) MarshalText() ([]byte, error) {
return []byte(m.String()), nil
}

View File

@ -0,0 +1,17 @@
package threedb
import (
"fmt"
"testing"
)
func TestVector_Array(t *testing.T) {
var vecs [][3]float32
vecs = append(vecs, Vector{1.0, 2.0, 3.0})
vecs = append(vecs, Vector{1.0, 2.0, 3.0})
vecs = append(vecs, Vector{1.0, 2.0, 3.0})
vecs = append(vecs, Vector{1.0, 2.0, 3.0})
fmt.Println(vecs)
vec := Vector{1.0, 2.0, 3.0}
fmt.Println([3]float32(vec))
}