Basic Fluid Sketch in JRuby
Here is another PixelFlow library sketch by Thomas Diewald:-
#
# PixelFlow | Copyright (C) 2016-17 Thomas Diewald (www.thomasdiewald.com)
# Translated to propane by Martin Prout
#
# src - www.github.com/diwi/PixelFlow
#
# A Processing/Java library for high performance GPU-Computing.
# MIT License: https://opensource.org/licenses/MIT
#
load_libraries :PixelFlow, :controlP5
java_import 'com.thomasdiewald.pixelflow.java.DwPixelFlow'
java_import 'com.thomasdiewald.pixelflow.java.fluid.DwFluid2D'
java_import 'controlP5.Accordion'
java_import 'controlP5.ControlP5'
java_import 'controlP5.Group'
java_import 'controlP5.RadioButton'
java_import 'controlP5.Toggle'
java_import 'controlP5.ControlListener'
include ControlListener
DISPLAY_MODE = %w[Density Temperature Pressure Velocity]
# This example shows a very basic fluid simulation setup.
# Multiple emitters add velocity/temperature/density each iteration.
# Obstacles are added at startup, by just drawing into a usual object.
# The same way, obstacles can be added/removed dynamically.
#
# additionally some locations add temperature to the scene, to show how
# buoyancy works.
#
#
# controls:
#
# LMB: add Density + Velocity
# MMB: draw obstacles
# RMB: clear obstacles
VIEWPORT_W = 800
VIEWPORT_H = 800
BACKGROUND_COLOR = 0
attr_reader :context, :fluid, :obstacle_painter, :pg_fluid, :pg_obstacles
attr_reader :update_fluid, :display_fluid_textures, :display_fluid_vectors
attr_reader :display_fluid_texture_mode, :cp5, :fluidgrid_scale, :gui_w
attr_reader :gui_x, :gui_y, :mouse_input, :rb_setdisplay_mode, :group_fluid
attr_reader :background_color, :iterations
def settings
size(VIEWPORT_W, VIEWPORT_H, P2D)
smooth(2)
end
def setup
@update_fluid = true
@display_fluid_textures = true
@display_fluid_vectors = false
@display_fluid_texture_mode = 0
@background_color = 0
# main library context
@context = DwPixelFlow.new(self)
context.print
context.printGL
@fluidgrid_scale = 1
@gui_w = 200
@gui_x = 20
@gui_y = 20
# fluid simulation
@fluid = DwFluid2D.new(context, VIEWPORT_W, VIEWPORT_H, fluidgrid_scale)
# interface for adding data to the fluid simulation
fluid.addCallback_FluiData do
# add impulse: density + temperature
intensity = 1.0
px = 1 * width / 3
py = 0
radius = 100
r = 0.0
g = 0.3
b = 1.0
fluid.add_density(px, py, radius, r, g, b, intensity)
if (fluid.simulation_step % 200).zero?
temperature = 50.0
fluid.addTemperature(px, py, radius, temperature)
end
# add impulse: density + temperature
animator = sin(fluid.simulation_step * 0.01)
intensity = 1.0
px = 2 * width / 3.0
py = 150
radius = 50
r = 1.0
g = 0.0
b = 0.3
fluid.add_density(px, py, radius, r, g, b, intensity)
temperature = animator * 20.0
fluid.addTemperature(px, py, radius, temperature)
# add impulse: density
px = 1 * width / 3.0
py = height - 2 * height / 3.0
radius = 50.5
r = g = b = 64 / 255.0
intensity = 1.0
fluid.add_density(px, py, radius, r, g, b, intensity, 3.0)
# add impulse: density + velocity
if mouse_input && mouse_button == LEFT
radius = 15
vscale = 15
px = mouse_x
py = height-mouse_y
vx = (mouse_x - pmouse_x) * +vscale
vy = (mouse_y - pmouse_y) * -vscale
fluid.add_density(px, py, radius, 0.25, 0.0, 0.1, 1.0)
fluid.add_velocity(px, py, radius, vx, vy)
end
end
# pgraphics for fluid
@pg_fluid = create_graphics(VIEWPORT_W, VIEWPORT_H, P2D)
pg_fluid.smooth(4)
pg_fluid.begin_draw
pg_fluid.background(background_color)
pg_fluid.end_draw
# pgraphics for obstacles
@pg_obstacles = create_graphics(VIEWPORT_W, VIEWPORT_H, P2D)
pg_obstacles.smooth(0)
pg_obstacles.begin_draw
pg_obstacles.clear
# circle-obstacles
pg_obstacles.stroke_weight(10)
pg_obstacles.noFill
pg_obstacles.no_stroke
pg_obstacles.fill(64)
radius = 100
pg_obstacles.ellipse(1 * width / 3.0, 2 * height / 3.0, radius, radius)
radius = 150
pg_obstacles.ellipse(2 * width / 3.0, 2 * height / 4.0, radius, radius)
radius = 200
pg_obstacles.stroke(64)
pg_obstacles.stroke_weight(10)
pg_obstacles.noFill
pg_obstacles.ellipse(1 * width / 2.0, 1 * height / 4.0, radius, radius)
# border-obstacle
pg_obstacles.stroke_weight(20)
pg_obstacles.stroke(64)
pg_obstacles.noFill
pg_obstacles.rect(0, 0, pg_obstacles.width, pg_obstacles.height)
pg_obstacles.end_draw
createGUI
# class, that manages interactive drawing (adding/removing) of obstacles
@obstacle_painter = ObstaclePainter.new(pg_obstacles)
@mouse_input = !cp5.isMouseOver && mousePressed && !obstacle_painter.drawing?
frame_rate(60)
end
def draw
# update simulation
if update_fluid
fluid.addObstacles(pg_obstacles)
fluid.update
end
# clear render target
pg_fluid.begin_draw
pg_fluid.background(background_color)
pg_fluid.end_draw
# render fluid stuff
if display_fluid_textures
# render: density (0), temperature (1), pressure (2), velocity (3)
fluid.renderFluidTextures(pg_fluid, display_fluid_texture_mode)
end
# render: velocity vector field
fluid.renderFluidVectors(pg_fluid, 10) if display_fluid_vectors
# display
image(pg_fluid, 0, 0)
image(pg_obstacles, 0, 0)
obstacle_painter.display_brush(g)
# info
format_string = 'Fluid Basic [size %d/%d] [frame %d] [fps: (%6.2f)]'
surface.set_title(format(format_string, fluid.fluid_w, fluid.fluid_h, fluid.simulation_step, frame_rate))
end
def mouse_pressed
obstacle_painter.begin_draw(1) if mouse_button == CENTER # add obstacles
obstacle_painter.begin_draw(2) if mouse_button == RIGHT # remove obstacles
end
def mouse_dragged
obstacle_painter.draw
end
def mouse_released
obstacle_painter.end_draw
end
def resize_up
@fluidgrid_scale -= 1 unless fluidgrid_scale < 2
fluid.resize(width, height, fluidgrid_scale)
end
def resize_down
@fluidgrid_scale += 1
fluid.resize(width, height, fluidgrid_scale)
end
def reset!
fluid.reset
end
def toggle_pause
@update_fluid = !update_fluid
end
def display_mode(val)
@display_fluid_texture_mode = val
@display_fluid_textures = display_fluid_texture_mode != -1
end
def display_velocity_vectors(val)
@display_fluid_vectors = val != -1
end
def key_released
case key
when 'p'
toggle_pause # pause / unpause simulation
when 'r'
reset!
when 's'
cp5.save_properties(data_path('fluid_basic.json'))
end
end
def createGUI
@cp5 = ControlP5.new(self)
sx = 100
sy = 14
oy = (sy * 1.5).to_i
######################################
# GUI - FLUID
######################################
group_fluid = cp5.addGroup('fluid')
group_fluid.setHeight(20)
.setSize(gui_w, 300)
.setBackgroundColor(color(16, 180))
.setColorBackground(color(16, 180))
group_fluid.getCaptionLabel.align(CENTER, CENTER)
px = 10
py = 15
cp5.addButton('reset')
.setGroup(group_fluid)
.setSize(80, 18)
.setPosition(px, py)
.addListener { reset! }
cp5.addButton('+')
.setGroup(group_fluid)
.setSize(39, 18)
.setPosition(px += 82, py)
.addListener { resize_up }
cp5.addButton('-')
.setGroup(group_fluid)
.setSize(39, 18)
.setPosition(px += 41, py)
.addListener { resize_down }
px = 10
cp5.addSlider('velocity')
.setGroup(group_fluid)
.setSize(sx, sy)
.setPosition(px, py += (oy*1.5).to_i)
.setRange(0, 1)
.setValue(1.0)
cp5.addSlider('density')
.setGroup(group_fluid)
.setSize(sx, sy)
.setPosition(px, py += oy)
.setRange(0, 1)
.setValue(0.99)
cp5.addSlider('temperature')
.setGroup(group_fluid)
.setSize(sx, sy)
.setPosition(px, py += oy)
.setRange(0, 1)
.setValue(0.8)
cp5.addSlider('vorticity')
.setGroup(group_fluid)
.setSize(sx, sy)
.setPosition(px, py += oy)
.setRange(0, 1)
.setValue(0.1)
cp5.addSlider('iterations')
.setGroup(group_fluid)
.setSize(sx, sy)
.setPosition(px, py += oy)
.setRange(0, 80)
.setValue(40)
cp5.addSlider('timestep')
.setGroup(group_fluid).setSize(sx, sy).setPosition(px, py += oy)
.setRange(0, 1)
.setValue(0.125)
cp5.addSlider('gridscale')
.setGroup(group_fluid)
.setSize(sx, sy)
.setPosition(px, py += oy)
.setRange(0, 50)
.setValue(1)
@rb_setdisplay_mode = cp5.addRadio('display_mode')
.setGroup(group_fluid)
.setSize(80, 18)
.setPosition(px, py += (oy*1.5).to_i)
.setSpacingColumn(2)
.setSpacingRow(2)
.setItemsPerRow(2)
DISPLAY_MODE.each_with_index do |item, i|
rb_setdisplay_mode.addItem item, i
end
rb_setdisplay_mode.getItems.each do |toggle|
toggle.getCaptionLabel.alignX(CENTER)
end
cp5.addRadio('display_velocity_vectors')
.setGroup(group_fluid)
.setSize(18,18)
.setPosition(px, py += (oy*2.5).to_i)
.setSpacingColumn(2)
.setSpacingRow(2)
.setItemsPerRow(1)
.addItem('Velocity Vectors', 0)
#.activate(display_fluid_vectors ? 0 : 2)
##########################################
# GUI - DISPLAY
##########################################
group_display = cp5.addGroup('display')
group_display.setHeight(20)
.setSize(gui_w, 50)
.setBackgroundColor(color(16, 180))
.setColorBackground(color(16, 180))
group_display.getCaptionLabel.align(CENTER, CENTER)
px = 10
py = 15
cp5.addSlider('background')
.setGroup(group_display)
.setSize(sx,sy)
.setPosition(px, py)
.setRange(0, 255)
.setValue(0)
##########################################
# GUI - ACCORDION
##########################################
cp5.addAccordion('acc').setPosition(gui_x, gui_y)
.setWidth(gui_w)
.setSize(gui_w, height)
.setCollapseMode(Accordion::MULTI)
.addItem(group_fluid)
.addItem(group_display)
.open(4)
end
def controlEvent(event)
if event.group?
display_mode(rb_setdisplay_mode.getValue) if event.getGroup.getName == 'display_mode'
display_velocity_vectors(event.getGroup.getValue) if event.getGroup.getName == 'display_velocity_vectors'
elsif event.controller?
case event.getController.getName
when 'gridscale'
@fluidgrid_scale = event.getController.getValue.to_i
when 'velocity'
fluid.param.dissipation_velocity = event.getController.getValue
when 'background'
@background_color = event.getController.getValue
when 'temperature'
fluid.param.dissipation_temperature = event.getController.getValue
when 'timestep'
fluid.param.timestep = event.getController.getValue
when 'iterations'
fluid.param.num_jacobi_projection = event.getController.getValue
when 'density'
fluid.param.dissipation_density = event.getController.getValue
when 'vorticity'
fluid.param.vorticity = event.getController.getValue
end
end
end
class ObstaclePainter
include Processing::Proxy
# 0 ... not drawing
# 1 ... adding obstacles
# 2 ... removing obstacles
attr_reader :pg, :draw_mode, :size_paint, :size_clear, :shading
attr_reader :paint_x, :paint_y, :clear_x, :clear_y
def initialize(pg)
@pg = pg
@draw_mode = 0
@size_paint = 15
@size_clear = size_paint * 2.5
@shading = 64
end
def begin_draw(mode)
@paint_x = mouse_x
@paint_y = mouse_y
@draw_mode = mode
if mode == 1
pg.begin_draw
pg.blend_mode(REPLACE)
pg.no_stroke
pg.fill(shading)
pg.ellipse(mouse_x, mouse_y, size_paint, size_paint)
pg.end_draw
end
clear(mouse_x, mouse_y) if mode == 2
end
def drawing?
!draw_mode.zero?
end
def draw
@paint_x = mouse_x
@paint_y = mouse_y
if draw_mode == 1
pg.begin_draw
pg.blend_mode(REPLACE)
pg.stroke_weight(size_paint)
pg.stroke(shading)
pg.line(mouse_x, mouse_y, pmouse_x, pmouse_y)
pg.end_draw
end
clear(mouse_x, mouse_y) if draw_mode == 2
end
def end_draw
@draw_mode = 0
end
def clear(x, y)
@clear_x = x
@clear_y = y
pg.begin_draw
pg.blend_mode(REPLACE)
pg.no_stroke
pg.fill(0, 0)
pg.ellipse(x, y, size_clear, size_clear)
pg.end_draw
end
def display_brush(dst)
if draw_mode == 1
dst.stroke_weight(1)
dst.stroke(0)
dst.fill(200, 50)
dst.ellipse(paint_x, paint_y, size_paint, size_paint)
elsif draw_mode == 2
dst.stroke_weight(1)
dst.stroke(200)
dst.fill(200, 100)
dst.ellipse(clear_x, clear_y, size_clear, size_clear)
end
end
end