2025-11-27 22:50:36 -05:00
|
|
|
package game
|
|
|
|
|
|
|
|
|
|
import (
|
|
|
|
|
"fluids/elements"
|
|
|
|
|
"fluids/gamedata"
|
|
|
|
|
"fmt"
|
2025-12-06 15:26:25 -05:00
|
|
|
"image/color"
|
2025-11-27 22:50:36 -05:00
|
|
|
"math"
|
|
|
|
|
|
|
|
|
|
"github.com/hajimehoshi/ebiten/v2"
|
2025-12-03 10:21:36 -05:00
|
|
|
"github.com/hajimehoshi/ebiten/v2/ebitenutil"
|
2025-11-27 22:50:36 -05:00
|
|
|
"github.com/hajimehoshi/ebiten/v2/inpututil"
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
const (
|
2025-12-03 10:21:36 -05:00
|
|
|
GameWidth = 640
|
|
|
|
|
GameHeight = 360
|
|
|
|
|
GameFSDW = 200
|
|
|
|
|
GameFSDH = 100
|
2025-12-06 15:26:25 -05:00
|
|
|
GameSims = 3 //number of total sims
|
2025-11-27 22:50:36 -05:00
|
|
|
)
|
|
|
|
|
|
|
|
|
|
type Game struct {
|
2025-12-03 10:21:36 -05:00
|
|
|
//control data
|
|
|
|
|
cycle int
|
|
|
|
|
paused bool
|
|
|
|
|
fluidsimidx int
|
|
|
|
|
|
|
|
|
|
//key elements
|
|
|
|
|
fluidsimd *elements.FluidSimD
|
|
|
|
|
fluidsim10 []*elements.FluidSim10
|
|
|
|
|
alertbox *elements.Alert
|
2025-12-06 15:26:25 -05:00
|
|
|
flask *elements.RoundedBottomFlask
|
2025-12-03 10:21:36 -05:00
|
|
|
|
|
|
|
|
//cache elements
|
|
|
|
|
fluidsim10width float64
|
|
|
|
|
fluidsim10height float64
|
|
|
|
|
|
|
|
|
|
//other
|
2025-12-03 10:28:19 -05:00
|
|
|
fluidsim10angle float64 //purely for debugging
|
2025-12-03 10:21:36 -05:00
|
|
|
mdown bool
|
|
|
|
|
mdx, mdy int
|
2025-12-06 15:26:25 -05:00
|
|
|
mdangle float64 //angle at mousedown
|
|
|
|
|
mfangle float64 //last flask angle
|
|
|
|
|
|
|
|
|
|
//colors
|
|
|
|
|
flaskcolor color.RGBA
|
2025-11-27 22:50:36 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func NewGame() *Game {
|
|
|
|
|
g := &Game{
|
2025-12-03 10:21:36 -05:00
|
|
|
paused: false,
|
|
|
|
|
fluidsimidx: 0,
|
|
|
|
|
alertbox: elements.NewAlert(),
|
|
|
|
|
fluidsimd: elements.NewFluidSimD(),
|
2025-12-06 15:26:25 -05:00
|
|
|
flask: elements.NewRoundedBottomFlask(),
|
2025-12-03 10:21:36 -05:00
|
|
|
//fluidsim10: elements.NewFluidSim10(gamedata.Vector{X: GameFSDW, Y: GameFSDH}),
|
|
|
|
|
//fluidsimgpt: elements.NewFlipFluidEntity(640, 480, 2, 1, 100),
|
|
|
|
|
|
|
|
|
|
//fluidsim10: elements.NewFluidSim10(),
|
2025-11-27 22:50:36 -05:00
|
|
|
}
|
|
|
|
|
|
2025-12-03 10:21:36 -05:00
|
|
|
g.Initialize()
|
2025-11-27 22:50:36 -05:00
|
|
|
return g
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (g *Game) Update() error {
|
|
|
|
|
|
|
|
|
|
g.ParseInputs()
|
|
|
|
|
|
|
|
|
|
if !g.paused {
|
2025-12-03 10:21:36 -05:00
|
|
|
switch g.fluidsimidx {
|
|
|
|
|
case 0:
|
|
|
|
|
g.fluidsimd.Update()
|
|
|
|
|
case 1:
|
|
|
|
|
//g.fluidsimgpt.Update()
|
|
|
|
|
g.UpdateFluidsim10()
|
2025-12-06 15:26:25 -05:00
|
|
|
case 2:
|
|
|
|
|
g.UpdateFlask()
|
2025-12-03 10:21:36 -05:00
|
|
|
default:
|
|
|
|
|
break
|
|
|
|
|
}
|
2025-11-27 22:50:36 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
g.cycle++
|
|
|
|
|
|
|
|
|
|
return nil
|
|
|
|
|
}
|
|
|
|
|
|
2025-12-03 10:21:36 -05:00
|
|
|
func (g *Game) UpdateFluidsim10() {
|
|
|
|
|
for _, sim := range g.fluidsim10 {
|
|
|
|
|
sim.Update()
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2025-11-27 22:50:36 -05:00
|
|
|
func (g *Game) Draw(screen *ebiten.Image) {
|
|
|
|
|
screen.Clear()
|
|
|
|
|
|
2025-12-03 10:21:36 -05:00
|
|
|
switch g.fluidsimidx {
|
|
|
|
|
case 0:
|
|
|
|
|
g.RenderFluidSimD(screen)
|
|
|
|
|
case 1:
|
|
|
|
|
g.RenderFluidSim10(screen)
|
2025-12-06 15:26:25 -05:00
|
|
|
case 2:
|
|
|
|
|
g.RenderFlask(screen)
|
2025-12-03 10:21:36 -05:00
|
|
|
default:
|
|
|
|
|
break
|
2025-11-27 22:50:36 -05:00
|
|
|
}
|
2025-11-28 18:10:37 -05:00
|
|
|
|
|
|
|
|
if g.paused {
|
|
|
|
|
op := &ebiten.DrawImageOptions{}
|
|
|
|
|
op.GeoM.Translate(50, 50)
|
|
|
|
|
screen.DrawImage(g.alertbox.Sprite, op)
|
|
|
|
|
}
|
2025-11-27 22:50:36 -05:00
|
|
|
}
|
|
|
|
|
|
2025-12-03 10:21:36 -05:00
|
|
|
func (g *Game) RenderFluidSimD(img *ebiten.Image) {
|
|
|
|
|
g.fluidsimd.Draw()
|
|
|
|
|
pos := g.fluidsimd.GetPosition()
|
|
|
|
|
op := &ebiten.DrawImageOptions{}
|
|
|
|
|
op.GeoM.Translate(pos.X, pos.Y)
|
|
|
|
|
img.DrawImage(g.fluidsimd.GetSprite(), op)
|
2025-11-27 22:50:36 -05:00
|
|
|
}
|
|
|
|
|
|
2025-12-03 10:21:36 -05:00
|
|
|
func (g *Game) RenderFluidSim10(img *ebiten.Image) {
|
2025-11-27 22:50:36 -05:00
|
|
|
|
2025-12-03 10:21:36 -05:00
|
|
|
for _, sim := range g.fluidsim10 {
|
|
|
|
|
sim.Draw()
|
2025-11-28 16:48:32 -05:00
|
|
|
|
2025-12-03 10:28:19 -05:00
|
|
|
angle := sim.GetAngle()
|
2025-12-03 10:21:36 -05:00
|
|
|
pos := sim.GetPosition()
|
2025-11-27 22:50:36 -05:00
|
|
|
op := &ebiten.DrawImageOptions{}
|
2025-12-03 10:21:36 -05:00
|
|
|
op.GeoM.Translate(-g.fluidsim10width/2, -g.fluidsim10height/2)
|
|
|
|
|
op.GeoM.Scale(1, -1)
|
2025-12-03 10:28:19 -05:00
|
|
|
op.GeoM.Rotate(angle)
|
2025-12-03 10:21:36 -05:00
|
|
|
// op.GeoM.Translate(g.fluidsim10width/2, g.fluidsim10height/2)
|
|
|
|
|
op.GeoM.Translate(pos.X, pos.Y)
|
|
|
|
|
img.DrawImage(sim.GetSprite(), op)
|
2025-11-27 22:50:36 -05:00
|
|
|
}
|
|
|
|
|
|
2025-12-03 10:21:36 -05:00
|
|
|
//debug info
|
|
|
|
|
x, y := ebiten.CursorPosition()
|
|
|
|
|
deg := g.fluidsim10angle * 180 / math.Pi
|
2025-11-27 22:50:36 -05:00
|
|
|
|
2025-12-03 10:21:36 -05:00
|
|
|
str := fmt.Sprintf("Mouse (x: %d, y: %d) Origin (x: %d, y: %d) Angle (%f)", x, y, GameWidth/2, GameHeight/2, deg)
|
|
|
|
|
ebitenutil.DebugPrint(img, str)
|
2025-11-27 22:50:36 -05:00
|
|
|
}
|
|
|
|
|
|
2025-12-03 10:21:36 -05:00
|
|
|
func (g *Game) Layout(x, y int) (int, int) {
|
|
|
|
|
return GameWidth, GameHeight
|
2025-11-27 22:50:36 -05:00
|
|
|
}
|
|
|
|
|
|
2025-12-03 10:21:36 -05:00
|
|
|
func (g *Game) ParseInputs() {
|
2025-11-27 22:50:36 -05:00
|
|
|
|
2025-12-03 10:21:36 -05:00
|
|
|
//simulation specific updates
|
|
|
|
|
switch g.fluidsimidx {
|
|
|
|
|
case 0:
|
|
|
|
|
g.ManageFluidSimDInputs()
|
|
|
|
|
case 1:
|
|
|
|
|
g.ManageFluidSim10Inputs()
|
2025-12-06 15:26:25 -05:00
|
|
|
case 2:
|
|
|
|
|
g.ManageFlaskInputs()
|
2025-12-03 10:21:36 -05:00
|
|
|
default:
|
|
|
|
|
break
|
2025-11-27 22:50:36 -05:00
|
|
|
}
|
|
|
|
|
|
2025-12-03 10:21:36 -05:00
|
|
|
//common updates
|
|
|
|
|
if inpututil.IsKeyJustPressed(ebiten.KeyP) {
|
|
|
|
|
g.paused = !g.paused
|
|
|
|
|
g.alertbox.SetText("PAUSED")
|
|
|
|
|
g.alertbox.Draw()
|
2025-11-27 22:50:36 -05:00
|
|
|
}
|
|
|
|
|
|
2025-12-03 10:21:36 -05:00
|
|
|
//swap fluid simulations
|
|
|
|
|
if inpututil.IsKeyJustPressed(ebiten.KeyPageUp) {
|
2025-12-06 15:26:25 -05:00
|
|
|
g.fluidsimidx = (g.fluidsimidx + 1) % GameSims
|
2025-11-27 22:50:36 -05:00
|
|
|
}
|
|
|
|
|
|
2025-12-03 10:21:36 -05:00
|
|
|
if inpututil.IsKeyJustPressed(ebiten.KeyPageDown) {
|
|
|
|
|
g.fluidsimidx = g.fluidsimidx - 1
|
|
|
|
|
if g.fluidsimidx < 0 {
|
2025-12-06 15:26:25 -05:00
|
|
|
g.fluidsimidx = GameSims - 1
|
2025-12-03 10:21:36 -05:00
|
|
|
}
|
2025-11-27 22:50:36 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
2025-12-03 10:21:36 -05:00
|
|
|
func (g *Game) ManageFluidSimDInputs() {
|
2025-11-28 18:10:37 -05:00
|
|
|
//refresh particles
|
2025-11-27 22:50:36 -05:00
|
|
|
if inpututil.IsKeyJustPressed(ebiten.KeyR) {
|
2025-12-03 10:21:36 -05:00
|
|
|
g.fluidsimd.InitializeParticles()
|
2025-11-27 22:50:36 -05:00
|
|
|
}
|
|
|
|
|
|
2025-11-28 18:10:37 -05:00
|
|
|
//pause simulation
|
2025-11-27 22:50:36 -05:00
|
|
|
if inpututil.IsKeyJustPressed(ebiten.KeyP) {
|
2025-12-03 10:21:36 -05:00
|
|
|
g.fluidsimd.SetPaused(!g.fluidsimd.Paused())
|
2025-11-27 22:50:36 -05:00
|
|
|
}
|
|
|
|
|
|
2025-11-28 18:10:37 -05:00
|
|
|
//show quadtree quadrants
|
2025-11-27 22:50:36 -05:00
|
|
|
if inpututil.IsKeyJustPressed(ebiten.KeyQ) {
|
2025-12-03 10:21:36 -05:00
|
|
|
g.fluidsimd.SetRenderQuads(!g.fluidsimd.RenderQuads())
|
2025-11-27 22:50:36 -05:00
|
|
|
}
|
|
|
|
|
|
2025-11-28 18:10:37 -05:00
|
|
|
//enable collision resolution
|
2025-11-27 22:50:36 -05:00
|
|
|
if inpututil.IsKeyJustPressed(ebiten.KeyC) {
|
2025-12-03 10:21:36 -05:00
|
|
|
g.fluidsimd.SetResolveCollisions(!g.fluidsimd.ResolveCollisions())
|
2025-11-27 22:50:36 -05:00
|
|
|
}
|
|
|
|
|
|
2025-11-28 18:10:37 -05:00
|
|
|
//switch between collision resolvers
|
2025-11-28 16:48:32 -05:00
|
|
|
if inpututil.IsKeyJustPressed(ebiten.KeyLeft) {
|
2025-12-03 10:21:36 -05:00
|
|
|
g.fluidsimd.PreviousSolver()
|
2025-11-28 16:48:32 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if inpututil.IsKeyJustPressed(ebiten.KeyRight) {
|
2025-12-03 10:21:36 -05:00
|
|
|
g.fluidsimd.NextSolver()
|
2025-11-28 16:48:32 -05:00
|
|
|
}
|
2025-11-27 22:50:36 -05:00
|
|
|
}
|
|
|
|
|
|
2025-12-03 10:21:36 -05:00
|
|
|
func (g *Game) ManageFluidSim10Inputs() {
|
|
|
|
|
//refresh particles
|
|
|
|
|
if inpututil.IsKeyJustPressed(ebiten.KeyR) {
|
|
|
|
|
for _, sim := range g.fluidsim10 {
|
|
|
|
|
sim.Initialize()
|
|
|
|
|
g.fluidsim10angle = 0
|
2025-11-27 22:50:36 -05:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2025-12-03 10:21:36 -05:00
|
|
|
if inpututil.IsMouseButtonJustPressed(ebiten.MouseButtonLeft) {
|
|
|
|
|
g.mdown = true
|
|
|
|
|
g.mdx, g.mdy = ebiten.CursorPosition()
|
2025-11-27 22:50:36 -05:00
|
|
|
}
|
|
|
|
|
|
2025-12-03 10:21:36 -05:00
|
|
|
if ebiten.IsMouseButtonPressed(ebiten.MouseButtonLeft) {
|
|
|
|
|
if g.mdown {
|
|
|
|
|
mx, my := ebiten.CursorPosition()
|
2025-11-27 22:50:36 -05:00
|
|
|
|
2025-12-03 10:21:36 -05:00
|
|
|
for _, sim := range g.fluidsim10 {
|
2025-12-03 10:28:19 -05:00
|
|
|
dx := float64(mx) - sim.GetPosition().X
|
|
|
|
|
dy := float64(my) - sim.GetPosition().Y
|
|
|
|
|
angle := math.Atan2(dy, dx)
|
|
|
|
|
g.fluidsim10angle = angle
|
2025-12-03 10:21:36 -05:00
|
|
|
sim.SetAngle(angle)
|
|
|
|
|
}
|
2025-11-27 22:50:36 -05:00
|
|
|
}
|
|
|
|
|
}
|
2025-11-28 16:48:32 -05:00
|
|
|
|
2025-12-03 10:21:36 -05:00
|
|
|
if inpututil.IsMouseButtonJustReleased(ebiten.MouseButtonLeft) {
|
|
|
|
|
g.mdown = false
|
2025-11-28 16:48:32 -05:00
|
|
|
}
|
|
|
|
|
|
2025-12-05 09:22:20 -05:00
|
|
|
if inpututil.IsKeyJustPressed(ebiten.KeyB) {
|
|
|
|
|
for _, sim := range g.fluidsim10 {
|
|
|
|
|
sim.ToggleShape()
|
|
|
|
|
}
|
|
|
|
|
}
|
2025-11-28 16:48:32 -05:00
|
|
|
|
2025-12-05 09:22:20 -05:00
|
|
|
if inpututil.IsKeyJustPressed(ebiten.KeyV) {
|
|
|
|
|
for _, sim := range g.fluidsim10 {
|
|
|
|
|
sim.ToggleParticles()
|
|
|
|
|
}
|
|
|
|
|
}
|
2025-11-28 16:48:32 -05:00
|
|
|
|
2025-12-03 10:21:36 -05:00
|
|
|
}
|
2025-11-28 16:48:32 -05:00
|
|
|
|
2025-12-03 10:21:36 -05:00
|
|
|
func (g *Game) Initialize() {
|
2025-11-28 16:48:32 -05:00
|
|
|
|
2025-12-06 15:26:25 -05:00
|
|
|
//10MP Fluid Simulation Initialization
|
2025-12-03 10:21:36 -05:00
|
|
|
g.fluidsim10 = append(g.fluidsim10, elements.NewFluidSim10())
|
|
|
|
|
g.fluidsim10 = append(g.fluidsim10, elements.NewFluidSim10())
|
2025-11-28 16:48:32 -05:00
|
|
|
|
2025-12-03 10:21:36 -05:00
|
|
|
g.fluidsim10width = float64(g.fluidsim10[0].GetSprite().Bounds().Dx())
|
|
|
|
|
g.fluidsim10height = float64(g.fluidsim10[0].GetSprite().Bounds().Dy())
|
2025-11-28 16:48:32 -05:00
|
|
|
|
2025-12-03 10:21:36 -05:00
|
|
|
x0 := float64(GameWidth / (len(g.fluidsim10) + 1))
|
2025-11-28 16:48:32 -05:00
|
|
|
|
2025-12-03 10:21:36 -05:00
|
|
|
for i, sim := range g.fluidsim10 {
|
|
|
|
|
|
|
|
|
|
pos := gamedata.Vector{
|
|
|
|
|
X: x0 * float64(i+1),
|
|
|
|
|
Y: GameHeight / 2.,
|
2025-11-28 16:48:32 -05:00
|
|
|
}
|
2025-12-03 10:21:36 -05:00
|
|
|
sim.SetPosition(pos)
|
2025-11-28 16:48:32 -05:00
|
|
|
}
|
2025-12-03 10:21:36 -05:00
|
|
|
|
2025-12-06 15:26:25 -05:00
|
|
|
//Flask Initialization
|
|
|
|
|
pos := gamedata.Vector{
|
|
|
|
|
X: GameWidth / 2,
|
|
|
|
|
Y: GameHeight / 2,
|
|
|
|
|
}
|
|
|
|
|
g.flask.SetPosition(pos)
|
|
|
|
|
g.flaskcolor = color.RGBA{R: 0xff, G: 0x00, B: 0x00, A: 0xff}
|
|
|
|
|
g.flask.SetFluidColor(g.flaskcolor)
|
|
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (g *Game) ManageFlaskInputs() {
|
|
|
|
|
if inpututil.IsKeyJustPressed(ebiten.KeyR) {
|
|
|
|
|
g.flask.Initialize()
|
|
|
|
|
g.mfangle = 0
|
|
|
|
|
g.flask.SetAngle(g.mfangle)
|
|
|
|
|
g.flask.SetFluidColor(g.flaskcolor)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if inpututil.IsMouseButtonJustPressed(ebiten.MouseButtonLeft) {
|
|
|
|
|
//angle at mouse down is the zero angle
|
|
|
|
|
g.mdown = true
|
|
|
|
|
g.mdx, g.mdy = ebiten.CursorPosition()
|
|
|
|
|
|
|
|
|
|
dx := float64(g.mdx) - g.flask.GetPosition().X
|
|
|
|
|
dy := float64(g.mdy) - g.flask.GetPosition().Y
|
|
|
|
|
g.mdangle = math.Atan2(dy, dx)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if ebiten.IsMouseButtonPressed(ebiten.MouseButtonLeft) {
|
|
|
|
|
|
|
|
|
|
if g.mdown {
|
|
|
|
|
mx, my := ebiten.CursorPosition()
|
|
|
|
|
|
|
|
|
|
dx := float64(mx) - g.flask.GetPosition().X
|
|
|
|
|
dy := float64(my) - g.flask.GetPosition().Y
|
|
|
|
|
angle := math.Atan2(dy, dx)
|
|
|
|
|
|
|
|
|
|
g.flask.SetAngle(g.mfangle + (angle - g.mdangle))
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if inpututil.IsMouseButtonJustReleased(ebiten.MouseButtonLeft) {
|
|
|
|
|
g.mdown = false
|
|
|
|
|
|
|
|
|
|
mx, my := ebiten.CursorPosition()
|
|
|
|
|
|
|
|
|
|
dx := float64(mx) - g.flask.GetPosition().X
|
|
|
|
|
dy := float64(my) - g.flask.GetPosition().Y
|
|
|
|
|
angle := math.Atan2(dy, dx)
|
|
|
|
|
|
|
|
|
|
g.mfangle = g.mfangle + (angle - g.mdangle)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (g *Game) UpdateFlask() {
|
|
|
|
|
g.flask.Update()
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (g *Game) RenderFlask(img *ebiten.Image) {
|
|
|
|
|
|
|
|
|
|
g.flask.Draw()
|
|
|
|
|
|
|
|
|
|
angle := g.flask.GetAngle()
|
|
|
|
|
dim := g.flask.GetDimensions()
|
|
|
|
|
|
|
|
|
|
// //TODO: use flask position for rendering, not screen
|
|
|
|
|
pos := g.flask.GetPosition()
|
|
|
|
|
|
|
|
|
|
op := &ebiten.DrawImageOptions{}
|
|
|
|
|
op.GeoM.Translate(-dim.X/2, -dim.Y/2)
|
|
|
|
|
op.GeoM.Rotate(angle)
|
|
|
|
|
op.GeoM.Scale(2, 2)
|
|
|
|
|
op.GeoM.Translate(pos.X, pos.Y)
|
|
|
|
|
|
|
|
|
|
img.DrawImage(g.flask.GetSprite(), op)
|
|
|
|
|
|
2025-11-28 16:48:32 -05:00
|
|
|
}
|