⚠️ Warning: This is a draft ⚠️
This means it might contain formatting issues, incorrect code, conceptual problems, or other severe issues.
If you want to help to improve and eventually enable this page, please fork RosettaGit's repository and open a merge request on GitHub.
Only uses about 2.5% cpu with 60 boids at 25 FPS. Split into two source files, for no particular reason. {{libheader|pGUI}}
--
-- demo\pGUI\boids3d.exw
--
-- Originally by by Matt Lewis
-- Ported from arwen to pGUI, Pete Lomax June 2017
--
include pGUI.e
include boids3d.e
constant TITLE = "Boids 3D on pGUI"
Ihandle canvas, dialog,
restart, speed_label, speed_txt, dist_label, dist_txt,
radius_label, radius_txt, boids_label, boids_txt, shadow_check
cdCanvas cddbuffer, cdcanvas
integer ox,oy,oz
procedure set_observer()
{ox,oy,oz} = {floor(X_MAX/2),floor(Y_MAX/2),Z_MAX*2}
end procedure
sequence colors = {}
sequence verts = {}
integer draw_shadows = 1
integer dx = 0, dy = 0, dw = 0, dh = 0
function project_point(sequence pt)
atom d
atom px,py,pz
{px,py,pz} = pt
if pz=oz then
d = 0.0001
else
d = 1-pz/(pz-oz)
end if
px = floor(ox+(ox-px)/d)
py = dh - floor(oy+(oy-py)/d)
return {px,py}
end function
procedure draw_lines(sequence coords)
--
-- Draw zero or more lines.
-- The lines are drawn between the sets of coordinates in coords.
-- This sequence can contain Colors, Lines, or Points:
-- A Color is a single atom that is a 24-bit color value. Subsequent lines use this color.
-- A Line is a 4-element sequence {X1,Y1,X2,Y2} that specifies the X,Y position of a lines
-- starting point and the X,Y position of its end point. The line is drawn from X1,Y1
-- to X2,Y2.
-- A Point is a 2-element sequence {X,Y} that gives the X,Y position of the end-point
-- of a line. The line is drawn to this position from the last end-point supplied.
-- There must have been a preceding Line (or Point), else x1 unassigned error.
--
-- If no color parameters are supplied, the current pen color for the control is used.
--
-- Example:
--
-- -- draw a shape in TheWindow
-- draw_lines({White,{40,0,0,80},{80,80},{40,0},
-- Blue,{40,5,0,85},{80,85},{40,5}})
--
-- draws white lines {40,0}..{0,80}, {0,80}..{80,80}, {80,80}..{40,0}
-- and blue lines {40,5}..{0,85}, {0,85}..{80,85}, {80,85}..{40,5}.
--
atom x1, y1, x2, y2
object ci
for i = 1 to length(coords) do
ci = coords[i]
if atom(ci) then
cdCanvasSetForeground(cddbuffer, ci)
elsif length(ci) = 4 then
{x1,y1,x2,y2} = ci
cdCanvasLine(cddbuffer,x1,y1,x2,y2)
{x1,y1} = {x2,y2}
elsif length(ci) = 2 then
{x2,y2} = ci
cdCanvasLine(cddbuffer,x1,y1,x2,y2)
{x1,y1} = {x2,y2}
else
?9/0
end if
end for
end procedure
procedure draw_polygon(sequence points)
integer {{x1,y1},{x2,y2},{x3,y3}} = points
cdCanvasBegin(cddbuffer,CD_FILL)
cdCanvasVertex(cddbuffer,x1,y1)
cdCanvasVertex(cddbuffer,x2,y2)
cdCanvasVertex(cddbuffer,x3,y3)
cdCanvasEnd(cddbuffer)
end procedure
function redraw_cb(Ihandle /*ih*/, integer /*posx*/, integer /*posy*/)
integer N, x, y, z
atom x1, y1, z1,
x2, y2, z2
{dw,dh} = IupGetIntInt(canvas, "DRAWSIZE")
{dx,dy} = {floor(dw/4)+1,floor(dh/4)+1}
{X_MAX,Y_MAX,Z_MAX} = {dw,dh,floor((dw+dh)/2)}
set_observer()
cdCanvasActivate(cddbuffer)
cdCanvasClear(cddbuffer)
cdCanvasSetForeground(cddbuffer, #909090)
{x1,y1,z1} = {X_MIN, 0.0,Z_MIN}
{x2,y2,z2} = {X_MAX,Y_MAX,Z_MAX}
-- draw the verticals on the sides and horizontals on the floor and ceiling
N = floor((z2-z1)/100)
if N<3 then N = 3 end if
for i=1 to N-1 do
z = z1+floor((i/N)*(z2-z1))
draw_lines({#808080,
project_point({x2, y2, z}) & project_point({x2, y1, z}),
project_point({x1, y1, z}),
project_point({x1, y2, z}),
#FFFFFF,
project_point({x2, y2, z})})
end for
-- draw the horizontals on the back and the not-quite horizontals on the sides
N = floor((y2-y1)/100)
if N<3 then N = 3 end if
for i=1 to N-1 do
y = y1+floor((i/N)*(y2-y1))
draw_lines({#808080,
project_point({x1,y,z1}) & project_point({x1,y,z2}),
project_point({x2,y,z2}),
project_point({x2,y,z1})})
end for
-- draw the verticals on the back and the not-quite-verticals on floor/ceiling
N = floor((x2-x1)/100)
if N<3 then N = 3 end if
for i=1 to N-1 do
x = x1+floor((i/N)*(x2-x1))
draw_lines({#808080,
project_point({x,y2,z2}) & project_point({x,y1,z2}),
project_point({x,y1,z1}),
#FFFFFF,
project_point({x,y2,z1}) & project_point({x,y2,z2})})
end for
-- draw the minimal room outline
draw_lines({CD_BLACK,
project_point({x1, y1, z2}) & project_point({x2, y1, z2}),
project_point({x2, y2, z2}),
project_point({x1, y2, z2}),
project_point({x1, y1, z2}),
project_point({x1, y1, z1}) & project_point({x1, y1, z2}),
project_point({x2, y1, z1}) & project_point({x2, y1, z2}),
project_point({x2, y2, z1}) & project_point({x2, y2, z2}),
project_point({x1, y2, z1}) & project_point({x1, y2, z2})})
if draw_shadows then
cdCanvasSetForeground(cddbuffer, #A0A0A0)
for i=1 to BOIDS do
draw_polygon(verts[i][6..8])
end for
end if
-- draw boids as polygons, starting with farthest from the POV
for i=BOIDS to 1 by -1 do
sequence v = verts[i]
cdCanvasSetForeground(cddbuffer, colors[v[5]])
draw_polygon(v[2..4])
end for
cdCanvasFlush(cddbuffer)
return IUP_DEFAULT
end function
constant null_u = {0,0}
function get_ortho(sequence v)
sequence y, yhat, z, u
u = v[1..2]
if equal(u, null_u) then
return {1,1,0}
end if
y = {1,1}
yhat = sq_mul(sq_div(dot(u,y),dot(u,u)),u)
z = sq_sub(y,yhat)
if equal(z, null_u) then
return {1,1,0}
end if
return z & 0
end function
function timer_cb(Ihandle /*ih*/)
sequence in,boid, pt, v, v1, v2, v3, sv
move_boids()
in = boidsnp1
if length(colors)=0 then
colors = repeat(0, BOIDS)
for i=1 to BOIDS do
colors[i] = rand(#FFFFFF)
end for
end if
-- convert boid location and velocities into triangles in 3D
-- and project them onto the screen
verts = repeat("", BOIDS)
for i=1 to BOIDS do
boid = in[i]
pt = boid[B_X..B_Z]
v = boid[B_XV..B_ZV]
if equal(v,{0,0,0}) then
v = {1,0,1}
end if
-- the 'nose' of the boid
v1 = sq_add(pt,make_length(v, 20))
-- make the base of the triangle
sv = make_length(get_ortho(v), 4)
v2 = sq_add(pt,sv)
v3 = sq_sub(pt,sv)
verts[i] = {distance3(pt, {ox,oy,oz}), v1, v2, v3, i, v1, v2, v3}
-- project the 3D points onto a 2D viewing surface
for j=2 to 4 do
verts[i][j] = project_point(verts[i][j])
end for
if draw_shadows then
for j=6 to 8 do
pt = verts[i][j]
pt[2] = 0.0
verts[i][j] = project_point(pt)
end for
end if
end for
-- sort by distance, so nearer boids clip those that are more distant
verts = sort(verts)
IupUpdate(canvas)
return IUP_IGNORE
end function
function add_boids(integer num)
sequence boid = repeat(0, B_ELEMENTS)
if num>BOIDS then
for i=BOIDS+1 to num do
boid[B_X] = rand(X_MAX)
boid[B_Y] = rand(Y_MAX)
boid[B_Z] = rand(Z_MAX)
boid[B_XV] = floor(V_MAX/2)-rand(V_MAX)
boid[B_YV] = floor(V_MAX/2)-rand(V_MAX)
boid[B_ZV] = floor(V_MAX/2)-rand(V_MAX)
boidsnp1 = append(boidsnp1, boid)
colors &= rand(#FFFFFF)
end for
boidsn = boidsnp1
end if
-- BOIDS = num
return num
end function
function valuechanged_cb(Ihandle ih)
integer i = IupGetInt(ih, "VALUE")
switch ih do
case speed_txt: V_MAX = max(i,1)
case dist_txt: DIST = max(i,10)
case radius_txt: N_DIST = max(i,1)
case boids_txt: BOIDS = add_boids(i)
end switch
return IUP_DEFAULT
end function
function shadow_cb(Ihandle /*ih*/, integer state)
draw_shadows = state
return IUP_DEFAULT
end function
function restart_cb(Ihandle /*ih*/)
setup()
return IUP_DEFAULT
end function
function map_cb(Ihandle ih)
cdcanvas = cdCreateCanvas(CD_IUP, ih)
cddbuffer = cdCreateCanvas(CD_DBUFFER, cdcanvas)
cdCanvasSetBackground(cddbuffer, CD_GREY)
return IUP_DEFAULT
end function
function esc_close(Ihandle /*ih*/, atom c)
if c=K_ESC then return IUP_CLOSE end if
return IUP_CONTINUE
end function
procedure main()
Ihandle hbox
IupOpen()
canvas = IupCanvas("RASTERSIZE=625x690")
IupSetCallback(canvas, "MAP_CB", Icallback("map_cb"))
IupSetCallback(canvas, "ACTION", Icallback("redraw_cb"))
speed_label = IupLabel("Max Speed","PADDING=0x4")
speed_txt = IupText("VALUECHANGED_CB", Icallback("valuechanged_cb"),
"SPIN=Yes, SPINMIN=1, RASTERSIZE=48x")
IupSetInt(speed_txt,"VALUE",V_MAX)
dist_label = IupLabel("Separation","PADDING=0x4")
dist_txt = IupText("VALUECHANGED_CB", Icallback("valuechanged_cb"),
"SPIN=Yes, SPINMIN=10, SPINMAX=600, RASTERSIZE=48x")
IupSetInt(dist_txt,"VALUE",DIST)
radius_label = IupLabel("Neighbor Radius","PADDING=0x4")
radius_txt = IupText("VALUECHANGED_CB", Icallback("valuechanged_cb"),
"SPIN=Yes, SPINMIN=1, SPINMAX=200, RASTERSIZE=48x")
IupSetInt(radius_txt,"VALUE",N_DIST)
boids_label = IupLabel("Boids","PADDING=0x4")
boids_txt = IupText("VALUECHANGED_CB", Icallback("valuechanged_cb"),
"SPIN=Yes, SPINMIN=1, SPINMAX=200, RASTERSIZE=48x")
IupSetInt(boids_txt,"VALUE",BOIDS)
shadow_check = IupToggle("Draw Shadows","ACTION",Icallback("shadow_cb"),"VALUE=ON")
restart = IupButton("Restart","ACTION",Icallback("restart_cb"),"GAP=0x14")
hbox = IupHbox({IupVbox({speed_label,speed_txt},"NORMALIZESIZE=HORIZONTAL"),
IupVbox({dist_label,dist_txt},"NORMALIZESIZE=HORIZONTAL"),
IupVbox({radius_label,radius_txt},"NORMALIZESIZE=HORIZONTAL"),
IupVbox({boids_label,boids_txt},"NORMALIZESIZE=HORIZONTAL"),
IupVbox({shadow_check,restart},"NORMALIZESIZE=HORIZONTAL")})
IupDestroy(IupNormalizer({speed_label,shadow_check},"NORMALIZE=VERTICAL"))
dialog = IupDialog(IupVbox({canvas,hbox}, "MARGIN=5x5, GAP=5"),"MINSIZE=455x170")
IupSetAttribute(dialog,"TITLE",TITLE);
IupSetCallback(dialog, "K_ANY", Icallback("esc_close"));
Ihandle hTimer = IupTimer(Icallback("timer_cb"), 40)
setup()
set_observer()
{} = timer_cb(hTimer)
IupShow(dialog)
IupSetAttribute(canvas, "RASTERSIZE", NULL)
IupMainLoop()
IupClose()
end procedure
main()
--
-- demo\pGUI\boids3d.e
--
### =============
--
global integer BOIDS = 60
-- OBSTACLES = 3
global atom N_DIST = 75.0,
DIST = 30.0,
V_MIN = 4.0,
V_MAX = 10.0,
X_MIN = 0.0,
Y_MIN = 0.0,
Z_MIN = 0.0,
X_MAX = 600.0,
Y_MAX = 600.0,
Z_MAX = 600.0
constant DIST_FACTOR = 1.0/100.0
global enum B_X, B_Y, B_Z, B_XV, B_YV, B_ZV, B_ELEMENTS = B_ZV
global sequence boidsn, boidsnp1
--, obstacles
function magnitude3(sequence v)
-- return the scalar magnitude of a 3D vector
return sqrt(v[1]*v[1]+v[2]*v[2]+v[3]*v[3])
end function
global function make_length(sequence v, atom l)
-- change the scalar magnitude of a 3D vector
return sq_mul(v,l/magnitude3(v))
end function
global function dot(sequence u, sequence v)
-- return the dot product of 2 2D vectors
return u[1]*v[1]+u[2]*v[2]
end function
global function distance3(sequence v1, sequence v2)
-- return the distance between two 3D vectors
return magnitude3(sq_sub(v1[1..3],v2[1..3]))
end function
function boids_dist(integer b1, integer b2)
-- return the distance between two boids, identified by their position in the boidsn sequence
return distance3(boidsn[b1], boidsn[b2])
end function
sequence n_id, n_dist
function neighbors(integer bid, atom distance)
-- return a list of boids within the N_DIST radius of a specified boid
atom dist
integer ix
sequence n, nid, ndist
if bid=1 then
n_id = repeat("", BOIDS)
n_dist = n_id
end if
nid = n_id[bid]
ndist = n_dist[bid]
n = repeat({}, BOIDS)
ix = 0
for i=1 to length(n_id[bid]) do
ix += 1
n[ix] = {nid[i], ndist[i]}
end for
for i=bid+1 to BOIDS do
dist = boids_dist(bid, i)
if dist<=distance then
ix += 1
n[ix] = {i,dist}
n_id[i] &= bid
n_dist[i] &= dist
end if
end for
return n[1..ix]
end function
procedure maintain_distance(integer bid, sequence n)
-- alter a boids velocity to try to stay at least DIST away from other boids
atom dx, dy, dz
sequence this, other
dx = 0.0
dy = 0.0
dz = 0.0
this = boidsn[bid]
for i=1 to length(n) do
if n[i][2]<DIST then
other = boidsn[n[i][1]]
dx -= (other[B_X]-this[B_X])*2
dy -= (other[B_Y]-this[B_Y])*2
dz -= (other[B_Z]-this[B_Z])*2
end if
end for
dx *= DIST_FACTOR
dy *= DIST_FACTOR
dz *= DIST_FACTOR
boidsnp1[bid][B_XV] += dx
boidsnp1[bid][B_YV] += dy
boidsnp1[bid][B_ZV] += dz
end procedure
procedure avoid_walls(integer bid)
-- avoid the boundaries of MAX and MIN for each dimension (X, Y, Z)
sequence this
atom dx, dy, dz, t
dx = 0.0
dy = 0.0
dz = 0.0
this = boidsn[bid]
t = this[B_X]
if t<DIST+X_MIN then
dx += 1
elsif t>X_MAX-DIST then
dx -= 1
end if
t = this[B_Y]
if t<DIST+Y_MIN then
dy += 1
elsif t>Y_MAX-DIST then
dy -= 1
end if
t = this[B_Z]
if t<DIST+Z_MIN then
dz += 1
elsif t>Z_MAX-DIST then
dz -= 1
end if
boidsnp1[bid][B_XV] += dx
boidsnp1[bid][B_YV] += dy
boidsnp1[bid][B_ZV] += dz
end procedure
procedure match_velocity(integer bid, sequence n)
-- try to match the velocity of a boid to its neighbors
atom dx, dy, dz
sequence this, other
if length(n) then
dx = 0.0
dy = 0.0
dz = 0.0
this = boidsn[bid]
for i=1 to length(n) do
other = boidsn[n[i][1]]
dx += other[B_XV]
dy += other[B_YV]
dz += other[B_ZV]
end for
dx /= length(n)
dy /= length(n)
dz /= length(n)
dx -= this[B_XV]
dy -= this[B_YV]
dz -= this[B_ZV]
dx *= DIST_FACTOR
dy *= DIST_FACTOR
dz *= DIST_FACTOR
boidsnp1[bid][B_XV] += dx
boidsnp1[bid][B_YV] += dy
boidsnp1[bid][B_ZV] += dz
end if
end procedure
procedure move_to_center(integer bid, sequence n)
-- try to move a boid toward the center of its neighbors
atom x, y, z
sequence other
if length(n) then
x = 0.0
y = 0.0
z = 0.0
for i=1 to length(n) do
other = boidsn[n[i][1]]
x += other[B_X]
y += other[B_Y]
z += other[B_Z]
end for
-- compute the center
x /= length(n)
y /= length(n)
z /= length(n)
-- figure out the direction...
other = boidsn[bid]
x -= other[B_X]
y -= other[B_Y]
z -= other[B_Z]
x *= DIST_FACTOR
y *= DIST_FACTOR
z *= DIST_FACTOR
boidsnp1[bid][B_XV] += x
boidsnp1[bid][B_YV] += y
boidsnp1[bid][B_ZV] += z
end if
end procedure
procedure constrain(integer bid)
-- don't let them go too fast or too slow
atom mag = magnitude3(boidsnp1[bid][B_XV..B_ZV])
if mag>V_MAX then
boidsnp1[bid][B_XV..B_ZV] = sq_div(boidsnp1[bid][B_XV..B_ZV],mag/V_MAX)
elsif mag<V_MIN then
if mag then
boidsnp1[bid][B_XV..B_ZV] = sq_mul(boidsnp1[bid][B_XV..B_ZV],V_MIN/mag)
else
boidsnp1[bid][B_XV] = V_MIN*rand(100)/100
boidsnp1[bid][B_YV] = V_MIN*rand(100)/100
boidsnp1[bid][B_ZV] = V_MIN*rand(100)/100
end if
end if
end procedure
procedure move(integer bid)
boidsnp1[bid][B_X..B_Z] = sq_add(boidsnp1[bid][B_X..B_Z],boidsnp1[bid][B_XV..B_ZV])
end procedure
global procedure setup()
atom mag
boidsn = repeat(repeat(0.0, B_ELEMENTS), BOIDS)
boidsnp1 = boidsn
-- place them randomly
for boid=1 to BOIDS do
boidsnp1[boid][B_X] = rand(X_MAX)
boidsnp1[boid][B_Y] = rand(Y_MAX)
boidsnp1[boid][B_Z] = rand(Z_MAX)
boidsnp1[boid][B_XV] = V_MAX-rand(2*V_MAX)
boidsnp1[boid][B_YV] = V_MAX-rand(2*V_MAX)
boidsnp1[boid][B_ZV] = V_MAX-rand(2*V_MAX)
mag = magnitude3(boidsnp1[boid])/V_MAX
if mag>1.0 then
boidsnp1[boid][B_XV..B_ZV] = sq_div(boidsnp1[boid][B_XV..B_ZV],mag)
end if
end for
-- obstacles = repeat({},OBSTACLES)
-- for o=1 to OBSTACLES do
-- obstacles[o] = {rand(X_MAX), rand(Y_MAX), rand(Z_MAX), 30}
-- end for
boidsn = boidsnp1
end procedure
global procedure move_boids()
sequence n
for boid=1 to BOIDS do
n = neighbors(boid, N_DIST)
maintain_distance(boid, n)
match_velocity(boid, n)
move_to_center(boid, n)
avoid_walls(boid)
constrain(boid)
move(boid)
end for
boidsn = boidsnp1
end procedure