Remove custom physics package in favour of harmonica

This commit is contained in:
Maas Lalani 2021-10-23 01:10:40 -04:00
parent fe126ff4df
commit f4ce9d632f
7 changed files with 20 additions and 244 deletions

View File

@ -4,8 +4,8 @@ import (
"math/rand"
"time"
"github.com/charmbracelet/harmonica"
"github.com/maaslalani/confetty/array"
"github.com/maaslalani/confetty/physics"
"github.com/maaslalani/confetty/simulation"
tea "github.com/charmbracelet/bubbletea"
@ -43,11 +43,11 @@ func Spawn(width, height int) []simulation.Particle {
y := float64(0)
p := simulation.Particle{
Physics: physics.New(
physics.Point{X: x + (float64(width/4) * (rand.Float64() - 0.5)), Y: y},
physics.Vector{X: (rand.Float64() - 0.5) * 100, Y: rand.Float64() * 50},
physics.Vector(physics.Gravity),
framesPerSecond,
Physics: harmonica.NewProjectile(
harmonica.FPS(framesPerSecond),
harmonica.Point{X: x + (float64(width/4) * (rand.Float64() - 0.5)), Y: y, Z: 0},
harmonica.Vector{X: (rand.Float64() - 0.5) * 100, Y: rand.Float64() * 50, Z: 0},
harmonica.Vector(harmonica.TerminalGravity),
),
Char: lipgloss.NewStyle().
Foreground(lipgloss.Color(array.Sample(colors))).

View File

@ -5,8 +5,8 @@ import (
"math/rand"
"time"
"github.com/charmbracelet/harmonica"
"github.com/maaslalani/confetty/array"
"github.com/maaslalani/confetty/physics"
"github.com/maaslalani/confetty/simulation"
tea "github.com/charmbracelet/bubbletea"
@ -48,11 +48,11 @@ func Spawn(width, height int) []simulation.Particle {
for i := 0; i < numParticles; i++ {
p := simulation.Particle{
Physics: physics.New(
physics.Point{X: x, Y: y},
physics.Vector{X: math.Cos(float64(i)) * v, Y: math.Sin(float64(i)) * v / 2},
physics.Vector(physics.Gravity),
framesPerSecond,
Physics: harmonica.NewProjectile(
harmonica.FPS(framesPerSecond),
harmonica.Point{X: x, Y: y},
harmonica.Vector{X: math.Cos(float64(i)) * v, Y: math.Sin(float64(i)) * v / 2},
harmonica.Vector(harmonica.TerminalGravity),
),
Char: lipgloss.NewStyle().Foreground(color).Render(array.Sample(characters)),
}

1
go.mod
View File

@ -5,6 +5,7 @@ go 1.16
require (
github.com/charmbracelet/bubbles v0.8.0
github.com/charmbracelet/bubbletea v0.14.1
github.com/charmbracelet/harmonica v0.1.1-0.20211007155451-1466d67a1d68 // indirect
github.com/charmbracelet/lipgloss v0.3.0
github.com/spf13/cobra v1.2.1
golang.org/x/term v0.0.0-20210422114643-f5beecf764ed

2
go.sum
View File

@ -52,6 +52,8 @@ github.com/charmbracelet/bubbles v0.8.0/go.mod h1:5WX1sSSjNCgCrzvRMN/z23HxvWaa+A
github.com/charmbracelet/bubbletea v0.13.1/go.mod h1:tp9tr9Dadh0PLhgiwchE5zZJXm5543JYjHG9oY+5qSg=
github.com/charmbracelet/bubbletea v0.14.1 h1:pD/bM5LBEH/nDo7nKcgNUgi4uRHQhpWTIHZbG5vuSlc=
github.com/charmbracelet/bubbletea v0.14.1/go.mod h1:b5lOf5mLjMg1tRn1HVla54guZB+jvsyV0yYAQja95zE=
github.com/charmbracelet/harmonica v0.1.1-0.20211007155451-1466d67a1d68 h1:Gsc/gIm3Fwp9P1hz0tY+7fJR7fASRsGacIErx5+B7uU=
github.com/charmbracelet/harmonica v0.1.1-0.20211007155451-1466d67a1d68/go.mod h1:KSri/1RMQOZLbw7AHqgcBycp8pgJnQMYYT8QZRqZ1Ao=
github.com/charmbracelet/lipgloss v0.1.2/go.mod h1:5D8zradw52m7QmxRF6QgwbwJi9je84g8MkWiGN07uKg=
github.com/charmbracelet/lipgloss v0.3.0 h1:5MysOD6sHr4RP4jkZNWGVIul5GKoOsP12NgbgXPvAlA=
github.com/charmbracelet/lipgloss v0.3.0/go.mod h1:VkhdBS2eNAmRkTwRKLJCFhCOVkjntMusBDxv7TXahuk=

View File

@ -1,101 +0,0 @@
package physics
import (
"math"
)
// Position is the location of an object on a 2-dimensional plane
type Position Point
// Velocity is the velocity vector of an object's motion
type Velocity Vector
// Acceleration is the acceleration vector of an object's motion
type Acceleration Vector
// Gravity is the acceleration of gravity
// Downward (+) by g m/s²
var Gravity = Acceleration{
X: 0,
Y: 9.81,
}
// Motion represents an objects motion
// it keeps track of the position, velocity, and acceleration
type Motion struct {
pos Position
vel Velocity
acc Acceleration
}
// Physics tracks the current motion and initial motion of an object along with
// fps to account for the Update in frames rather than per second
type Physics struct {
current Motion
initial Motion
fps float64
}
// Vector represents a magnitude and a direction in the form of a Point
// from the origin (0, 0)
type Vector struct {
X float64
Y float64
}
// Point is a coordinate on a 2-dimensional plane
type Point struct {
X float64
Y float64
}
// Distance calculates the euclidean distance between two points
func (a Point) Distance(b Point) float64 {
return math.Sqrt(math.Pow(b.X-a.X, 2) + math.Pow(b.Y-a.Y, 2))
}
// New initialize a physics simulation with simple motion
func New(pos Point, vel, acc Vector, fps float64) *Physics {
motion := Motion{
pos: Position(pos),
vel: Velocity(vel),
acc: Acceleration(acc),
}
return &Physics{
initial: motion,
current: motion,
fps: fps,
}
}
// Reset resets the current motion back to the initial
func (p *Physics) Reset() {
p.current = p.initial
}
// Update increases the position of the motion by the velocity
// and increases the velocity by the acceleration
func (p *Physics) Update() {
p.current.pos.X += p.current.vel.X / p.fps
p.current.pos.Y += p.current.vel.Y / p.fps
p.current.vel.X += p.current.acc.X / p.fps
p.current.vel.Y += p.current.acc.Y / p.fps
}
// Displacement calculates the displacement between the current position and
// its initial position
func (p Physics) Displacement() float64 {
return Point(p.initial.pos).Distance(Point(p.current.pos))
}
// PosX returns the integer value of the current x coordinate for motion
// not to be confused with Posix :D
func (p Physics) PosX() int {
return int(math.Round(p.current.pos.X))
}
// PosY returns the integer value of the current y coordinate for motion
func (p Physics) PosY() int {
return int(math.Round(p.current.pos.Y))
}

View File

@ -1,126 +0,0 @@
package physics_test
import (
"testing"
"time"
. "github.com/maaslalani/confetty/physics"
)
const fps = 60
func simulate(p *Physics, d time.Duration) {
frames := int(d.Seconds() * fps)
for i := 0; i < frames; i++ {
p.Update()
}
}
func TestNew(t *testing.T) {
x := 8
y := 20
physics := New(Point{float64(x), float64(y)}, Vector{1, 1}, Vector(Gravity), 60)
if x != physics.PosX() {
t.Fatal("x coordinate unexpected")
}
if y != physics.PosY() {
t.Fatal("y coordinate unexpected")
}
}
func TestUpdate(t *testing.T) {
physics := New(Point{0, 0}, Vector{5, 5}, Vector(Gravity), float64(fps))
// coordinates is the location at which the object should be
// after i+1 seconds of simulation.
coordinates := []Point{
{5, 10},
{10, 29},
{15, 59},
{20, 98},
{25, 147},
{30, 206},
{35, 275},
}
for _, c := range coordinates {
simulate(physics, time.Second)
x := physics.PosX()
y := physics.PosY()
if x != int(c.X) {
t.Logf("Want: %d, Got: %d", x, int(c.X))
t.Fatal("x coordinate unexpected")
}
if y != int(c.Y) {
t.Logf("Want: %d, Got: %d", y, int(c.Y))
t.Fatal("y coordinate unexpected")
}
}
}
func TestDisplacement(t *testing.T) {
tt := []struct {
x int
y int
vel Vector
d float64
}{
{x: 5, y: 5, vel: Vector{5, 10}, d: 11.180339887498933},
{x: 0, y: 0, vel: Vector{1, 1}, d: 1.414213562373097},
}
for _, tc := range tt {
physics := New(Point{float64(tc.x), float64(tc.y)}, tc.vel, Vector{}, float64(fps))
simulate(physics, time.Second)
if physics.Displacement() != tc.d {
t.Log(physics.Displacement())
t.Fatal("expected displacement to be 15")
}
}
}
func TestReset(t *testing.T) {
x := 5
y := 10
vel := Vector{20, 20}
physics := New(Point{float64(x), float64(y)}, vel, Vector(Gravity), float64(fps))
simulate(physics, time.Second)
// store the current position after 1 second of simulation we will simulate 1
// second again and ensure that the object reaches the same position, we
// don't care what the position is but if the object reaches the same
// position again then we ensure that the velocity and acceleration was reset
cx := physics.PosX()
cy := physics.PosY()
physics.Reset()
if physics.PosX() != x {
t.Fatal("expected x to be reset")
}
if physics.PosY() != y {
t.Fatal("expected y to be reset")
}
// here we are simply checking that the object reaches the same position
// after being reset in the same amount of time. This ensures velocity and
// acceleration were reset without checking them explicitly
simulate(physics, time.Second)
if physics.PosX() != cx {
t.Fatal("expected simulation to be repeatable")
}
if physics.PosY() != cy {
t.Fatal("expected simulation to be repeatable")
}
}

View File

@ -4,7 +4,7 @@ import (
"fmt"
"strings"
"github.com/maaslalani/confetty/physics"
"github.com/charmbracelet/harmonica"
)
type System struct {
@ -14,7 +14,7 @@ type System struct {
type Particle struct {
Char string
Physics *physics.Physics
Physics *harmonica.Projectile
Hidden bool
}
@ -39,8 +39,8 @@ func (s *System) Update() {
}
func (s *System) Visible(p Particle) bool {
y := p.Physics.PosY()
x := p.Physics.PosX()
y := int(p.Physics.Position().Y)
x := int(p.Physics.Position().X)
return y >= 0 && y < s.Frame.Height-1 && x >= 0 && x < s.Frame.Width-1
}
@ -52,7 +52,7 @@ func (s *System) Render() string {
}
for _, p := range s.Particles {
if s.Visible(p) {
plane[p.Physics.PosY()][p.Physics.PosX()] = p.Char
plane[int(p.Physics.Position().Y)][int(p.Physics.Position().X)] = p.Char
}
}
for i := range plane {