Compare commits

..

No commits in common. "2413c7d3d4125d9bf3d7b502b136cd47a6dc6e6b" and "27afe594fe59c38651fa7959b63b561d2d9f829d" have entirely different histories.

36 changed files with 209 additions and 632 deletions

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.0 KiB

View File

@ -1,74 +0,0 @@
tile-dirt: images/tile_dirt.png
tile-grass: images/tile_grass.png
tile-snow: images/tile_snow.png
cactus-small-1: images/cactus_short_NE.png
cactus-small-2: images/cactus_short_NW.png
cactus-small-3: images/cactus_short_SW.png
cactus-small-4: images/cactus_short_SE.png
cactus-tall-1: images/cactus_tall_NE.png
cactus-tall-2: images/cactus_tall_NW.png
cactus-tall-3: images/cactus_tall_SW.png
cactus-tall-4: images/cactus_tall_SE.png
tree-fat-1: images/tree_fat_NE.png
tree-fat-2: images/tree_fat_NW.png
tree-fat-3: images/tree_fat_SW.png
tree-fat-4: images/tree_fat_SE.png
tree-pine-1: images/tree_pineDefaultA_NE.png
tree-pine-2: images/tree_pineDefaultA_NW.png
tree-pine-3: images/tree_pineDefaultA_SW.png
tree-pine-4: images/tree_pineDefaultA_SE.png
grass-small-1: images/grass_NE.png
grass-small-2: images/grass_NW.png
grass-small-3: images/grass_SW.png
grass-small-4: images/grass_SE.png
grass-leafs-1: images/grass_leafs_NE.png
grass-leafs-2: images/grass_leafs_NW.png
grass-leafs-3: images/grass_leafs_SW.png
grass-leafs-4: images/grass_leafs_SE.png
bush-small-1: images/plant_bushSmall_NE.png
bush-small-2: images/plant_bushSmall_NW.png
bush-small-3: images/plant_bushSmall_SW.png
bush-small-4: images/plant_bushSmall_SE.png
bush-large-1: images/plant_bushLarge_NE.png
bush-large-2: images/plant_bushLarge_NW.png
bush-large-3: images/plant_bushLarge_SW.png
bush-large-4: images/plant_bushLarge_SE.png
bush-large-1: images/plant_bushLarge_NE.png
bush-large-2: images/plant_bushLarge_NW.png
bush-large-3: images/plant_bushLarge_SW.png
bush-large-4: images/plant_bushLarge_SE.png
flower-poppy-1: images/flower_yellowC_NE.png
flower-poppy-2: images/flower_yellowC_NW.png
flower-poppy-3: images/flower_yellowC_SW.png
flower-poppy-4: images/flower_yellowC_SE.png
flower-red-c-1: images/flower_redC_NE.png
flower-red-c-2: images/flower_redC_NW.png
flower-red-c-3: images/flower_redC_SW.png
flower-red-c-4: images/flower_redC_SE.png
flower-red-a-1: images/flower_redA_NE.png
flower-red-a-2: images/flower_redA_NW.png
flower-red-a-3: images/flower_redA_SW.png
flower-red-a-4: images/flower_redA_SE.png
flower-purple-a-1: images/flower_purpleA_NE.png
flower-purple-a-2: images/flower_purpleA_NW.png
flower-purple-a-3: images/flower_purpleA_SW.png
flower-purple-a-4: images/flower_purpleA_SE.png
flower-purple-c-1: images/flower_purpleC_NE.png
flower-purple-c-2: images/flower_purpleC_NW.png
flower-purple-c-3: images/flower_purpleC_SW.png
flower-purple-c-4: images/flower_purpleC_SE.png

View File

