package elements import ( "fluids/fluid" "fluids/gamedata" "fluids/resources" "image/color" "github.com/hajimehoshi/ebiten/v2" ) const ( RoundedBottomFlaskWidth = 32 //pixels RoundedBottomFlaskHeight = 32 //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 RoundedBottomFlaskFluidWidth = 0.5 //meters RoundedBottomFlaskFluidHeight = 0.5 //meters RoundedBottomFlaskFluidResolution = 20 ) 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 color business fluidcolor color.RGBA //premultiplied fluid color, as set by external sources fluidcolorF []float32 //for caching of individual color values, we compute rarely } 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 } flask.Initialize() return flask } func (flask *RoundedBottomFlask) Initialize() { //prepare our internal data flask.dimensions = gamedata.Vector{ X: RoundedBottomFlaskWidth, Y: RoundedBottomFlaskHeight, } flask.Sprite = ebiten.NewImage(RoundedBottomFlaskWidth, RoundedBottomFlaskHeight) flask.fluidcellbuff.Fill(color.White) //prepare and initialize the fluid fluiddimensions := fluid.FieldVector{ X: RoundedBottomFlaskFluidWidth, Y: RoundedBottomFlaskFluidHeight, } flask.fluid = fluid.NewFluid(fluiddimensions, RoundedBottomFlaskFluidHeight/RoundedBottomFlaskFluidResolution) flask.fluid.Initialize() flask.fluid.Block = false //rounded flask, not a rect volume //compute fieldscale using newly created fluid flask.fieldscale = gamedata.Vector{ X: RoundedBottomFlaskFluidRadius * 2 / float64(flask.fluid.Field.Nx-1), Y: RoundedBottomFlaskFluidRadius * 2 / float64(flask.fluid.Field.Ny-1), } //setup default fluid color flask.SetFluidColor(color.RGBA{R: 0x0, G: 0x0, B: 0xff, A: 0xff}) } func (flask *RoundedBottomFlask) Update() { if flask.paused { return } flask.fluid.Step() } func (flask *RoundedBottomFlask) Draw() { flask.Sprite.Clear() //render flask background flask.Sprite.DrawImage(flask.flaskbase, nil) //render fluid flask.RenderFluid() //render flask foreground flask.Sprite.DrawImage(flask.flaskhighlight, nil) } func (flask *RoundedBottomFlask) SetAngle(angle float64) { flask.angle = angle flask.fluid.SetAngle(float32(flask.angle)) } func (flask *RoundedBottomFlask) GetAngle() float64 { return flask.angle } func (flask *RoundedBottomFlask) RenderFluid() { flask.fluidbuff.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.fieldscale.X oy := float64(j) * flask.fieldscale.Y op := &ebiten.DrawImageOptions{} op.GeoM.Translate(-.5, -.5) op.GeoM.Scale(flask.fieldscale.X, flask.fieldscale.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) } } //transform buffer for our flask space op := &ebiten.DrawImageOptions{} op.GeoM.Translate(-RoundedBottomFlaskFluidRadius, -RoundedBottomFlaskFluidRadius) op.GeoM.Scale(1, -1) op.GeoM.Translate(RoundedBottomFlaskFluidOriginX, RoundedBottomFlaskFluidOriginY) flask.Sprite.DrawImage(flask.fluidbuff, op) } func (flask *RoundedBottomFlask) SetFluidColor(c color.RGBA) { flask.fluidcolor = c flask.fluidcolorF[0] = float32(flask.fluidcolor.R) / float32(flask.fluidcolor.A) flask.fluidcolorF[1] = float32(flask.fluidcolor.G) / float32(flask.fluidcolor.A) flask.fluidcolorF[2] = float32(flask.fluidcolor.B) / float32(flask.fluidcolor.A) flask.fluidcolorF[3] = float32(flask.fluidcolor.A) / 0xff }