matrices, etc
This commit is contained in:
parent
88fdaf18ac
commit
ebbf76d312
5 changed files with 215 additions and 12 deletions
|
@ -36,6 +36,22 @@ func (b Box) Size() Int3 {
|
||||||
return b.Max.Sub(b.Min)
|
return b.Max.Sub(b.Min)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Add offsets the box by vector p.
|
||||||
|
func (b Box) Add(p Int3) Box {
|
||||||
|
return Box{
|
||||||
|
Min: b.Min.Add(p),
|
||||||
|
Max: b.Max.Add(p),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Sub offsets the box by (-p).
|
||||||
|
func (b Box) Sub(p Int3) Box {
|
||||||
|
return Box{
|
||||||
|
Min: b.Min.Sub(p),
|
||||||
|
Max: b.Max.Sub(p),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Back returns an image.Rectangle representing the back of the box, using
|
// Back returns an image.Rectangle representing the back of the box, using
|
||||||
// the given projection π.
|
// the given projection π.
|
||||||
func (b Box) Back(π IntProjection) image.Rectangle {
|
func (b Box) Back(π IntProjection) image.Rectangle {
|
||||||
|
|
|
@ -1,6 +1,9 @@
|
||||||
package engine
|
package engine
|
||||||
|
|
||||||
import "image"
|
import (
|
||||||
|
"errors"
|
||||||
|
"image"
|
||||||
|
)
|
||||||
|
|
||||||
// IntMatrix3 implements a 3x3 integer matrix.
|
// IntMatrix3 implements a 3x3 integer matrix.
|
||||||
type IntMatrix3 [3][3]int
|
type IntMatrix3 [3][3]int
|
||||||
|
@ -36,7 +39,7 @@ func (a IntMatrix3) Concat(b IntMatrix3) IntMatrix3 {
|
||||||
}
|
}
|
||||||
|
|
||||||
// IntMatrix3x4 implements a 3 row, 4 column integer matrix, capable of
|
// IntMatrix3x4 implements a 3 row, 4 column integer matrix, capable of
|
||||||
//describing any integer affine transformation.
|
// describing any integer affine transformation.
|
||||||
type IntMatrix3x4 [3][4]int
|
type IntMatrix3x4 [3][4]int
|
||||||
|
|
||||||
// Apply applies the matrix to a vector to obtain a transformed vector.
|
// Apply applies the matrix to a vector to obtain a transformed vector.
|
||||||
|
@ -48,6 +51,21 @@ func (a IntMatrix3x4) Apply(v Int3) Int3 {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ToRatMatrix3 returns the 3x3 submatrix as a rational matrix equivalent.
|
||||||
|
func (a IntMatrix3x4) ToRatMatrix3() RatMatrix3 {
|
||||||
|
return RatMatrix3{
|
||||||
|
0: [3]Rat{{a[0][0], 1}, {a[0][1], 1}, {a[0][2], 1}},
|
||||||
|
1: [3]Rat{{a[1][0], 1}, {a[1][1], 1}, {a[1][2], 1}},
|
||||||
|
2: [3]Rat{{a[2][0], 1}, {a[2][1], 1}, {a[2][2], 1}},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Translation returns the translation component of the matrix (last column)
|
||||||
|
// i.e. what you get if you Apply the matrix to the zero vector.
|
||||||
|
func (a IntMatrix3x4) Translation() Int3 {
|
||||||
|
return Int3{X: a[0][3], Y: a[1][3], Z: a[2][3]}
|
||||||
|
}
|
||||||
|
|
||||||
// IntMatrix2x3 implements a 2 row, 3 column matrix (as two row vectors).
|
// IntMatrix2x3 implements a 2 row, 3 column matrix (as two row vectors).
|
||||||
type IntMatrix2x3 struct{ X, Y Int3 }
|
type IntMatrix2x3 struct{ X, Y Int3 }
|
||||||
|
|
||||||
|
@ -58,3 +76,53 @@ func (a IntMatrix2x3) Apply(v Int3) image.Point {
|
||||||
Y: v.Dot(a.Y),
|
Y: v.Dot(a.Y),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// RatMatrix3 implements a 3x3 matrix with rational number entries.
|
||||||
|
type RatMatrix3 [3][3]Rat
|
||||||
|
|
||||||
|
// IdentityRatMatrix3x4 is the identity matrix for RatMatrix3x4.
|
||||||
|
var IdentityRatMatrix3 = RatMatrix3{
|
||||||
|
0: [3]Rat{0: {1, 1}},
|
||||||
|
1: [3]Rat{1: {1, 1}},
|
||||||
|
2: [3]Rat{2: {1, 1}},
|
||||||
|
}
|
||||||
|
|
||||||
|
// IntApply applies the matrix to the integer vector v. Any remainder is lost.
|
||||||
|
func (a RatMatrix3) IntApply(v Int3) Int3 {
|
||||||
|
x, y, z := IntRat(v.X), IntRat(v.Y), IntRat(v.Z)
|
||||||
|
return Int3{
|
||||||
|
X: x.Mul(a[0][0]).Add(y.Mul(a[0][1])).Add(z.Mul(a[0][2])).Int(),
|
||||||
|
Y: x.Mul(a[1][0]).Add(y.Mul(a[1][1])).Add(z.Mul(a[1][2])).Int(),
|
||||||
|
Z: x.Mul(a[2][0]).Add(y.Mul(a[2][1])).Add(z.Mul(a[2][2])).Int(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Inverse returns the inverse of the matrix.
|
||||||
|
func (a RatMatrix3) Inverse() (RatMatrix3, error) {
|
||||||
|
adj := RatMatrix3{
|
||||||
|
0: [3]Rat{
|
||||||
|
0: a[1][1].Mul(a[2][2]).Sub(a[1][2].Mul(a[2][1])),
|
||||||
|
},
|
||||||
|
1: [3]Rat{
|
||||||
|
0: a[1][0].Mul(a[2][2]).Sub(a[1][2].Mul(a[2][0])).Neg(),
|
||||||
|
},
|
||||||
|
2: [3]Rat{
|
||||||
|
0: a[1][0].Mul(a[2][1]).Sub(a[1][1].Mul(a[2][0])),
|
||||||
|
},
|
||||||
|
// other columns after determinant...
|
||||||
|
}
|
||||||
|
det := a[0][0].Mul(adj[0][0]).Add(a[0][1].Mul(adj[1][0])).Add(a[0][2].Mul(adj[2][0]))
|
||||||
|
if det.N == 0 {
|
||||||
|
return RatMatrix3{}, errors.New("matrix is singular")
|
||||||
|
}
|
||||||
|
adj[0][0] = adj[0][0].Div(det)
|
||||||
|
adj[1][0] = adj[1][0].Div(det)
|
||||||
|
adj[2][0] = adj[2][0].Div(det)
|
||||||
|
adj[0][1] = a[0][1].Mul(a[2][2]).Sub(a[0][2].Mul(a[2][1])).Neg().Div(det)
|
||||||
|
adj[0][2] = a[0][1].Mul(a[1][2]).Sub(a[0][2].Mul(a[1][1])).Div(det)
|
||||||
|
adj[1][1] = a[0][0].Mul(a[2][2]).Sub(a[0][2].Mul(a[2][0])).Div(det)
|
||||||
|
adj[1][2] = a[0][0].Mul(a[1][2]).Sub(a[0][2].Mul(a[1][0])).Neg().Div(det)
|
||||||
|
adj[2][1] = a[0][0].Mul(a[2][1]).Sub(a[0][1].Mul(a[2][0])).Neg().Div(det)
|
||||||
|
adj[2][2] = a[0][0].Mul(a[1][1]).Sub(a[0][1].Mul(a[1][0])).Div(det)
|
||||||
|
return adj, nil
|
||||||
|
}
|
||||||
|
|
|
@ -2,7 +2,9 @@ package engine
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"encoding/gob"
|
"encoding/gob"
|
||||||
|
"fmt"
|
||||||
"image"
|
"image"
|
||||||
|
"log"
|
||||||
|
|
||||||
"github.com/hajimehoshi/ebiten/v2"
|
"github.com/hajimehoshi/ebiten/v2"
|
||||||
)
|
)
|
||||||
|
@ -28,31 +30,57 @@ func init() {
|
||||||
gob.Register(&Prism{})
|
gob.Register(&Prism{})
|
||||||
}
|
}
|
||||||
|
|
||||||
// PrismMap
|
// PrismMap is a generalised tilemap/wallmap/etc.
|
||||||
type PrismMap struct {
|
type PrismMap struct {
|
||||||
ID
|
ID
|
||||||
Disabled
|
Disabled
|
||||||
Hidden
|
Hidden
|
||||||
|
Ersatz bool
|
||||||
Map map[Int3]*Prism // pos -> prism
|
Map map[Int3]*Prism // pos -> prism
|
||||||
DrawOrderBias image.Point // dot with pos.XY() = bias value
|
DrawOrderBias image.Point // dot with pos.XY() = bias value
|
||||||
DrawOffset image.Point // offset applies to whole map
|
DrawOffset image.Point // offset applies to whole map
|
||||||
PosToWorld IntMatrix3x4 // p.pos -> voxelspace
|
PosToWorld IntMatrix3x4 // p.pos -> world voxelspace
|
||||||
PrismSize Int3 // in voxelspace
|
PrismSize Int3 // in world voxelspace units
|
||||||
Sheet Sheet
|
Sheet Sheet
|
||||||
|
|
||||||
game *Game
|
game *Game
|
||||||
|
pwinverse RatMatrix3
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m *PrismMap) CollidesWith(b Box) bool {
|
func (m *PrismMap) CollidesWith(b Box) bool {
|
||||||
// Back corner of a prism p is:
|
if m.Ersatz {
|
||||||
// m.PrismPos.Apply(p.pos)
|
return false
|
||||||
|
}
|
||||||
|
// To find the prisms need to test, we need to invert PosToWorld.
|
||||||
|
|
||||||
|
// Step 1: subtract whatever the translation component of PosToWorld is,
|
||||||
|
// reducing the rest of the problem to the 3x3 submatrix.
|
||||||
|
b = b.Sub(m.PosToWorld.Translation())
|
||||||
|
// Step 2: invert the rest of the fucking matrix.
|
||||||
|
// (Spoilers: I did this already in Prepare)
|
||||||
|
b.Min = m.pwinverse.IntApply(b.Min)
|
||||||
|
b.Max = m.pwinverse.IntApply(b.Max.Sub(Int3{1, 1, 1}))
|
||||||
|
for k := b.Min.Z; k < b.Max.Z; k++ {
|
||||||
|
for j := b.Min.Y; j < b.Max.Y; j++ {
|
||||||
|
for i := b.Min.X; i < b.Max.X; i++ {
|
||||||
|
// TODO: take into account the prism shape...
|
||||||
|
if _, found := m.Map[Int3{i, j, k}]; found {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m *PrismMap) Prepare(g *Game) error {
|
func (m *PrismMap) Prepare(g *Game) error {
|
||||||
m.game = g
|
m.game = g
|
||||||
|
pwi, err := m.PosToWorld.ToRatMatrix3().Inverse()
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("inverting PosToWorld: %w", err)
|
||||||
|
}
|
||||||
|
m.pwinverse = pwi
|
||||||
|
log.Printf("inverted PosToWorld: %v", pwi)
|
||||||
for v, p := range m.Map {
|
for v, p := range m.Map {
|
||||||
p.pos = v
|
p.pos = v
|
||||||
p.m = m
|
p.m = m
|
||||||
|
|
92
engine/rational.go
Normal file
92
engine/rational.go
Normal file
|
@ -0,0 +1,92 @@
|
||||||
|
package engine
|
||||||
|
|
||||||
|
// Rat is a (small) rational number implementation. Overflow can happen.
|
||||||
|
type Rat struct{ N, D int }
|
||||||
|
|
||||||
|
// IntRat returns the rational representation of n.
|
||||||
|
func IntRat(n int) Rat { return Rat{N: n, D: 1} }
|
||||||
|
|
||||||
|
// Int returns r.N / r.D.
|
||||||
|
func (r Rat) Int() int { return r.N / r.D }
|
||||||
|
|
||||||
|
// Rem returns r.N % r.D.
|
||||||
|
func (r Rat) Rem() int { return r.N % r.D }
|
||||||
|
|
||||||
|
// Canon puts the rational number into reduced form.
|
||||||
|
func (r Rat) Canon() Rat {
|
||||||
|
if r.D == 0 {
|
||||||
|
panic("division by zero")
|
||||||
|
}
|
||||||
|
if r.N == 0 {
|
||||||
|
r.D = 1
|
||||||
|
return r
|
||||||
|
}
|
||||||
|
if r.D < 0 {
|
||||||
|
r.N, r.D = -r.N, -r.D
|
||||||
|
}
|
||||||
|
if d := gcd(abs(r.N), r.D); d > 1 {
|
||||||
|
r.N /= d
|
||||||
|
r.D /= d
|
||||||
|
}
|
||||||
|
return r
|
||||||
|
}
|
||||||
|
|
||||||
|
// Neg returns -r.
|
||||||
|
func (r Rat) Neg() Rat {
|
||||||
|
r.N = -r.N
|
||||||
|
return r
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add returns r + q.
|
||||||
|
func (r Rat) Add(q Rat) Rat {
|
||||||
|
return Rat{
|
||||||
|
N: r.N*q.D + q.N*r.D,
|
||||||
|
D: r.D * q.D,
|
||||||
|
}.Canon()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Sub returns r - q.
|
||||||
|
func (r Rat) Sub(q Rat) Rat {
|
||||||
|
return Rat{
|
||||||
|
N: r.N*q.D - q.N*r.D,
|
||||||
|
D: r.D * q.D,
|
||||||
|
}.Canon()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Mul returns r * q.
|
||||||
|
func (r Rat) Mul(q Rat) Rat {
|
||||||
|
return Rat{
|
||||||
|
N: r.N * q.N,
|
||||||
|
D: r.D * q.D,
|
||||||
|
}.Canon()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Invert returns 1/r if it exists, otherwise panics.
|
||||||
|
func (r Rat) Invert() Rat {
|
||||||
|
return Rat{N: r.D, D: r.N}.Canon()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Div returns r/q.
|
||||||
|
func (r Rat) Div(q Rat) Rat {
|
||||||
|
return Rat{
|
||||||
|
N: r.N * q.D,
|
||||||
|
D: r.D * q.N,
|
||||||
|
}.Canon()
|
||||||
|
}
|
||||||
|
|
||||||
|
func abs(n int) int {
|
||||||
|
if n < 0 {
|
||||||
|
return -n
|
||||||
|
}
|
||||||
|
return n
|
||||||
|
}
|
||||||
|
|
||||||
|
func gcd(a, b int) int {
|
||||||
|
if a < b {
|
||||||
|
return gcd(b, a)
|
||||||
|
}
|
||||||
|
for b != 0 {
|
||||||
|
a, b = b, a%b
|
||||||
|
}
|
||||||
|
return a
|
||||||
|
}
|
5
main.go
5
main.go
|
@ -140,7 +140,6 @@ func level1() *engine.Scene {
|
||||||
&engine.PrismMap{
|
&engine.PrismMap{
|
||||||
ID: "hexagons",
|
ID: "hexagons",
|
||||||
DrawOrderBias: image.Pt(0, -1), // draw higher Y after lower Y
|
DrawOrderBias: image.Pt(0, -1), // draw higher Y after lower Y
|
||||||
DrawOffset: image.Pt(-8, 0),
|
|
||||||
PosToWorld: engine.IntMatrix3x4{
|
PosToWorld: engine.IntMatrix3x4{
|
||||||
0: [4]int{24, 0, 0, 0},
|
0: [4]int{24, 0, 0, 0},
|
||||||
1: [4]int{0, 16, 0, 0},
|
1: [4]int{0, 16, 0, 0},
|
||||||
|
@ -159,9 +158,9 @@ func level1() *engine.Scene {
|
||||||
engine.Pt3(4, -1, 0): {},
|
engine.Pt3(4, -1, 0): {},
|
||||||
engine.Pt3(4, 0, 0): {},
|
engine.Pt3(4, 0, 0): {},
|
||||||
|
|
||||||
engine.Pt3(6, 0, -4): {},
|
|
||||||
engine.Pt3(6, 0, -3): {},
|
|
||||||
engine.Pt3(6, 0, -2): {},
|
engine.Pt3(6, 0, -2): {},
|
||||||
|
engine.Pt3(6, 0, -1): {},
|
||||||
|
engine.Pt3(6, 0, 0): {},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
&engine.Tilemap{
|
&engine.Tilemap{
|
||||||
|
|
Loading…
Reference in a new issue