unique metaballs - April 11

Post your FreeBASIC source, examples, tips and tricks here. Please don’t post code without including an explanation.
Post Reply
dafhi
Posts: 1641
Joined: Jun 04, 2005 9:51

unique metaballs - April 11

Post by dafhi »

Code: Select all

/' -- unique metaballs - 2024 Jan 3 - by dafhi

  no alpha blending here .. just adds, where r,g,b can be negative.    
    
    - some specs -

  MX Linux - HP Spectre 1165g7
  215-ish fps
  
  combined gcc options from UEZ / DeltaRho comment
  -arch native -Wc -Ofast,-mfpmath=sse,-funroll-loops    
  
    update
  tweaked visibility / performance

'/


'#include "workspace.bas"
' ----- workspace.bas - 2023 Feb 27 - by dafhi
'

' #include "boilerplate.bas"
' -- boilerplate.bas - 2023 Feb 24 - by dafhi

'' replaces int()
#define flo(x) (((x)*2.0-0.5)shr 1) '' http://www.freebasic.net/forum/viewtopic.php?p=118633
#define ceil(x) (-((-(x)*2.0-0.5)shr 1))

#undef int
#define int     as Integer
#define sng     as single
#define dbl     as double

#define decl    declare
#define oper    operator
#define prop    property

#define min( a, b)        iif( (a)<(b), (a), (b) )
#define max( a, b)        iif( (a)>(b), (a), (b) )

function bclamp( i sng ) as ubyte '' Feb 23
  return min( max( i, 0), 255 )
End Function

function int2float( i as ulong) as single
  return i / ((culngint(1) shl 32) + 128)
end function

const tau = 8 * atn(1)
'
' ------------------- boilerplate


' - workspace.bas continued ..
  
  namespace gfx_workspace '' 2023 Feb 27 - by dafhi

function c( _c as ubyte) int
  return _c
end function


type pixel
  sng                 x,y,z
  
  decl oper           cast as ulong
  
  decl sub            in_rgb( as ulong, sng = 1)
  decl sub            subm_rgb( as ulong, sng = 1)  '' Feb 19
  decl sub            add( as pixel, sng = 1)       '' Feb 19
end type

sub pixel.add( in as pixel, alpha sng)      '' Feb 19
  x += alpha * in.x
  y += alpha * in.y
  z += alpha * in.z
end sub

sub pixel.subm_rgb( col as ulong, alpha sng) '' Feb 19 (old name add_rgb)
  x += alpha * ( c( col shr 16 )-127.5 )
  y += alpha * ( c( col shr 8 )-127.5 )
  z += alpha * ( c( col shr 0 )-127.5 )
end sub

sub pixel.in_rgb( col as ulong, alpha sng)
  x = alpha * c( col shr 16 )
  y = alpha * c( col shr 8 )
  z = alpha * c( col shr 0 )
end sub

oper pixel.cast as ulong
  return rgb( bclamp(x), bclamp(y), bclamp(z) )
end oper

dim as pixel          buf(any, any)

dim int               wm
dim int               hm

sub setup( w as short, h as short )
  const dimension_thresh = 11000
  if w > dimension_thresh orelse h > dimension_thresh then exit sub
  if w < 1 orelse h < 1 then exit sub
  wm = w - 1
  hm = h - 1
  redim buf(wm, hm)
end sub

sub fill( col as ulong = rgb(128,128,128), stren sng = 0.5)
  dim as pixel iwa:  iwa.in_rgb col, stren
  for p as pixel ptr = @buf(0,0) to @buf(wm,hm)
    *p = iwa
  next
end sub

end namespace ' ------ workspace

  

  namespace metaball2D  ' 2024 Jan 3 - by dafhi

type int_rect
  as long     x0, x1 = -1
  as long     y0, y1 = -1
  declare operator   cast as string
end type

operator int_rect.cast as string
  return "rect (" + str(x0) + "," + str(y0) + _
  ") - "  + str(x1) + "," + str(y1) + ")"
end operator

dim as int_rect       _clipped '' namespace globals

dim sng               _slope_by_rad', _clipSQ '' April 11
dim sng               _metaball_alpha_scalar '' March 21

dim sng               dx, dy, dx0', dSQ, dSQ_alpha_clip

sub _cliprect_calc( x sng, y sng, rad_multed sng ) '' Feb 24
  _clipped.x0 = max( flo( x - rad_multed ), 0 )
  _clipped.x1 = min( flo( x + rad_multed ), gfx_workspace.wm )
  _clipped.y0 = max( flo( y - rad_multed ), 0 )
  _clipped.y1 = min( flo( y + rad_multed ), gfx_workspace.hm )
end sub
  
dim as gfx_workspace.pixel pel '' March 24
  
sub _scan( col as ulong, plot_y int )
  
  static sng alpha, dSQ, dySQ
  dySQ = dy * dy
  
  dx = dx0
  
  for plot_x int = _clipped.x0 to _clipped.x1
    
    dSQ = dx*dx+dySQ
    var alpha = _metaball_alpha_scalar / (dSQ^3 + .00011) '' small = sharp
      
    gfx_workspace.buf(plot_x, plot_y).add pel, alpha '' Feb 19
    dx += _slope_by_rad
  next
  
  dy += _slope_by_rad
end sub


sub draw( x sng, y sng, col as ulong = -1, rad sng = 10)

  var draw_dist_from_center = .6 '' smaller:  rect more visible
  
  _metaball_alpha_scalar = -min( rad, .0002 ) '' precalc where true = -1
  
  _cliprect_calc x, y, rad * draw_dist_from_center * .91
  
  _slope_by_rad = 1 / max(rad, .001)
  
