feat: initial commit
This commit is contained in:
commit
1b81917307
17 changed files with 1565 additions and 0 deletions
230
internal/service/merger.go
Normal file
230
internal/service/merger.go
Normal file
|
|
@ -0,0 +1,230 @@
|
|||
package service
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
|
||||
"github.com/hytale-tools/blockymodel-merger/pkg/blockymodel"
|
||||
"github.com/hytale-tools/blockymodel-merger/pkg/character"
|
||||
"github.com/hytale-tools/blockymodel-merger/pkg/export"
|
||||
"github.com/hytale-tools/blockymodel-merger/pkg/merger"
|
||||
"github.com/hytale-tools/blockymodel-merger/pkg/registry"
|
||||
"github.com/hytale-tools/blockymodel-merger/pkg/texture"
|
||||
)
|
||||
|
||||
const (
|
||||
basePath = "assets/Characters/Player.blockymodel"
|
||||
baseTexturePath = "Characters/Player_Textures/Player_Greyscale.png"
|
||||
)
|
||||
|
||||
// MergeService handles character merging operations
|
||||
type MergeService struct {
|
||||
registry *registry.Registry
|
||||
gradientSets *texture.GradientSets
|
||||
baseModel *blockymodel.BlockyModel
|
||||
}
|
||||
|
||||
// MergeResult contains the results of a merge operation
|
||||
type MergeResult struct {
|
||||
Model *blockymodel.BlockyModel
|
||||
Atlas *texture.Atlas
|
||||
GLBBytes []byte
|
||||
}
|
||||
|
||||
// NewMergeService creates a new merge service with all required data loaded
|
||||
func NewMergeService() (*MergeService, error) {
|
||||
// Load gradient sets for tinting
|
||||
gradientSets, err := texture.LoadGradientSets()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("loading gradient sets: %w", err)
|
||||
}
|
||||
|
||||
// Load accessory registry
|
||||
reg, err := registry.Load()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("loading registry: %w", err)
|
||||
}
|
||||
|
||||
// Load base player model
|
||||
baseModel, err := blockymodel.Load(basePath)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("loading base model: %w", err)
|
||||
}
|
||||
|
||||
return &MergeService{
|
||||
registry: reg,
|
||||
gradientSets: gradientSets,
|
||||
baseModel: baseModel,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// MergeFromJSON merges a character from JSON data and returns the result
|
||||
func (s *MergeService) MergeFromJSON(charJSON []byte) (*MergeResult, error) {
|
||||
// Parse character data
|
||||
var charData character.CharacterData
|
||||
if err := json.Unmarshal(charJSON, &charData); err != nil {
|
||||
return nil, fmt.Errorf("parsing character JSON: %w", err)
|
||||
}
|
||||
|
||||
// Resolve accessories
|
||||
result, err := charData.ResolveAccessories(s.registry)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("resolving accessories: %w", err)
|
||||
}
|
||||
|
||||
// Create merger from base model
|
||||
m, err := merger.New(s.baseModel)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("creating merger: %w", err)
|
||||
}
|
||||
|
||||
// Merge each accessory
|
||||
for _, acc := range result.Accessories {
|
||||
accessory, err := blockymodel.Load(acc.Path)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("loading accessory %s: %w", acc.Path, err)
|
||||
}
|
||||
|
||||
if err := m.Merge(accessory, acc.Spec.ID); err != nil {
|
||||
return nil, fmt.Errorf("merging accessory %s: %w", acc.Path, err)
|
||||
}
|
||||
}
|
||||
|
||||
// Get merged model
|
||||
mergedModel := m.Result()
|
||||
|
||||
// Process textures
|
||||
var tintedTextures []*texture.TintedTexture
|
||||
skinTone := charData.GetSkinTone()
|
||||
|
||||
// Load and tint base player texture
|
||||
if skinTone != "" {
|
||||
baseTinted, err := texture.ProcessAccessoryTexture(
|
||||
"_base",
|
||||
baseTexturePath,
|
||||
"Skin",
|
||||
skinTone,
|
||||
s.gradientSets,
|
||||
)
|
||||
if err == nil {
|
||||
tintedTextures = append(tintedTextures, baseTinted)
|
||||
}
|
||||
} else {
|
||||
baseImg, err := texture.LoadImage(baseTexturePath)
|
||||
if err == nil {
|
||||
baseTex := &texture.TintedTexture{
|
||||
Name: "_base",
|
||||
Image: baseImg,
|
||||
OriginalPath: baseTexturePath,
|
||||
}
|
||||
tintedTextures = append(tintedTextures, baseTex)
|
||||
}
|
||||
}
|
||||
|
||||
// Process accessory textures
|
||||
for _, acc := range result.Accessories {
|
||||
if acc.ResolvedTexture == nil {
|
||||
continue
|
||||
}
|
||||
|
||||
var tinted *texture.TintedTexture
|
||||
|
||||
if acc.ResolvedTexture.DirectTexture != "" {
|
||||
img, err := texture.LoadImage(acc.ResolvedTexture.DirectTexture)
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
tinted = &texture.TintedTexture{
|
||||
Name: acc.Spec.ID,
|
||||
Image: img,
|
||||
OriginalPath: acc.ResolvedTexture.DirectTexture,
|
||||
}
|
||||
} else if acc.ResolvedTexture.GreyscaleTexture != "" {
|
||||
var err error
|
||||
tinted, err = texture.ProcessAccessoryTexture(
|
||||
acc.Spec.ID,
|
||||
acc.ResolvedTexture.GreyscaleTexture,
|
||||
acc.ResolvedTexture.GradientSet,
|
||||
acc.Spec.Color,
|
||||
s.gradientSets,
|
||||
)
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
} else {
|
||||
continue
|
||||
}
|
||||
|
||||
tintedTextures = append(tintedTextures, tinted)
|
||||
}
|
||||
|
||||
// Pack textures into atlas
|
||||
var atlas *texture.Atlas
|
||||
if len(tintedTextures) > 0 {
|
||||
var err error
|
||||
atlas, err = texture.PackAtlasSimple(tintedTextures, 1)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("packing atlas: %w", err)
|
||||
}
|
||||
|
||||
// Update texture offsets in the merged model
|
||||
for _, tex := range tintedTextures {
|
||||
if tex.Name == "_base" {
|
||||
continue
|
||||
}
|
||||
|
||||
x, y, _, _, ok := atlas.GetPixelCoords(tex.Name)
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
|
||||
// Find all node IDs that came from this accessory
|
||||
nodeIDs := make(map[string]bool)
|
||||
for nodeID, accessoryID := range m.NodeSources {
|
||||
if accessoryID == tex.Name {
|
||||
nodeIDs[nodeID] = true
|
||||
}
|
||||
}
|
||||
|
||||
if len(nodeIDs) > 0 {
|
||||
offset := blockymodel.AtlasOffset{X: float64(x), Y: float64(y)}
|
||||
blockymodel.UpdateTextureOffsets(mergedModel.Nodes, nodeIDs, offset)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Export to GLB
|
||||
exporter := export.NewGLBExporter()
|
||||
|
||||
var materialIdx uint32
|
||||
if atlas != nil {
|
||||
w, h := atlas.Image.Bounds().Dx(), atlas.Image.Bounds().Dy()
|
||||
exporter.SetAtlasSize(float64(w), float64(h))
|
||||
|
||||
atlasBytes, err := texture.EncodePNG(atlas.Image)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("encoding atlas: %w", err)
|
||||
}
|
||||
|
||||
texIdx := exporter.AddTexture(atlasBytes)
|
||||
materialIdx = exporter.AddMaterial("textured", texIdx)
|
||||
} else {
|
||||
exporter.SetAtlasSize(64, 64)
|
||||
materialIdx = 0
|
||||
}
|
||||
|
||||
if err := exporter.ExportModel(mergedModel, materialIdx); err != nil {
|
||||
return nil, fmt.Errorf("exporting model: %w", err)
|
||||
}
|
||||
|
||||
glbBytes, err := exporter.Bytes()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("getting GLB bytes: %w", err)
|
||||
}
|
||||
|
||||
return &MergeResult{
|
||||
Model: mergedModel,
|
||||
Atlas: atlas,
|
||||
GLBBytes: glbBytes,
|
||||
}, nil
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue