feat: update AI
This commit is contained in:
parent
4b40610cfd
commit
907dea02d9
2
.gitignore
vendored
2
.gitignore
vendored
@ -157,4 +157,6 @@ Temporary Items
|
|||||||
# Ignore models
|
# Ignore models
|
||||||
models/*
|
models/*
|
||||||
!models/.keep
|
!models/.keep
|
||||||
|
data
|
||||||
|
textures
|
||||||
|
|
||||||
|
|||||||
@ -4,7 +4,6 @@ import (
|
|||||||
"bytes"
|
"bytes"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
"log"
|
|
||||||
"math/rand/v2"
|
"math/rand/v2"
|
||||||
"os"
|
"os"
|
||||||
"path"
|
"path"
|
||||||
@ -13,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/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"
|
||||||
)
|
)
|
||||||
@ -26,7 +26,7 @@ const (
|
|||||||
func main() {
|
func main() {
|
||||||
texturesPath := os.Getenv("TEXTURES_PATH")
|
texturesPath := os.Getenv("TEXTURES_PATH")
|
||||||
if texturesPath == "" {
|
if texturesPath == "" {
|
||||||
log.Println("TEXTURES_PATH environment variable not set")
|
logger.Info("TEXTURES_PATH environment variable not set")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -35,14 +35,14 @@ func main() {
|
|||||||
|
|
||||||
f, err := os.Open(path.Join(inputPath, inputModel+".3db"))
|
f, err := os.Open(path.Join(inputPath, inputModel+".3db"))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatal(err)
|
logger.Fatal(err)
|
||||||
}
|
}
|
||||||
defer f.Close()
|
defer f.Close()
|
||||||
|
|
||||||
model := threedb.Model{}
|
model := threedb.Model{}
|
||||||
err = threedb.NewDecoder(f).Decode(&model)
|
err = threedb.NewDecoder(f).Decode(&model)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Println(err)
|
logger.Error(err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
//log.Printf("DB Version: %v\n", model.DBVersion)
|
//log.Printf("DB Version: %v\n", model.DBVersion)
|
||||||
@ -64,14 +64,14 @@ func main() {
|
|||||||
|
|
||||||
logFile, err := os.OpenFile(fmt.Sprintf("./%s.json", inputModel), os.O_TRUNC|os.O_CREATE|os.O_WRONLY, 0644)
|
logFile, err := os.OpenFile(fmt.Sprintf("./%s.json", inputModel), os.O_TRUNC|os.O_CREATE|os.O_WRONLY, 0644)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Println(err)
|
logger.Error(err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
defer logFile.Close()
|
defer logFile.Close()
|
||||||
enc := json.NewEncoder(logFile)
|
enc := json.NewEncoder(logFile)
|
||||||
enc.SetIndent("", " ")
|
enc.SetIndent("", " ")
|
||||||
if err = enc.Encode(model); err != nil {
|
if err = enc.Encode(model); err != nil {
|
||||||
log.Println(err)
|
logger.Error(err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -81,7 +81,7 @@ func main() {
|
|||||||
exporter.WithAnimation(),
|
exporter.WithAnimation(),
|
||||||
)
|
)
|
||||||
if err = export.Export(outputPath, &model); err != nil {
|
if err = export.Export(outputPath, &model); err != nil {
|
||||||
log.Println(err)
|
logger.Error(err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -109,7 +109,7 @@ func exportFn(model *threedb.Model, inputModel string, cache map[string]string)
|
|||||||
if !ok {
|
if !ok {
|
||||||
texturePath, ok := cache[material.Name]
|
texturePath, ok := cache[material.Name]
|
||||||
if !ok {
|
if !ok {
|
||||||
log.Printf("Invalid texture cache %q", material.Name)
|
logger.Warnf("Invalid texture cache %q", material.Name)
|
||||||
continue
|
continue
|
||||||
return fmt.Errorf("invalid texture cache %q", material.Name)
|
return fmt.Errorf("invalid texture cache %q", material.Name)
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,9 +1,9 @@
|
|||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"log"
|
|
||||||
|
|
||||||
"github.com/qmuntal/gltf"
|
"github.com/qmuntal/gltf"
|
||||||
|
|
||||||
|
"git.influ.su/artmares/digglestool/internal/pkg/logger"
|
||||||
)
|
)
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
@ -40,6 +40,6 @@ func main() {
|
|||||||
Scenes: []*gltf.Scene{{Name: "Root Scene", Nodes: []int{0}}},
|
Scenes: []*gltf.Scene{{Name: "Root Scene", Nodes: []int{0}}},
|
||||||
}
|
}
|
||||||
if err := gltf.Save(doc, "./test.gltf"); err != nil {
|
if err := gltf.Save(doc, "./test.gltf"); err != nil {
|
||||||
log.Println(err)
|
logger.Error(err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
214
docs/3db_format_specification.md
Normal file
214
docs/3db_format_specification.md
Normal file
@ -0,0 +1,214 @@
|
|||||||
|
# Спецификация формата файла .3db
|
||||||
|
|
||||||
|
## Обзор
|
||||||
|
|
||||||
|
Формат файла .3db используется для хранения трехмерных моделей, включая геометрию, текстуры, анимации и другие данные. Этот документ описывает структуру и формат файла .3db на основе реализации декодера в проекте Digglestool.
|
||||||
|
|
||||||
|
## Общая структура файла
|
||||||
|
|
||||||
|
Файл .3db имеет бинарный формат с порядком байтов little-endian. Файл состоит из нескольких последовательных секций:
|
||||||
|
|
||||||
|
1. Заголовок файла
|
||||||
|
2. Секция материалов
|
||||||
|
3. Секция мешей
|
||||||
|
4. Секция объектов
|
||||||
|
5. Секция анимаций
|
||||||
|
6. Секция теней
|
||||||
|
7. Секция кубических карт
|
||||||
|
8. Секция данных (треугольники, текстурные координаты, точки, яркость)
|
||||||
|
|
||||||
|
## Типы данных
|
||||||
|
|
||||||
|
### Базовые типы
|
||||||
|
|
||||||
|
- **uint8**: 8-битное беззнаковое целое число
|
||||||
|
- **uint16**: 16-битное беззнаковое целое число
|
||||||
|
- **uint32**: 32-битное беззнаковое целое число
|
||||||
|
- **float32**: 32-битное число с плавающей точкой
|
||||||
|
|
||||||
|
### Строки
|
||||||
|
|
||||||
|
Строки хранятся в следующем формате:
|
||||||
|
- **uint32**: длина строки в байтах
|
||||||
|
- **byte[]**: массив байтов, представляющий строку
|
||||||
|
|
||||||
|
### Векторы и координаты
|
||||||
|
|
||||||
|
- **Vector**: массив из трех float32 значений [X, Y, Z]
|
||||||
|
- **Coordinate**: массив из двух float32 значений [U, V]
|
||||||
|
|
||||||
|
## Детальное описание секций
|
||||||
|
|
||||||
|
### 1. Заголовок файла
|
||||||
|
|
||||||
|
Заголовок файла содержит:
|
||||||
|
- **string**: версия базы данных (DBVersion)
|
||||||
|
- **string**: имя модели (Name)
|
||||||
|
|
||||||
|
### 2. Секция материалов
|
||||||
|
|
||||||
|
Секция материалов начинается с:
|
||||||
|
- **uint16**: количество материалов
|
||||||
|
|
||||||
|
За этим следуют записи материалов, каждая из которых содержит:
|
||||||
|
- **string**: имя материала
|
||||||
|
- **string**: путь к текстуре материала
|
||||||
|
- **uint32**: неизвестное значение
|
||||||
|
|
||||||
|
### 3. Секция мешей
|
||||||
|
|
||||||
|
Секция мешей начинается с:
|
||||||
|
- **uint32**: количество мешей
|
||||||
|
|
||||||
|
За этим следуют записи мешей, каждая из которых содержит:
|
||||||
|
- **Секция связей меша**:
|
||||||
|
- **uint16**: количество связей
|
||||||
|
- Для каждой связи:
|
||||||
|
- **uint16**: индекс материала
|
||||||
|
- **uint16**: неизвестное значение
|
||||||
|
- **uint16**: индекс массива треугольников
|
||||||
|
- **uint16**: индекс массива текстурных координат
|
||||||
|
- **uint16**: индекс массива точек
|
||||||
|
- **uint16**: индекс массива яркости
|
||||||
|
- **Vector**: первый вектор (возможно, позиция или ориентация)
|
||||||
|
- **Vector**: второй вектор (возможно, масштаб или ограничивающий бокс)
|
||||||
|
- **пропуск 0x80 байт**: неиспользуемые данные
|
||||||
|
- **uint16**: индекс тени
|
||||||
|
- **пропуск 0x30 байт**: неиспользуемые данные
|
||||||
|
- **uint16**: индекс кубической карты
|
||||||
|
|
||||||
|
### 4. Секция объектов
|
||||||
|
|
||||||
|
Секция объектов начинается с:
|
||||||
|
- **uint16**: количество пар ключ-значение
|
||||||
|
|
||||||
|
За этим следуют пары ключ-значение, каждая из которых содержит:
|
||||||
|
- **string**: ключ
|
||||||
|
- **uint16**: количество объектов
|
||||||
|
- **uint32[]**: массив индексов объектов
|
||||||
|
|
||||||
|
### 5. Секция анимаций
|
||||||
|
|
||||||
|
Секция анимаций начинается с:
|
||||||
|
- **uint16**: количество анимаций
|
||||||
|
|
||||||
|
За этим следуют записи анимаций, каждая из которых содержит:
|
||||||
|
- **string**: имя анимации
|
||||||
|
- **uint16**: количество индексов мешей
|
||||||
|
- **uint32[]**: массив индексов мешей
|
||||||
|
- **uint16**: неизвестное значение 1
|
||||||
|
- **uint16**: неизвестное значение 2
|
||||||
|
- **uint16**: неизвестное значение 3
|
||||||
|
- **string**: неизвестная строка
|
||||||
|
- **Vector**: вектор перемещения
|
||||||
|
- **Vector**: вектор вращения
|
||||||
|
|
||||||
|
### 6. Секция теней
|
||||||
|
|
||||||
|
Секция теней начинается с:
|
||||||
|
- **uint16**: количество теней
|
||||||
|
|
||||||
|
За этим следуют данные теней, каждая из которых содержит:
|
||||||
|
- **пропуск 32*32 байт**: данные изображения тени (предположительно 32x32 пикселя)
|
||||||
|
|
||||||
|
### 7. Секция кубических карт
|
||||||
|
|
||||||
|
Секция кубических карт начинается с:
|
||||||
|
- **uint16**: количество кубических карт
|
||||||
|
|
||||||
|
За этим следуют записи кубических карт, каждая из которых содержит:
|
||||||
|
- **uint16**: ширина
|
||||||
|
- **uint16**: высота
|
||||||
|
- **uint16**: неизвестное значение 1
|
||||||
|
- **uint16**: неизвестное значение 2
|
||||||
|
- **пропуск width*height байт**: данные пикселей
|
||||||
|
|
||||||
|
### 8. Секция данных
|
||||||
|
|
||||||
|
Секция данных содержит:
|
||||||
|
- **uint16**: количество массивов треугольников
|
||||||
|
- **uint16**: количество массивов текстурных координат
|
||||||
|
- **uint16**: количество массивов точек
|
||||||
|
- **uint16**: количество массивов яркости
|
||||||
|
- **uint32**: неизвестное количество
|
||||||
|
|
||||||
|
Затем следуют размеры массивов:
|
||||||
|
- **uint16[]**: размеры массивов треугольников
|
||||||
|
- **uint16[]**: размеры массивов текстурных координат
|
||||||
|
- **uint16[]**: размеры массивов точек
|
||||||
|
- **uint16[]**: размеры массивов яркости
|
||||||
|
|
||||||
|
Затем следует пропуск неизвестных данных:
|
||||||
|
- **пропуск 20*unknownCount байт**: неизвестные данные
|
||||||
|
|
||||||
|
Затем следуют данные массивов:
|
||||||
|
- **Массивы треугольников**: для каждого массива:
|
||||||
|
- **uint16[]**: индексы вершин треугольников
|
||||||
|
- **Массивы текстурных координат**: для каждого массива:
|
||||||
|
- **Coordinate[]**: текстурные координаты (U, V)
|
||||||
|
- **Массивы точек**: для каждого массива:
|
||||||
|
- **Vector[]**: точки (X, Y, Z), хранятся как нормализованные uint16 значения
|
||||||
|
- **Массивы яркости**: для каждого массива:
|
||||||
|
- **uint8[]**: значения яркости
|
||||||
|
|
||||||
|
## Структуры данных
|
||||||
|
|
||||||
|
### Material (Материал)
|
||||||
|
```
|
||||||
|
type Material struct {
|
||||||
|
Name, Path string
|
||||||
|
Unknown uint32
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Mesh (Меш)
|
||||||
|
```
|
||||||
|
type Mesh struct {
|
||||||
|
Links []MeshLink
|
||||||
|
Vector1 *Vector
|
||||||
|
Vector2 *Vector
|
||||||
|
Shadow uint16
|
||||||
|
CMap uint16
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### MeshLink (Связь меша)
|
||||||
|
```
|
||||||
|
type MeshLink struct {
|
||||||
|
Material uint16
|
||||||
|
Triangles uint16
|
||||||
|
TextureCoordinates uint16
|
||||||
|
Points uint16
|
||||||
|
Brightness uint16
|
||||||
|
Unknown uint16
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Animation (Анимация)
|
||||||
|
```
|
||||||
|
type Animation struct {
|
||||||
|
Name string
|
||||||
|
MeshIndexes []uint32
|
||||||
|
Unknown, Unknown1, Unknown2 uint16
|
||||||
|
Unknown3 string
|
||||||
|
MoveVector *Vector
|
||||||
|
RotationVector *Vector
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Vector (Вектор)
|
||||||
|
```
|
||||||
|
type Vector [3]float32
|
||||||
|
```
|
||||||
|
|
||||||
|
### Coordinate (Координата)
|
||||||
|
```
|
||||||
|
type Coordinate [2]float32
|
||||||
|
```
|
||||||
|
|
||||||
|
## Примечания по реализации
|
||||||
|
|
||||||
|
1. Все строки хранятся с предшествующей длиной (uint32).
|
||||||
|
2. Векторы точек хранятся как нормализованные значения в диапазоне [0, 1], которые затем преобразуются в координаты модели.
|
||||||
|
3. Некоторые поля помечены как "неизвестные", так как их точное назначение не определено в текущей реализации.
|
||||||
|
4. Файл использует порядок байтов little-endian для всех числовых значений.
|
||||||
91
docs/tasks.md
Normal file
91
docs/tasks.md
Normal file
@ -0,0 +1,91 @@
|
|||||||
|
# Задачи по улучшению Digglestool
|
||||||
|
|
||||||
|
Этот документ содержит приоритезированный список задач по улучшению проекта Digglestool. Каждая задача отмечена флажком, который можно отметить при выполнении.
|
||||||
|
|
||||||
|
## Архитектура и структура
|
||||||
|
|
||||||
|
- [ ] Создать подробный README.md с описанием проекта, инструкциями по установке и примерами использования
|
||||||
|
- [x] Добавить надлежащую документацию для спецификации формата файла .3db
|
||||||
|
- [ ] Внедрить единую стратегию обработки ошибок в кодовой базе
|
||||||
|
- [ ] Рефакторинг жестко закодированных путей в decoder/main.go для использования конфигурационных файлов или аргументов командной строки
|
||||||
|
- [x] Создать единую систему логирования вместо прямого использования log.Println
|
||||||
|
- [ ] Реализовать правильный CLI-интерфейс с флагами и командами, используя библиотеку типа cobra или urfave/cli
|
||||||
|
- [ ] Добавить информацию о версии в приложение
|
||||||
|
- [ ] Разделить декодер и экспортер на отдельные команды с общими библиотеками
|
||||||
|
|
||||||
|
## Качество кода
|
||||||
|
|
||||||
|
- [x] Добавить более подробные комментарии для объяснения сложных алгоритмов, особенно в декодере
|
||||||
|
- [ ] Удалить закомментированный код в decoder/main.go и других файлах
|
||||||
|
- [ ] Внедрить единые шаблоны обработки ошибок в кодовой базе
|
||||||
|
- [ ] Добавить поддержку контекста для операций, которые могут занять много времени
|
||||||
|
- [ ] Рефакторинг функции exportFn в decoder/main.go для большей модульности
|
||||||
|
- [ ] Улучшить именование переменных для лучшей читаемости кода
|
||||||
|
- [ ] Добавить правильную валидацию для входных файлов и параметров
|
||||||
|
- [ ] Последовательно реализовать правильную очистку ресурсов с помощью операторов defer
|
||||||
|
|
||||||
|
## Тестирование
|
||||||
|
|
||||||
|
- [ ] Добавить модульные тесты для пакета threedb
|
||||||
|
- [ ] Добавить модульные тесты для сервиса экспортера
|
||||||
|
- [ ] Создать интеграционные тесты для полного процесса конвертации
|
||||||
|
- [ ] Добавить бенчмарки для критически важных с точки зрения производительности частей кода
|
||||||
|
- [ ] Реализовать тестовые фикстуры с примерами файлов .3db
|
||||||
|
- [ ] Добавить CI/CD-конвейер для автоматизированного тестирования
|
||||||
|
- [ ] Реализовать отчеты о покрытии кода
|
||||||
|
|
||||||
|
## Производительность
|
||||||
|
|
||||||
|
- [ ] Профилировать приложение для выявления узких мест производительности
|
||||||
|
- [ ] Оптимизировать использование памяти при загрузке и конвертации моделей
|
||||||
|
- [ ] Реализовать параллельную обработку для работы с несколькими моделями
|
||||||
|
- [ ] Улучшить реализацию пула буферов для обработки буферов разных размеров
|
||||||
|
- [ ] Добавить отчеты о прогрессе для длительных операций
|
||||||
|
- [ ] Реализовать механизмы кэширования для часто используемых ресурсов
|
||||||
|
- [ ] Оптимизировать загрузку и обработку текстур
|
||||||
|
|
||||||
|
## Функциональность
|
||||||
|
|
||||||
|
- [ ] Добавить поддержку пакетной обработки нескольких файлов .3db
|
||||||
|
- [ ] Реализовать режим предварительного просмотра для быстрой визуализации моделей без полного экспорта
|
||||||
|
- [ ] Добавить поддержку большего количества форматов экспорта помимо GLTF
|
||||||
|
- [ ] Реализовать генератор текстурных атласов для оптимизации использования текстур
|
||||||
|
- [ ] Добавить поддержку оптимизации моделей (уменьшение количества полигонов и т.д.)
|
||||||
|
- [ ] Реализовать улучшения экспорта анимации
|
||||||
|
- [ ] Добавить поддержку пользовательских свойств материалов
|
||||||
|
- [ ] Создать простой веб-интерфейс для конвертации моделей
|
||||||
|
|
||||||
|
## Безопасность
|
||||||
|
|
||||||
|
- [ ] Реализовать правильную валидацию входных данных для предотвращения потенциальных проблем безопасности
|
||||||
|
- [ ] Добавить ограничения размера файлов для предотвращения атак типа "отказ в обслуживании"
|
||||||
|
- [ ] Реализовать безопасную обработку путей к файлам для предотвращения атак с обходом пути
|
||||||
|
- [ ] Добавить контрольные суммы для проверки целостности файлов
|
||||||
|
|
||||||
|
## Документация
|
||||||
|
|
||||||
|
- [ ] Создать документацию API для всех публичных пакетов
|
||||||
|
- [ ] Добавить примеры для типичных случаев использования
|
||||||
|
- [ ] Документировать процесс конвертации моделей
|
||||||
|
- [ ] Создать руководства по использованию инструмента
|
||||||
|
- [ ] Добавить встроенную документацию для сложных алгоритмов
|
||||||
|
- [ ] Документировать процесс обработки текстур
|
||||||
|
- [ ] Создать журнал изменений для отслеживания изменений версий
|
||||||
|
|
||||||
|
## Сборка и распространение
|
||||||
|
|
||||||
|
- [ ] Создать правильные сборки релизов для нескольких платформ
|
||||||
|
- [ ] Реализовать стратегию версионирования
|
||||||
|
- [ ] Добавить установочные скрипты или пакеты
|
||||||
|
- [ ] Создать Docker-контейнеры для простого развертывания
|
||||||
|
- [ ] Реализовать улучшения управления зависимостями
|
||||||
|
- [ ] Добавить скрипты сборки для обеспечения единообразия сборок в разных средах
|
||||||
|
|
||||||
|
## Пользовательский опыт
|
||||||
|
|
||||||
|
- [ ] Улучшить сообщения об ошибках, сделав их более понятными для пользователя
|
||||||
|
- [ ] Добавить индикаторы прогресса для длительных операций
|
||||||
|
- [ ] Реализовать улучшенное логирование с различными уровнями детализации
|
||||||
|
- [ ] Создать простой графический интерфейс для нетехнических пользователей
|
||||||
|
- [ ] Добавить поддержку конфигурационных файлов для постоянных настроек
|
||||||
|
- [ ] Реализовать автодополнение команд для CLI
|
||||||
236
internal/pkg/logger/logger.go
Normal file
236
internal/pkg/logger/logger.go
Normal file
@ -0,0 +1,236 @@
|
|||||||
|
package logger
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"log"
|
||||||
|
"os"
|
||||||
|
"sync"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Level represents the severity level of a log message
|
||||||
|
type Level int
|
||||||
|
|
||||||
|
const (
|
||||||
|
// DEBUG level for detailed troubleshooting information
|
||||||
|
DEBUG Level = iota
|
||||||
|
// INFO level for general operational information
|
||||||
|
INFO
|
||||||
|
// WARN level for warning conditions
|
||||||
|
WARN
|
||||||
|
// ERROR level for error conditions
|
||||||
|
ERROR
|
||||||
|
// FATAL level for critical errors that cause the program to exit
|
||||||
|
FATAL
|
||||||
|
)
|
||||||
|
|
||||||
|
var levelNames = map[Level]string{
|
||||||
|
DEBUG: "DEBUG",
|
||||||
|
INFO: "INFO",
|
||||||
|
WARN: "WARN",
|
||||||
|
ERROR: "ERROR",
|
||||||
|
FATAL: "FATAL",
|
||||||
|
}
|
||||||
|
|
||||||
|
// String returns the string representation of the log level
|
||||||
|
func (l Level) String() string {
|
||||||
|
if name, ok := levelNames[l]; ok {
|
||||||
|
return name
|
||||||
|
}
|
||||||
|
return fmt.Sprintf("LEVEL(%d)", l)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Logger is the interface that defines the logging methods
|
||||||
|
type Logger interface {
|
||||||
|
Debug(v ...interface{})
|
||||||
|
Debugf(format string, v ...interface{})
|
||||||
|
Info(v ...interface{})
|
||||||
|
Infof(format string, v ...interface{})
|
||||||
|
Warn(v ...interface{})
|
||||||
|
Warnf(format string, v ...interface{})
|
||||||
|
Error(v ...interface{})
|
||||||
|
Errorf(format string, v ...interface{})
|
||||||
|
Fatal(v ...interface{})
|
||||||
|
Fatalf(format string, v ...interface{})
|
||||||
|
SetLevel(level Level)
|
||||||
|
GetLevel() Level
|
||||||
|
SetOutput(w io.Writer)
|
||||||
|
}
|
||||||
|
|
||||||
|
// DefaultLogger is the standard implementation of the Logger interface
|
||||||
|
type DefaultLogger struct {
|
||||||
|
mu sync.Mutex
|
||||||
|
logger *log.Logger
|
||||||
|
level Level
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewDefaultLogger creates a new DefaultLogger with the specified level
|
||||||
|
func NewDefaultLogger(level Level) *DefaultLogger {
|
||||||
|
return &DefaultLogger{
|
||||||
|
logger: log.New(os.Stdout, "", log.LstdFlags),
|
||||||
|
level: level,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetOutput sets the output destination for the logger
|
||||||
|
func (l *DefaultLogger) SetOutput(w io.Writer) {
|
||||||
|
l.mu.Lock()
|
||||||
|
defer l.mu.Unlock()
|
||||||
|
l.logger.SetOutput(w)
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetLevel sets the minimum log level that will be output
|
||||||
|
func (l *DefaultLogger) SetLevel(level Level) {
|
||||||
|
l.mu.Lock()
|
||||||
|
defer l.mu.Unlock()
|
||||||
|
l.level = level
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetLevel returns the current log level
|
||||||
|
func (l *DefaultLogger) GetLevel() Level {
|
||||||
|
l.mu.Lock()
|
||||||
|
defer l.mu.Unlock()
|
||||||
|
return l.level
|
||||||
|
}
|
||||||
|
|
||||||
|
// log logs a message at the specified level
|
||||||
|
func (l *DefaultLogger) log(level Level, v ...interface{}) {
|
||||||
|
if level < l.level {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
l.mu.Lock()
|
||||||
|
defer l.mu.Unlock()
|
||||||
|
l.logger.Print(fmt.Sprintf("[%s] ", level), fmt.Sprint(v...))
|
||||||
|
}
|
||||||
|
|
||||||
|
// logf logs a formatted message at the specified level
|
||||||
|
func (l *DefaultLogger) logf(level Level, format string, v ...interface{}) {
|
||||||
|
if level < l.level {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
l.mu.Lock()
|
||||||
|
defer l.mu.Unlock()
|
||||||
|
l.logger.Print(fmt.Sprintf("[%s] %s", level, fmt.Sprintf(format, v...)))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Debug logs a message at DEBUG level
|
||||||
|
func (l *DefaultLogger) Debug(v ...interface{}) {
|
||||||
|
l.log(DEBUG, v...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Debugf logs a formatted message at DEBUG level
|
||||||
|
func (l *DefaultLogger) Debugf(format string, v ...interface{}) {
|
||||||
|
l.logf(DEBUG, format, v...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Info logs a message at INFO level
|
||||||
|
func (l *DefaultLogger) Info(v ...interface{}) {
|
||||||
|
l.log(INFO, v...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Infof logs a formatted message at INFO level
|
||||||
|
func (l *DefaultLogger) Infof(format string, v ...interface{}) {
|
||||||
|
l.logf(INFO, format, v...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Warn logs a message at WARN level
|
||||||
|
func (l *DefaultLogger) Warn(v ...interface{}) {
|
||||||
|
l.log(WARN, v...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Warnf logs a formatted message at WARN level
|
||||||
|
func (l *DefaultLogger) Warnf(format string, v ...interface{}) {
|
||||||
|
l.logf(WARN, format, v...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Error logs a message at ERROR level
|
||||||
|
func (l *DefaultLogger) Error(v ...interface{}) {
|
||||||
|
l.log(ERROR, v...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Errorf logs a formatted message at ERROR level
|
||||||
|
func (l *DefaultLogger) Errorf(format string, v ...interface{}) {
|
||||||
|
l.logf(ERROR, format, v...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fatal logs a message at FATAL level and then exits the program
|
||||||
|
func (l *DefaultLogger) Fatal(v ...interface{}) {
|
||||||
|
l.log(FATAL, v...)
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fatalf logs a formatted message at FATAL level and then exits the program
|
||||||
|
func (l *DefaultLogger) Fatalf(format string, v ...interface{}) {
|
||||||
|
l.logf(FATAL, format, v...)
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Global logger instance
|
||||||
|
var (
|
||||||
|
defaultLogger Logger = NewDefaultLogger(INFO)
|
||||||
|
)
|
||||||
|
|
||||||
|
// SetDefaultLogger sets the global default logger
|
||||||
|
func SetDefaultLogger(logger Logger) {
|
||||||
|
defaultLogger = logger
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetDefaultLogger returns the global default logger
|
||||||
|
func GetDefaultLogger() Logger {
|
||||||
|
return defaultLogger
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetLevel sets the log level for the default logger
|
||||||
|
func SetLevel(level Level) {
|
||||||
|
defaultLogger.SetLevel(level)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Debug logs a message at DEBUG level using the default logger
|
||||||
|
func Debug(v ...interface{}) {
|
||||||
|
defaultLogger.Debug(v...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Debugf logs a formatted message at DEBUG level using the default logger
|
||||||
|
func Debugf(format string, v ...interface{}) {
|
||||||
|
defaultLogger.Debugf(format, v...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Info logs a message at INFO level using the default logger
|
||||||
|
func Info(v ...interface{}) {
|
||||||
|
defaultLogger.Info(v...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Infof logs a formatted message at INFO level using the default logger
|
||||||
|
func Infof(format string, v ...interface{}) {
|
||||||
|
defaultLogger.Infof(format, v...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Warn logs a message at WARN level using the default logger
|
||||||
|
func Warn(v ...interface{}) {
|
||||||
|
defaultLogger.Warn(v...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Warnf logs a formatted message at WARN level using the default logger
|
||||||
|
func Warnf(format string, v ...interface{}) {
|
||||||
|
defaultLogger.Warnf(format, v...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Error logs a message at ERROR level using the default logger
|
||||||
|
func Error(v ...interface{}) {
|
||||||
|
defaultLogger.Error(v...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Errorf logs a formatted message at ERROR level using the default logger
|
||||||
|
func Errorf(format string, v ...interface{}) {
|
||||||
|
defaultLogger.Errorf(format, v...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fatal logs a message at FATAL level using the default logger and then exits
|
||||||
|
func Fatal(v ...interface{}) {
|
||||||
|
defaultLogger.Fatal(v...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fatalf logs a formatted message at FATAL level using the default logger and then exits
|
||||||
|
func Fatalf(format string, v ...interface{}) {
|
||||||
|
defaultLogger.Fatalf(format, v...)
|
||||||
|
}
|
||||||
@ -4,7 +4,6 @@ import (
|
|||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
_ "image/png"
|
_ "image/png"
|
||||||
"log"
|
|
||||||
"os"
|
"os"
|
||||||
"path"
|
"path"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
@ -13,6 +12,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/logger"
|
||||||
"git.influ.su/artmares/digglestool/pkg/threedb"
|
"git.influ.su/artmares/digglestool/pkg/threedb"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -67,7 +67,7 @@ func (e *Exporter) Export(basePath string, model *threedb.Model) error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
if e.onlyBaseMesh {
|
if e.onlyBaseMesh {
|
||||||
log.Printf("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 err
|
||||||
@ -83,10 +83,10 @@ func (e *Exporter) Export(basePath string, model *threedb.Model) error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
log.Printf("Export Animation to %s\n", dirPath)
|
logger.Infof("Export Animation to %s", dirPath)
|
||||||
length := len(model.Animations)
|
length := len(model.Animations)
|
||||||
for index, animation := range model.Animations {
|
for index, animation := range model.Animations {
|
||||||
log.Printf("Export progress: %d/%d\r", index+1, length)
|
logger.Infof("Export progress: %d/%d\r", index+1, length)
|
||||||
indexes := make([]int, len(animation.MeshIndexes))
|
indexes := make([]int, len(animation.MeshIndexes))
|
||||||
for _, index := range animation.MeshIndexes {
|
for _, index := range animation.MeshIndexes {
|
||||||
indexes = append(indexes, int(index))
|
indexes = append(indexes, int(index))
|
||||||
|
|||||||
@ -5,13 +5,13 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
"image"
|
"image"
|
||||||
"image/png"
|
"image/png"
|
||||||
"log"
|
|
||||||
"os"
|
"os"
|
||||||
|
|
||||||
_ "github.com/ftrvxmtrx/tga"
|
_ "github.com/ftrvxmtrx/tga"
|
||||||
"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/logger"
|
||||||
"git.influ.su/artmares/digglestool/pkg/threedb"
|
"git.influ.su/artmares/digglestool/pkg/threedb"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -24,7 +24,7 @@ func (m *Model) Generate(model *threedb.Model, cache *Cache[*CacheItem], meshInd
|
|||||||
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)
|
||||||
log.Println("Generate use mesh indexes", len(meshIndexes))
|
logger.Info("Generate use mesh indexes", len(meshIndexes))
|
||||||
for i, meshIndex := range meshIndexes {
|
for i, meshIndex := range meshIndexes {
|
||||||
mesh := model.Meshes[meshIndex]
|
mesh := model.Meshes[meshIndex]
|
||||||
var nodeIndexes []int
|
var nodeIndexes []int
|
||||||
|
|||||||
@ -1,31 +1,59 @@
|
|||||||
|
// Package threedb provides functionality for working with .3db file format.
|
||||||
|
// This package contains the decoder for parsing 3D model data from the proprietary .3db format
|
||||||
|
// used in the Diggles game. The format stores mesh data, materials, animations, and other
|
||||||
|
// 3D model information in a binary structure.
|
||||||
package threedb
|
package threedb
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"encoding/binary"
|
"encoding/binary"
|
||||||
"errors"
|
"errors"
|
||||||
"io"
|
"io"
|
||||||
"log"
|
|
||||||
|
"git.influ.su/artmares/digglestool/internal/pkg/logger"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// Decoder is responsible for reading and parsing .3db file format.
|
||||||
|
// It uses an io.ReadSeeker to navigate through the binary file structure
|
||||||
|
// and extract model data according to the .3db format specification.
|
||||||
type Decoder struct {
|
type Decoder struct {
|
||||||
reader io.ReadSeeker
|
reader io.ReadSeeker // The source from which the 3D model data is read
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// NewDecoder creates a new Decoder instance with the provided reader.
|
||||||
|
// The reader must implement io.ReadSeeker interface to allow both reading
|
||||||
|
// and seeking within the file.
|
||||||
func NewDecoder(reader io.ReadSeeker) *Decoder {
|
func NewDecoder(reader io.ReadSeeker) *Decoder {
|
||||||
return &Decoder{reader: reader}
|
return &Decoder{reader: reader}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Decode parses the entire .3db file and populates the provided Model structure.
|
||||||
|
// The .3db file format has a specific structure with sections for different types of data:
|
||||||
|
// 1. File header (version and model name)
|
||||||
|
// 2. Materials list
|
||||||
|
// 3. Meshes with their properties
|
||||||
|
// 4. Object definitions
|
||||||
|
// 5. Animation data
|
||||||
|
// 6. Shadow maps
|
||||||
|
// 7. Cube maps (environment textures)
|
||||||
|
// 8. Geometry data (triangles, texture coordinates, vertices, etc.)
|
||||||
|
//
|
||||||
|
// This method reads each section in sequence, following the file format specification.
|
||||||
|
// If any error occurs during reading, the decoding process is aborted.
|
||||||
func (dec *Decoder) Decode(model *Model) error {
|
func (dec *Decoder) Decode(model *Model) error {
|
||||||
if model == nil {
|
if model == nil {
|
||||||
return errors.New("model is nil")
|
return errors.New("model is nil")
|
||||||
}
|
}
|
||||||
var err error
|
var err error
|
||||||
|
|
||||||
|
// Read file header information
|
||||||
if model.DBVersion, err = dec.readString(); err != nil {
|
if model.DBVersion, err = dec.readString(); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
if model.Name, err = dec.readString(); err != nil {
|
if model.Name, err = dec.readString(); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 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 err
|
||||||
}
|
}
|
||||||
@ -44,6 +72,8 @@ func (dec *Decoder) Decode(model *Model) error {
|
|||||||
if err = dec.readCubeMaps(model); err != nil {
|
if err = dec.readCubeMaps(model); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 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 err
|
||||||
}
|
}
|
||||||
@ -51,40 +81,58 @@ func (dec *Decoder) Decode(model *Model) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// seek advances the reader position by the specified offset.
|
||||||
|
// This is used to skip over sections of the file that are not needed
|
||||||
|
// or not yet understood in the file format.
|
||||||
func (dec *Decoder) seek(offset int64) (err error) {
|
func (dec *Decoder) seek(offset int64) (err error) {
|
||||||
_, err = dec.reader.Seek(offset, io.SeekCurrent)
|
_, err = dec.reader.Seek(offset, io.SeekCurrent)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// read is a helper method that reads binary data from the reader into the provided destination.
|
||||||
|
// All data in the .3db format is stored in little-endian byte order.
|
||||||
func (dec *Decoder) read(dst any) error {
|
func (dec *Decoder) read(dst any) error {
|
||||||
return binary.Read(dec.reader, binary.LittleEndian, dst)
|
return binary.Read(dec.reader, binary.LittleEndian, dst)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// readUInt8 reads an unsigned 8-bit integer from the binary stream.
|
||||||
func (dec *Decoder) readUInt8() (result uint8, err error) {
|
func (dec *Decoder) readUInt8() (result uint8, err error) {
|
||||||
err = dec.read(&result)
|
err = dec.read(&result)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// readUInt16 reads an unsigned 16-bit integer from the binary stream.
|
||||||
|
// These are commonly used for indices, counts, and references in the .3db format.
|
||||||
func (dec *Decoder) readUInt16() (result uint16, err error) {
|
func (dec *Decoder) readUInt16() (result uint16, err error) {
|
||||||
err = dec.read(&result)
|
err = dec.read(&result)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// readUInt32 reads an unsigned 32-bit integer from the binary stream.
|
||||||
|
// These are used for larger indices and counts in the .3db format.
|
||||||
func (dec *Decoder) readUInt32() (result uint32, err error) {
|
func (dec *Decoder) readUInt32() (result uint32, err error) {
|
||||||
err = dec.read(&result)
|
err = dec.read(&result)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// readFloat32 reads a 32-bit floating point number from the binary stream.
|
||||||
|
// These are used for coordinates, vectors, and other numeric values in the .3db format.
|
||||||
func (dec *Decoder) readFloat32() (result float32, err error) {
|
func (dec *Decoder) readFloat32() (result float32, err error) {
|
||||||
err = dec.read(&result)
|
err = dec.read(&result)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// readString reads a string from the binary stream.
|
||||||
|
// In the .3db format, strings are stored as a 32-bit length followed by the string data.
|
||||||
|
// This is used for names, paths, and other textual information in the model.
|
||||||
func (dec *Decoder) readString() (string, error) {
|
func (dec *Decoder) readString() (string, error) {
|
||||||
|
// First read the length of the string
|
||||||
length, err := dec.readUInt32()
|
length, err := dec.readUInt32()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", err
|
return "", err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 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 "", err
|
||||||
@ -93,9 +141,14 @@ func (dec *Decoder) readString() (string, error) {
|
|||||||
return string(buf), nil
|
return string(buf), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// readVector reads a 3D vector (x, y, z coordinates) from the binary stream.
|
||||||
|
// Vectors are used for positions, directions, and other spatial information in the model.
|
||||||
|
// Each component of the vector is stored as a 32-bit floating point number.
|
||||||
func (dec *Decoder) readVector() (*Vector, error) {
|
func (dec *Decoder) readVector() (*Vector, error) {
|
||||||
var vec Vector
|
var vec Vector
|
||||||
var err error
|
var err error
|
||||||
|
|
||||||
|
// 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, err
|
||||||
}
|
}
|
||||||
@ -105,76 +158,126 @@ func (dec *Decoder) readVector() (*Vector, error) {
|
|||||||
if vec[2], err = dec.readFloat32(); err != nil {
|
if vec[2], err = dec.readFloat32(); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
return &vec, nil
|
return &vec, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// readMaterials reads the materials section of the .3db file.
|
||||||
|
// Materials define the visual appearance of the model's surfaces and include
|
||||||
|
// information about textures, colors, and other surface properties.
|
||||||
|
// Each material has a name, a path to its texture file, and some additional properties.
|
||||||
func (dec *Decoder) readMaterials(model *Model) error {
|
func (dec *Decoder) readMaterials(model *Model) error {
|
||||||
|
// Read the number of materials in the file
|
||||||
materialCount, err := dec.readUInt16()
|
materialCount, err := dec.readUInt16()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
count := int(materialCount)
|
count := int(materialCount)
|
||||||
|
|
||||||
|
// Read each material's data
|
||||||
for i := 0; i < count; i++ {
|
for i := 0; i < count; i++ {
|
||||||
material := Material{}
|
material := Material{}
|
||||||
|
|
||||||
|
// Read the material name (usually corresponds to a texture name)
|
||||||
if material.Name, err = dec.readString(); err != nil {
|
if material.Name, err = dec.readString(); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Read the path to the material's texture file
|
||||||
if material.Path, err = dec.readString(); err != nil {
|
if material.Path, err = dec.readString(); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Read an unknown property (possibly related to material properties or flags)
|
||||||
if material.Unknown, err = dec.readUInt32(); err != nil {
|
if material.Unknown, err = dec.readUInt32(); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Add the material to the model's materials list
|
||||||
model.Materials = append(model.Materials, material)
|
model.Materials = append(model.Materials, material)
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// readMeshes reads the meshes section of the .3db file.
|
||||||
|
// Meshes are the 3D objects that make up the model. Each mesh consists of
|
||||||
|
// a collection of triangles, texture coordinates, and vertices, organized into "links".
|
||||||
|
// Meshes also contain transformation data and references to shadows and cube maps.
|
||||||
func (dec *Decoder) readMeshes(model *Model) error {
|
func (dec *Decoder) readMeshes(model *Model) error {
|
||||||
|
// Read the number of meshes in the file
|
||||||
meshCount, err := dec.readUInt32()
|
meshCount, err := dec.readUInt32()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
count := int(meshCount)
|
count := int(meshCount)
|
||||||
|
|
||||||
|
// Read each mesh's data
|
||||||
for i := 0; i < count; i++ {
|
for i := 0; i < count; i++ {
|
||||||
mesh := Mesh{}
|
mesh := Mesh{}
|
||||||
|
|
||||||
|
// Read the mesh links (connections to materials, triangles, etc.)
|
||||||
if err = dec.readMeshLink(&mesh); err != nil {
|
if err = dec.readMeshLink(&mesh); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Read two vectors that might represent position, scale, or other transformation data
|
||||||
if mesh.Vector1, err = dec.readVector(); err != nil {
|
if mesh.Vector1, err = dec.readVector(); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
if mesh.Vector2, err = dec.readVector(); err != nil {
|
if mesh.Vector2, err = dec.readVector(); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Skip 128 bytes of unknown data
|
||||||
_ = dec.seek(0x80)
|
_ = dec.seek(0x80)
|
||||||
|
|
||||||
|
// Read the shadow index (reference to a shadow in the shadows section)
|
||||||
if mesh.Shadow, err = dec.readUInt16(); err != nil {
|
if mesh.Shadow, err = dec.readUInt16(); err != nil {
|
||||||
return err
|
return err
|
||||||
} // Mesh Shadow Index ?
|
}
|
||||||
|
|
||||||
|
// Skip 48 bytes of unknown data
|
||||||
_ = dec.seek(0x30)
|
_ = dec.seek(0x30)
|
||||||
|
|
||||||
|
// Read the cube map index (reference to a cube map in the cube maps section)
|
||||||
if mesh.CMap, err = dec.readUInt16(); err != nil {
|
if mesh.CMap, err = dec.readUInt16(); err != nil {
|
||||||
return err
|
return err
|
||||||
} // Mesh CMap Index ?
|
}
|
||||||
|
|
||||||
|
// Add the mesh to the model's meshes list
|
||||||
model.Meshes = append(model.Meshes, mesh)
|
model.Meshes = append(model.Meshes, mesh)
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// readMeshLink reads the links section of a mesh.
|
||||||
|
// Links connect a mesh to its materials, triangles, texture coordinates, vertices, and brightness values.
|
||||||
|
// Each mesh can have multiple links, typically one for each material used in the mesh.
|
||||||
func (dec *Decoder) readMeshLink(mesh *Mesh) error {
|
func (dec *Decoder) readMeshLink(mesh *Mesh) error {
|
||||||
|
// Read the number of links in the mesh
|
||||||
meshLinkCount, err := dec.readUInt16()
|
meshLinkCount, err := dec.readUInt16()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
count := int(meshLinkCount)
|
count := int(meshLinkCount)
|
||||||
|
|
||||||
|
// Read each link's data
|
||||||
for i := 0; i < count; i++ {
|
for i := 0; i < count; i++ {
|
||||||
meshLink := MeshLink{}
|
meshLink := MeshLink{}
|
||||||
|
|
||||||
|
// Read the material index (reference to a material in the materials section)
|
||||||
if meshLink.Material, err = dec.readUInt16(); err != nil {
|
if meshLink.Material, err = dec.readUInt16(); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Read an unknown property
|
||||||
if meshLink.Unknown, err = dec.readUInt16(); err != nil {
|
if meshLink.Unknown, err = dec.readUInt16(); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Read indices to geometry data that will be read later in readData
|
||||||
|
// These are indices into arrays of triangles, texture coordinates, points, and brightness values
|
||||||
if meshLink.Triangles, err = dec.readUInt16(); err != nil {
|
if meshLink.Triangles, err = dec.readUInt16(); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@ -187,30 +290,48 @@ func (dec *Decoder) readMeshLink(mesh *Mesh) error {
|
|||||||
if meshLink.Brightness, err = dec.readUInt16(); err != nil {
|
if meshLink.Brightness, err = dec.readUInt16(); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Add the link to the mesh's links list
|
||||||
mesh.Links = append(mesh.Links, meshLink)
|
mesh.Links = append(mesh.Links, meshLink)
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// readObjects reads the objects section of the .3db file.
|
||||||
|
// Objects are named collections of mesh indices that group meshes together
|
||||||
|
// for logical organization or animation purposes. For example, all meshes
|
||||||
|
// that make up a character's arm might be grouped under an "arm" object.
|
||||||
func (dec *Decoder) readObjects(model *Model) error {
|
func (dec *Decoder) readObjects(model *Model) error {
|
||||||
|
// Read the number of key-value pairs (object definitions)
|
||||||
keyValuePairCount, err := dec.readUInt16()
|
keyValuePairCount, err := dec.readUInt16()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
count := int(keyValuePairCount)
|
count := int(keyValuePairCount)
|
||||||
|
|
||||||
|
// Initialize the objects map if there are objects to read
|
||||||
if count > 0 && model.Objects == nil {
|
if count > 0 && model.Objects == nil {
|
||||||
model.Objects = make(map[string][]uint32)
|
model.Objects = make(map[string][]uint32)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Read each object definition
|
||||||
for i := 0; i < count; i++ {
|
for i := 0; i < count; i++ {
|
||||||
|
// Read the object name (key)
|
||||||
var key string
|
var key string
|
||||||
if key, err = dec.readString(); err != nil {
|
if key, err = dec.readString(); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Read the number of mesh indices in this object
|
||||||
var objectCount uint16
|
var objectCount uint16
|
||||||
if objectCount, err = dec.readUInt16(); err != nil {
|
if objectCount, err = dec.readUInt16(); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Initialize the array for this object's mesh indices
|
||||||
model.Objects[key] = make([]uint32, objectCount)
|
model.Objects[key] = make([]uint32, objectCount)
|
||||||
|
|
||||||
|
// Read each mesh index
|
||||||
for j := 0; j < int(objectCount); j++ {
|
for j := 0; j < int(objectCount); j++ {
|
||||||
var n uint32
|
var n uint32
|
||||||
if n, err = dec.readUInt32(); err != nil {
|
if n, err = dec.readUInt32(); err != nil {
|
||||||
@ -222,23 +343,36 @@ func (dec *Decoder) readObjects(model *Model) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// readAnimations reads the animations section of the .3db file.
|
||||||
|
// Animations define how meshes move and rotate over time. Each animation
|
||||||
|
// has a name, a list of mesh indices it affects, and transformation data.
|
||||||
func (dec *Decoder) readAnimations(model *Model) error {
|
func (dec *Decoder) readAnimations(model *Model) error {
|
||||||
|
// Read the number of animations
|
||||||
animationCount, err := dec.readUInt16()
|
animationCount, err := dec.readUInt16()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
count := int(animationCount)
|
count := int(animationCount)
|
||||||
|
|
||||||
|
// Read each animation
|
||||||
for i := 0; i < count; i++ {
|
for i := 0; i < count; i++ {
|
||||||
animation := Animation{}
|
animation := Animation{}
|
||||||
|
|
||||||
|
// Read the animation name
|
||||||
if animation.Name, err = dec.readString(); err != nil {
|
if animation.Name, err = dec.readString(); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Read the number of mesh indices this animation affects
|
||||||
var meshIndexesCount uint16
|
var meshIndexesCount uint16
|
||||||
if meshIndexesCount, err = dec.readUInt16(); err != nil {
|
if meshIndexesCount, err = dec.readUInt16(); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Initialize the array for mesh indices
|
||||||
animation.MeshIndexes = make([]uint32, meshIndexesCount)
|
animation.MeshIndexes = make([]uint32, meshIndexesCount)
|
||||||
|
|
||||||
|
// Read each mesh index
|
||||||
for j := 0; j < int(meshIndexesCount); j++ {
|
for j := 0; j < int(meshIndexesCount); j++ {
|
||||||
var n uint32
|
var n uint32
|
||||||
if n, err = dec.readUInt32(); err != nil {
|
if n, err = dec.readUInt32(); err != nil {
|
||||||
@ -246,6 +380,9 @@ func (dec *Decoder) readAnimations(model *Model) error {
|
|||||||
}
|
}
|
||||||
animation.MeshIndexes[j] = n
|
animation.MeshIndexes[j] = n
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Read animation properties (some are not fully understood)
|
||||||
|
// These might control timing, interpolation, or other animation parameters
|
||||||
if animation.Unknown, err = dec.readUInt16(); err != nil {
|
if animation.Unknown, err = dec.readUInt16(); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@ -255,43 +392,60 @@ func (dec *Decoder) readAnimations(model *Model) error {
|
|||||||
if animation.Unknown2, err = dec.readUInt16(); err != nil {
|
if animation.Unknown2, err = dec.readUInt16(); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
//if animation.Unknown1, err = dec.readFloat32(); err != nil {
|
|
||||||
// return err
|
// Read additional animation data
|
||||||
//}
|
|
||||||
if animation.Unknown3, err = dec.readString(); err != nil {
|
if animation.Unknown3, err = dec.readString(); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Read movement and rotation vectors that define the animation transformation
|
||||||
if animation.MoveVector, err = dec.readVector(); err != nil {
|
if animation.MoveVector, err = dec.readVector(); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
if animation.RotationVector, err = dec.readVector(); err != nil {
|
if animation.RotationVector, err = dec.readVector(); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Add the animation to the model's animations list
|
||||||
model.Animations = append(model.Animations, animation)
|
model.Animations = append(model.Animations, animation)
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// readShadows reads the shadows section of the .3db file.
|
||||||
|
// Shadows appear to be stored as 32x32 pixel images, but this implementation
|
||||||
|
// currently just skips over them without processing the data.
|
||||||
func (dec *Decoder) readShadows(_ *Model) error {
|
func (dec *Decoder) readShadows(_ *Model) error {
|
||||||
|
// Read the number of shadows
|
||||||
shadowCount, err := dec.readUInt16()
|
shadowCount, err := dec.readUInt16()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
count := int(shadowCount)
|
count := int(shadowCount)
|
||||||
|
|
||||||
|
// Skip over each shadow's data
|
||||||
|
// Each shadow appears to be a 32x32 pixel image (1024 bytes)
|
||||||
for i := 0; i < count; i++ {
|
for i := 0; i < count; i++ {
|
||||||
// Skip // TODO: возможно это картинка 32х32
|
// Skip the shadow data
|
||||||
_ = dec.seek(32 * 32)
|
_ = dec.seek(32 * 32)
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// readCubeMaps reads the cube maps section of the .3db file.
|
||||||
|
// Cube maps are used for environment reflections and are stored as
|
||||||
|
// rectangular images with dimensions specified in the file.
|
||||||
func (dec *Decoder) readCubeMaps(_ *Model) error {
|
func (dec *Decoder) readCubeMaps(_ *Model) error {
|
||||||
|
// Read the number of cube maps
|
||||||
cubeMapCount, err := dec.readUInt16()
|
cubeMapCount, err := dec.readUInt16()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
count := int(cubeMapCount)
|
count := int(cubeMapCount)
|
||||||
|
|
||||||
|
// Read each cube map
|
||||||
for i := 0; i < count; i++ {
|
for i := 0; i < count; i++ {
|
||||||
|
// Read the dimensions of the cube map image
|
||||||
var width, height uint16
|
var width, height uint16
|
||||||
if width, err = dec.readUInt16(); err != nil {
|
if width, err = dec.readUInt16(); err != nil {
|
||||||
return err
|
return err
|
||||||
@ -299,29 +453,48 @@ func (dec *Decoder) readCubeMaps(_ *Model) error {
|
|||||||
if height, err = dec.readUInt16(); err != nil {
|
if height, err = dec.readUInt16(); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Read two unknown values (possibly format or flags)
|
||||||
_, _ = dec.readUInt16()
|
_, _ = dec.readUInt16()
|
||||||
_, _ = dec.readUInt16()
|
_, _ = dec.readUInt16()
|
||||||
// Skip pixel data
|
|
||||||
|
// Skip the pixel data (width * height bytes)
|
||||||
_ = dec.seek(int64(width * height))
|
_ = dec.seek(int64(width * height))
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// readData reads the geometry data section of the .3db file.
|
||||||
|
// This is the most complex part of the file format, containing all the actual 3D geometry
|
||||||
|
// information that defines the model's shape and appearance. The data is organized into
|
||||||
|
// several arrays:
|
||||||
|
//
|
||||||
|
// 1. Triangles: Define the faces of the 3D model by referencing vertices
|
||||||
|
// 2. Texture Coordinates: Define how textures are mapped onto the model's surface
|
||||||
|
// 3. Points: The actual 3D vertices that make up the model's shape
|
||||||
|
// 4. Brightness: Per-vertex lighting information
|
||||||
|
//
|
||||||
|
// The function first reads counts for each type of data, then reads arrays of counts
|
||||||
|
// that indicate how many elements are in each sub-array. Finally, it reads the actual
|
||||||
|
// geometry data by calling specialized functions for each data type.
|
||||||
func (dec *Decoder) readData(model *Model) error {
|
func (dec *Decoder) readData(model *Model) error {
|
||||||
var (
|
var (
|
||||||
triangleCount uint16
|
// These variables store the number of sub-arrays for each data type
|
||||||
trianglesCounts []uint16
|
triangleCount uint16 // Number of triangle arrays
|
||||||
textureCoordCount uint16
|
trianglesCounts []uint16 // Number of triangles in each array
|
||||||
textureCoordCounts []uint16
|
textureCoordCount uint16 // Number of texture coordinate arrays
|
||||||
pointCount uint16
|
textureCoordCounts []uint16 // Number of texture coordinates in each array
|
||||||
pointCounts []uint16
|
pointCount uint16 // Number of point (vertex) arrays
|
||||||
brightnessCount uint16
|
pointCounts []uint16 // Number of points in each array
|
||||||
brightnessCounts []uint16
|
brightnessCount uint16 // Number of brightness arrays
|
||||||
unknownCount uint32
|
brightnessCounts []uint16 // Number of brightness values in each array
|
||||||
|
unknownCount uint32 // Number of unknown data blocks to skip
|
||||||
|
|
||||||
cnt uint16
|
cnt uint16 // Temporary variable for reading counts
|
||||||
err error
|
err error
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// Read the number of arrays for each data type
|
||||||
if triangleCount, err = dec.readUInt16(); err != nil {
|
if triangleCount, err = dec.readUInt16(); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@ -334,74 +507,77 @@ func (dec *Decoder) readData(model *Model) error {
|
|||||||
if brightnessCount, err = dec.readUInt16(); err != nil {
|
if brightnessCount, err = dec.readUInt16(); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Read the count of unknown data blocks (possibly animation or physics related)
|
||||||
if unknownCount, err = dec.readUInt32(); err != nil {
|
if unknownCount, err = dec.readUInt32(); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
log.Println("unknownCount:", unknownCount)
|
logger.Debug("unknownCount:", unknownCount)
|
||||||
|
|
||||||
|
// Read the number of elements in each triangle array
|
||||||
|
// Each value tells us how many triangles are in the corresponding array
|
||||||
for i := 0; i < int(triangleCount); i++ {
|
for i := 0; i < int(triangleCount); i++ {
|
||||||
if cnt, err = dec.readUInt16(); err != nil {
|
if cnt, err = dec.readUInt16(); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
trianglesCounts = append(trianglesCounts, cnt)
|
trianglesCounts = append(trianglesCounts, cnt)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Read the number of elements in each texture coordinate array
|
||||||
|
// Each value tells us how many texture coordinates are in the corresponding array
|
||||||
for i := 0; i < int(textureCoordCount); i++ {
|
for i := 0; i < int(textureCoordCount); i++ {
|
||||||
if cnt, err = dec.readUInt16(); err != nil {
|
if cnt, err = dec.readUInt16(); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
textureCoordCounts = append(textureCoordCounts, cnt)
|
textureCoordCounts = append(textureCoordCounts, cnt)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Read the number of elements in each point (vertex) array
|
||||||
|
// Each value tells us how many vertices are in the corresponding array
|
||||||
for i := 0; i < int(pointCount); i++ {
|
for i := 0; i < int(pointCount); i++ {
|
||||||
if cnt, err = dec.readUInt16(); err != nil {
|
if cnt, err = dec.readUInt16(); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
pointCounts = append(pointCounts, cnt)
|
pointCounts = append(pointCounts, cnt)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Read the number of elements in each brightness array
|
||||||
|
// Each value tells us how many brightness values are in the corresponding array
|
||||||
for i := 0; i < int(brightnessCount); i++ {
|
for i := 0; i < int(brightnessCount); i++ {
|
||||||
if cnt, err = dec.readUInt16(); err != nil {
|
if cnt, err = dec.readUInt16(); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
brightnessCounts = append(brightnessCounts, cnt)
|
brightnessCounts = append(brightnessCounts, cnt)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Skip over blocks of unknown data
|
||||||
|
// Each block is 20 bytes long and may contain additional model information
|
||||||
|
// that is not currently used by this decoder
|
||||||
for i := 0; i < int(unknownCount); i++ {
|
for i := 0; i < int(unknownCount); i++ {
|
||||||
//x, err := dec.readFloat32()
|
// Note: There was experimental code here to try to decode this data,
|
||||||
//if err != nil {
|
// but it's been commented out as the format is not fully understood.
|
||||||
// return err
|
// Each block appears to be 20 bytes in size.
|
||||||
//}
|
|
||||||
//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)
|
_ = dec.seek(20)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Now read the actual geometry data using the counts we've collected
|
||||||
|
|
||||||
|
// Read triangle indices that define the model's faces
|
||||||
if err = dec.readTriangles(model, int(triangleCount), trianglesCounts); err != nil {
|
if err = dec.readTriangles(model, int(triangleCount), trianglesCounts); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Read texture coordinates that define how textures map onto the model
|
||||||
if err = dec.readTextureCoordinates(model, int(textureCoordCount), textureCoordCounts); err != nil {
|
if err = dec.readTextureCoordinates(model, int(textureCoordCount), textureCoordCounts); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Read 3D points (vertices) that define the model's shape
|
||||||
if err = dec.readPoint(model, int(pointCount), pointCounts); err != nil {
|
if err = dec.readPoint(model, int(pointCount), pointCounts); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Read brightness values that define per-vertex lighting
|
||||||
if err = dec.readBrightness(model, int(brightnessCount), brightnessCounts); err != nil {
|
if err = dec.readBrightness(model, int(brightnessCount), brightnessCounts); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@ -409,10 +585,27 @@ func (dec *Decoder) readData(model *Model) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// readTriangles reads the triangle data from the .3db file.
|
||||||
|
// Triangles are the fundamental building blocks of 3D models, defining the faces
|
||||||
|
// that make up the model's surface. Each triangle is defined by three indices that
|
||||||
|
// reference vertices in the corresponding point array.
|
||||||
|
//
|
||||||
|
// The function reads 'count' arrays of triangles, where each array contains a variable
|
||||||
|
// number of triangle indices as specified in the 'counts' slice. Each triangle index
|
||||||
|
// is stored as a 16-bit unsigned integer.
|
||||||
|
//
|
||||||
|
// In 3D graphics, triangles are used because they are always planar (flat) and can
|
||||||
|
// approximate any 3D surface when used in sufficient numbers. The triangles in this
|
||||||
|
// format appear to reference vertices in the corresponding point arrays, allowing
|
||||||
|
// the renderer to construct the 3D mesh.
|
||||||
func (dec *Decoder) readTriangles(model *Model, count int, counts []uint16) error {
|
func (dec *Decoder) readTriangles(model *Model, count int, counts []uint16) error {
|
||||||
|
// Process each array of triangles
|
||||||
for i := 0; i < count; i++ {
|
for i := 0; i < count; i++ {
|
||||||
|
// Get the number of triangle indices in this array
|
||||||
cnt := int(counts[i])
|
cnt := int(counts[i])
|
||||||
var triangles []uint16
|
var triangles []uint16
|
||||||
|
|
||||||
|
// Read each triangle index
|
||||||
for j := 0; j < cnt; j++ {
|
for j := 0; j < cnt; j++ {
|
||||||
n, err := dec.readUInt16()
|
n, err := dec.readUInt16()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -420,70 +613,160 @@ func (dec *Decoder) readTriangles(model *Model, count int, counts []uint16) erro
|
|||||||
}
|
}
|
||||||
triangles = append(triangles, n)
|
triangles = append(triangles, n)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Add the array of triangles to the model
|
||||||
model.Triangles = append(model.Triangles, triangles)
|
model.Triangles = append(model.Triangles, triangles)
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// readTextureCoordinates reads the texture coordinate data from the .3db file.
|
||||||
|
// Texture coordinates (also known as UV coordinates) define how 2D textures are mapped
|
||||||
|
// onto the 3D model's surface. Each texture coordinate is a 2D point (u,v) that maps
|
||||||
|
// a vertex of the 3D model to a specific point on the texture image.
|
||||||
|
//
|
||||||
|
// The function reads 'count' arrays of texture coordinates, where each array contains
|
||||||
|
// a variable number of coordinates as specified in the 'counts' slice. Each coordinate
|
||||||
|
// consists of two 32-bit floating point values (u,v) that range from 0.0 to 1.0,
|
||||||
|
// representing relative positions on the texture image:
|
||||||
|
// - u: Horizontal position (0.0 = left edge, 1.0 = right edge)
|
||||||
|
// - v: Vertical position (0.0 = top edge, 1.0 = bottom edge)
|
||||||
|
//
|
||||||
|
// Texture mapping is a critical part of 3D rendering as it allows detailed 2D images
|
||||||
|
// to be applied to 3D surfaces, giving models realistic appearance without requiring
|
||||||
|
// extremely complex geometry.
|
||||||
func (dec *Decoder) readTextureCoordinates(model *Model, count int, counts []uint16) error {
|
func (dec *Decoder) readTextureCoordinates(model *Model, count int, counts []uint16) error {
|
||||||
|
// Process each array of texture coordinates
|
||||||
for i := 0; i < count; i++ {
|
for i := 0; i < count; i++ {
|
||||||
|
// Get the number of texture coordinates in this array
|
||||||
cnt := int(counts[i])
|
cnt := int(counts[i])
|
||||||
var cords []Coordinate
|
var cords []Coordinate
|
||||||
|
|
||||||
|
// Read each texture coordinate (u,v pair)
|
||||||
for j := 0; j < cnt; j++ {
|
for j := 0; j < cnt; j++ {
|
||||||
cord := Coordinate{}
|
cord := Coordinate{}
|
||||||
|
|
||||||
|
// Read the u coordinate (horizontal position on texture)
|
||||||
u, err := dec.readFloat32()
|
u, err := dec.readFloat32()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Read the v coordinate (vertical position on texture)
|
||||||
v, err := dec.readFloat32()
|
v, err := dec.readFloat32()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Set the coordinate values and add to the array
|
||||||
cord.Set(u, v)
|
cord.Set(u, v)
|
||||||
cords = append(cords, cord)
|
cords = append(cords, cord)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Add the array of texture coordinates to the model
|
||||||
model.TextureCoordinates = append(model.TextureCoordinates, cords)
|
model.TextureCoordinates = append(model.TextureCoordinates, cords)
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// readPoint reads the vertex data from the .3db file.
|
||||||
|
// Points (or vertices) are the fundamental 3D coordinates that define the shape of the model.
|
||||||
|
// Each point is a 3D vector with x, y, and z coordinates that specify its position in 3D space.
|
||||||
|
//
|
||||||
|
// The function reads 'count' arrays of points, where each array contains a variable
|
||||||
|
// number of vertices as specified in the 'counts' slice. Interestingly, in the .3db format,
|
||||||
|
// each coordinate is stored as a 16-bit unsigned integer (0-65535) rather than a floating point,
|
||||||
|
// which is then normalized to a floating point value between 0.0 and 1.0 by dividing by 0xFFFF (65535).
|
||||||
|
//
|
||||||
|
// This compression technique reduces file size while maintaining reasonable precision.
|
||||||
|
// The actual world-space coordinates are likely calculated by applying transformations
|
||||||
|
// (scaling, rotation, translation) to these normalized coordinates during rendering.
|
||||||
|
//
|
||||||
|
// The normalization formula is:
|
||||||
|
//
|
||||||
|
// float_value = uint16_value / 65535.0
|
||||||
|
//
|
||||||
|
// This gives a value in the range [0.0, 1.0] which can later be transformed to the
|
||||||
|
// appropriate scale and position in the 3D world.
|
||||||
func (dec *Decoder) readPoint(model *Model, count int, counts []uint16) error {
|
func (dec *Decoder) readPoint(model *Model, count int, counts []uint16) error {
|
||||||
|
// Process each array of points (vertices)
|
||||||
for i := 0; i < count; i++ {
|
for i := 0; i < count; i++ {
|
||||||
|
// Get the number of points in this array
|
||||||
cnt := int(counts[i])
|
cnt := int(counts[i])
|
||||||
var vectors []Vector
|
var vectors []Vector
|
||||||
|
|
||||||
|
// Read each point (3D vertex)
|
||||||
for j := 0; j < cnt; j++ {
|
for j := 0; j < cnt; j++ {
|
||||||
vec := Vector{}
|
vec := Vector{}
|
||||||
|
|
||||||
|
// Read the x coordinate as a 16-bit unsigned integer
|
||||||
ux, err := dec.readUInt16()
|
ux, err := dec.readUInt16()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Read the y coordinate as a 16-bit unsigned integer
|
||||||
uy, err := dec.readUInt16()
|
uy, err := dec.readUInt16()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Read the z coordinate as a 16-bit unsigned integer
|
||||||
uz, err := dec.readUInt16()
|
uz, err := dec.readUInt16()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Convert the integer coordinates to normalized floating point values [0.0, 1.0]
|
||||||
|
// by dividing by the maximum 16-bit value (0xFFFF = 65535)
|
||||||
vec.Set(float32(ux)/float32(0xffff), float32(uy)/float32(0xffff), float32(uz)/float32(0xffff))
|
vec.Set(float32(ux)/float32(0xffff), float32(uy)/float32(0xffff), float32(uz)/float32(0xffff))
|
||||||
vectors = append(vectors, vec)
|
vectors = append(vectors, vec)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Add the array of points to the model
|
||||||
model.Points = append(model.Points, vectors)
|
model.Points = append(model.Points, vectors)
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// readBrightness reads the brightness (lighting) data from the .3db file.
|
||||||
|
// Brightness values represent the lighting or shading information for each vertex
|
||||||
|
// in the model. This allows for pre-calculated lighting effects that don't need
|
||||||
|
// to be computed at runtime, which was important for older 3D engines with limited
|
||||||
|
// processing power.
|
||||||
|
//
|
||||||
|
// The function reads 'count' arrays of brightness values, where each array contains
|
||||||
|
// a variable number of values as specified in the 'counts' slice. Each brightness
|
||||||
|
// value is stored as an 8-bit unsigned integer (0-255), where:
|
||||||
|
// - 0 represents complete darkness (black)
|
||||||
|
// - 255 represents maximum brightness (white)
|
||||||
|
// - Values in between represent varying levels of gray
|
||||||
|
//
|
||||||
|
// These brightness values are typically used during rendering to modulate the color
|
||||||
|
// of each vertex, creating lighting effects like shadows, highlights, and ambient
|
||||||
|
// occlusion. The values might be applied directly to vertex colors or used as
|
||||||
|
// multipliers for texture colors.
|
||||||
|
//
|
||||||
|
// This approach to lighting was common in older 3D games where dynamic lighting
|
||||||
|
// was computationally expensive, so pre-calculated lighting was stored in the model.
|
||||||
func (dec *Decoder) readBrightness(model *Model, count int, counts []uint16) error {
|
func (dec *Decoder) readBrightness(model *Model, count int, counts []uint16) error {
|
||||||
|
// Process each array of brightness values
|
||||||
for i := 0; i < count; i++ {
|
for i := 0; i < count; i++ {
|
||||||
|
// Get the number of brightness values in this array
|
||||||
cnt := int(counts[i])
|
cnt := int(counts[i])
|
||||||
var brightness []byte
|
var brightness []byte
|
||||||
|
|
||||||
|
// Read each brightness value
|
||||||
for j := 0; j < cnt; j++ {
|
for j := 0; j < cnt; j++ {
|
||||||
|
// Read the brightness as an 8-bit unsigned integer (0-255)
|
||||||
b, err := dec.readUInt8()
|
b, err := dec.readUInt8()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
brightness = append(brightness, b)
|
brightness = append(brightness, b)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Add the array of brightness values to the model
|
||||||
model.Brightness = append(model.Brightness, brightness)
|
model.Brightness = append(model.Brightness, brightness)
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user