package gg import ( //"github.com/hajimehoshi/ebiten/v2" //"math" ) type Transformer interface { GetTransform() *Transform } // The structure represents basic transformation // features: positioning, rotating and scaling. type Transform struct { // Absolute (if no parent) position and // the scale. position, scale Vector // The object rotation in radians. rotation Float // The not scaled offset vector from upper left corner // which the object should be rotated around. around Vector // If is not nil then the upper values will be relational to // the parent ones. parent *Transform // Dirty is true if we anyhow changed matrix. dirty, parentDirty bool matrix, parentMatrix, parentInverted Matrix } // For implementing the Transformer on embedding. func (t *Transform) GetTransform() *Transform { return t } // Returns the default Transform structure. func T() Transform { ret := Transform{ // Rotate around scale: Vector{1, 1}, // Rotate around the center. around: V(.5, .5), } return ret } // Set the absolute object position. func (t *Transform) SetPosition(position Vector) { t.dirty = true t.parentDirty = true if t.parent == nil { t.position = position return } _, mi := t.parent.MatrixForParenting() t.position = position.Apply(mi) } // Set the absolute object rotation. func (t *Transform) SetRotation(rotation Float) { t.dirty = true t.parentDirty = true if t.parent == nil { t.rotation = rotation return } t.rotation -= t.parent.Rotation() } // Set the absolute object scale. func (t *Transform) SetScale(scale Vector) { t.dirty = true t.scale = scale } func (t *Transform) AddScale(add ...Vector) { t.dirty = true t.scale = t.scale.Add(add...) } func (t *Transform) SetAround(around Vector) { t.dirty = true t.around = around } // Get the absolute representation of the transform. func (t *Transform) Abs() Transform { if t.parent == nil { return *t } ret := T() ret.position = t.Position() ret.rotation = t.Rotation() // Do not need that // cause parent does not affect // scaling. // (Should think if scaling from parent // can be used anyhow) //ret.scale = t.Scale() return ret } func (t *Transform) Rel() Transform { ret := *t ret.parent = nil return ret } // Get the absolute object position. func (t *Transform) Position() Vector { if t.parent == nil { return t.position } pm, _ := t.parent.MatrixForParenting() return t.position.Apply(pm) } func (t *Transform) Move(v ...Vector) { t.SetPosition(t.Position().Add(v...)) } // Get the absolute object scale. func (t *Transform) Scale() Vector { return t.scale } // Get the absolute object rotation. func (t *Transform) Rotation() Float { if t.parent == nil { return t.rotation } return t.rotation + t.parent.Rotation() } func (t *Transform) Rotate(add Float) { t.rotation += add t.dirty = true } func (t *Transform) Around() Vector { return t.around } // Returns true if the object is connected // to some parent. func (t *Transform) Connected() bool { return t.parent != nil } // Connect the object to another one making // it its parent. func (t *Transform) Connect(parent Transformer) { absPosition := t.Position() absRotation := t.Rotation() t.parent = parent.GetTransform() t.SetPosition(absPosition) t.SetRotation(absRotation) } // Disconnect from the parent. func (t *Transform) Disconnect() { if t.parent == nil { return } *t = t.Abs() } // Return the matrix and the inverted one for parenting children. func (t *Transform) MatrixForParenting() (Matrix, Matrix) { var m, mi Matrix if t.parentDirty { m.Scale(t.scale.X, t.scale.Y) m.Rotate(t.rotation) m.Translate(t.position.X, t.position.Y) t.parentMatrix = m mi := m mi.Invert() t.parentInverted = mi t.parentDirty = false } else { m = t.parentMatrix mi = t.parentInverted } if t.parent != nil { pm, pmi := t.parent.MatrixForParenting() m.Concat(pm) mi.Concat(pmi) } return m, mi } // Returns the GeoM with corresponding // to the transfrom transformation. func (t *Transform)Matrix() Matrix { var m, pm Matrix // Calculating only if we changed the structure anyhow. if t.dirty { // Scale first. m.Scale(t.scale.X, t.scale.Y) // Then move and rotate. m.Translate( -t.around.X * t.scale.X, -t.around.Y * t.scale.Y, ) m.Rotate(t.rotation) // And finally move to the absolute position. m.Translate(t.position.X, t.position.Y) t.matrix = m // Setting the flag so we d t.dirty = false } else { m = t.matrix } if t.parent != nil { pm, _ = t.parent.MatrixForParenting() m.Concat(pm) } return m }