Part 1/2 .. (part 2 is four posts below)
Code: Select all
/' -- procedural spaceships - 2025 Apr 30 - by Google Gemini 2.5 Pro (thinking) and dafhi
update:
thickLine (my new rasterizer)
design tweaks
'/
'#include "rast.bas"
' -- boilerplate
'
sub _gfx_release( byref im as any ptr )
if im <> 0 then if imageinfo(im) = 0 then imagedestroy im
im = 0
end sub
Type imvars '' 2025 Apr 12 .. Gemini minified
As Long w,h, bypp, pitch, wm, hm, pitchBy
As Any Ptr pixels, im
End Type
Sub fill_imvars( Byref i As imvars, im As Any Ptr = 0) '' 2025 Apr 12 .. Gemini minified. old version released imvars.im before new assignment
If im = 0 Then
ScreenInfo i.w, i.h, , i.bypp, i.pitch: i.pixels = screenptr
Else
ImageInfo im, i.w, i.h, i.bypp, i.pitch, i.pixels: i.im = im
End If
i.wm = i.w - 1: i.hm = i.h - 1: i.pitchBy = i.pitch \ i.bypp
End Sub
Function min overload ( a As Double, b As Double ) As Double: Return Iif( a < b, a, b): End Function
Function max overload ( a As Double, b As Double ) As Double: Return Iif( a > b, a, b): End Function
#define ceil(x) -int(-(x)) ' // Standard integer ceiling //
#Macro Alpha256( ret, back, fore, a256) 'blend colors. alpha max = 256 (2025 Apr 8)
scope
dim as long _a = (a256)
ret=((_
(fore And &Hff00ff) * _a + _
(back And &Hff00ff) * (256-_a) + &H800080) And &Hff00ff00 Or (_
(fore And &H00ff00) * _a + _
(back And &H00ff00) * (256-_a) + &H008000) And &H00ff0000) Shr 8
end scope
#EndMacro
function clamp( in As double, hi As double = 1, lo As double = 0) As double
return min( max(in, lo), hi ) '' 2023 June 12
End Function
#macro sw( _a, _b, _tmp )
_tmp = _a : _a = _b : _b = _tmp
#endmacro
Const EPSILON As single = 1e-6 ' Small value for float comparisons
function f_a256( c as ulong ) as long '' 0-255 -> 0-256 for ALpha256
return ((c shr 24)/255) * 257 - .5
end function
function sqr_safe( d as double ) as double '' used for aadot (part of my full framework)
return sgn(d) * sqr( abs(d))
end function
Type v2d: As single x, y: End Type
Operator - ( a As v2d, b As v2d ) As v2d: Return Type( a.x - b.x, a.y - b.y ): End Operator
Operator * ( a As v2d, s As single ) As v2d: Return Type( a.x * s, a.y * s ): End Operator
Function dot ( a As v2d, b As v2d ) As single: Return a.x * b.x + a.y * b.y : End Function
Function LenSq ( a As v2d ) As Double: Return a.x * a.x + a.y * a.y : End Function
function edgeFunction( a as v2d, b as v2d, p as v2d ) as single ' determines which side of a line a point is on
return (p.x - a.x) * (b.y - a.y) - (p.y - a.y) * (b.x - a.x)
end function
' --- boilerplate END
type t_fij '' New: 2025 Apr 27
as single v
declare property i as long
declare property j as long
end type
property t_fij.i as long: return int(v): end property
property t_fij.j as long: return ceil(v): end property
type v2di '' New: 2025 Apr 27
as single x, y
declare property ix as long
declare property iy as long
declare property jx as long
declare property jy as long
end type
property v2di.ix as long: return int(x): end property
property v2di.iy as long: return int(y): end property
property v2di.jx as long: return ceil(x): end property
property v2di.jy as long: return ceil(y): end property
Namespace Rast
Dim Shared As imvars imv ' Shared within the namespace
' -- a main function
'
Sub render_target( _im As Any Ptr = 0 )
fill_imvars imv, _im
End Sub
dim as ulong ptr row
dim as long scan_x0, scan_x1, alpha_max, final_a, gx0, gy0, gx1, gy1, col
dim as single xm5, ym5, xp5, rp5_sq, invert, alpha_sa, edge_iRSq
dim as single dySq, dx, dy, rSq, dist_sq, break_sq, coverage, cov0, cov1, cov2
sub _cliprect( x0 as single, y0 as single, x1 as single, y1 as single )
gx0 = max( 0, int( x0+.0 )) '' +.5 used previously
gy0 = max( 0, int( y0+.0 ))
gx1 = min( imv.wm, int( x1+.0 ))
gy1 = min( imv.hm, int( y1+.0 ))
end sub
sub _aadot_precalcs( x as single, y as single, c as ulong, r as single, edge as single )
_cliprect x - r, y - r, x + r, y + r
alpha_sa = min(1, edge*r) * f_a256(c)
rSq = r^2 : break_sq = rSq * .8 : alpha_max = alpha_sa
edge_irSq = edge * alpha_sa / rSq
xm5 = x - .5 : xp5 = x + .5 : ym5 = y - .5 : rp5_sq = (r + .5)^2
end sub
sub _aadot_dy_and_scanline_ends( y as single, iy as long, rad as single )
dy = iy-ym5
dx = sqr_safe( rp5_sq - dy^2 )
scan_x0 = max( int(xp5 - dx), gx0 ) ' scan segment hugs circle
scan_x1 = min( int(xm5 + dx), gx1 )
dySq = dy * dy
row = cast( ulong ptr, imv.pixels + iy * imv.pitch ) '' pitch = scanline cbytes
end sub
sub _aadot_scan( x0 as long, x1 as long, _step as long, c as ulong )
for ix as long = x0 to x1 step _step
dist_sq = ( (ix - xm5)^2 + dySq )' : if dist_sq < break_sq then exit for
invert = rSq - dist_sq
final_a = min( alpha_max, edge_irSq * max(0,invert) )
alpha256( row[ix], row[ix], c, final_a )
next
end sub
sub _aadot_draw( x as single = 0, y as single = 0, c as ulong = -1, rad as single = 5 )
for iy as long = gy0 to gy1
_aadot_dy_and_scanline_ends y, iy, rad
_aadot_scan scan_x0, scan_x1, 1, c
_aadot_scan scan_x1, scan_x0, -1, c
next
end sub
sub aadot( x as single = 0, y as single = 0, c as ulong = -1, rad as single = 5, edge as single = 1 )
_aadot_precalcs x, y, c, rad, edge
_aadot_draw x,y,c,rad
end sub
' // --- Anti-aliased Triangle --- //
const as double AA_WIDTH_TRI = 1.0
const as double AA_HALF_WIDTH_TRI = AA_WIDTH_TRI * 0.5
sub drawtriangle_aa( x0 as single, y0 as single, _
x1 as single, y1 as single, _
x2 as single, y2 as single, _
col as ulong = &hFFFFFFFF )
dim v0 as v2d = type(x0, y0)
dim v1 as v2d = type(x1, y1)
dim v2 as v2d = type(x2, y2)
' Edge vectors
dim as single dx01 = v1.x - v0.x, dy01 = v1.y - v0.y
dim as single dx12 = v2.x - v1.x, dy12 = v2.y - v1.y
dim as single dx20 = v0.x - v2.x, dy20 = v0.y - v2.y
' Inverse lengths (as before)
dim as single lenSq01 = dx01*dx01 + dy01*dy01
dim as single lenSq12 = dx12*dx12 + dy12*dy12
dim as single lenSq20 = dx20*dx20 + dy20*dy20
dim invLen01 as double = iif(lenSq01 > EPSILON, 1.0 / sqr(lenSq01), 0)
dim invLen12 as double = iif(lenSq12 > EPSILON, 1.0 / sqr(lenSq12), 0)
dim invLen20 as double = iif(lenSq20 > EPSILON, 1.0 / sqr(lenSq20), 0)
' Bounding box (as before)
dim minX as single = min(x0, min(x1, x2))
dim minY as single = min(y0, min(y1, y2))
dim maxX as single = max(x0, max(x1, x2))
dim maxY as single = max(y0, max(y1, y2))
dim startX as long = max(0, int(minX - AA_HALF_WIDTH_TRI))
dim startY as long = max(0, int(minY - AA_HALF_WIDTH_TRI))
dim endX as long = min(imv.wm, ceil(maxX + AA_HALF_WIDTH_TRI))
dim endY as long = min(imv.hm, ceil(maxY + AA_HALF_WIDTH_TRI))
' Precalculate edge function deltas (rename vars for clarity)
' Delta for w0 (edge v1-v2) when moving +1 in X
dim w0_deltaX as single = dy12 ' v2.y - v1.y
' Delta for w0 (edge v1-v2) when moving +1 in Y
dim w0_deltaY as single = -dx12 ' -(v2.x - v1.x)
' Delta for w1 (edge v2-v0) when moving +1 in X
dim w1_deltaX as single = dy20 ' v0.y - v2.y
' Delta for w1 (edge v2-v0) when moving +1 in Y
dim w1_deltaY as single = -dx20 ' -(v0.x - v2.x)
' Delta for w2 (edge v0-v1) when moving +1 in X
dim w2_deltaX as single = dy01 ' v1.y - v0.y
' Delta for w2 (edge v0-v1) when moving +1 in Y
dim w2_deltaY as single = -dx01 ' -(v1.x - v0.x)
' Precalculate for early exit check & coverage
dim w0_thresh as single = -AA_HALF_WIDTH_TRI / invLen12
dim w1_thresh as single = -AA_HALF_WIDTH_TRI / invLen20
dim w2_thresh as single = -AA_HALF_WIDTH_TRI / invLen01
dim inv_aa_width as double = 1.0 / AA_WIDTH_TRI
alpha_max = f_a256(col)
dim p as v2d ' Current pixel center
p.y = startY + 0.5 ' Y for the first row
' Calculate initial w values for the first pixel (startX, startY)
p.x = startX + 0.5
dim w0_rowStart as single = edgeFunction(v1, v2, p) - w0_deltaX '' subtract allows w0 w1 w2 advance for concise "if .. continue for"
dim w1_rowStart as single = edgeFunction(v2, v0, p) - w1_deltaX
dim w2_rowStart as single = edgeFunction(v0, v1, p) - w2_deltaX
for y as long = startY to endY
row = imv.pixels : row += y * imv.pitchBy
' Reset w for the start of the scanline
dim as single w0 = w0_rowStart, w1 = w1_rowStart, w2 = w2_rowStart
for x as long = startX to endX
w0 += w0_deltaX: w1 += w1_deltaX: w2 += w2_deltaX
if (w0 < w0_thresh) and (w1 < w1_thresh) and (w2 < w2_thresh) then continue for'' outside
' Calculate actual signed distances
dim as single d0 = w0 * invLen12, d1 = w1 * invLen20, d2 = w2 * invLen01
' Calculate coverage (using multiplication instead of division)
cov0 = clamp(0.5 + d0 * inv_aa_width)
cov1 = clamp(0.5 + d1 * inv_aa_width)
cov2 = clamp(0.5 + d2 * inv_aa_width)
coverage = min(cov0, min(cov1, cov2))
' Blend pixel if coverage > 0
if coverage > EPSILON then ' Avoid blending for zero coverage
dim as long final_a = coverage * alpha_max
alpha256( row[x], row[x], col, final_a )
end if
next x
' Increment w_rowStart for the next row (using y delta)
w0_rowStart += w0_deltaY
w1_rowStart += w1_deltaY
w2_rowStart += w2_deltaY
' p.y += 1.0 ' Implicitly handled by using wX_rowStart
next y
end sub
Sub triangle( byval a As V2d, byval b As V2d, byval c As V2d, col As ULong)
' Sort by Y primarily (ensures a is top, b.y <= c.y)
If b.y < a.y Then Swap a, b
If c.y < a.y Then Swap a, c
If c.y < b.y Then Swap b, c
' Example using cross product for proper a b c winding. (clockwise atm? no comprendo)
Dim As Double cross_product = (b.x - a.x) * (c.y - a.y) - (b.y - a.y) * (c.x - a.x)
If cross_product > 0 Then Swap b, c
drawtriangle_AA( a.x,a.y, b.x,b.y, c.x,c.y, col )
End Sub
' --- edge-following thickline - 2025 Apr 28 - by dafhi --
dim as single cosa, sina, slen, len_inverse, wid_inverse, wid, tmp
dim as single cx, cy, halfw_cos, halfw_sin, sL, iSL, xi_ab, xi_ac, xi_cd, xi_bd
dim as v2di a,b,c,d
dim as t_fij ac, bd
sub set_corners( byref hi as v2di, byref lo as v2di, x as single, y as single)
hi = type( x - halfw_sin, y + halfw_cos )
lo = type( x + halfw_sin, y - halfw_cos )
end sub
sub _scan( y as long )
if sina >= 0 then
ac.v = max( max( a.x, xi_ab ), xi_ac ) ' end perp
bd.v = min( min( d.x, xi_cd ), xi_bd ) ' end perp
else
bd.v = min( min( b.x, xi_ab ), xi_bd )
ac.v = max( max( c.x, xi_cd ), xi_ac )
endif
row = imv.pixels + y * imv.pitch
for x as long = max(ac.i, 0) to min(bd.i, imv.wm) '' 2025 Apr 30
alpha256( row[x], row[x], col, alpha_max )
next
xi_ab += iSL
xi_cd += iSL
xi_ac -= sL
xi_bd -= sL
end sub
sub thickline( x0 as single, y0 as single, x1 as single, y1 as single, _col as ulong = -1, _wid as single = 1, dummy as long = 0 )
col = _col : wid = _wid
'_epsilon_dx x0, x1, y0, y1 '' 2025 Apr 28
if x0 > x1 then sw( x0, x1, tmp) : sw( y0, y1, tmp)
dx = x1 - x0
dy = y1 - y0
slen = sqr(dx*dx + dy*dy)
len_inverse = 1 / slen
cosa = dx * len_inverse : halfw_cos = cosa * wid / 2
sina = dy * len_inverse : halfw_sin = sina * wid / 2
set_corners a, c, x0, y0
set_corners b, d, x1, y1
'_cliprect min(a.ix,c.ix), min(c.iy,d.iy), max(b.ix,d.ix), max(a.iy,b.iy) '' 2025 Apr 30
sl = dy / dx : iSL = dx / dy
dim as long iy0 = max( 0, min( c.iy, d.iy )) '' 2025 Apr 30
if sina >= 0 then
xi_ab = a.x - iSL * (a.y - iy0)
xi_cd = d.x + iSL * (iy0+1 - d.y)
xi_ac = a.x - sL * (iy0+1 - a.y)
xi_bd = d.x + sL * (d.y - iy0)
else
xi_ab = b.x - iSL * (b.y - iy0)
xi_cd = c.x + iSL * (iy0+1 - c.y)
xi_bd = b.x - SL * (iy0+1 - b.y)
xi_ac = c.x + sL * (c.y - iy0)
endif
alpha_max = f_a256(col)
for y as long = iy0 to min( imv.hm, max( a.iy, b.iy )) '' 2025 Apr 30
_scan y
next
end sub
End Namespace ' -- Rast
const pi = 4 * atn(1)
Const DEG2RAD As Double = PI / 180.0
function triwave( i as single ) as single
return abs( i - int(i) - .5 ) - .25 ' by Stonemonkey
end function
function _cchsv(h as single, s as single, v as single) as ubyte ' 2024 July 24
var wave_hgt = s * v
return 255.499 * (wave_hgt * (clamp(triwave(h)*6+.5)-1) + v)
end function
function hsv( h as single=0, s as single=1, v as single=1, a as ubyte = 255 ) as ulong ' 2024 May 21
return rgba( _
_cchsv( h + 0/3, s,v ), _
_cchsv( h + 2/3, s,v ), _
_cchsv( h + 1/3, s,v ), a )
end function
function ror64( in as ulongint, r as ubyte = 1 ) as ulongint
return (in shl (64 - r)) or (in shr r)
end function
namespace myhash '' dafhi
const as ulongint Knuth_ADD = 442695040888963407 ' Knuth LCG
const as ulongint Knuth_MUL = 6364136223846793005
const as ulongint xorA = &b0101010101010101010101010101010101010101010101010101010101010101
const as ulongint xorC = &b0000000001000000000100000000100000001000000100000100001000100101
dim as ulongint a, b
sub reset( _a as ulongint, _b as ulongint = 0 )
a=_a : b=_b
end sub
function v( seed as ulongint = 0 ) as ulongint
b += a * xorC
b xor= b shr 31
a xor= (b xor seed) * xorA
a xor= a shr 31
return ror64( a, a shr 58 )
end function
end namespace ' -- myhash
function float32( i as ulong) as single
return i / (2^32 + 2^7)
end function
' -- epoch-inspired randomization - 2025 Apr 15 by dafhi
'
#define rng float32(myhash.v)
type lerp_duo
declare constructor( bas_A as single = 0, bas_B as single = 1, vari_A as single = 0, vari_B as single = 0 )
declare operator cast as const single
declare operator cast as string
as single ret, bas, bas_v, vari, vari_v, b, v
end type
sub gen_LD( byref i as lerp_duo )
i.ret = i.b + rng * i.v
end sub
sub epoch_LD( byref i as lerp_duo )
i.b = i.bas + rng * i.bas_v
i.v = i.vari + rng * i.vari_v
end sub
constructor lerp_duo( a as single, b as single, va as single, vb as single )
bas = a : bas_v = b - a : vari = va : vari_v = vb - va
end constructor
operator lerp_duo.cast as const single '' 2025 Apr 15
return ret 'b + rng * v
end operator
operator lerp_duo.cast as string
return str(ret)
end operator
'
' --- Part 1 of 2 (procedural spaceships)