From 2e937353471abe254a2f6e6717220dc1b27b6970 Mon Sep 17 00:00:00 2001 From: weqqr Date: Sun, 28 Apr 2024 22:55:16 +0300 Subject: [PATCH 1/2] Add golangci-lint --- .github/workflows/build.yml | 11 ++++++++--- .golangci.yaml | 13 +++++++++++++ cmd/panorama/main.go | 7 ++++++- 3 files changed, 27 insertions(+), 4 deletions(-) create mode 100644 .golangci.yaml diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 3517084..388f65d 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -1,4 +1,4 @@ -name: Build and test +name: Build, lint, test on: push: @@ -14,12 +14,17 @@ jobs: - uses: actions/checkout@v3 - name: Set up Go - uses: actions/setup-go@v3 + uses: actions/setup-go@v5 with: - go-version: '1.21' + go-version: '1.22' - name: Build run: go build -v ./... + - name: Lint + run: | + go install github.com/golangci/golangci-lint/cmd/golangci-lint@v1.57.2 + golangci-lint run --fix=false + - name: Test run: go test -v ./... diff --git a/.golangci.yaml b/.golangci.yaml new file mode 100644 index 0000000..247ff5d --- /dev/null +++ b/.golangci.yaml @@ -0,0 +1,13 @@ +linters: + enable: + - bodyclose + - funlen + - gocognit + - goconst + - gosec + - nilerr + - nilnil + - nolintlint + - varnamelen + - wastedassign + - wsl diff --git a/cmd/panorama/main.go b/cmd/panorama/main.go index 8ce336d..5e5b6a3 100644 --- a/cmd/panorama/main.go +++ b/cmd/panorama/main.go @@ -76,12 +76,17 @@ func main() { commonFlags := flag.NewFlagSet("common flags", flag.ExitOnError) commonFlags.StringVar(&args.ConfigPath, "config", "config.toml", "Path to config file") - commonFlags.Parse(os.Args[2:]) + err := commonFlags.Parse(os.Args[2:]) + if err != nil { + slog.Error("unable to parse flags") + os.Exit(1) + } slog.Info("loading config", "config_path", args.ConfigPath) config, err := config.LoadConfig(args.ConfigPath) if err != nil { slog.Error("unable to load config", "error", err) + os.Exit(1) } switch subcommand { From f1a4e6d83290218a137517bea6186c3fe06a3f78 Mon Sep 17 00:00:00 2001 From: weqqr Date: Sun, 28 Apr 2024 23:38:15 +0300 Subject: [PATCH 2/2] Fix linter errors --- .golangci.yaml | 3 +- cmd/panorama/main.go | 4 ++ internal/game/game.go | 5 ++ internal/game/media.go | 5 ++ internal/game/node.go | 10 +++ internal/lm/matrix3.go | 1 + internal/lm/vector2.go | 6 ++ internal/lm/vector3.go | 9 +++ internal/lm/vector4.go | 2 + internal/mesh/cuboid.go | 99 +++++++++++++++++++++++++++ internal/mesh/mesh.go | 94 ------------------------- internal/mesh/obj.go | 13 +++- internal/raster/depth.go | 3 + internal/raster/png.go | 2 + internal/raster/renderbuffer.go | 2 + internal/render/isometric/renderer.go | 53 ++++++++------ internal/render/light.go | 1 + internal/render/neighborhood.go | 2 + internal/render/rasterizer.go | 97 +++++++++++++------------- internal/spatial/region.go | 1 + internal/tile/downscale.go | 7 ++ internal/tile/tiler.go | 11 ++- internal/world/block.go | 23 ++++++- internal/world/world.go | 3 + 24 files changed, 287 insertions(+), 169 deletions(-) create mode 100644 internal/mesh/cuboid.go diff --git a/.golangci.yaml b/.golangci.yaml index 247ff5d..92e4a4a 100644 --- a/.golangci.yaml +++ b/.golangci.yaml @@ -6,8 +6,7 @@ linters: - goconst - gosec - nilerr - - nilnil - nolintlint - - varnamelen + # - varnamelen - wastedassign - wsl diff --git a/cmd/panorama/main.go b/cmd/panorama/main.go index 5e5b6a3..06c1597 100644 --- a/cmd/panorama/main.go +++ b/cmd/panorama/main.go @@ -31,6 +31,7 @@ func fullrender(config config.Config) error { slog.Error("unable to load game description", "error", err) return err } + backend, err := world.NewPostgresBackend(config.System.WorldDSN) if err != nil { slog.Error("unable to connect to world DB", "error", err) @@ -56,6 +57,7 @@ func run(config config.Config) error { quit := make(chan bool) slog.Info("starting web server", "address", config.Web.ListenAddress) + go func() { web.Serve(&config) quit <- true @@ -76,6 +78,7 @@ func main() { commonFlags := flag.NewFlagSet("common flags", flag.ExitOnError) commonFlags.StringVar(&args.ConfigPath, "config", "config.toml", "Path to config file") + err := commonFlags.Parse(os.Args[2:]) if err != nil { slog.Error("unable to parse flags") @@ -83,6 +86,7 @@ func main() { } slog.Info("loading config", "config_path", args.ConfigPath) + config, err := config.LoadConfig(args.ConfigPath) if err != nil { slog.Error("unable to load config", "error", err) diff --git a/internal/game/game.go b/internal/game/game.go index 7ec9c96..f87b77a 100644 --- a/internal/game/game.go +++ b/internal/game/game.go @@ -100,6 +100,7 @@ func makeMeshNode(model *mesh.Model, tiles []*image.NRGBA) NodeDefinition { if i >= len(tiles) { break } + textures[i] = tiles[i] } @@ -152,6 +153,7 @@ func LoadGame(desc string, path string, modpath string) (Game, error) { } var descriptor gameDescriptor + err = json.Unmarshal(descJSON, &descriptor) if err != nil { return Game{}, err @@ -163,12 +165,14 @@ func LoadGame(desc string, path string, modpath string) (Game, error) { if err != nil { return Game{}, err } + err = mediaCache.fetchMedia(modpath) if err != nil { return Game{}, err } nodes := make(map[string]NodeDefinition) + for name, gameNode := range descriptor.Nodes { node := ResolveNode(gameNode, mediaCache) @@ -190,5 +194,6 @@ func (g *Game) NodeDef(node string) NodeDefinition { if def, ok := g.Nodes[node]; ok { return def } + return g.unknown } diff --git a/internal/game/media.go b/internal/game/media.go index 620aec2..33a44a3 100644 --- a/internal/game/media.go +++ b/internal/game/media.go @@ -20,6 +20,7 @@ type MediaCache struct { func NewMediaCache() *MediaCache { dummyImage := image.NewNRGBA(image.Rect(0, 0, 2, 2)) + dummyImage.SetNRGBA(0, 0, color.NRGBA{255, 0, 255, 255}) dummyImage.SetNRGBA(0, 1, color.NRGBA{0, 0, 0, 255}) dummyImage.SetNRGBA(1, 0, color.NRGBA{0, 0, 0, 255}) @@ -39,15 +40,18 @@ func (m *MediaCache) fetchMedia(path string) error { } basePath := filepath.Base(path) + switch filepath.Ext(path) { case ".png": img, _ := raster.LoadPNG(path) m.images[basePath] = img + case ".obj": model, err := mesh.LoadOBJ(path) if err != nil { return err } + m.models[basePath] = &model } @@ -72,6 +76,7 @@ func (m *MediaCache) Mesh(name string) *mesh.Model { return model } else { slog.Warn("unknown image", "name", name) + return nil } } diff --git a/internal/game/node.go b/internal/game/node.go index cd2be89..3c108a2 100644 --- a/internal/game/node.go +++ b/internal/game/node.go @@ -55,6 +55,7 @@ func (t DrawType) IsLiquid() bool { func (t *DrawType) UnmarshalJSON(data []byte) error { var name string + err := json.Unmarshal(data, &name) if err != nil { return err @@ -83,6 +84,7 @@ var ParamTypeNames = map[string]ParamType{ func (t *ParamType) UnmarshalJSON(data []byte) error { name := "light" + err := json.Unmarshal(data, &name) if err != nil { return err @@ -133,6 +135,7 @@ var ParamType2Names = map[string]ParamType2{ func (t *ParamType2) UnmarshalJSON(data []byte) error { name := "none" + err := json.Unmarshal(data, &name) if err != nil { return err @@ -157,6 +160,7 @@ func (n *NodeBox) UnmarshalJSON(data []byte) error { Type string `json:"type"` Fixed []interface{} `json:"fixed"` } + inner := &nodeBox{} if err := json.Unmarshal(data, inner); err != nil { return err @@ -164,6 +168,7 @@ func (n *NodeBox) UnmarshalJSON(data []byte) error { n.Type = inner.Type n.Fixed = make([][]float64, 0) + if inner.Type != "fixed" { return nil } @@ -174,10 +179,12 @@ func (n *NodeBox) UnmarshalJSON(data []byte) error { if _, ok := inner.Fixed[0].(float64); ok { box := make([]float64, 0) + for i := 0; i < 6; i++ { v, _ := inner.Fixed[i].(float64) box = append(box, v) } + n.Fixed = append(n.Fixed, box) } @@ -185,10 +192,12 @@ func (n *NodeBox) UnmarshalJSON(data []byte) error { for _, boxInterface := range inner.Fixed { boxFloat64 := boxInterface.([]interface{}) box := make([]float64, 0) + for i := 0; i < 6; i++ { v, _ := boxFloat64[i].(float64) box = append(box, v) } + n.Fixed = append(n.Fixed, box) } } @@ -207,6 +216,7 @@ type NodeDescriptor struct { func (n *NodeDescriptor) UnmarshalJSON(data []byte) error { type nodeDescriptor NodeDescriptor + inner := &nodeDescriptor{ DrawType: DrawTypeNormal, Tiles: []string{}, diff --git a/internal/lm/matrix3.go b/internal/lm/matrix3.go index a82fac3..be40f7d 100644 --- a/internal/lm/matrix3.go +++ b/internal/lm/matrix3.go @@ -28,5 +28,6 @@ func (lhs *Matrix3) MulVec(rhs Vector3) Vector3 { x := lhs.m[0]*rhs.X + lhs.m[1]*rhs.Y + lhs.m[2]*rhs.Z y := lhs.m[3]*rhs.X + lhs.m[4]*rhs.Y + lhs.m[5]*rhs.Z z := lhs.m[6]*rhs.X + lhs.m[7]*rhs.Y + lhs.m[8]*rhs.Z + return Vec3(x, y, z) } diff --git a/internal/lm/vector2.go b/internal/lm/vector2.go index 07e2476..4c4cc2f 100644 --- a/internal/lm/vector2.go +++ b/internal/lm/vector2.go @@ -16,35 +16,41 @@ func Vec2(x, y float64) Vector2 { func (lhs Vector2) Add(rhs Vector2) Vector2 { x := lhs.X + rhs.X y := lhs.Y + rhs.Y + return Vec2(x, y) } func (lhs Vector2) Sub(rhs Vector2) Vector2 { x := lhs.X - rhs.X y := lhs.Y - rhs.Y + return Vec2(x, y) } func (lhs Vector2) Mul(rhs Vector2) Vector2 { x := lhs.X * rhs.X y := lhs.Y * rhs.Y + return Vec2(x, y) } func (lhs Vector2) MulScalar(rhs float64) Vector2 { x := lhs.X * rhs y := lhs.Y * rhs + return Vec2(x, y) } func (lhs Vector2) Min(rhs Vector2) Vector2 { x := math.Min(lhs.X, rhs.X) y := math.Min(lhs.Y, rhs.Y) + return Vec2(x, y) } func (lhs Vector2) Max(rhs Vector2) Vector2 { x := math.Max(lhs.X, rhs.X) y := math.Max(lhs.Y, rhs.Y) + return Vec2(x, y) } diff --git a/internal/lm/vector3.go b/internal/lm/vector3.go index c862186..ae80fae 100644 --- a/internal/lm/vector3.go +++ b/internal/lm/vector3.go @@ -18,6 +18,7 @@ func (lhs Vector3) Add(rhs Vector3) Vector3 { x := lhs.X + rhs.X y := lhs.Y + rhs.Y z := lhs.Z + rhs.Z + return Vec3(x, y, z) } @@ -25,6 +26,7 @@ func (lhs Vector3) MulScalar(rhs float64) Vector3 { x := lhs.X * rhs y := lhs.Y * rhs z := lhs.Z * rhs + return Vec3(x, y, z) } @@ -33,6 +35,7 @@ func (lhs Vector3) DivScalar(rhs float64) Vector3 { x := lhs.X * reciprocal y := lhs.Y * reciprocal z := lhs.Z * reciprocal + return Vec3(x, y, z) } @@ -40,6 +43,7 @@ func (lhs Vector3) PowScalar(power float64) Vector3 { x := math.Pow(lhs.X, power) y := math.Pow(lhs.Y, power) z := math.Pow(lhs.Z, power) + return Vec3(x, y, z) } @@ -47,6 +51,7 @@ func (lhs Vector3) Cross(rhs Vector3) Vector3 { x := lhs.Y*rhs.Z - lhs.Z*rhs.Y y := lhs.Z*rhs.X - lhs.X*rhs.Z z := lhs.X*rhs.Y - lhs.Y*rhs.X + return Vec3(x, y, z) } @@ -66,6 +71,7 @@ func (lhs Vector3) ClampScalar(min, max float64) Vector3 { x := Clamp(lhs.X, min, max) y := Clamp(lhs.Y, min, max) z := Clamp(lhs.Z, min, max) + return Vec3(x, y, z) } @@ -80,17 +86,20 @@ func (lhs Vector3) MaxComponent() float64 { func (lhs Vector3) RotateXY(angle float64) Vector3 { cos := math.Cos(angle) sin := math.Sin(angle) + return Vec3(lhs.X*cos-lhs.Y*sin, lhs.X*sin+lhs.Y*cos, lhs.Z) } func (lhs Vector3) RotateXZ(angle float64) Vector3 { cos := math.Cos(angle) sin := math.Sin(angle) + return Vec3(lhs.X*cos-lhs.Z*sin, lhs.Y, lhs.X*sin+lhs.Z*cos) } func (lhs Vector3) RotateYZ(angle float64) Vector3 { cos := math.Cos(angle) sin := math.Sin(angle) + return Vec3(lhs.X, lhs.Y*cos-lhs.Z*sin, lhs.Y*sin+lhs.Z*cos) } diff --git a/internal/lm/vector4.go b/internal/lm/vector4.go index f8570f7..a5a1928 100644 --- a/internal/lm/vector4.go +++ b/internal/lm/vector4.go @@ -18,6 +18,7 @@ func (lhs Vector4) MulScalar(rhs float64) Vector4 { y := lhs.Y * rhs z := lhs.Z * rhs w := lhs.W * rhs + return Vec4(x, y, z, w) } @@ -26,6 +27,7 @@ func (lhs Vector4) ClampScalar(min, max float64) Vector4 { y := Clamp(lhs.Y, min, max) z := Clamp(lhs.Z, min, max) w := Clamp(lhs.W, min, max) + return Vec4(x, y, z, w) } diff --git a/internal/mesh/cuboid.go b/internal/mesh/cuboid.go new file mode 100644 index 0000000..cc9e315 --- /dev/null +++ b/internal/mesh/cuboid.go @@ -0,0 +1,99 @@ +package mesh + +import "github.com/lord-server/panorama/internal/lm" + +type CubeFaces uint8 + +const ( + CubeFaceNone CubeFaces = 0 + CubeFaceEast = 1 << iota + CubeFaceWest + CubeFaceTop + CubeFaceDown + CubeFaceNorth + CubeFaceSouth +) + +//nolint:funlen // FIXME: embed and scale .obj file? +func Cuboid(x1, y1, z1, x2, y2, z2 float64, hiddenFaces CubeFaces) []Mesh { + yp := NewMesh() + yp.Vertices = []Vertex{ + {Position: lm.Vec3(x1, y2, z1), Texcoord: lm.Vec2(0.0, 0.0), Normal: lm.Vec3(0.0, 1.0, 0.0)}, + {Position: lm.Vec3(x1, y2, z2), Texcoord: lm.Vec2(0.0, 1.0), Normal: lm.Vec3(0.0, 1.0, 0.0)}, + {Position: lm.Vec3(x2, y2, z2), Texcoord: lm.Vec2(1.0, 1.0), Normal: lm.Vec3(0.0, 1.0, 0.0)}, + {Position: lm.Vec3(x1, y2, z1), Texcoord: lm.Vec2(0.0, 0.0), Normal: lm.Vec3(0.0, 1.0, 0.0)}, + {Position: lm.Vec3(x2, y2, z1), Texcoord: lm.Vec2(1.0, 0.0), Normal: lm.Vec3(0.0, 1.0, 0.0)}, + {Position: lm.Vec3(x2, y2, z2), Texcoord: lm.Vec2(1.0, 1.0), Normal: lm.Vec3(0.0, 1.0, 0.0)}, + } + + ym := NewMesh() + ym.Vertices = []Vertex{ + {Position: lm.Vec3(x1, y1, z1), Texcoord: lm.Vec2(0.0, 0.0), Normal: lm.Vec3(0.0, -1.0, 0.0)}, + {Position: lm.Vec3(x1, y1, z2), Texcoord: lm.Vec2(0.0, 1.0), Normal: lm.Vec3(0.0, -1.0, 0.0)}, + {Position: lm.Vec3(x2, y1, z2), Texcoord: lm.Vec2(1.0, 1.0), Normal: lm.Vec3(0.0, -1.0, 0.0)}, + {Position: lm.Vec3(x1, y1, z1), Texcoord: lm.Vec2(0.0, 0.0), Normal: lm.Vec3(0.0, -1.0, 0.0)}, + {Position: lm.Vec3(x2, y1, z1), Texcoord: lm.Vec2(1.0, 0.0), Normal: lm.Vec3(0.0, -1.0, 0.0)}, + {Position: lm.Vec3(x2, y1, z2), Texcoord: lm.Vec2(1.0, 1.0), Normal: lm.Vec3(0.0, -1.0, 0.0)}, + } + + xp := NewMesh() + xp.Vertices = []Vertex{ + {Position: lm.Vec3(x2, y1, z1), Texcoord: lm.Vec2(1.0, 1.0), Normal: lm.Vec3(1.0, 0.0, 0.0)}, + {Position: lm.Vec3(x2, y1, z2), Texcoord: lm.Vec2(0.0, 1.0), Normal: lm.Vec3(1.0, 0.0, 0.0)}, + {Position: lm.Vec3(x2, y2, z2), Texcoord: lm.Vec2(0.0, 0.0), Normal: lm.Vec3(1.0, 0.0, 0.0)}, + {Position: lm.Vec3(x2, y1, z1), Texcoord: lm.Vec2(1.0, 1.0), Normal: lm.Vec3(1.0, 0.0, 0.0)}, + {Position: lm.Vec3(x2, y2, z1), Texcoord: lm.Vec2(1.0, 0.0), Normal: lm.Vec3(1.0, 0.0, 0.0)}, + {Position: lm.Vec3(x2, y2, z2), Texcoord: lm.Vec2(0.0, 0.0), Normal: lm.Vec3(1.0, 0.0, 0.0)}, + } + + xm := NewMesh() + xm.Vertices = []Vertex{ + {Position: lm.Vec3(x1, y1, z1), Texcoord: lm.Vec2(1.0, 0.0), Normal: lm.Vec3(-1.0, 0.0, 0.0)}, + {Position: lm.Vec3(x1, y1, z2), Texcoord: lm.Vec2(0.0, 0.0), Normal: lm.Vec3(-1.0, 0.0, 0.0)}, + {Position: lm.Vec3(x1, y2, z2), Texcoord: lm.Vec2(0.0, 1.0), Normal: lm.Vec3(-1.0, 0.0, 0.0)}, + {Position: lm.Vec3(x1, y1, z1), Texcoord: lm.Vec2(1.0, 0.0), Normal: lm.Vec3(-1.0, 0.0, 0.0)}, + {Position: lm.Vec3(x1, y2, z1), Texcoord: lm.Vec2(1.0, 1.0), Normal: lm.Vec3(-1.0, 0.0, 0.0)}, + {Position: lm.Vec3(x1, y2, z2), Texcoord: lm.Vec2(0.0, 1.0), Normal: lm.Vec3(-1.0, 0.0, 0.0)}, + } + + zp := NewMesh() + zp.Vertices = []Vertex{ + {Position: lm.Vec3(x1, y1, z2), Texcoord: lm.Vec2(0.0, 0.0), Normal: lm.Vec3(0.0, 0.0, 1.0)}, + {Position: lm.Vec3(x1, y2, z2), Texcoord: lm.Vec2(0.0, 1.0), Normal: lm.Vec3(0.0, 0.0, 1.0)}, + {Position: lm.Vec3(x2, y2, z2), Texcoord: lm.Vec2(1.0, 1.0), Normal: lm.Vec3(0.0, 0.0, 1.0)}, + {Position: lm.Vec3(x1, y1, z2), Texcoord: lm.Vec2(0.0, 0.0), Normal: lm.Vec3(0.0, 0.0, 1.0)}, + {Position: lm.Vec3(x2, y1, z2), Texcoord: lm.Vec2(1.0, 0.0), Normal: lm.Vec3(0.0, 0.0, 1.0)}, + {Position: lm.Vec3(x2, y2, z2), Texcoord: lm.Vec2(1.0, 1.0), Normal: lm.Vec3(0.0, 0.0, 1.0)}, + } + + zm := NewMesh() + zm.Vertices = []Vertex{ + {Position: lm.Vec3(x1, y1, z1), Texcoord: lm.Vec2(0.0, 0.0), Normal: lm.Vec3(0.0, 0.0, -1.0)}, + {Position: lm.Vec3(x1, y2, z1), Texcoord: lm.Vec2(0.0, 1.0), Normal: lm.Vec3(0.0, 0.0, -1.0)}, + {Position: lm.Vec3(x2, y2, z1), Texcoord: lm.Vec2(1.0, 1.0), Normal: lm.Vec3(0.0, 0.0, -1.0)}, + {Position: lm.Vec3(x1, y1, z1), Texcoord: lm.Vec2(0.0, 0.0), Normal: lm.Vec3(0.0, 0.0, -1.0)}, + {Position: lm.Vec3(x2, y1, z1), Texcoord: lm.Vec2(1.0, 0.0), Normal: lm.Vec3(0.0, 0.0, -1.0)}, + {Position: lm.Vec3(x2, y2, z1), Texcoord: lm.Vec2(1.0, 1.0), Normal: lm.Vec3(0.0, 0.0, -1.0)}, + } + + meshes := []Mesh{yp, ym, xp, xm, zp, zm} + meshFaces := []CubeFaces{CubeFaceTop, CubeFaceDown, CubeFaceEast, CubeFaceWest, CubeFaceNorth, CubeFaceSouth} + + culledMesh := []Mesh{} + + for i, mesh := range meshes { + if hiddenFaces&meshFaces[i] == 0 { + culledMesh = append(culledMesh, mesh) + } + } + + return culledMesh +} + +func Cube(hiddenFaces CubeFaces) *Model { + model := NewModel() + + model.Meshes = append(model.Meshes, Cuboid(-0.5, -0.5, -0.5, 0.5, 0.5, 0.5, hiddenFaces)...) + + return &model +} diff --git a/internal/mesh/mesh.go b/internal/mesh/mesh.go index f1beff9..30b612e 100644 --- a/internal/mesh/mesh.go +++ b/internal/mesh/mesh.go @@ -29,97 +29,3 @@ func NewModel() Model { Meshes: []Mesh{}, } } - -type CubeFaces uint8 - -const ( - CubeFaceNone CubeFaces = 0 - CubeFaceEast = 1 << iota - CubeFaceWest - CubeFaceTop - CubeFaceDown - CubeFaceNorth - CubeFaceSouth -) - -func Cuboid(x1, y1, z1, x2, y2, z2 float64, hiddenFaces CubeFaces) []Mesh { - yp := NewMesh() - yp.Vertices = []Vertex{ - {Position: lm.Vec3(x1, y2, z1), Texcoord: lm.Vec2(0.0, 0.0), Normal: lm.Vec3(0.0, 1.0, 0.0)}, - {Position: lm.Vec3(x1, y2, z2), Texcoord: lm.Vec2(0.0, 1.0), Normal: lm.Vec3(0.0, 1.0, 0.0)}, - {Position: lm.Vec3(x2, y2, z2), Texcoord: lm.Vec2(1.0, 1.0), Normal: lm.Vec3(0.0, 1.0, 0.0)}, - {Position: lm.Vec3(x1, y2, z1), Texcoord: lm.Vec2(0.0, 0.0), Normal: lm.Vec3(0.0, 1.0, 0.0)}, - {Position: lm.Vec3(x2, y2, z1), Texcoord: lm.Vec2(1.0, 0.0), Normal: lm.Vec3(0.0, 1.0, 0.0)}, - {Position: lm.Vec3(x2, y2, z2), Texcoord: lm.Vec2(1.0, 1.0), Normal: lm.Vec3(0.0, 1.0, 0.0)}, - } - - ym := NewMesh() - ym.Vertices = []Vertex{ - {Position: lm.Vec3(x1, y1, z1), Texcoord: lm.Vec2(0.0, 0.0), Normal: lm.Vec3(0.0, -1.0, 0.0)}, - {Position: lm.Vec3(x1, y1, z2), Texcoord: lm.Vec2(0.0, 1.0), Normal: lm.Vec3(0.0, -1.0, 0.0)}, - {Position: lm.Vec3(x2, y1, z2), Texcoord: lm.Vec2(1.0, 1.0), Normal: lm.Vec3(0.0, -1.0, 0.0)}, - {Position: lm.Vec3(x1, y1, z1), Texcoord: lm.Vec2(0.0, 0.0), Normal: lm.Vec3(0.0, -1.0, 0.0)}, - {Position: lm.Vec3(x2, y1, z1), Texcoord: lm.Vec2(1.0, 0.0), Normal: lm.Vec3(0.0, -1.0, 0.0)}, - {Position: lm.Vec3(x2, y1, z2), Texcoord: lm.Vec2(1.0, 1.0), Normal: lm.Vec3(0.0, -1.0, 0.0)}, - } - - xp := NewMesh() - xp.Vertices = []Vertex{ - {Position: lm.Vec3(x2, y1, z1), Texcoord: lm.Vec2(1.0, 1.0), Normal: lm.Vec3(1.0, 0.0, 0.0)}, - {Position: lm.Vec3(x2, y1, z2), Texcoord: lm.Vec2(0.0, 1.0), Normal: lm.Vec3(1.0, 0.0, 0.0)}, - {Position: lm.Vec3(x2, y2, z2), Texcoord: lm.Vec2(0.0, 0.0), Normal: lm.Vec3(1.0, 0.0, 0.0)}, - {Position: lm.Vec3(x2, y1, z1), Texcoord: lm.Vec2(1.0, 1.0), Normal: lm.Vec3(1.0, 0.0, 0.0)}, - {Position: lm.Vec3(x2, y2, z1), Texcoord: lm.Vec2(1.0, 0.0), Normal: lm.Vec3(1.0, 0.0, 0.0)}, - {Position: lm.Vec3(x2, y2, z2), Texcoord: lm.Vec2(0.0, 0.0), Normal: lm.Vec3(1.0, 0.0, 0.0)}, - } - - xm := NewMesh() - xm.Vertices = []Vertex{ - {Position: lm.Vec3(x1, y1, z1), Texcoord: lm.Vec2(1.0, 0.0), Normal: lm.Vec3(-1.0, 0.0, 0.0)}, - {Position: lm.Vec3(x1, y1, z2), Texcoord: lm.Vec2(0.0, 0.0), Normal: lm.Vec3(-1.0, 0.0, 0.0)}, - {Position: lm.Vec3(x1, y2, z2), Texcoord: lm.Vec2(0.0, 1.0), Normal: lm.Vec3(-1.0, 0.0, 0.0)}, - {Position: lm.Vec3(x1, y1, z1), Texcoord: lm.Vec2(1.0, 0.0), Normal: lm.Vec3(-1.0, 0.0, 0.0)}, - {Position: lm.Vec3(x1, y2, z1), Texcoord: lm.Vec2(1.0, 1.0), Normal: lm.Vec3(-1.0, 0.0, 0.0)}, - {Position: lm.Vec3(x1, y2, z2), Texcoord: lm.Vec2(0.0, 1.0), Normal: lm.Vec3(-1.0, 0.0, 0.0)}, - } - - zp := NewMesh() - zp.Vertices = []Vertex{ - {Position: lm.Vec3(x1, y1, z2), Texcoord: lm.Vec2(0.0, 0.0), Normal: lm.Vec3(0.0, 0.0, 1.0)}, - {Position: lm.Vec3(x1, y2, z2), Texcoord: lm.Vec2(0.0, 1.0), Normal: lm.Vec3(0.0, 0.0, 1.0)}, - {Position: lm.Vec3(x2, y2, z2), Texcoord: lm.Vec2(1.0, 1.0), Normal: lm.Vec3(0.0, 0.0, 1.0)}, - {Position: lm.Vec3(x1, y1, z2), Texcoord: lm.Vec2(0.0, 0.0), Normal: lm.Vec3(0.0, 0.0, 1.0)}, - {Position: lm.Vec3(x2, y1, z2), Texcoord: lm.Vec2(1.0, 0.0), Normal: lm.Vec3(0.0, 0.0, 1.0)}, - {Position: lm.Vec3(x2, y2, z2), Texcoord: lm.Vec2(1.0, 1.0), Normal: lm.Vec3(0.0, 0.0, 1.0)}, - } - - zm := NewMesh() - zm.Vertices = []Vertex{ - {Position: lm.Vec3(x1, y1, z1), Texcoord: lm.Vec2(0.0, 0.0), Normal: lm.Vec3(0.0, 0.0, -1.0)}, - {Position: lm.Vec3(x1, y2, z1), Texcoord: lm.Vec2(0.0, 1.0), Normal: lm.Vec3(0.0, 0.0, -1.0)}, - {Position: lm.Vec3(x2, y2, z1), Texcoord: lm.Vec2(1.0, 1.0), Normal: lm.Vec3(0.0, 0.0, -1.0)}, - {Position: lm.Vec3(x1, y1, z1), Texcoord: lm.Vec2(0.0, 0.0), Normal: lm.Vec3(0.0, 0.0, -1.0)}, - {Position: lm.Vec3(x2, y1, z1), Texcoord: lm.Vec2(1.0, 0.0), Normal: lm.Vec3(0.0, 0.0, -1.0)}, - {Position: lm.Vec3(x2, y2, z1), Texcoord: lm.Vec2(1.0, 1.0), Normal: lm.Vec3(0.0, 0.0, -1.0)}, - } - - meshes := []Mesh{yp, ym, xp, xm, zp, zm} - meshFaces := []CubeFaces{CubeFaceTop, CubeFaceDown, CubeFaceEast, CubeFaceWest, CubeFaceNorth, CubeFaceSouth} - - culledMesh := []Mesh{} - for i, mesh := range meshes { - if hiddenFaces&meshFaces[i] == 0 { - culledMesh = append(culledMesh, mesh) - } - } - - return culledMesh -} - -func Cube(hiddenFaces CubeFaces) *Model { - model := NewModel() - - model.Meshes = append(model.Meshes, Cuboid(-0.5, -0.5, -0.5, 0.5, 0.5, 0.5, hiddenFaces)...) - - return &model -} diff --git a/internal/mesh/obj.go b/internal/mesh/obj.go index 489ac56..2571198 100644 --- a/internal/mesh/obj.go +++ b/internal/mesh/obj.go @@ -19,10 +19,12 @@ func parseVector3(fields []string) (lm.Vector3, error) { if err != nil { return lm.Vector3{}, err } + y, err := strconv.ParseFloat(fields[1], 32) if err != nil { return lm.Vector3{}, err } + z, err := strconv.ParseFloat(fields[2], 32) if err != nil { return lm.Vector3{}, err @@ -40,6 +42,7 @@ func parseVector2(fields []string) (lm.Vector2, error) { if err != nil { return lm.Vector2{}, err } + y, err := strconv.ParseFloat(fields[1], 32) if err != nil { return lm.Vector2{}, err @@ -60,10 +63,12 @@ func parseFace(fields []string) ([]Triplet, error) { } triplets := []Triplet{} + for _, field := range fields { parts := strings.SplitN(field, "/", 3) var err error + triplet := Triplet{} triplet.positionIndex, err = strconv.Atoi(parts[0]) @@ -76,6 +81,7 @@ func parseFace(fields []string) ([]Triplet, error) { if err != nil { return []Triplet{}, err } + triplet.texcoordIndex = &texcoordIndex } @@ -84,8 +90,10 @@ func parseFace(fields []string) ([]Triplet, error) { if err != nil { return []Triplet{}, err } + triplet.normalIndex = &normalIndex } + triplets = append(triplets, triplet) } @@ -175,9 +183,6 @@ func (o *objParser) processLine(line string) error { vertices := o.triangulatePolygon(triplets) o.mesh.Vertices = append(o.mesh.Vertices, vertices...) - - default: - // log.Printf("unknown attribute %v; ignoring\n", fields[0]) } return nil @@ -200,8 +205,10 @@ func LoadOBJ(path string) (Model, error) { } lineNumber := 1 + for scanner.Scan() { lineNumber += 1 + err := parser.processLine(scanner.Text()) if err != nil { return Model{}, err diff --git a/internal/raster/depth.go b/internal/raster/depth.go index b5ee569..55b2456 100644 --- a/internal/raster/depth.go +++ b/internal/raster/depth.go @@ -12,6 +12,7 @@ type Depth struct { func NewDepth(rect image.Rectangle) *Depth { pix := make([]float64, rect.Dx()*rect.Dy()) + for i := range pix { pix[i] = math.MaxFloat64 } @@ -26,6 +27,7 @@ func (d *Depth) At(x, y int) float64 { if x < d.Rect.Min.X || y < d.Rect.Min.Y || x >= d.Rect.Max.X || y >= d.Rect.Max.Y { return -math.MaxFloat64 } + return d.Pix[d.Rect.Dx()*y+x] } @@ -33,5 +35,6 @@ func (d *Depth) Set(x, y int, depth float64) { if x < d.Rect.Min.X || y < d.Rect.Min.Y || x > d.Rect.Max.X || y > d.Rect.Max.Y { return } + d.Pix[d.Rect.Dx()*y+x] = depth } diff --git a/internal/raster/png.go b/internal/raster/png.go index 6c1b7e8..c782a24 100644 --- a/internal/raster/png.go +++ b/internal/raster/png.go @@ -11,6 +11,7 @@ import ( func toNRGBA(img image.Image) *image.NRGBA { dst := image.NewNRGBA(img.Bounds()) draw.Draw(dst, img.Bounds(), img, img.Bounds().Min, draw.Src) + return dst } @@ -19,6 +20,7 @@ func LoadPNG(path string) (*image.NRGBA, error) { if err != nil { return nil, err } + defer file.Close() img, err := png.Decode(file) diff --git a/internal/raster/renderbuffer.go b/internal/raster/renderbuffer.go index dee0ff8..c88ae0e 100644 --- a/internal/raster/renderbuffer.go +++ b/internal/raster/renderbuffer.go @@ -21,6 +21,7 @@ func NewRenderBuffer(rect image.Rectangle) *RenderBuffer { func (target *RenderBuffer) OverlayDepthAwareWithAlpha(source *RenderBuffer, origin image.Point, depthOffset float64) { target.Dirty = true + if source == nil { return } @@ -81,6 +82,7 @@ func (target *RenderBuffer) OverlayDepthAware(source *RenderBuffer, origin image for y := bbox.Min.Y; y < bbox.Max.Y; y++ { sourcePixelBaseOffset := (y-origin.Y)*source.Depth.Rect.Max.X - origin.X targetPixelBaseOffset := y * target.Depth.Rect.Max.X + for x := bbox.Min.X; x < bbox.Max.X; x++ { sourcePixelOffset := sourcePixelBaseOffset + x targetPixelOffset := targetPixelBaseOffset + x diff --git a/internal/render/isometric/renderer.go b/internal/render/isometric/renderer.go index d0f5750..a39e222 100644 --- a/internal/render/isometric/renderer.go +++ b/internal/render/isometric/renderer.go @@ -56,6 +56,36 @@ func (r *IsometricRenderer) renderNode( needsAlphaBlending = false } + maxParam1, hiddenFaces := r.estimateVisibility(nodeDef, neighborhood, param1, pos) + + // Make underground edges visible (otherwise the edge becomes oddly thin and + // that doesn't look good) + if r.region.IsAtEdge(worldPos) && maxParam1 == render.ZeroIntensity { + maxParam1 = render.MapEdgeIntensity + } + + renderableNode := render.RenderableNode{ + Name: name, + Light: render.DecodeLight(maxParam1), + Param2: param2, + HiddenFaces: hiddenFaces, + } + renderedNode := r.nr.Render(renderableNode, &nodeDef) + + depthOffset = -float64(pos.Z+pos.X)/math.Sqrt2 - 0.5*(float64(pos.Y)) + depthOffset + if needsAlphaBlending { + target.OverlayDepthAwareWithAlpha(renderedNode, offset, depthOffset) + } else { + target.OverlayDepthAware(renderedNode, offset, depthOffset) + } +} + +func (r *IsometricRenderer) estimateVisibility( + nodeDef game.NodeDefinition, + neighborhood *render.BlockNeighborhood, + param1 uint8, + pos spatial.NodePosition, +) (uint8, mesh.CubeFaces) { // Estimate lighting by sampling neighboring nodes and using the brightest one neighborOffsets := []spatial.NodePosition{ {X: 1, Y: 0, Z: 0}, @@ -71,9 +101,11 @@ func (r *IsometricRenderer) renderNode( maxParam1 := param1 hiddenFaces := mesh.CubeFaces(0) + for i, offset := range neighborOffsets { neighborPos := pos.Add(offset) neighborName, param1, _ := neighborhood.GetNode(neighborPos) + if param1 > maxParam1 { maxParam1 = param1 } @@ -89,26 +121,7 @@ func (r *IsometricRenderer) renderNode( } } - // Make underground edges visible (otherwise the edge becomes oddly thin and - // that doesn't look good) - if r.region.IsAtEdge(worldPos) && maxParam1 == render.ZeroIntensity { - maxParam1 = render.MapEdgeIntensity - } - - renderableNode := render.RenderableNode{ - Name: name, - Light: render.DecodeLight(maxParam1), - Param2: param2, - HiddenFaces: hiddenFaces, - } - renderedNode := r.nr.Render(renderableNode, &nodeDef) - - depthOffset = -float64(pos.Z+pos.X)/math.Sqrt2 - 0.5*(float64(pos.Y)) + depthOffset - if needsAlphaBlending { - target.OverlayDepthAwareWithAlpha(renderedNode, offset, depthOffset) - } else { - target.OverlayDepthAware(renderedNode, offset, depthOffset) - } + return maxParam1, hiddenFaces } func (r *IsometricRenderer) renderBlock( diff --git a/internal/render/light.go b/internal/render/light.go index 7979a4e..a7eae8d 100644 --- a/internal/render/light.go +++ b/internal/render/light.go @@ -25,5 +25,6 @@ func DecodeLight(param1 uint8) float64 { 0.918, 1.000, } + return LUT[param1&0xF] } diff --git a/internal/render/neighborhood.go b/internal/render/neighborhood.go index 4fc5a64..59cb4fe 100644 --- a/internal/render/neighborhood.go +++ b/internal/render/neighborhood.go @@ -51,7 +51,9 @@ func (b *BlockNeighborhood) GetNode(pos spatial.NodePosition) (string, uint8, ui Y: pos.Y % spatial.BlockSize, Z: pos.Z % spatial.BlockSize, }) + name := block.ResolveName(node.ID) + return name, node.Param1, node.Param2 } diff --git a/internal/render/rasterizer.go b/internal/render/rasterizer.go index ac42cc4..38d861d 100644 --- a/internal/render/rasterizer.go +++ b/internal/render/rasterizer.go @@ -61,6 +61,7 @@ func sampleTexture(tex *image.NRGBA, texcoord lm.Vector2) lm.Vector4 { x := int(texcoord.X * float64(tex.Rect.Dx())) y := int(texcoord.Y * float64(tex.Rect.Dy())) c := tex.NRGBAAt(x, y) + return lm.Vector4{ X: float64(c.R) / 255, Y: float64(c.G) / 255, @@ -72,6 +73,29 @@ func sampleTexture(tex *image.NRGBA, texcoord lm.Vector2) lm.Vector4 { var SunLightDir = lm.Vec3(-0.5, 1, -0.8).Normalize() var SunLightIntensity = 0.95 / SunLightDir.MaxComponent() +func shadePixel(lighting float64, texture *image.NRGBA, normal lm.Vector3, texcoord lm.Vector2) color.NRGBA { + light := SunLightIntensity * lighting * lm.Clamp(math.Abs(normal.Dot(SunLightDir))*0.8+0.2, 0.0, 1.0) + + if texture != nil { + rgba := sampleTexture(texture, texcoord) + col := rgba.XYZ().PowScalar(Gamma).MulScalar(lighting).PowScalar(1.0/Gamma).ClampScalar(0.0, 1.0) + + return color.NRGBA{ + R: uint8(255 * col.X), + G: uint8(255 * col.Y), + B: uint8(255 * col.Z), + A: uint8(255 * rgba.W), + } + } else { + return color.NRGBA{ + R: uint8(255 * light), + G: uint8(255 * light), + B: uint8(255 * light), + A: 255, + } + } +} + func (r *NodeRasterizer) drawTriangle(target *raster.RenderBuffer, tex *image.NRGBA, lighting float64, a, b, c mesh.Vertex) { origin := lm.Vector2{ X: float64(target.Color.Bounds().Dx()) / 2, @@ -82,16 +106,16 @@ func (r *NodeRasterizer) drawTriangle(target *raster.RenderBuffer, tex *image.NR b.Position = r.projection.MulVec(b.Position) c.Position = r.projection.MulVec(c.Position) - pa := a.Position.XY().Mul(lm.Vec2(1, -1)).MulScalar(BaseResolution * math.Sqrt2 / 2).Add(origin) - pb := b.Position.XY().Mul(lm.Vec2(1, -1)).MulScalar(BaseResolution * math.Sqrt2 / 2).Add(origin) - pc := c.Position.XY().Mul(lm.Vec2(1, -1)).MulScalar(BaseResolution * math.Sqrt2 / 2).Add(origin) + screenSpaceA := a.Position.XY().Mul(lm.Vec2(1, -1)).MulScalar(BaseResolution * math.Sqrt2 / 2).Add(origin) + screenSpaceB := b.Position.XY().Mul(lm.Vec2(1, -1)).MulScalar(BaseResolution * math.Sqrt2 / 2).Add(origin) + screenSpaceC := c.Position.XY().Mul(lm.Vec2(1, -1)).MulScalar(BaseResolution * math.Sqrt2 / 2).Add(origin) - bboxMin := pa.Min(pb).Min(pc) - bboxMax := pa.Max(pb).Max(pc) + bboxMin := screenSpaceA.Min(screenSpaceB).Min(screenSpaceC) + bboxMax := screenSpaceA.Max(screenSpaceB).Max(screenSpaceC) for y := int(bboxMin.Y); y < int(bboxMax.Y)+1; y++ { for x := int(bboxMin.X); x < int(bboxMax.X)+1; x++ { - pointIsInsideTriangle, barycentric := sampleTriangle(x, y, pa, pb, pc) + pointIsInsideTriangle, barycentric := sampleTriangle(x, y, screenSpaceA, screenSpaceB, screenSpaceC) if !pointIsInsideTriangle { continue @@ -103,30 +127,11 @@ func (r *NodeRasterizer) drawTriangle(target *raster.RenderBuffer, tex *image.NR Add(b.Normal.MulScalar(barycentric.Y)). Add(c.Normal.MulScalar(barycentric.Z)) - lighting := SunLightIntensity * lighting * lm.Clamp(math.Abs(normal.Dot(SunLightDir))*0.8+0.2, 0.0, 1.0) - - var finalColor color.NRGBA - if tex != nil { - texcoord := a.Texcoord.MulScalar(barycentric.X). - Add(b.Texcoord.MulScalar(barycentric.Y)). - Add(c.Texcoord.MulScalar(barycentric.Z)) - rgba := sampleTexture(tex, texcoord) - col := rgba.XYZ().PowScalar(Gamma).MulScalar(lighting).PowScalar(1.0/Gamma).ClampScalar(0.0, 1.0) - - finalColor = color.NRGBA{ - R: uint8(255 * col.X), - G: uint8(255 * col.Y), - B: uint8(255 * col.Z), - A: uint8(255 * rgba.W), - } - } else { - finalColor = color.NRGBA{ - R: uint8(255 * lighting), - G: uint8(255 * lighting), - B: uint8(255 * lighting), - A: 255, - } - } + texcoord := a.Texcoord.MulScalar(barycentric.X). + Add(b.Texcoord.MulScalar(barycentric.Y)). + Add(c.Texcoord.MulScalar(barycentric.Z)) + + finalColor := shadePixel(lighting, tex, normal, texcoord) if finalColor.A > 10 { if pixelDepth > target.Depth.At(x, y) { @@ -200,28 +205,28 @@ func (r *NodeRasterizer) Render(node RenderableNode, nodeDef *game.NodeDefinitio triangleCount := len(mesh.Vertices) / 3 for i := 0; i < triangleCount; i++ { - a := mesh.Vertices[i*3] - b := mesh.Vertices[i*3+1] - c := mesh.Vertices[i*3+2] + vertexA := mesh.Vertices[i*3] + vertexB := mesh.Vertices[i*3+1] + vertexC := mesh.Vertices[i*3+2] if nodeDef.ParamType2 == game.ParamType2FaceDir { - a.Position = transformToFaceDir(a.Position, node.Param2) - b.Position = transformToFaceDir(b.Position, node.Param2) - c.Position = transformToFaceDir(c.Position, node.Param2) - a.Normal = transformToFaceDir(a.Normal, node.Param2) - b.Normal = transformToFaceDir(b.Normal, node.Param2) - c.Normal = transformToFaceDir(c.Normal, node.Param2) + vertexA.Position = transformToFaceDir(vertexA.Position, node.Param2) + vertexB.Position = transformToFaceDir(vertexB.Position, node.Param2) + vertexC.Position = transformToFaceDir(vertexC.Position, node.Param2) + vertexA.Normal = transformToFaceDir(vertexA.Normal, node.Param2) + vertexB.Normal = transformToFaceDir(vertexB.Normal, node.Param2) + vertexC.Normal = transformToFaceDir(vertexC.Normal, node.Param2) } - a.Position.Z = -a.Position.Z - b.Position.Z = -b.Position.Z - c.Position.Z = -c.Position.Z + vertexA.Position.Z = -vertexA.Position.Z + vertexB.Position.Z = -vertexB.Position.Z + vertexC.Position.Z = -vertexC.Position.Z - a.Position.X = -a.Position.X - b.Position.X = -b.Position.X - c.Position.X = -c.Position.X + vertexA.Position.X = -vertexA.Position.X + vertexB.Position.X = -vertexB.Position.X + vertexC.Position.X = -vertexC.Position.X - r.drawTriangle(target, nodeDef.Textures[j], node.Light, a, b, c) + r.drawTriangle(target, nodeDef.Textures[j], node.Light, vertexA, vertexB, vertexC) } } diff --git a/internal/spatial/region.go b/internal/spatial/region.go index d44f289..80d09bf 100644 --- a/internal/spatial/region.go +++ b/internal/spatial/region.go @@ -26,6 +26,7 @@ func (lhs Region) IsAtEdge(pos NodePosition) bool { isAtXEdge := pos.X == lhs.XBounds.Max || pos.X == lhs.XBounds.Min isAtYEdge := pos.Y == lhs.YBounds.Max || pos.Y == lhs.YBounds.Min isAtZEdge := pos.Z == lhs.ZBounds.Max || pos.Z == lhs.ZBounds.Min + return isAtXEdge || isAtYEdge || isAtZEdge } diff --git a/internal/tile/downscale.go b/internal/tile/downscale.go index c90363f..16e02f9 100644 --- a/internal/tile/downscale.go +++ b/internal/tile/downscale.go @@ -24,20 +24,25 @@ func uniquePositions(input []render.TilePosition) []render.TilePosition { if input[i].X < input[j].X { return true } + if input[i].X > input[j].X { return false } + if input[i].Y < input[j].Y { return true } + if input[i].Y > input[j].Y { return false } + return false }) // Loop over the slice and skip repeating elements j := 1 + for i := 1; i < len(input); i++ { // Skip element if it repeats if input[i] == input[i-1] { @@ -77,6 +82,7 @@ func (t *Tiler) downscalePositions(zoom int, positions []render.TilePosition) [] } imagePath := t.tilePath(pos.X, pos.Y, zoom) + err := raster.SavePNG(target, imagePath) if err != nil { slog.Error("unable to save image", "err", err, "path", imagePath) @@ -89,5 +95,6 @@ func (t *Tiler) downscalePositions(zoom int, positions []render.TilePosition) [] } nextPositions = uniquePositions(nextPositions) + return nextPositions } diff --git a/internal/tile/tiler.go b/internal/tile/tiler.go index 72e066a..831ce1b 100644 --- a/internal/tile/tiler.go +++ b/internal/tile/tiler.go @@ -46,10 +46,12 @@ func (t *Tiler) worker(wg *sync.WaitGroup, game *game.Game, world *world.World, } tilePath := t.tilePath(position.X, position.Y, 0) + err := raster.SavePNG(output.Color, tilePath) if err != nil { return } + slog.Info("saved", "path", tilePath) } @@ -60,14 +62,16 @@ type CreateRendererFunc func() render.Renderer func (t *Tiler) FullRender(game *game.Game, world *world.World, workers int, region spatial.Region, createRenderer CreateRendererFunc) { var wg sync.WaitGroup - positions := make(chan render.TilePosition) + positions := make(chan render.TilePosition) projectedRegion := spatial.ProjectedRegion{} for i := 0; i < workers; i++ { wg.Add(1) + renderer := createRenderer() projectedRegion = renderer.ProjectRegion(region) + go t.worker(&wg, game, world, renderer, positions) } @@ -97,6 +101,7 @@ func (t *Tiler) DownscaleTiles() { // Collect tile positions var positions []render.TilePosition + err = filepath.WalkDir(tileDir, func(path string, d fs.DirEntry, _ error) error { if d == nil || d.IsDir() { return nil @@ -106,11 +111,15 @@ func (t *Tiler) DownscaleTiles() { y, err := strconv.Atoi(strings.TrimSuffix(file, filepath.Ext(file))) if err != nil { + slog.Warn("skipped file due to error", "path", path, "err", err) + return nil } x, err := strconv.Atoi(filepath.Base(dir)) if err != nil { + slog.Warn("skipped file due to error", "path", path, "err", err) + return nil } diff --git a/internal/world/block.go b/internal/world/block.go index e27b0cd..bba9f2f 100644 --- a/internal/world/block.go +++ b/internal/world/block.go @@ -21,12 +21,14 @@ type Node struct { func readU8(r io.Reader) (uint8, error) { var value uint8 err := binary.Read(r, binary.BigEndian, &value) + return value, err } func readU16(r io.Reader) (uint16, error) { var value uint16 err := binary.Read(r, binary.BigEndian, &value) + return value, err } @@ -37,6 +39,7 @@ func readString(r io.Reader) (string, error) { } buf := make([]byte, length) + _, err = io.ReadFull(r, buf) if err != nil { return "", err @@ -65,12 +68,14 @@ func NewReaderCounter(r *bytes.Reader) *ReaderCounter { func (r *ReaderCounter) Read(p []byte) (n int, err error) { n, err = r.inner.Read(p) r.count += int64(n) + return } func (r *ReaderCounter) ReadByte() (byte, error) { b, err := r.inner.ReadByte() r.count += 1 + return b, err } @@ -78,13 +83,15 @@ func inflate(reader *bytes.Reader) ([]byte, error) { position, _ := reader.Seek(0, io.SeekCurrent) counter := NewReaderCounter(reader) - z, err := zlib.NewReader(counter) + + zstdReader, err := zlib.NewReader(counter) if err != nil { return nil, err } - defer z.Close() - data, err := io.ReadAll(z) + defer zstdReader.Close() + + data, err := io.ReadAll(zstdReader) if err != nil { return nil, err } @@ -104,11 +111,13 @@ func readMappings(reader *bytes.Reader) (map[uint16]string, error) { } mappings := make(map[uint16]string) + for i := 0; i < int(mappingCount); i++ { id, err := readU16(reader) if err != nil { return nil, err } + name, err := readString(reader) if err != nil { return nil, err @@ -120,6 +129,7 @@ func readMappings(reader *bytes.Reader) (map[uint16]string, error) { return mappings, nil } +//nolint:funlen // linear decoding with almost no logic func decodeLegacyBlock(reader *bytes.Reader, version uint8) (*MapBlock, error) { if version >= 27 { // - uint8 flags @@ -168,10 +178,12 @@ func decodeLegacyBlock(reader *bytes.Reader, version uint8) (*MapBlock, error) { if err != nil { return nil, err } + dataSize, err := readU16(reader) if err != nil { return nil, err } + _, err = reader.Seek(int64(dataSize), io.SeekCurrent) if err != nil { return nil, err @@ -232,6 +244,7 @@ func decodeBlock(reader *bytes.Reader) (*MapBlock, error) { } nodeData := make([]byte, spatial.BlockVolume*NodeSizeInBytes) + _, err = io.ReadFull(reader, nodeData) if err != nil { return nil, err @@ -256,6 +269,7 @@ func DecodeMapBlock(data []byte) (*MapBlock, error) { if err != nil { return nil, err } + return mapblock, nil } @@ -268,10 +282,13 @@ func (b *MapBlock) ResolveName(id uint16) string { func (b *MapBlock) GetNode(pos spatial.NodePosition) Node { index := pos.Z*spatial.BlockSize*spatial.BlockSize + pos.Y*spatial.BlockSize + pos.X + idHi := uint16(b.nodeData[2*index]) idLo := uint16(b.nodeData[2*index+1]) + param1 := b.nodeData[2*spatial.BlockVolume+index] param2 := b.nodeData[3*spatial.BlockVolume+index] + return Node{ ID: (idHi << 8) | idLo, Param1: param1, diff --git a/internal/world/world.go b/internal/world/world.go index 1b1dc87..70c16a5 100644 --- a/internal/world/world.go +++ b/internal/world/world.go @@ -36,6 +36,7 @@ func (p *PostgresBackend) Close() { func (p *PostgresBackend) GetBlockData(pos spatial.BlockPosition) ([]byte, error) { var data []byte + err := p.conn.QueryRow(context.Background(), "SELECT data FROM blocks WHERE posx=$1 and posy=$2 and posz=$3", pos.X, pos.Y, pos.Z).Scan(&data) if errors.Is(err, pgx.ErrNoRows) { return nil, nil @@ -58,6 +59,7 @@ func NewWorldWithBackend(backend Backend) World { if err != nil { panic(err) } + return World{ backend: backend, decodedBlockCache: decodedBlockCache, @@ -71,6 +73,7 @@ func (w *World) GetBlock(pos spatial.BlockPosition) (*MapBlock, error) { if cachedBlock == nil { return nil, nil } + return cachedBlock, nil }