296 lines
9.2 KiB
Go
296 lines
9.2 KiB
Go
package assets
|
|
|
|
import (
|
|
"fmt"
|
|
"os"
|
|
|
|
"gitea.boner.be/bdnugget/goonscape/types"
|
|
rl "github.com/gen2brain/raylib-go/raylib"
|
|
)
|
|
|
|
// ModelLoader handles loading and fallback for 3D models
|
|
type ModelLoader struct {
|
|
safeMode bool
|
|
}
|
|
|
|
// NewModelLoader creates a new model loader instance
|
|
func NewModelLoader() *ModelLoader {
|
|
return &ModelLoader{
|
|
safeMode: os.Getenv("GOONSCAPE_SAFE_MODE") == "1",
|
|
}
|
|
}
|
|
|
|
// IsSafeMode returns if we should avoid loading external models
|
|
func (ml *ModelLoader) IsSafeMode() bool {
|
|
return ml.safeMode || os.Getenv("GOONSCAPE_SAFE_MODE") == "1"
|
|
}
|
|
|
|
// LoadModel attempts to load a model, returning a placeholder if it fails
|
|
func (ml *ModelLoader) LoadModel(fileName string, fallbackShape int, fallbackColor rl.Color) (rl.Model, bool, rl.Color) {
|
|
// Don't even try to load external models in safe mode
|
|
if ml.IsSafeMode() {
|
|
rl.TraceLog(rl.LogInfo, "Safe mode enabled, using primitive shape instead of %s", fileName)
|
|
return ml.createPrimitiveShape(fallbackShape), false, fallbackColor
|
|
}
|
|
|
|
defer func() {
|
|
// Recover from any panics during model loading
|
|
if r := recover(); r != nil {
|
|
rl.TraceLog(rl.LogError, "Panic in LoadModel: %v", r)
|
|
}
|
|
}()
|
|
|
|
// Try to load the model
|
|
model := rl.LoadModel(fileName)
|
|
|
|
// Check if the model is valid
|
|
if model.Meshes == nil || model.Meshes.VertexCount <= 0 {
|
|
rl.TraceLog(rl.LogWarning, "Failed to load model %s, using placeholder", fileName)
|
|
return ml.createPrimitiveShape(fallbackShape), false, fallbackColor
|
|
}
|
|
|
|
// For real models, return zero color since we don't need it
|
|
return model, true, rl.Color{}
|
|
}
|
|
|
|
// createPrimitiveShape creates a simple shape without loading external models
|
|
func (ml *ModelLoader) createPrimitiveShape(shapeType int) rl.Model {
|
|
var mesh rl.Mesh
|
|
|
|
switch shapeType {
|
|
case 0: // Cube
|
|
mesh = rl.GenMeshCube(1.0, 2.0, 1.0)
|
|
case 1: // Sphere
|
|
mesh = rl.GenMeshSphere(1.0, 8, 8)
|
|
case 2: // Cylinder
|
|
mesh = rl.GenMeshCylinder(0.8, 2.0, 8)
|
|
case 3: // Cone
|
|
mesh = rl.GenMeshCone(1.0, 2.0, 8)
|
|
default: // Default to cube
|
|
mesh = rl.GenMeshCube(1.0, 2.0, 1.0)
|
|
}
|
|
|
|
model := rl.LoadModelFromMesh(mesh)
|
|
return model
|
|
}
|
|
|
|
// Helper function to load animations for a model
|
|
func loadModelAnimations(animPaths map[string]string) (types.AnimationSet, error) {
|
|
var animSet types.AnimationSet
|
|
|
|
// Only try to load animations if environment variable isn't set
|
|
if os.Getenv("GOONSCAPE_DISABLE_ANIMATIONS") == "1" {
|
|
return animSet, nil
|
|
}
|
|
|
|
// Load idle animations if specified
|
|
if idlePath, ok := animPaths["idle"]; ok {
|
|
idleAnims := rl.LoadModelAnimations(idlePath)
|
|
if len(idleAnims) > 0 {
|
|
animSet.Idle = idleAnims
|
|
rl.TraceLog(rl.LogInfo, "Loaded idle animation: %s (%d frames, %f seconds)",
|
|
idlePath, idleAnims[0].FrameCount, float32(idleAnims[0].FrameCount)/60.0)
|
|
}
|
|
}
|
|
|
|
// Load walk animations if specified
|
|
if walkPath, ok := animPaths["walk"]; ok {
|
|
walkAnims := rl.LoadModelAnimations(walkPath)
|
|
if len(walkAnims) > 0 {
|
|
animSet.Walk = walkAnims
|
|
rl.TraceLog(rl.LogInfo, "Loaded walk animation: %s (%d frames, %f seconds)",
|
|
walkPath, walkAnims[0].FrameCount, float32(walkAnims[0].FrameCount)/60.0)
|
|
}
|
|
}
|
|
|
|
return animSet, nil
|
|
}
|
|
|
|
// ValidateModel checks if a model is valid and properly loaded
|
|
func ValidateModel(model rl.Model) error {
|
|
if model.Meshes == nil {
|
|
return fmt.Errorf("model has nil meshes")
|
|
}
|
|
if model.Meshes.VertexCount <= 0 {
|
|
return fmt.Errorf("model has invalid vertex count")
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// CompletelyAvoidExternalModels determines if we should avoid loading external models
|
|
func CompletelyAvoidExternalModels() bool {
|
|
return os.Getenv("GOONSCAPE_SAFE_MODE") == "1"
|
|
}
|
|
|
|
// SafeLoadModel attempts to load a model, returning a placeholder if it fails
|
|
func SafeLoadModel(fileName string, fallbackShape int, fallbackColor rl.Color) (rl.Model, bool, rl.Color) {
|
|
loader := NewModelLoader()
|
|
return loader.LoadModel(fileName, fallbackShape, fallbackColor)
|
|
}
|
|
|
|
func LoadModels() ([]types.ModelAsset, error) {
|
|
// Force safe mode for now until we fix the segfault
|
|
os.Setenv("GOONSCAPE_SAFE_MODE", "1")
|
|
|
|
models := make([]types.ModelAsset, 0, 3)
|
|
modelLoader := NewModelLoader()
|
|
|
|
// Colors for the different models
|
|
goonerColor := rl.Color{R: 255, G: 200, B: 200, A: 255} // Pinkish
|
|
coomerColor := rl.Color{R: 200, G: 230, B: 255, A: 255} // Light blue
|
|
shrekeColor := rl.Color{R: 180, G: 255, B: 180, A: 255} // Light green
|
|
|
|
// If in safe mode, create all models directly without loading
|
|
if modelLoader.IsSafeMode() {
|
|
// Gooner model (cube)
|
|
cube := modelLoader.createPrimitiveShape(0)
|
|
models = append(models, types.ModelAsset{
|
|
Model: cube,
|
|
YOffset: 0.0,
|
|
PlaceholderColor: goonerColor,
|
|
})
|
|
|
|
// Coomer model (sphere)
|
|
sphere := modelLoader.createPrimitiveShape(1)
|
|
models = append(models, types.ModelAsset{
|
|
Model: sphere,
|
|
YOffset: -4.0,
|
|
PlaceholderColor: coomerColor,
|
|
})
|
|
|
|
// Shreke model (cylinder)
|
|
cylinder := modelLoader.createPrimitiveShape(2)
|
|
models = append(models, types.ModelAsset{
|
|
Model: cylinder,
|
|
YOffset: 0.0,
|
|
PlaceholderColor: shrekeColor,
|
|
})
|
|
|
|
return models, nil
|
|
}
|
|
|
|
// The rest of the function with normal model loading
|
|
// Load Goonion model with error handling
|
|
var goonerModel rl.Model
|
|
var success bool
|
|
var modelColor rl.Color
|
|
|
|
goonerModel, success, modelColor = modelLoader.LoadModel("resources/models/gooner/walk_no_y_transform.glb", 0, goonerColor)
|
|
|
|
// Create animations only if model was loaded successfully
|
|
var goonerAnims types.AnimationSet
|
|
if success {
|
|
goonerAnims, _ = loadModelAnimations(map[string]string{
|
|
"idle": "resources/models/gooner/idle_no_y_transform.glb",
|
|
"walk": "resources/models/gooner/walk_no_y_transform.glb",
|
|
})
|
|
|
|
// Apply transformations
|
|
transform := rl.MatrixIdentity()
|
|
transform = rl.MatrixMultiply(transform, rl.MatrixRotateY(180*rl.Deg2rad))
|
|
transform = rl.MatrixMultiply(transform, rl.MatrixRotateX(-90*rl.Deg2rad))
|
|
transform = rl.MatrixMultiply(transform, rl.MatrixScale(1.0, 1.0, 1.0))
|
|
goonerModel.Transform = transform
|
|
}
|
|
|
|
// Always add a model (real or placeholder)
|
|
models = append(models, types.ModelAsset{
|
|
Model: goonerModel,
|
|
Animation: append(goonerAnims.Idle, goonerAnims.Walk...),
|
|
AnimFrames: int32(len(goonerAnims.Idle) + len(goonerAnims.Walk)),
|
|
Animations: goonerAnims,
|
|
YOffset: 0.0,
|
|
PlaceholderColor: modelColor,
|
|
})
|
|
|
|
// Coomer model with safe loading - using a sphere shape
|
|
var coomerModel rl.Model
|
|
coomerModel, success, modelColor = modelLoader.LoadModel("resources/models/coomer/idle_notransy.glb", 1, coomerColor)
|
|
|
|
if success {
|
|
// Only load animations if the model loaded successfully
|
|
coomerAnims, _ := loadModelAnimations(map[string]string{
|
|
"idle": "resources/models/coomer/idle_notransy.glb",
|
|
"walk": "resources/models/coomer/unsteadywalk_notransy.glb",
|
|
})
|
|
|
|
// Apply transformations
|
|
transform := rl.MatrixIdentity()
|
|
transform = rl.MatrixMultiply(transform, rl.MatrixRotateY(180*rl.Deg2rad))
|
|
transform = rl.MatrixMultiply(transform, rl.MatrixRotateX(-90*rl.Deg2rad))
|
|
transform = rl.MatrixMultiply(transform, rl.MatrixScale(1.0, 1.0, 1.0))
|
|
coomerModel.Transform = transform
|
|
|
|
models = append(models, types.ModelAsset{
|
|
Model: coomerModel,
|
|
Animation: append(coomerAnims.Idle, coomerAnims.Walk...),
|
|
AnimFrames: int32(len(coomerAnims.Idle) + len(coomerAnims.Walk)),
|
|
Animations: coomerAnims,
|
|
YOffset: -4.0,
|
|
PlaceholderColor: rl.Color{}, // Not a placeholder
|
|
})
|
|
} else {
|
|
// Add a placeholder with different shape/color
|
|
models = append(models, types.ModelAsset{
|
|
Model: coomerModel,
|
|
YOffset: -4.0,
|
|
PlaceholderColor: modelColor,
|
|
})
|
|
}
|
|
|
|
// Shreke model with safe loading - using a cylinder shape
|
|
var shrekeModel rl.Model
|
|
shrekeModel, success, modelColor = modelLoader.LoadModel("resources/models/shreke.obj", 2, shrekeColor)
|
|
|
|
if success {
|
|
// Only proceed with texture if model loaded
|
|
shrekeTexture := rl.LoadTexture("resources/models/shreke.png")
|
|
if shrekeTexture.ID <= 0 {
|
|
rl.TraceLog(rl.LogWarning, "Failed to load shreke texture")
|
|
} else {
|
|
rl.SetMaterialTexture(shrekeModel.Materials, rl.MapDiffuse, shrekeTexture)
|
|
|
|
models = append(models, types.ModelAsset{
|
|
Model: shrekeModel,
|
|
Texture: shrekeTexture,
|
|
YOffset: 0.0,
|
|
PlaceholderColor: rl.Color{}, // Not a placeholder
|
|
})
|
|
}
|
|
} else {
|
|
// Add another placeholder with different shape/color
|
|
models = append(models, types.ModelAsset{
|
|
Model: shrekeModel,
|
|
YOffset: 0.0,
|
|
PlaceholderColor: modelColor,
|
|
})
|
|
}
|
|
|
|
if len(models) == 0 {
|
|
return nil, fmt.Errorf("failed to load any models")
|
|
}
|
|
|
|
return models, nil
|
|
}
|
|
|
|
func LoadMusic(filename string) (rl.Music, error) {
|
|
defer func() {
|
|
// Recover from any panics during music loading
|
|
if r := recover(); r != nil {
|
|
rl.TraceLog(rl.LogError, "Panic in LoadMusic: %v", r)
|
|
}
|
|
}()
|
|
|
|
// Skip loading music if environment variable is set
|
|
if os.Getenv("GOONSCAPE_DISABLE_AUDIO") == "1" {
|
|
rl.TraceLog(rl.LogInfo, "Audio disabled, skipping music loading")
|
|
return rl.Music{}, fmt.Errorf("audio disabled")
|
|
}
|
|
|
|
music := rl.LoadMusicStream(filename)
|
|
if music.Stream.Buffer == nil {
|
|
return music, fmt.Errorf("failed to load music: %s", filename)
|
|
}
|
|
return music, nil
|
|
}
|