@ -47,18 +47,10 @@ func run() error {
Y: sdl.WINDOWPOS_UNDEFINED, Y: sdl.WINDOWPOS_UNDEFINED,
} }
} }
if ctx.Settings.Window.Size == nil {
ctx.Settings.Window.Size = &tins2020.Point{
X: 800,
Y: 600,
}
}
sdl.SetHint(sdl.HINT_RENDER_SCALE_QUALITY, "1") sdl.SetHint(sdl.HINT_RENDER_SCALE_QUALITY, "1")
window, err := sdl.CreateWindow("TINS 2020", window, err := sdl.CreateWindow("TINS 2020", ctx.Settings.Window.Location.X, ctx.Settings.Window.Location.Y,
ctx.Settings.Window.Location.X, ctx.Settings.Window.Location.Y, 800, 600, sdl.WINDOW_SHOWN|sdl.WINDOW_RESIZABLE)
ctx.Settings.Window.Size.X, ctx.Settings.Window.Size.Y,
sdl.WINDOW_SHOWN|sdl.WINDOW_RESIZABLE)
if err != nil { if err != nil {
return err return err
} }
@ -72,31 +64,62 @@ func run() error {
ctx.Init(renderer) ctx.Init(renderer)
err = ctx.Fonts.LoadDesc( err = ctx.Fonts.Load("default", "fonts/OpenSans-Regular.ttf", 12)
tins2020.FontDescriptor{Name: "debug", Path: "fonts/OpenSans-Regular.ttf", Size: 12},
tins2020.FontDescriptor{Name: "default", Path: "fonts/FiraMono-Regular.ttf", Size: 10},
)
if err != nil { if err != nil {
return err return err
} }
textureLoader := tins2020.NewResourceLoader() err = ctx.Textures.Load(
err = textureLoader.LoadFromFile(&ctx.Resources, "textures.txt", func(name, content string) error { "tile-dirt", "images/tile_dirt.png",
return ctx.Textures.Load(name, content) "tile-grass", "images/tile_grass.png",
}) "tile-snow", "images/tile_snow.png",
"cactus-small-1", "images/cactus_short_NE.png",
"cactus-small-2", "images/cactus_short_NW.png",
"cactus-small-3", "images/cactus_short_SW.png",
"cactus-small-4", "images/cactus_short_SE.png",
"cactus-tall-1", "images/cactus_tall_NE.png",
"cactus-tall-2", "images/cactus_tall_NW.png",
"cactus-tall-3", "images/cactus_tall_SW.png",
"cactus-tall-4", "images/cactus_tall_SE.png",
"tree-fat-1", "images/tree_fat_NE.png",
"tree-fat-2", "images/tree_fat_NW.png",
"tree-fat-3", "images/tree_fat_SW.png",
"tree-fat-4", "images/tree_fat_SE.png",
"tree-pine-1", "images/tree_pineDefaultA_NE.png",
"tree-pine-2", "images/tree_pineDefaultA_NW.png",
"tree-pine-3", "images/tree_pineDefaultA_SW.png",
"tree-pine-4", "images/tree_pineDefaultA_SE.png",
"grass-small-1", "images/grass_NE.png",
"grass-small-2", "images/grass_NW.png",
"grass-small-3", "images/grass_SW.png",
"grass-small-4", "images/grass_SE.png",
"grass-leafs-1", "images/grass_leafs_NE.png",
"grass-leafs-2", "images/grass_leafs_NW.png",
"grass-leafs-3", "images/grass_leafs_SW.png",
"grass-leafs-4", "images/grass_leafs_SE.png",
"bush-small-1", "images/plant_bushSmall_NE.png",
"bush-small-2", "images/plant_bushSmall_NW.png",
"bush-small-3", "images/plant_bushSmall_SW.png",
"bush-small-4", "images/plant_bushSmall_SE.png",
"bush-large-1", "images/plant_bushLarge_NE.png",
"bush-large-2", "images/plant_bushLarge_NW.png",
"bush-large-3", "images/plant_bushLarge_SW.png",
"bush-large-4", "images/plant_bushLarge_SE.png",
)
if err != nil { if err != nil {
return err return err
} }
game := tins2020.NewGame() game := tins2020.NewGame()
control := tins2020.NewTerrainRenderer(game.Terrain)
app := tins2020.NewContainer() err = control.Init(ctx)
overlays := tins2020.NewContainer()
overlays.AddChild(&tins2020.FPS{})
content := tins2020.NewContainer()
app.AddChild(content)
app.AddChild(overlays)
content.AddChild(tins2020.NewTerrainRenderer(game.Terrain))
err = app.Init(ctx)
if err != nil { if err != nil {
return err return err
} }
@ -112,9 +135,6 @@ func run() error {
case sdl.WINDOWEVENT_MOVED: case sdl.WINDOWEVENT_MOVED:
x, y := window.GetPosition() x, y := window.GetPosition()
ctx.Settings.Window.Location = &tins2020.Point{X: x, Y: y} ctx.Settings.Window.Location = &tins2020.Point{X: x, Y: y}
case sdl.WINDOWEVENT_SIZE_CHANGED:
w, h := window.GetSize()
ctx.Settings.Window.Size = &tins2020.Point{X: w, Y: h}
} }
case *sdl.KeyboardEvent: case *sdl.KeyboardEvent:
switch e.Keysym.Sym { switch e.Keysym.Sym {
@ -122,9 +142,8 @@ func run() error {
ctx.Quit() ctx.Quit()
} }
} }
app.Handle(ctx, event) control.Handle(ctx, event)
} }
game.Update()
if ctx.ShouldQuit { if ctx.ShouldQuit {
break break
@ -132,7 +151,7 @@ func run() error {
renderer.SetDrawColor(0, 0, 0, 255) renderer.SetDrawColor(0, 0, 0, 255)
renderer.Clear() renderer.Clear()
app.Render(ctx) control.Render(ctx)
renderer.Present() renderer.Present()
} }
return nil return nil

View File

@ -1,39 +0,0 @@
package tins2020
import (
"github.com/veandco/go-sdl2/sdl"
)
type Container struct {
Children []Control
}
func NewContainer() *Container {
return &Container{}
}
func (c *Container) AddChild(child Control) {
c.Children = append(c.Children, child)
}
func (c *Container) Handle(ctx *Context, event sdl.Event) {
for _, child := range c.Children {
child.Handle(ctx, event)
}
}
func (c *Container) Render(ctx *Context) {
for _, child := range c.Children {
child.Render(ctx)
}
}
func (c *Container) Init(ctx *Context) error {
for _, child := range c.Children {
err := child.Init(ctx)
if err != nil {
return err
}
}
return nil
}

View File

@ -1,18 +0,0 @@
package tins2020
import "github.com/veandco/go-sdl2/sdl"
type Control interface {
Init(*Context) error
Handle(*Context, sdl.Event)
Render(*Context)
}
type ControlBase struct {
}
func (b *ControlBase) Handle(*Context, sdl.Event) {}
func (b *ControlBase) Render(*Context) {}
func (b *ControlBase) Init(*Context) error { return nil }

View File

@ -1,46 +0,0 @@
package tins2020
type Flower struct {
Traits FlowerTraits
}
type FlowerTraits struct {
Spread float32
Life float32
LifeModifier float32
Resistance FlowerResistance
}
func (t *FlowerTraits) UpdateModifier(temp, humid float32) {
mod := float32(1)
cold := temp * 2
mod *= Min32(1, Sqrt32(t.Resistance.Cold*t.Resistance.Cold+cold*cold))
hot := (1 - temp) * 2
mod *= Min32(1, Sqrt32(t.Resistance.Hot*t.Resistance.Hot+hot*hot))
dry := humid * 2
mod *= Min32(1, Sqrt32(t.Resistance.Dry*t.Resistance.Dry+dry*dry))
wet := (1 - humid) * 2
mod *= Min32(1, Sqrt32(t.Resistance.Wet*t.Resistance.Wet+wet*wet))
t.LifeModifier = mod
}
type FlowerResistance struct {
Cold float32 // 0 will die almost instantly, 1 is completely resistant.
Hot float32
Dry float32
Wet float32
}
// NewPoppyTraits creates the traits of a poppy, a very generic flower that thrives in a moderate climate.
func NewPoppyTraits() FlowerTraits {
return FlowerTraits{
Spread: 0.01,
Life: 0.9991,
Resistance: FlowerResistance{
Cold: 0.3,
Hot: 0.3,
Dry: 0.3,
Wet: 0.3,
},
}
}

View File

@ -1,66 +1,18 @@
package tins2020 package tins2020
import ( import (
"fmt"
"github.com/veandco/go-sdl2/sdl"
"github.com/veandco/go-sdl2/ttf" "github.com/veandco/go-sdl2/ttf"
"opslag.de/schobers/fs/vfs" "opslag.de/schobers/fs/vfs"
) )
type Font struct {
*ttf.Font
}
func (f *Font) Render(renderer *sdl.Renderer, text string, pos Point, color sdl.Color) (*Texture, error) {
surface, err := f.RenderUTF8Solid(text, color)
if err != nil {
return nil, err
}
defer surface.Free()
texture, err := NewTextureFromSurface(renderer, surface)
if err != nil {
return nil, err
}
return texture, nil
}
func (f *Font) RenderCopy(renderer *sdl.Renderer, text string, pos Point, color sdl.Color) error {
texture, err := f.Render(renderer, text, pos, color)
if err != nil {
return err
}
defer texture.Destroy()
texture.Copy(renderer, texture.RectOffset(pos))
return nil
}
type Fonts struct { type Fonts struct {
dir vfs.CopyDir dir vfs.CopyDir
fonts map[string]*Font fonts map[string]*ttf.Font
} }
func (f *Fonts) Init(dir vfs.CopyDir) { func (f *Fonts) Init(dir vfs.CopyDir) {
f.dir = dir f.dir = dir
f.fonts = map[string]*Font{} f.fonts = map[string]*ttf.Font{}
}
func (f *Fonts) Font(name string) *Font { return f.fonts[name] }
type FontDescriptor struct {
Name string
Path string
Size int
}
func (f *Fonts) LoadDesc(fonts ...FontDescriptor) error {
for _, desc := range fonts {
err := f.Load(desc.Name, desc.Path, desc.Size)
if err != nil {
return fmt.Errorf("error loading '%s'; error: %v", desc.Name, err)
}
}
return nil
} }
func (f *Fonts) Load(name, path string, size int) error { func (f *Fonts) Load(name, path string, size int) error {
@ -75,7 +27,7 @@ func (f *Fonts) Load(name, path string, size int) error {
if font, ok := f.fonts[name]; ok { if font, ok := f.fonts[name]; ok {
font.Close() font.Close()
} }
f.fonts[name] = &Font{font} f.fonts[name] = font
return nil return nil
} }

View File

@ -1,41 +0,0 @@
package tins2020
import (
"fmt"
"time"
"github.com/veandco/go-sdl2/sdl"
)
type FPS struct {
ControlBase
start time.Time
stamp time.Duration
slot int
ticks []int
total int
}
func (f *FPS) Init(*Context) error {
f.start = time.Now()
f.stamp = 0
f.ticks = make([]int, 51)
return nil
}
func (f *FPS) Render(ctx *Context) {
elapsed := time.Since(f.start)
stamp := elapsed / (20 * time.Millisecond)
for f.stamp < stamp {
f.total += f.ticks[f.slot]
f.slot = (f.slot + 1) % len(f.ticks)
f.total -= f.ticks[f.slot]
f.ticks[f.slot] = 0
f.stamp++
}
f.ticks[f.slot]++
font := ctx.Fonts.Font("debug")
font.RenderCopy(ctx.Renderer, fmt.Sprintf("FPS: %d", f.total), Pt(5, 5), sdl.Color{R: 255, G: 255, B: 255, A: 255})
}

109
game.go
View File

@ -2,15 +2,14 @@ package tins2020
import ( import (
"math/rand" "math/rand"
"time"
"opslag.de/schobers/geom/noise"
) )
type Game struct { type Game struct {
Money int
Terrain *Map Terrain *Map
Flowers []Flower
start time.Time Bees []Bee
lastUpdate time.Duration
} }
type Map struct { type Map struct {
@ -19,21 +18,55 @@ type Map struct {
Variant NoiseMap Variant NoiseMap
PlaceX NoiseMap // displacement map of props PlaceX NoiseMap // displacement map of props
PlaceY NoiseMap PlaceY NoiseMap
Flowers map[Point]Flower
} }
func (m *Map) AddFlower(pos Point, traits FlowerTraits) { type NoiseMap interface {
m.Flowers[pos] = m.NewFlower(pos, traits) Value(x, y int32) float64
} }
func (m *Map) NewFlower(pos Point, traits FlowerTraits) Flower { func NewNoiseMap(seed int64) NoiseMap {
flower := Flower{ return &noiseMap{
Traits: traits, noise: noise.NewPerlin(seed),
alpha: 2,
beta: 4,
harmonics: 2,
} }
temp, humid := float32(m.Temp.Value(pos.X, pos.Y)), float32(m.Humid.Value(pos.X, pos.Y)) }
flower.Traits.UpdateModifier(temp, humid)
return flower func NewRandomNoiseMap(seed int64) NoiseMap {
return &randomNoiseMap{noise.NewPerlin(seed)}
}
type noiseMap struct {
noise *noise.Perlin
alpha, beta float64
harmonics int
}
func clipNormalized(x float64) float64 {
if x < 0 {
return 0
}
if x > 1 {
return 1
}
return x
}
// Value generates the noise value for an x/y pair. The range of the output is approximately [-1.325825214,1.325825214] (for 4 harmonics).
func (m *noiseMap) Value(x, y int32) float64 {
value := m.noise.Noise2D(float64(x)*.01, float64(y)*.01, m.alpha, m.beta, m.harmonics)*.565 + .5
return clipNormalized(value)
}
type randomNoiseMap struct {
*noise.Perlin
}
// Value generates the noise value for an x/y pair. The range of the output is approximately [-1.325825214,1.325825214] (for 4 harmonics).
func (m randomNoiseMap) Value(x, y int32) float64 {
value := m.Noise2D(float64(x)*.53, float64(y)*.53, 1.01, 2, 2)*.5 + .5
return clipNormalized(value)
} }
func NewGame() *Game { func NewGame() *Game {
@ -43,52 +76,18 @@ func NewGame() *Game {
Variant: NewRandomNoiseMap(rand.Int63()), Variant: NewRandomNoiseMap(rand.Int63()),
PlaceX: NewRandomNoiseMap(rand.Int63()), PlaceX: NewRandomNoiseMap(rand.Int63()),
PlaceY: NewRandomNoiseMap(rand.Int63()), PlaceY: NewRandomNoiseMap(rand.Int63()),
Flowers: map[Point]Flower{},
} }
terrain.AddFlower(Pt(0, 0), NewPoppyTraits())
return &Game{ return &Game{
Money: 100,
Terrain: terrain, Terrain: terrain,
Flowers: []Flower{},
start: time.Now(), Bees: []Bee{},
} }
} }
func (g *Game) Update() { type Flower struct {
update := time.Since(g.start) Location PointF
for g.lastUpdate < update {
g.tick()
g.lastUpdate += time.Millisecond * 10
}
} }
func (g *Game) tick() { type Bee struct {
randomNeighbor := func(pos Point) Point { Location PointF
switch rand.Intn(4) {
case 0:
return Pt(pos.X-1, pos.Y)
case 1:
return Pt(pos.X, pos.Y-1)
case 2:
return Pt(pos.X+1, pos.Y)
case 3:
return Pt(pos.X, pos.Y+1)
}
return pos
}
flowers := map[Point]Flower{}
for pos, flower := range g.Terrain.Flowers {
if rand.Float32() < flower.Traits.Spread {
dst := randomNeighbor(pos)
if _, ok := g.Terrain.Flowers[dst]; !ok {
if _, ok := flowers[dst]; !ok {
flowers[dst] = g.Terrain.NewFlower(dst, flower.Traits)
}
}
}
if rand.Float32() < flower.Traits.Life*flower.Traits.LifeModifier {
flowers[pos] = flower
}
}
g.Terrain.Flowers = flowers
} }

37
math.go
View File

@ -1,37 +0,0 @@
package tins2020
import "math"
func Abs32(x float32) float32 {
if x < 0 {
return -x
}
return x
}
func AbsSub32(a, b float32) float32 {
if a > b {
return a - b
}
return b - a
}
func Ceil32(x float32) float32 { return float32(math.Ceil(float64(x))) }
func Floor32(x float32) float32 { return float32(math.Floor(float64(x))) }
func Max32(a, b float32) float32 {
if a > b {
return a
}
return b
}
func Min32(a, b float32) float32 {
if a < b {
return a
}
return b
}
func Sqrt32(x float32) float32 { return float32(math.Sqrt(float64(x))) }

View File

@ -1,52 +0,0 @@
package tins2020
import "opslag.de/schobers/geom/noise"
type NoiseMap interface {
Value(x, y int32) float64
}
func NewNoiseMap(seed int64) NoiseMap {
return &noiseMap{
noise: noise.NewPerlin(seed),
alpha: 2,
beta: 4,
harmonics: 2,
}
}
func NewRandomNoiseMap(seed int64) NoiseMap {
return &randomNoiseMap{noise.NewPerlin(seed)}
}
type noiseMap struct {
noise *noise.Perlin
alpha, beta float64
harmonics int
}
func clipNormalized(x float64) float64 {
if x < 0 {
return 0
}
if x > 1 {
return 1
}
return x
}
// Value generates the noise value for an x/y pair. The range of the output is approximately [-1.325825214,1.325825214] (for 4 harmonics).
func (m *noiseMap) Value(x, y int32) float64 {
value := m.noise.Noise2D(float64(x)*.01, float64(y)*.01, m.alpha, m.beta, m.harmonics)*.565 + .5
return clipNormalized(value)
}
type randomNoiseMap struct {
*noise.Perlin
}
// Value generates the noise value for an x/y pair. The range of the output is approximately [-1.325825214,1.325825214] (for 4 harmonics).
func (m randomNoiseMap) Value(x, y int32) float64 {
value := m.Noise2D(float64(x)*.53, float64(y)*.53, 1.01, 2, 2)*.5 + .5
return clipNormalized(value)
}

View File

@ -1,90 +0,0 @@
package tins2020
import (
"log"
"github.com/veandco/go-sdl2/sdl"
)
type projection struct {
center PointF
zoom float32
zoomInv float32
windowRect sdl.Rect
tileScreenDelta PointF
tileScreenDeltaInv PointF
tileScreenOffset Point
tileScreenSize Point
tileFitScreenSize Point
windowCenter Point
}
func newProjection() projection {
return projection{zoom: 1, tileScreenDelta: PtF(65, 32), tileScreenDeltaInv: PtF(1./65, 1./32)}
}
func (p *projection) mapToScreen(x, y int32) Point {
return p.mapToScreenF(float32(x), float32(y))
}
func (p *projection) mapToScreenF(x, y float32) Point {
translated := PtF(x-p.center.X, y-p.center.Y)
return Pt(p.windowCenter.X+int32((translated.X-translated.Y)*65*p.zoomInv), p.windowCenter.Y+int32((translated.X+translated.Y)*32*p.zoomInv))
}
func (p *projection) screenToMap(x, y int32) PointF {
pos := p.screenToMapRel(x-p.windowCenter.X, y-p.windowCenter.Y)
return p.center.Add(pos)
}
func (p *projection) screenToMapRel(x, y int32) PointF {
normX := p.zoom * float32(x)
normY := p.zoom * float32(y)
return PtF(.5*(p.tileScreenDeltaInv.X*normX+p.tileScreenDeltaInv.Y*normY), .5*(-p.tileScreenDeltaInv.X*normX+p.tileScreenDeltaInv.Y*normY))
}
func (p *projection) screenToTileFitRect(pos Point) *sdl.Rect {
return &sdl.Rect{X: pos.X - p.tileFitScreenSize.X, Y: pos.Y - p.tileFitScreenSize.Y, W: 2 * p.tileFitScreenSize.X, H: 2 * p.tileFitScreenSize.Y}
}
func (p *projection) screenToTileRect(pos Point) *sdl.Rect {
return &sdl.Rect{X: pos.X - p.tileScreenOffset.X, Y: pos.Y - p.tileScreenOffset.Y, W: p.tileScreenSize.X, H: p.tileScreenSize.Y}
}
func (p *projection) update(renderer *sdl.Renderer) {
p.zoomInv = 1 / p.zoom
p.tileScreenOffset = Pt(int32(p.zoomInv*256), int32(p.zoomInv*300))
p.tileScreenSize = Pt(int32(p.zoomInv*512), int32(p.zoomInv*512))
p.tileFitScreenSize = Pt(int32(p.zoomInv*65), int32(p.zoomInv*32))
windowW, windowH, err := renderer.GetOutputSize()
if err != nil {
log.Fatal(err)
}
p.windowCenter = Pt(windowW/2, windowH/2)
p.windowRect = sdl.Rect{X: 0, Y: 0, W: windowW - 0, H: windowH - 0}
}
func (p *projection) visibleTiles(action func(int32, int32, Point)) {
topLeft := p.screenToMap(p.windowRect.X, p.windowRect.Y)
topRight := p.screenToMap(p.windowRect.X+p.windowRect.W, p.windowRect.Y)
bottomLeft := p.screenToMap(p.windowRect.X, p.windowRect.Y+p.windowRect.H)
bottomRight := p.screenToMap(p.windowRect.X+p.windowRect.W, p.windowRect.Y+p.windowRect.H)
minY, maxY := int32(Floor32(topRight.Y)), int32(Ceil32(bottomLeft.Y))
minX, maxX := int32(Floor32(topLeft.X)), int32(Ceil32(bottomRight.X))
for y := minY; y <= maxY; y++ {
for x := minX; x <= maxX; x++ {
pos := p.mapToScreen(x, y)
rectFit := p.screenToTileFitRect(pos)
if rectFit.X+rectFit.W < p.windowRect.X || rectFit.Y+rectFit.H < p.windowRect.Y {
continue
}
if rectFit.X > p.windowRect.X+p.windowRect.W || rectFit.Y > p.windowRect.Y+p.windowRect.H {
break
}
action(x, y, pos)
}
}
}

9
renderer.go Normal file
View File

@ -0,0 +1,9 @@
package tins2020
import "github.com/veandco/go-sdl2/sdl"
type Control interface {
Init(*Context) error
Handle(*Context, sdl.Event)
Render(*Context)
}

View File

@ -1,51 +0,0 @@
package tins2020
import (
"bufio"
"fmt"
"strings"
)
type ResourceLoader struct {
Resources map[string]string
}
func NewResourceLoader() *ResourceLoader {
return &ResourceLoader{}
}
func (l *ResourceLoader) parseResourcesFile(res *Resources, name string) error {
f, err := res.Fs().Open(name)
if err != nil {
return err
}
defer f.Close()
l.Resources = map[string]string{}
scanner := bufio.NewScanner(f)
for scanner.Scan() {
definition := scanner.Text()
sep := strings.Index(definition, ":")
if sep == -1 {
continue
}
name := strings.TrimSpace(definition[:sep])
content := strings.TrimSpace(definition[sep+1:])
l.Resources[name] = content
}
return nil
}
func (l *ResourceLoader) LoadFromFile(res *Resources, name string, action func(string, string) error) error {
err := l.parseResourcesFile(res, name)
if err != nil {
return err
}
for name, content := range l.Resources {
err = action(name, content)
if err != nil {
return fmt.Errorf("cannot load resource '%s'; error: %v", name, err)
}
}
return nil
}

View File

@ -31,5 +31,4 @@ func (s *Settings) Store() error {
type WindowSettings struct { type WindowSettings struct {
Location *Point Location *Point
Size *Point
} }

View File

@ -2,9 +2,11 @@ package tins2020
import ( import (
"fmt" "fmt"
"log"
"opslag.de/schobers/geom"
"github.com/veandco/go-sdl2/sdl" "github.com/veandco/go-sdl2/sdl"
"opslag.de/schobers/geom"
) )
type terrainRenderer struct { type terrainRenderer struct {
@ -21,6 +23,59 @@ type interaction struct {
mouseDrag *Point mouseDrag *Point
} }
type projection struct {
center PointF
zoom float32
zoomInv float32
tileScreenDelta PointF
tileScreenDeltaInv PointF
tileScreenOffset Point
tileScreenSize Point
windowCenter Point
}
func newProjection() projection {
return projection{zoom: 1, tileScreenDelta: PtF(65, 32), tileScreenDeltaInv: PtF(1./65, 1./32)}
}
func (p *projection) update(renderer *sdl.Renderer) {
p.zoomInv = 1 / p.zoom
p.tileScreenOffset = Pt(int32(p.zoomInv*256), int32(p.zoomInv*300))
p.tileScreenSize = Pt(int32(p.zoomInv*512), int32(p.zoomInv*512))
windowW, windowH, err := renderer.GetOutputSize()
if err != nil {
log.Fatal(err)
}
p.windowCenter = Pt(windowW/2, windowH/2)
}
func (p *projection) mapToScreen(x, y int32) Point {
return p.mapToScreenF(float32(x), float32(y))
}
func (p *projection) mapToScreenF(x, y float32) Point {
translated := PtF(x-p.center.X, y-p.center.Y)
return Pt(p.windowCenter.X+int32((translated.X-translated.Y)*65*p.zoomInv), p.windowCenter.Y+int32((translated.X+translated.Y)*32*p.zoomInv))
}
func (p *projection) screenToMapRel(x, y int32) PointF {
normX := p.zoom * float32(x)
normY := p.zoom * float32(y)
return PtF(.5*(p.tileScreenDeltaInv.X*normX+p.tileScreenDeltaInv.Y*normY), .5*(-p.tileScreenDeltaInv.X*normX+p.tileScreenDeltaInv.Y*normY))
}
func (p *projection) screenToMap(x, y int32) PointF {
pos := p.screenToMapRel(x-p.windowCenter.X, y-p.windowCenter.Y)
return p.center.Add(pos)
}
func (p *projection) screenToTileRect(pos Point) *sdl.Rect {
return &sdl.Rect{X: pos.X - p.tileScreenOffset.X, Y: pos.Y - p.tileScreenOffset.Y, W: p.tileScreenSize.X, H: p.tileScreenSize.Y}
}
func NewTerrainRenderer(terrain *Map) Control { func NewTerrainRenderer(terrain *Map) Control {
return &terrainRenderer{terrain: terrain, project: newProjection()} return &terrainRenderer{terrain: terrain, project: newProjection()}
} }
@ -37,22 +92,25 @@ func (r *terrainRenderer) Handle(ctx *Context, event sdl.Event) {
r.interact.mouseLeftDown = e.Type == sdl.MOUSEBUTTONDOWN r.interact.mouseLeftDown = e.Type == sdl.MOUSEBUTTONDOWN
if r.interact.mouseLeftDown && r.interact.mouseDrag == nil { if r.interact.mouseLeftDown && r.interact.mouseDrag == nil {
r.interact.mouseDrag = &Point{e.X, e.Y} r.interact.mouseDrag = &Point{e.X, e.Y}
fmt.Println("Drag start", r.interact.mouseDrag)
} else if !r.interact.mouseLeftDown && r.interact.mouseDrag != nil { } else if !r.interact.mouseLeftDown && r.interact.mouseDrag != nil {
fmt.Println("Drag end", r.interact.mouseDrag)
r.interact.mouseDrag = nil r.interact.mouseDrag = nil
} }
} }
case *sdl.MouseMotionEvent: case *sdl.MouseMotionEvent:
r.hover = r.project.screenToMap(e.X, e.Y) r.hover = r.project.screenToMap(e.X, e.Y)
if r.interact.mouseDrag != nil { if r.interact.mouseDrag != nil {
fmt.Println("Dragging...", r.project.center)
r.project.center = r.project.center.Sub(r.project.screenToMapRel(e.X-r.interact.mouseDrag.X, e.Y-r.interact.mouseDrag.Y)) r.project.center = r.project.center.Sub(r.project.screenToMapRel(e.X-r.interact.mouseDrag.X, e.Y-r.interact.mouseDrag.Y))
r.project.update(ctx.Renderer) r.project.update(ctx.Renderer)
r.interact.mouseDrag = &Point{e.X, e.Y} r.interact.mouseDrag = &Point{e.X, e.Y}
} }
case *sdl.MouseWheelEvent: case *sdl.MouseWheelEvent:
if e.Y > 0 && r.project.zoom > .5 { if e.Y > 0 {
r.project.zoom *= .5 r.project.zoom *= .5
r.project.update(ctx.Renderer) r.project.update(ctx.Renderer)
} else if e.Y < 0 && r.project.zoom < 4 { } else if e.Y < 0 {
r.project.zoom *= 2 r.project.zoom *= 2
r.project.update(ctx.Renderer) r.project.update(ctx.Renderer)
} }
@ -127,48 +185,41 @@ func (r *terrainRenderer) Render(ctx *Context) {
return variantToTexture("bush-large-%d", stretch(variant, .8, 1)*multiplier) return variantToTexture("bush-large-%d", stretch(variant, .8, 1)*multiplier)
} }
toItemTexture := func(x, y int32) *Texture {
variant := r.terrain.Variant.Value(x, y)
_, ok := r.terrain.Flowers[Pt(x, y)]
if ok {
return variantToTexture("flower-poppy-%d", variant)
}
temp := r.terrain.Temp.Value(x, y)
humid := r.terrain.Humid.Value(x, y)
return toPropTexture(temp, humid, variant)
}
// horizontal: [191, 321) = 130 // horizontal: [191, 321) = 130
// vertical: [267,332) = 65 // vertical: [267,332) = 65
hover := Pt(int32(geom.Round32(r.hover.X)), int32(geom.Round32(r.hover.Y))) hover := Pt(int32(geom.Round32(r.hover.X)), int32(geom.Round32(r.hover.Y)))
r.project.visibleTiles(func(x, y int32, pos Point) { for y := int32(-100); y < 100; y++ {
for x := int32(-100); x < 100; x++ {
if x == hover.X && y == hover.Y { if x == hover.X && y == hover.Y {
return continue
} }
temp := r.terrain.Temp.Value(x, y) temp := r.terrain.Temp.Value(x, y)
humid := r.terrain.Humid.Value(x, y) humid := r.terrain.Humid.Value(x, y)
text := toTileTexture(temp, humid) text := toTileTexture(temp, humid)
rect := r.project.screenToTileRect(pos) pos := r.project.mapToScreen(x, y)
text.Copy(ctx.Renderer, rect) text.Copy(ctx.Renderer, r.project.screenToTileRect(pos))
}) }
}
r.project.visibleTiles(func(x, y int32, pos Point) { for y := int32(-100); y < 100; y++ {
text := toItemTexture(x, y) for x := int32(-100); x < 100; x++ {
variant := r.terrain.Variant.Value(x, y)
if variant < -1 || variant > 1 {
continue
}
temp := r.terrain.Temp.Value(x, y)
humid := r.terrain.Humid.Value(x, y)
text := toPropTexture(temp, humid, variant)
if text == nil { if text == nil {
return continue
} }
placeX, placeY := r.terrain.PlaceX.Value(x, y), r.terrain.PlaceY.Value(x, y) placeX, placeY := r.terrain.PlaceX.Value(x, y), r.terrain.PlaceY.Value(x, y)
pos = r.project.mapToScreenF(float32(x)-.2+float32(.8*placeX-.4), float32(y)-.2+float32(.8*placeY-.4)) pos := r.project.mapToScreenF(float32(x)-.2+float32(.8*placeX-.4), float32(y)-.2+float32(.8*placeY-.4))
text.Copy(ctx.Renderer, r.project.screenToTileRect(pos))
})
// gfx.RectangleColor(ctx.Renderer, r.project.windowRect.X, r.project.windowRect.Y, r.project.windowRect.X+r.project.windowRect.W, r.project.windowRect.Y+r.project.windowRect.H, sdl.Color{R: 255, A: 255}) text.Copy(ctx.Renderer, r.project.screenToTileRect(pos))
// for y := int32(-40); y < 40; y++ { }
// for x := int32(-40); x < 40; x++ { }
// pos := r.project.mapToScreen(x, y)
// ctx.Fonts.Font("debug").RenderCopy(ctx.Renderer, fmt.Sprintf("(%d, %d)", x, y), pos, sdl.Color{R: 255, A: 255})
// }
// }
} }

View File

@ -24,10 +24,6 @@ func NewTextureFromSurface(renderer *sdl.Renderer, surface *sdl.Surface) (*Textu
func (t *Texture) Rect() *sdl.Rect { return t.rect } func (t *Texture) Rect() *sdl.Rect { return t.rect }
func (t *Texture) RectOffset(offset Point) *sdl.Rect {
return &sdl.Rect{X: offset.X, Y: offset.Y, W: t.rect.W, H: t.rect.H}
}
func (t *Texture) Copy(renderer *sdl.Renderer, target *sdl.Rect) { func (t *Texture) Copy(renderer *sdl.Renderer, target *sdl.Rect) {
renderer.Copy(t.texture, t.rect, target) renderer.Copy(t.texture, t.rect, target)
} }