aminal/vendor/github.com/go-gl/mathgl/mgl32/matstack/transformStack.go

175 lines
5.7 KiB
Go

package matstack
import (
"errors"
"fmt"
"github.com/go-gl/mathgl/mgl32"
)
// A transform stack is a linear fully-persistent data structure of matrix multiplications
// Each push to a TransformStack multiplies the current top of the stack with thew new matrix
// and appends it to the top. Each pop undoes the previous multiplication.
//
// This allows arbitrary unwinding of transformations, at the cost of a lot of memory. A notable feature
// is the reseed and rebase, which allow invertible transformations to be rewritten as if a different transform
// had been made in the middle.
type TransformStack []mgl32.Mat4
// Returns a matrix stack where the top element is the identity.
func NewTransformStack() *TransformStack {
ms := make(TransformStack, 1)
ms[0] = mgl32.Ident4()
return &ms
}
// Multiplies the current top matrix by m, and pushes the result
// on the stack.
func (ms *TransformStack) Push(m mgl32.Mat4) {
prev := (*ms)[len(*ms)-1]
(*ms) = append(*ms, prev.Mul4(m))
}
// Pops the current matrix off the top of the stack and returns it.
// If the matrix stack only has one element left, this will return an error.
func (ms *TransformStack) Pop() (mgl32.Mat4, error) {
if len(*ms) == 1 {
return mgl32.Mat4{}, errors.New("attempt to pop last element of the stack; Matrix Stack must have at least one element")
}
retVal := (*ms)[len(*ms)-1]
(*ms) = (*ms)[:len(*ms)-1]
return retVal, nil
}
// Returns the value of the current top element of the stack, without
// removing it.
func (ms *TransformStack) Peek() mgl32.Mat4 {
return (*ms)[len(*ms)-1]
}
// Returns the size of the matrix stack. This value will never be less
// than 1.
func (ms *TransformStack) Len() int {
return len(*ms)
}
// This cuts down the matrix as if Pop had been called n times. If n would
// bring the matrix down below 1 element, this does nothing and returns an error.
func (ms *TransformStack) Unwind(n int) error {
if n > len(*ms)-1 {
return errors.New("Cannot unwind a matrix to below 1 value")
}
(*ms) = (*ms)[:len(*ms)-n]
return nil
}
// Copy will create a new "branch" of the current matrix stack,
// the copy will contain all elements of the current stack in a new stack. Changes to
// one will never affect the other.
func (ms *TransformStack) Copy() *TransformStack {
v := append(TransformStack{}, (*ms)...)
return &v
}
// Reseed is tricky. It attempts to seed an arbitrary point in the matrix and replay all transformations
// as if that point in the push had been the argument "change" instead of the original value.
// The matrix stack does NOT keep track of arguments so this is done via consecutive inverses.
// If the inverse of element i can be found, we can calculate the transformation that was given at point i+1.
// This transformation can then be multiplied by the NEW matrix at point i to complete the "what if".
// If no such inverse can be found at any given point along the rebase, it will be aborted, and the original
// stack will NOT be visibly affected. The error returned will be of type NoInverseError.
//
// If n is out of bounds (n <= 0 || n >= len(*ms)), a generic error from the errors package will be returned.
//
// If you have the old transformations retained, it is recommended
// that you use Unwind followed by Push(change) and then further calling Push for each transformation. Rebase is
// imprecise by nature, and sometimes impossible. It's also expensive due to the inverse calculation at each point.
func (ms *TransformStack) Reseed(n int, change mgl32.Mat4) error {
if n >= len(*ms) || n <= 0 {
return errors.New("Cannot rebase at the given point on the stack, it is out of bounds.")
}
return ms.reseed(n, change)
}
// Operates like reseed with no bounds checking; allows us to overwrite
// the leading identity matrix with Rebase.
func (ms *TransformStack) reseed(n int, change mgl32.Mat4) error {
backup := []mgl32.Mat4((*ms)[n:])
backup = append([]mgl32.Mat4{}, backup...) // copy into new slice
curr := (*ms)[n]
(*ms)[n] = (*ms)[n-1].Mul4(change)
for i := n + 1; i < len(*ms); i++ {
inv := curr.Inv()
blank := mgl32.Mat4{}
if inv == blank {
ms.undoRebase(n, backup)
return NoInverseError{Loc: i - 1, Mat: curr}
}
ghost := inv.Mul4((*ms)[i])
curr = (*ms)[i]
(*ms)[i] = (*ms)[i-1].Mul4(ghost)
}
return nil
}
func (ms *TransformStack) undoRebase(n int, prev []mgl32.Mat4) {
for i := n; i < len(*ms); i++ {
(*ms)[i] = prev[i-n]
}
}
// Rebase replays the current matrix stack as if the transformation that occurred at index "from"
// in ms had instead started at the top of m.
//
// This returns a brand new stack containing all of m followed by all transformations
// at from and after on ms as if they has been done on m instead.
func Rebase(ms *TransformStack, from int, m *TransformStack) (*TransformStack, error) {
if from <= 0 || from >= len(*ms) {
return nil, errors.New("Cannot rebase, index out of range")
}
// Shift tmp so that the element immediately
// preceding our target is the "top" element of the list.
tmp := ms.Copy()
if from == 1 {
(*tmp) = append(*tmp, mgl32.Mat4{})
}
copy((*tmp)[1:], (*tmp)[from-1:])
if from-2 > 0 {
(*tmp) = (*tmp)[:len(*tmp)-(from-2)]
}
err := tmp.Reseed(1, m.Peek())
if err != nil {
return nil, err
}
(*tmp) = append(*m, (*tmp)[2:]...)
return tmp, nil
}
// A NoInverseError is returned on rebase when an inverse cannot be found along the chain,
// due to a transformation projecting the matrix into a singularity. The values include the matrix
// no inverse can be found for, and the location of that matrix.
type NoInverseError struct {
Mat mgl32.Mat4
Loc int
}
func (nie NoInverseError) Error() string {
return fmt.Sprintf("cannot find inverse of matrix %v at location %d in matrix stack, aborting rebase/reseed", nie.Mat, nie.Loc)
}