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)
|
||||
}
|
||||
|
||||
// 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
|
||||
// the given projection π.
|
||||
func (b Box) Back(π IntProjection) image.Rectangle {
|
||||
|
|
|
@ -1,6 +1,9 @@
|
|||
package engine
|
||||
|
||||
import "image"
|
||||
import (
|
||||
"errors"
|
||||
"image"
|
||||
)
|
||||
|
||||
// IntMatrix3 implements a 3x3 integer matrix.
|
||||
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
|
||||
//describing any integer affine transformation.
|
||||
// describing any integer affine transformation.
|
||||
type IntMatrix3x4 [3][4]int
|
||||
|
||||
// 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).
|
||||
type IntMatrix2x3 struct{ X, Y Int3 }
|
||||
|
||||
|
@ -58,3 +76,53 @@ func (a IntMatrix2x3) Apply(v Int3) image.Point {
|
|||
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 (
|
||||
"encoding/gob"
|
||||
"fmt"
|
||||
"image"
|
||||
"log"
|
||||
|
||||
"github.com/hajimehoshi/ebiten/v2"
|
||||
)
|
||||
|
@ -28,31 +30,57 @@ func init() {
|
|||
gob.Register(&Prism{})
|
||||
}
|
||||
|
||||
// PrismMap
|
||||
// PrismMap is a generalised tilemap/wallmap/etc.
|
||||
type PrismMap struct {
|
||||
ID
|
||||
Disabled
|
||||
Hidden
|
||||
|
||||
Ersatz bool
|
||||
Map map[Int3]*Prism // pos -> prism
|
||||
DrawOrderBias image.Point // dot with pos.XY() = bias value
|
||||
DrawOffset image.Point // offset applies to whole map
|
||||
PosToWorld IntMatrix3x4 // p.pos -> voxelspace
|
||||
PrismSize Int3 // in voxelspace
|
||||
PosToWorld IntMatrix3x4 // p.pos -> world voxelspace
|
||||
PrismSize Int3 // in world voxelspace units
|
||||
Sheet Sheet
|
||||
|
||||
game *Game
|
||||
game *Game
|
||||
pwinverse RatMatrix3
|
||||
}
|
||||
|
||||
func (m *PrismMap) CollidesWith(b Box) bool {
|
||||
// Back corner of a prism p is:
|
||||
// m.PrismPos.Apply(p.pos)
|
||||
if m.Ersatz {
|
||||
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
|
||||
}
|
||||
|
||||
func (m *PrismMap) Prepare(g *Game) error {
|
||||
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 {
|
||||
p.pos = v
|
||||
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{
|
||||
ID: "hexagons",
|
||||
DrawOrderBias: image.Pt(0, -1), // draw higher Y after lower Y
|
||||
DrawOffset: image.Pt(-8, 0),
|
||||
PosToWorld: engine.IntMatrix3x4{
|
||||
0: [4]int{24, 0, 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, 0, 0): {},
|
||||
|
||||
engine.Pt3(6, 0, -4): {},
|
||||
engine.Pt3(6, 0, -3): {},
|
||||
engine.Pt3(6, 0, -2): {},
|
||||
engine.Pt3(6, 0, -1): {},
|
||||
engine.Pt3(6, 0, 0): {},
|
||||
},
|
||||
},
|
||||
&engine.Tilemap{
|
||||
|
|
Loading…
Reference in a new issue