Files
aego/modules/camera/pose.go
T

131 lines
3.1 KiB
Go

package camera
import (
"math"
"aego/core/mathx"
"aego/render/view"
)
type Pose struct {
Position mathx.Vec2
Rotation float32
Zoom float32
}
func poseOf(c view.Camera2D) Pose {
return Pose{Position: c.Position, Rotation: c.Rotation, Zoom: c.Zoom}
}
func (p Pose) camera() view.Camera2D {
return view.Camera2D{Position: p.Position, Rotation: p.Rotation, Zoom: p.Zoom}
}
func wrapAngle(a float32) float32 {
return float32(math.Remainder(float64(a), 2*math.Pi))
}
func logLerp(a, b, k float32) float32 {
if a <= 0 || b <= 0 {
return a + (b-a)*k
}
la, lb := math.Log(float64(a)), math.Log(float64(b))
return float32(math.Exp(la + (lb-la)*float64(k)))
}
func lerpPose(a, b Pose, k float32) Pose {
return Pose{
Position: a.Position.Lerp(b.Position, k),
Rotation: a.Rotation + wrapAngle(b.Rotation-a.Rotation)*k,
Zoom: logLerp(a.Zoom, b.Zoom, k),
}
}
func ViewSize(v view.View, zoom float32) mathx.Vec2 {
if zoom > 0 {
v.Camera.Zoom = zoom
}
ez := v.EffectiveZoom()
if ez <= 0 {
return mathx.Vec2{}
}
lb := v.LetterboxViewport()
return mathx.Vec2{X: lb.W / ez, Y: lb.H / ez}
}
func Frame(center, size mathx.Vec2) mathx.Rect {
return mathx.Rect{X: center.X - size.X/2, Y: center.Y - size.Y/2, W: size.X, H: size.Y}
}
func DeadzoneRect(center, size mathx.Vec2, dz mathx.Rect) mathx.Rect {
return mathx.Rect{X: center.X + (dz.X-0.5)*size.X, Y: center.Y + (dz.Y-0.5)*size.Y, W: dz.W * size.X, H: dz.H * size.Y}
}
func ZoneCorners(m mathx.Mat3, a mathx.Rect) [4]mathx.Vec2 {
return [4]mathx.Vec2{
m.Apply(mathx.Vec2{X: a.X, Y: a.Y}),
m.Apply(mathx.Vec2{X: a.X + a.W, Y: a.Y}),
m.Apply(mathx.Vec2{X: a.X + a.W, Y: a.Y + a.H}),
m.Apply(mathx.Vec2{X: a.X, Y: a.Y + a.H}),
}
}
func zoneContains(m mathx.Mat3, a mathx.Rect, p mathx.Vec2) bool {
c := ZoneCorners(m, a)
lo, hi := c[0], c[0]
for _, v := range c[1:] {
lo = mathx.Vec2{X: min(lo.X, v.X), Y: min(lo.Y, v.Y)}
hi = mathx.Vec2{X: max(hi.X, v.X), Y: max(hi.Y, v.Y)}
}
return p.X >= lo.X && p.X <= hi.X && p.Y >= lo.Y && p.Y <= hi.Y
}
func clampAxis(c, lo, size, half float32) float32 {
if size <= 2*half {
return lo + size/2
}
return mathx.Clamp(c, lo+half, lo+size-half)
}
func clampView(base view.View, p Pose, b mathx.Rect) mathx.Vec2 {
size := ViewSize(base, p.Zoom)
return mathx.Vec2{
X: clampAxis(p.Position.X, b.X, b.W, size.X/2),
Y: clampAxis(p.Position.Y, b.Y, b.H, size.Y/2),
}
}
func expK(rate, dt float32) float32 {
if rate <= 0 {
return 1
}
return 1 - float32(math.Exp(-float64(rate*dt)))
}
func axisStep(pos, goal, size, dzMin, dzSize, rate, dt float32) float32 {
lo := pos + (dzMin-0.5)*size
hi := lo + dzSize*size
var err float32
switch {
case goal < lo:
err = goal - lo
case goal > hi:
err = goal - hi
}
return pos + err*expK(rate, dt)
}
func followStep(pos, goal, size mathx.Vec2, dz mathx.Rect, damping mathx.Vec2, dt float32) mathx.Vec2 {
return mathx.Vec2{
X: axisStep(pos.X, goal.X, size.X, dz.X, dz.W, damping.X, dt),
Y: axisStep(pos.Y, goal.Y, size.Y, dz.Y, dz.H, damping.Y, dt),
}
}
func limit(v, m float32) float32 {
if m <= 0 {
return v
}
return mathx.Clamp(v, -m, m)
}