Skip to content

Commit

Permalink
Rendering improvements (#57)
Browse files Browse the repository at this point in the history
  • Loading branch information
mokiat authored Sep 8, 2024
1 parent a852917 commit b67f309
Show file tree
Hide file tree
Showing 51 changed files with 3,028 additions and 1,566 deletions.
26 changes: 26 additions & 0 deletions game/asset/dsl/operation_light.go
Original file line number Diff line number Diff line change
Expand Up @@ -170,3 +170,29 @@ func SetRefractionTexture(textureProvider Provider[*mdl.Texture]) Operation {
},
)
}

// SetCastShadow configures the cast shadow of the target.
func SetCastShadow(castShadowProvider Provider[bool]) Operation {
return FuncOperation(
// apply function
func(target any) error {
castShadow, err := castShadowProvider.Get()
if err != nil {
return fmt.Errorf("error getting cast shadow: %w", err)
}

caster, ok := target.(mdl.ShadowCaster)
if !ok {
return fmt.Errorf("target %T is not a shadow caster", target)
}
caster.SetCastShadow(castShadow)

return nil
},

// digest function
func() ([]byte, error) {
return CreateDigest("set-cast-shadow", castShadowProvider)
},
)
}
12 changes: 7 additions & 5 deletions game/asset/dsl/provider_image.go
Original file line number Diff line number Diff line change
Expand Up @@ -130,14 +130,16 @@ func ResizedCubeImage(imageProvider Provider[*mdl.CubeImage], newSizeProvider Pr
// IrradianceCubeImage creates an irradiance cube image from the provided
// HDR skybox cube image.
func IrradianceCubeImage(imageProvider Provider[*mdl.CubeImage], opts ...Operation) Provider[*mdl.CubeImage] {
var cfg irradianceConfig
for _, opt := range opts {
opt.Apply(&cfg)
}

return OnceProvider(FuncProvider(
// get function
func() (*mdl.CubeImage, error) {
var cfg irradianceConfig
for _, opt := range opts {
if err := opt.Apply(&cfg); err != nil {
return nil, fmt.Errorf("failed to configure irradiance cube image: %w", err)
}
}

image, err := imageProvider.Get()
if err != nil {
return nil, fmt.Errorf("error getting image: %w", err)
Expand Down
46 changes: 23 additions & 23 deletions game/asset/dsl/provider_model.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ import (
"github.com/mokiat/lacking/game/asset/mdl"
"github.com/mokiat/lacking/util/gltfutil"
"github.com/qmuntal/gltf"
lightspunctual "github.com/qmuntal/gltf/ext/lightspuntual"
"github.com/qmuntal/gltf/ext/lightspunctual"
)

// CreateModel creates a new model with the specified name and operations.
Expand Down Expand Up @@ -55,7 +55,7 @@ func OpenGLTFModel(path string, opts ...Operation) Provider[*mdl.Model] {
var cfg openGLTFModelConfig
for _, opt := range opts {
if err := opt.Apply(&cfg); err != nil {
return nil, err
return nil, fmt.Errorf("failed to configure gltf model: %w", err)
}
}

Expand Down Expand Up @@ -116,17 +116,17 @@ func BuildModelResource(gltfDoc *gltf.Document, forceCollision bool) (*mdl.Model
model := &mdl.Model{}

// build images
imagesFromIndex := make(map[uint32]*mdl.Image)
imagesFromIndex := make(map[int]*mdl.Image)
for i, gltfImage := range gltfDoc.Images {
img, err := openGLTFImage(gltfDoc, gltfImage)
if err != nil {
return nil, fmt.Errorf("error loading image: %w", err)
}
imagesFromIndex[uint32(i)] = img
imagesFromIndex[i] = img
}

// build textures
texturesFromIndex := make(map[uint32]*mdl.Texture)
texturesFromIndex := make(map[int]*mdl.Texture)
for i, img := range imagesFromIndex {
texture := &mdl.Texture{}
texture.SetName(img.Name())
Expand All @@ -139,7 +139,7 @@ func BuildModelResource(gltfDoc *gltf.Document, forceCollision bool) (*mdl.Model
}

// build samplers
samplersFromIndex := make(map[uint32]*mdl.Sampler)
samplersFromIndex := make(map[int]*mdl.Sampler)
for i, gltfTexture := range gltfDoc.Textures {
sampler := &mdl.Sampler{}
if gltfTexture.Sampler != nil {
Expand Down Expand Up @@ -186,11 +186,11 @@ func BuildModelResource(gltfDoc *gltf.Document, forceCollision bool) (*mdl.Model
} else {
return nil, fmt.Errorf("texture source not set")
}
samplersFromIndex[uint32(i)] = sampler
samplersFromIndex[i] = sampler
}

// build materials
materialFromIndex := make(map[uint32]*mdl.Material)
materialFromIndex := make(map[int]*mdl.Material)
for i, gltfMaterial := range gltfDoc.Materials {
var (
color sprec.Vec4
Expand All @@ -199,9 +199,9 @@ func BuildModelResource(gltfDoc *gltf.Document, forceCollision bool) (*mdl.Model
normalScale float32
alphaThreshold float32

colorTextureIndex *uint32
metallicRoughnessTextureIndex *uint32
normalTextureIndex *uint32
colorTextureIndex *int
metallicRoughnessTextureIndex *int
normalTextureIndex *int
)

if gltfPBR := gltfMaterial.PBRMetallicRoughness; gltfPBR != nil {
Expand Down Expand Up @@ -288,12 +288,12 @@ func BuildModelResource(gltfDoc *gltf.Document, forceCollision bool) (*mdl.Model
material.SetSampler("normalSampler", samplersFromIndex[*normalTextureIndex])
}

materialFromIndex[uint32(i)] = material
materialFromIndex[i] = material
}

// build mesh definitions
meshDefinitionFromIndex := make(map[uint32]*mdl.MeshDefinition)
bodyDefinitionFromIndex := make(map[uint32]*mdl.BodyDefinition)
meshDefinitionFromIndex := make(map[int]*mdl.MeshDefinition)
bodyDefinitionFromIndex := make(map[int]*mdl.BodyDefinition)
for i, gltfMesh := range gltfDoc.Meshes {
bodyMaterial := mdl.NewBodyMaterial()
bodyDefinition := mdl.NewBodyDefinition(bodyMaterial)
Expand Down Expand Up @@ -448,17 +448,17 @@ func BuildModelResource(gltfDoc *gltf.Document, forceCollision bool) (*mdl.Model

meshDefinition.SetName(gltfMesh.Name)
meshDefinition.SetGeometry(geometry)
meshDefinitionFromIndex[uint32(i)] = meshDefinition
meshDefinitionFromIndex[i] = meshDefinition

if (geometry.Metadata().HasCollision() || forceCollision) && len(bodyDefinition.CollisionMeshes()) > 0 {
bodyDefinitionFromIndex[uint32(i)] = bodyDefinition
bodyDefinitionFromIndex[i] = bodyDefinition
}
}

// prepare armatures
armatureFromIndex := make(map[uint32]*mdl.Armature)
armatureFromIndex := make(map[int]*mdl.Armature)
for i := range gltfDoc.Skins {
armatureFromIndex[uint32(i)] = mdl.NewArmature()
armatureFromIndex[i] = mdl.NewArmature()
}

createPointLight := func(gltfLight *lightspunctual.Light) *mdl.PointLight {
Expand Down Expand Up @@ -544,9 +544,9 @@ func BuildModelResource(gltfDoc *gltf.Document, forceCollision bool) (*mdl.Model
}

// build nodes
nodeFromIndex := make(map[uint32]*mdl.Node)
var visitNode func(nodeIndex uint32) *mdl.Node
visitNode = func(nodeIndex uint32) *mdl.Node {
nodeFromIndex := make(map[int]*mdl.Node)
var visitNode func(nodeIndex int) *mdl.Node
visitNode = func(nodeIndex int) *mdl.Node {
gltfNode := gltfDoc.Nodes[nodeIndex]

node := mdl.NewNode(gltfNode.Name)
Expand Down Expand Up @@ -599,7 +599,7 @@ func BuildModelResource(gltfDoc *gltf.Document, forceCollision bool) (*mdl.Model

// finalize armatures (now that all nodes are available)
for i, gltfSkin := range gltfDoc.Skins {
armature := armatureFromIndex[uint32(i)]
armature := armatureFromIndex[i]
for j, gltfJoint := range gltfSkin.Joints {
joint := mdl.NewJoint()
joint.SetNode(nodeFromIndex[gltfJoint])
Expand All @@ -610,7 +610,7 @@ func BuildModelResource(gltfDoc *gltf.Document, forceCollision bool) (*mdl.Model

// prepare animations
for _, gltfAnimation := range gltfDoc.Animations {
bindingFromNodeIndex := make(map[uint32]*mdl.AnimationBinding)
bindingFromNodeIndex := make(map[int]*mdl.AnimationBinding)
animation := &mdl.Animation{}
animation.SetName(gltfAnimation.Name)
for _, gltfChannel := range gltfAnimation.Channels {
Expand Down
38 changes: 26 additions & 12 deletions game/asset/dsl/provider_texture.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,14 +10,16 @@ import (
// Create2DTexture creates a new 2D texture with the specified format and
// source image.
func Create2DTexture(imageProvider Provider[*mdl.Image], opts ...Operation) Provider[*mdl.Texture] {
var cfg textureConfig
for _, opt := range opts {
opt.Apply(&cfg)
}

return OnceProvider(FuncProvider(
// get function
func() (*mdl.Texture, error) {
var cfg textureConfig
for _, opt := range opts {
if err := opt.Apply(&cfg); err != nil {
return nil, fmt.Errorf("failed to configure 2D texture: %w", err)
}
}

image, err := imageProvider.Get()
if err != nil {
return nil, fmt.Errorf("failed to get image: %w", err)
Expand All @@ -27,7 +29,7 @@ func Create2DTexture(imageProvider Provider[*mdl.Image], opts ...Operation) Prov
texture.SetName(image.Name())
texture.SetKind(mdl.TextureKind2D)
texture.SetFormat(cfg.format.ValueOrDefault(mdl.TextureFormatRGBA8))
texture.SetGenerateMipmaps(true)
texture.SetGenerateMipmaps(cfg.mipmapping)
texture.Resize(image.Width(), image.Height())
texture.SetLayerImage(0, image)
return &texture, nil
Expand All @@ -43,14 +45,16 @@ func Create2DTexture(imageProvider Provider[*mdl.Image], opts ...Operation) Prov
// CreateCubeTexture creates a new cube texture with the specified format and
// source image.
func CreateCubeTexture(cubeImageProvider Provider[*mdl.CubeImage], opts ...Operation) Provider[*mdl.Texture] {
var cfg textureConfig
for _, opt := range opts {
opt.Apply(&cfg)
}

return OnceProvider(FuncProvider(
// get function
func() (*mdl.Texture, error) {
var cfg textureConfig
for _, opt := range opts {
if err := opt.Apply(&cfg); err != nil {
return nil, fmt.Errorf("failed to configure cube texture: %w", err)
}
}

cubeImage, err := cubeImageProvider.Get()
if err != nil {
return nil, fmt.Errorf("failed to get cube image: %w", err)
Expand All @@ -66,6 +70,7 @@ func CreateCubeTexture(cubeImageProvider Provider[*mdl.CubeImage], opts ...Opera
var texture mdl.Texture
texture.SetKind(mdl.TextureKindCube)
texture.SetFormat(cfg.format.ValueOrDefault(mdl.TextureFormatRGBA16F))
texture.SetGenerateMipmaps(cfg.mipmapping)
texture.Resize(frontImage.Width(), frontImage.Height())
texture.SetLayerImage(0, frontImage)
texture.SetLayerImage(1, rearImage)
Expand All @@ -84,11 +89,20 @@ func CreateCubeTexture(cubeImageProvider Provider[*mdl.CubeImage], opts ...Opera
}

type textureConfig struct {
format opt.T[mdl.TextureFormat]
format opt.T[mdl.TextureFormat]
mipmapping bool
}

func (c *textureConfig) SetFormat(format mdl.TextureFormat) {
c.format = opt.V(format)
}

func (c *textureConfig) Mipmapping() bool {
return c.mipmapping
}

func (c *textureConfig) SetMipmapping(mipmapping bool) {
c.mipmapping = mipmapping
}

var defaultCubeTextureProvider = CreateCubeTexture(defaultCubeImageProvider)
53 changes: 45 additions & 8 deletions game/controller.go
Original file line number Diff line number Diff line change
@@ -1,8 +1,6 @@
package game

import (
"time"

"github.com/mokiat/lacking/app"
"github.com/mokiat/lacking/debug/metric"
"github.com/mokiat/lacking/game/asset"
Expand All @@ -12,6 +10,11 @@ import (
"github.com/mokiat/lacking/util/async"
)

// NewController creates a new game controller that manages the lifecycle
// of a game engine. The controller will use the provided asset registry
// to load and manage assets. The provided shader collection will be used
// to render the game. The provided shader builder will be used to create
// new shaders when needed.
func NewController(registry *asset.Registry, shaders graphics.ShaderCollection, shaderBuilder graphics.ShaderBuilder) *Controller {
return &Controller{
registry: registry,
Expand All @@ -22,16 +25,24 @@ func NewController(registry *asset.Registry, shaders graphics.ShaderCollection,

var _ app.Controller = (*Controller)(nil)

// Controller is an implementation of the app.Controller interface which
// initializes a game engine and manages its lifecycle. Furthermore, it
// ensures that the game engine is updated and rendered on each frame.
type Controller struct {
app.NopController

registry *asset.Registry
shaders graphics.ShaderCollection
shaderBuilder graphics.ShaderBuilder

gfxEngine *graphics.Engine
ecsEngine *ecs.Engine
physicsEngine *physics.Engine
gfxOptions []graphics.Option
gfxEngine *graphics.Engine

ecsOptions []ecs.Option
ecsEngine *ecs.Engine

physicsOptions []physics.Option
physicsEngine *physics.Engine

window app.Window
ioWorker *async.Worker
Expand All @@ -40,19 +51,45 @@ type Controller struct {
viewport graphics.Viewport
}

// Registry returns the asset registry to be used by the game.
func (c *Controller) Registry() *asset.Registry {
return c.registry
}

// UseGraphicsOptions allows to specify options that will be used
// when initializing the graphics engine. This method should be
// called before the controller is initialized by the app framework.
func (c *Controller) UseGraphicsOptions(opts ...graphics.Option) {
c.gfxOptions = opts
}

// UseECSOptions allows to specify options that will be used
// when initializing the ECS engine. This method should be
// called before the controller is initialized by the app framework.
func (c *Controller) UseECSOptions(opts ...ecs.Option) {
c.ecsOptions = opts
}

// UsePhysicsOptions allows to specify options that will be used
// when initializing the physics engine. This method should be
// called before the controller is initialized by the app framework.
func (c *Controller) UsePhysicsOptions(opts ...physics.Option) {
c.physicsOptions = opts
}

// Engine returns the game engine that is managed by the controller.
//
// This method should only be called after the controller has been
// initialized by the app framework.
func (c *Controller) Engine() *Engine {
return c.engine
}

func (c *Controller) OnCreate(window app.Window) {
c.window = window
c.gfxEngine = graphics.NewEngine(window.RenderAPI(), c.shaders, c.shaderBuilder)
c.ecsEngine = ecs.NewEngine()
c.physicsEngine = physics.NewEngine(16 * time.Millisecond)
c.gfxEngine = graphics.NewEngine(window.RenderAPI(), c.shaders, c.shaderBuilder, c.gfxOptions...)
c.ecsEngine = ecs.NewEngine(c.ecsOptions...)
c.physicsEngine = physics.NewEngine(c.physicsOptions...)

c.ioWorker = async.NewWorker(4)
go c.ioWorker.ProcessAll()
Expand Down
2 changes: 1 addition & 1 deletion game/ecs/engine.go
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
package ecs

// NewEngine creates a new ECS engine.
func NewEngine() *Engine {
func NewEngine(opts ...Option) *Engine {
return &Engine{}
}

Expand Down
7 changes: 7 additions & 0 deletions game/ecs/option.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
package ecs

// Option is a configuration function that can be used to customize the
// behavior of the ECS engine.
type Option func(*config)

type config struct{}
Loading

0 comments on commit b67f309

Please sign in to comment.