Major progress in introducing arbitrary boundaries! EDT in the house.

This commit is contained in:
2025-12-08 21:14:30 -05:00
parent 45b286b66e
commit 48df951042
8 changed files with 521 additions and 29 deletions

View File

@@ -12,6 +12,8 @@ import (
const (
RoundedBottomFlaskWidth = 32 //pixels
RoundedBottomFlaskHeight = 32 //pixels
RBFlaskMaskWidth = 46 //pixels
RBFlaskMaskHeight = 46 //pixels
RoundedBottomFlaskFluidRadius = 9 //pixels: represents spherical portion of the flask where fluid will be contained
RoundedBottomFlaskFluidOriginX = 16 //pixels: x origin of fluid area
RoundedBottomFlaskFluidOriginY = 20 //pixels: y origin of fluid area
@@ -22,27 +24,38 @@ const (
type RoundedBottomFlask struct {
MappedEntityBase
fluid *fluid.Fluid //our physical representation of the fluid
fluidbuff *ebiten.Image //predraw for the fluid
fluidcellbuff *ebiten.Image //persistent fluid sprite, we redraw this everywhere we want to represent fluid
flaskbase *ebiten.Image //flask background (container)
flaskhighlight *ebiten.Image //flask foreground (glassware highlights)
fieldscale gamedata.Vector //used for transforming from fluid-space to sprite-space
angle float64
fluid *fluid.Fluid //our physical representation of the fluid
fluidbuffS *ebiten.Image //predraw for the fluid, static
fluidbuffD *ebiten.Image //predraw for the fluid, dynamic
fluidcellbuff *ebiten.Image //persistent fluid sprite, we redraw this everywhere we want to represent fluid
flaskbase *ebiten.Image //flask background (container)
flaskhighlight *ebiten.Image //flask foreground (glassware highlights)
flaskboundarymask *ebiten.Image //flask boundary mask
fieldscaleStatic gamedata.Vector //used for transforming from fluid-space to sprite-space: static
fieldscaleDyanmic gamedata.Vector //used for transforming from fluid-space to sprite-space: dynamic
angle float64
//fluid color business
fluidcolor color.RGBA //premultiplied fluid color, as set by external sources
fluidcolorF []float32 //for caching of individual color values, we compute rarely
//boundary
boundaryinitialized bool
container *fluid.Boundary
}
func NewRoundedBottomFlask() *RoundedBottomFlask {
flask := &RoundedBottomFlask{
fluidbuff: ebiten.NewImage(RoundedBottomFlaskFluidRadius*2, RoundedBottomFlaskFluidRadius*2),
fluidcellbuff: ebiten.NewImage(1, 1),
flaskbase: ebiten.NewImageFromImage(resources.ImageBank[resources.RoundedBottomFlaskBase]),
flaskhighlight: ebiten.NewImageFromImage(resources.ImageBank[resources.RoundedBottomFlaskHighlights]),
angle: 0,
fluidcolorF: make([]float32, 4), //one field each for R,G,B,A
fluidbuffS: ebiten.NewImage(RoundedBottomFlaskFluidRadius*2, RoundedBottomFlaskFluidRadius*2),
fluidbuffD: ebiten.NewImage(RBFlaskMaskWidth, RBFlaskMaskHeight),
fluidcellbuff: ebiten.NewImage(1, 1),
flaskbase: ebiten.NewImageFromImage(resources.ImageBank[resources.RoundedBottomFlaskBase]),
flaskhighlight: ebiten.NewImageFromImage(resources.ImageBank[resources.RoundedBottomFlaskHighlights]),
flaskboundarymask: ebiten.NewImageFromImage(resources.ImageBank[resources.RoundedBottomFlaskBoundaryMap46]),
//flaskboundarymask: ebiten.NewImageFromImage(resources.ImageBank[resources.RoundedBottomFlaskBoundaryMap]),
angle: 0,
fluidcolorF: make([]float32, 4), //one field each for R,G,B,A
boundaryinitialized: false,
}
flask.Initialize()
return flask
@@ -66,17 +79,23 @@ func (flask *RoundedBottomFlask) Initialize() {
flask.fluid.Initialize()
flask.fluid.Block = false //rounded flask, not a rect volume
//compute fieldscale using newly created fluid
flask.fieldscale = gamedata.Vector{
//compute fieldscales using newly created fluid
flask.fieldscaleStatic = gamedata.Vector{
X: RoundedBottomFlaskFluidRadius * 2 / float64(flask.fluid.Field.Nx-1),
Y: RoundedBottomFlaskFluidRadius * 2 / float64(flask.fluid.Field.Ny-1),
}
flask.fieldscaleDyanmic = gamedata.Vector{
X: RBFlaskMaskWidth / float64(flask.fluid.Field.Nx-2),
Y: RBFlaskMaskHeight / float64(flask.fluid.Field.Ny-2),
}
//setup default fluid color
flask.SetFluidColor(color.RGBA{R: 0x0, G: 0x0, B: 0xff, A: 0xff})
}
func (flask *RoundedBottomFlask) Update() {
if flask.paused {
return
}
@@ -91,7 +110,11 @@ func (flask *RoundedBottomFlask) Draw() {
flask.Sprite.DrawImage(flask.flaskbase, nil)
//render fluid
flask.RenderFluid()
if flask.boundaryinitialized {
flask.RenderFluidDynamic()
} else {
flask.RenderFluidStatic()
}
//render flask foreground
flask.Sprite.DrawImage(flask.flaskhighlight, nil)
@@ -107,8 +130,10 @@ func (flask *RoundedBottomFlask) GetAngle() float64 {
return flask.angle
}
func (flask *RoundedBottomFlask) RenderFluid() {
flask.fluidbuff.Clear()
func (flask *RoundedBottomFlask) RenderFluidDynamic() {
flask.fluidbuffD.Clear()
//flask.fluidbuffD.Fill(color.White)
//vector.StrokeRect(flask.fluidbuffD, 0, 0, 46, 46, 1, color.White, true)
//construct fluid buffer from fluid simulation
for i := range flask.fluid.Field.Nx {
@@ -125,17 +150,56 @@ func (flask *RoundedBottomFlask) RenderFluid() {
celldensity = 1
}*/
ox := float64(i) * flask.fieldscale.X
oy := float64(j) * flask.fieldscale.Y
ox := float64(i) * flask.fieldscaleDyanmic.X
oy := float64(j) * flask.fieldscaleDyanmic.Y
op := &ebiten.DrawImageOptions{}
op.GeoM.Translate(-.5, -.5)
op.GeoM.Scale(flask.fieldscale.X, flask.fieldscale.Y)
op.GeoM.Scale(flask.fieldscaleDyanmic.X, flask.fieldscaleDyanmic.Y)
op.GeoM.Translate(ox, oy)
op.ColorScale.ScaleAlpha(celldensity)
op.ColorScale.Scale(flask.fluidcolorF[0], flask.fluidcolorF[1], flask.fluidcolorF[2], flask.fluidcolorF[3])
// op.ColorM.Scale(0, 0, 1, 1)
//flask.Sprite.DrawImage(flask.fluidcellbuff, op)
flask.fluidbuff.DrawImage(flask.fluidcellbuff, op)
flask.fluidbuffD.DrawImage(flask.fluidcellbuff, op)
}
}
//transform buffer for our flask space
s := float64(RoundedBottomFlaskWidth) / RBFlaskMaskWidth * 67. / 104
op := &ebiten.DrawImageOptions{}
op.GeoM.Translate(-RBFlaskMaskWidth/2, -RBFlaskMaskHeight/2)
op.GeoM.Scale(s, -s*1.15)
op.GeoM.Translate(RoundedBottomFlaskWidth/2, RoundedBottomFlaskHeight/2+2)
//op.GeoM.Translate(RoundedBottomFlaskFluidOriginX, RoundedBottomFlaskFluidOriginY)
flask.Sprite.DrawImage(flask.fluidbuffD, op)
}
func (flask *RoundedBottomFlask) RenderFluidStatic() {
flask.fluidbuffS.Clear()
//construct fluid buffer from fluid simulation
for i := range flask.fluid.Field.Nx {
for j := range flask.fluid.Field.Ny {
idx := i*flask.fluid.Field.Ny + j
if flask.fluid.Field.CellType[idx] != fluid.CellTypeFluid {
continue
}
celldensity := flask.fluid.ParticleDensity[idx] / flask.fluid.ParticleRestDensity
/*if celldensity > 0.8 {
celldensity = 1
}*/
ox := float64(i) * flask.fieldscaleStatic.X
oy := float64(j) * flask.fieldscaleStatic.Y
op := &ebiten.DrawImageOptions{}
op.GeoM.Translate(-.5, -.5)
op.GeoM.Scale(flask.fieldscaleStatic.X, flask.fieldscaleStatic.Y)
op.GeoM.Translate(ox, oy)
op.ColorScale.ScaleAlpha(celldensity)
op.ColorScale.Scale(flask.fluidcolorF[0], flask.fluidcolorF[1], flask.fluidcolorF[2], flask.fluidcolorF[3])
flask.fluidbuffS.DrawImage(flask.fluidcellbuff, op)
}
}
@@ -145,7 +209,7 @@ func (flask *RoundedBottomFlask) RenderFluid() {
op.GeoM.Translate(-RoundedBottomFlaskFluidRadius, -RoundedBottomFlaskFluidRadius)
op.GeoM.Scale(1, -1)
op.GeoM.Translate(RoundedBottomFlaskFluidOriginX, RoundedBottomFlaskFluidOriginY)
flask.Sprite.DrawImage(flask.fluidbuff, op)
flask.Sprite.DrawImage(flask.fluidbuffS, op)
}
func (flask *RoundedBottomFlask) SetFluidColor(c color.RGBA) {
@@ -155,3 +219,47 @@ func (flask *RoundedBottomFlask) SetFluidColor(c color.RGBA) {
flask.fluidcolorF[2] = float32(flask.fluidcolor.B) / float32(flask.fluidcolor.A)
flask.fluidcolorF[3] = float32(flask.fluidcolor.A) / 0xff
}
func (flask *RoundedBottomFlask) InitializeBoundary() {
//prepare the dimensions of our boundary map
dimensions := fluid.BoundaryDimensions{
X: RBFlaskMaskWidth,
Y: RBFlaskMaskHeight,
}
//instantiate the boundary structure
flask.container = fluid.NewBoundary(dimensions)
//load pixel data from boundary
var pixels []byte = make([]byte, dimensions.X*dimensions.Y*4)
flask.flaskboundarymask.ReadPixels(pixels)
//populate our boundary map based on the pixel data
for i := 0; i < len(pixels); i += 4 {
cellidx := i / 4
boundary := pixels[i] == 0
flask.container.Cells[cellidx] = !boundary
}
//apply to fluid simulation
flask.fluid.SetBoundary(flask.container)
flask.boundaryinitialized = true
}
// set new boundary mask and reinitialize associated data, including computing and
// passing that into the associated fluid simulation
func (flask *RoundedBottomFlask) SetBoundaryMap(img *ebiten.Image) {
flask.flaskboundarymask = img
flask.InitializeBoundary()
}
func (flask *RoundedBottomFlask) ToggleBoundaryMask() {
if flask.boundaryinitialized {
flask.fluid.SetBoundary(nil)
flask.boundaryinitialized = false
} else {
flask.InitializeBoundary()
flask.boundaryinitialized = true
}
}