'  dSQ_alpha_clip = .28
  
  dx0 = (_clipped.x0 - x) * _slope_by_rad
  dy = (_clipped.y0 - y) * _slope_by_rad
  
  pel = type(0,0,0)
  pel.subm_rgb col

  for plot_y int = _clipped.y0 to _clipped.y1
    _scan col, plot_y
  next
  
end sub

end namespace ' ---- metaball2D  


  namespace myhash '' 2023 Feb 19

type base_literal     as ulong '' size: ubyte to ulongint (ubyte during development)

const 					      lenx8 = len(base_literal) * 8

const 					      lenByv = lenx8 \ 2 + 1'' integer divide

const as ulongint 		mulC = &b1000000001000000000100000000100000001000000100000100001000100101
const as ulongint     xorA = &b0101010101010101010101010101010101010101010101010101010101010101

dim as base_literal   a, b, c

const                 _rotbits_count = log(lenx8) / log(2) '' inspired by PCG
const as ubyte        _rota_mask  = 2 ^ (_rotbits_count-2) - 1

sub reset(aa as base_literal = 0, bb as base_literal = 0)
  a = aa
  b = bb
  c = 0
End Sub

function prng( seed_a as base_literal = 0 ) as single

  c xor= seed_a xor xora + a
  b xor= c + c shl (c and _rota_mask)
  a xor= b shr 1
  a *= mulC
  a xor= a shr lenbyv
  
  return int2float(a)
end function

end namespace
'
' --------- workspace.bas


sub show
  
  dim int w, h, bpp, bypp, pitch, rate
  dim as string driver_name
  
  ScreenInfo w,h, bpp, bypp, pitch, rate, driver_name
  
  var pixels = screenptr
  
    for y int = 0 to h - 1
  dim as ulong ptr p = pixels + y * pitch
    for x int = 0 to w - 1
  p[x] = gfx_workspace.buf(x,y)
  next
  next

end sub

  
const w = 800
const h = 600

dim shared sng diagonal

#undef rnd
#define rnd myhash.prng


type dotvars
  sng           rot_cenx = rnd * w
  sng           rot_ceny = rnd * h
  sng           rad = (.2 * (.1 + rnd * rnd)) * diagonal
  
  as ulong      col = rgb(rnd*255.499, rnd*255.499, rnd*255.499)
  
  sng           rad_offset = diagonal * 0.07 * (.4 + rnd)
  sng           angle = rnd * tau
  sng           iangle
end type

dim shared as dotvars dot(any)


sub ini
  gfx_workspace.setup w,h
  screenres w, h, 32
  diagonal = sqr(w*w+h*h)
  
  var seed = 39
  myhash.reset seed
  
  redim dot(139)
  for i int = 0 to ubound(dot)
    var direction = 2 * flo(rnd + .5) - 1
    dot(i).iangle = 0.7 * (.03 + rnd*rnd) * direction
  next
end sub


sub animate( seconds as ubyte = 80 )
  dim dbl seconds_remain = seconds
  dim dbl dt(1)
  dim dbl report_next = seconds_remain - 1
  var tp = timer
  while seconds_remain > 0
    var strength = 1
    gfx_workspace.fill rgb(128,128,128), strength
    for i int = 0 to ubound(dot)
    with dot(i)
      metaball2d.draw _
      .rot_cenx + .rad_offset * cos(.angle),_
      .rot_ceny + .rad_offset * sin(.angle), _
      .col, .rad
      .angle += .iangle * dt(0)
    end with
    next

    show
    screenlock
    screenunlock
    
    sleep 1
    if inkey<>"" then exit sub

    var t = timer: dt(1) = dt(0): dt(0) = t - tp
    seconds_remain -= dt(0):  tp = t
    if seconds_remain < report_next then
      windowtitle "FPS: " + str( 2 / ( dt(0)+dt(1) ) )
      report_next -= 1
    endif
  wend
end sub


ini
animate

sleep
Last edited by dafhi on Jan 03, 2024 10:50, edited 27 times in total.
UEZ
Posts: 972
Joined: May 05, 2017 19:59
Location: Germany

Re: unique metaballs

Post by UEZ »

Looks very nice - thanks for sharing it.
dafhi
Posts: 1641
Joined: Jun 04, 2005 9:51

Re: unique metaballs

Post by dafhi »

thanks UEZ. update includes a (hopefully) faster _rect_scan() which benefits from fast sqr

[edit]
on my win10 i get 21 fps
on MX Linux 27
UEZ
Posts: 972
Joined: May 05, 2017 19:59
Location: Germany

Re: unique metaballs

Post by UEZ »

I get ~18 fps on Win11 x64 with -gen gcc -Wc -Ofast -Wc -march=native -Wc -funroll-loops -Wc -mfpmath=sse.
x86 is slower.
dafhi
Posts: 1641
Joined: Jun 04, 2005 9:51

Re: unique metaballs

Post by dafhi »

didn't know about all those

on MX Linux, plugged in i get 65 fps, and on battery i get 44
UEZ
Posts: 972
Joined: May 05, 2017 19:59
Location: Germany

Re: unique metaballs

Post by UEZ »

That's really fast compared to my AMD CPU AMD Ryzen 5 PRO Mobile 3500U. With default setting the FPS is ~2 fps (x64).
srvaldez
Posts: 3373
Joined: Sep 25, 2005 21:54

Re: unique metaballs

Post by srvaldez »

Hi dafhi and UEZ :)
I only get about 32 fps, @UEZ, the compile options could have something to do with your low performance especially -exx
-exx can dramatically slow down your program
UEZ
Posts: 972
Joined: May 05, 2017 19:59
Location: Germany

Re: unique metaballs

Post by UEZ »

Hi srvaldez,

with -exx it runs only at ~1 fps. Might be an issue with AMD CPU. I don't have an Intel CPU to test...
Post Reply