(phixonline)-->
--
-- demo\rosetta\DrawRotatingCube.exw
-- =================================
--
-- credits: http://petercollingridge.appspot.com/3D-tutorial/rotating-objects
-- https://github.com/ssloy/tinyrenderer/wiki/Lesson-4:-Perspective-projection
--
-- Aside: low CPU usage, at least when using a 30ms timer (33 FPS, which is plenty).
--
with javascript_semantics
include pGUI.e
constant title = "Draw a Rotating Cube"
Ihandle dlg, canvas
cdCanvas cd_canvas
--
-- First, define 8 corners equidistant from {0,0,0}:
--
-- 6-----2
-- 5-----1 3
-- 8-----4
--
-- ie the right face is 1-2-3-4 clockwise, and the left face
-- is 5-6-7-8 counter-clockwise (unless using x-ray vision).
-- (since this is not drawing textures, clockwise-ness does
-- not matter, as shown by the corrected orange face, but
-- it will if you (figure out how to) apply any textures.)
-- (a quick (online) study of opengl texture documentation
-- should convince you that stuff is best left to opengl.)
--
enum X, Y, Z
constant l = 100
constant corners = {{+l,+l,+l}, -- 1 (front top right)
{+l,+l,-l}, -- 2 (back top "right")
{+l,-l,-l}, -- 3 (back btm "right")
{+l,-l,+l}, -- 4 (front btm right)
{-l,+l,+l}, -- 5 (front top left)
{-l,+l,-l}, -- 6 (back top "left")
{-l,-l,-l}, -- 7 (back btm "left")
{-l,-l,+l}} -- 8 (front btm left)
-- I put left/right in quotes for the back face as a reminder
-- those match the above diagram, but of course they would be
-- swapped were you looking "at" the face/rotated it by 180.
constant faces = {{CD_RED, 1,2,3,4}, -- right
{CD_YELLOW, 1,5,6,2}, -- top
{CD_DARK_GREEN, 1,4,8,5}, -- front
{CD_BLUE, 2,3,7,6}, -- back
{CD_WHITE, 3,4,8,7}, -- bottom
-- {CD_ORANGE, 5,6,7,8}} -- left
{CD_ORANGE, 8,7,6,5}} -- left
-- rotation angles, 0..359, on a timer
atom rx = 45, -- initially makes cube like a H
ry = 35, -- " " " italic H
rz = 0
constant naxes = {{Y,Z}, -- (rotate about the X-axis)
{X,Z}, -- (rotate about the Y-axis)
{X,Y}} -- (rotate about the Z-axis)
function rotate(sequence points, atom angle, integer axis)
--
-- rotate points by the specified angle about the given axis
--
atom radians = angle*CD_DEG2RAD,
sin_t = sin(radians),
cos_t = cos(radians)
integer {nx,ny} = naxes[axis]
for i=1 to length(points) do
atom x = points[i][nx],
y = points[i][ny]
points[i][nx] = x*cos_t - y*sin_t
points[i][ny] = y*cos_t + x*sin_t
end for
return points
end function
function projection(sequence points, atom d)
--
-- project points from {0,0,d} onto the perpendicular plane through {0,0,0}
--
for i=1 to length(points) do
atom {x,y,z} = points[i],
denom = (1-z/d)
points[i][X] = x/denom
points[i][Y] = y/denom
end for
return points
end function
function nearest(sequence points)
--
-- return the index of the nearest point (highest z value)
--
return largest(vslice(points,Z),true)
end function
procedure draw_cube(integer cx, cy)
-- {cx,cy} is the centre point of the canvas
sequence points = deep_copy(corners)
points = rotate(points,rx,X)
points = rotate(points,ry,Y)
points = rotate(points,rz,Z)
points = projection(points,1000)
integer np = nearest(points)
--
-- find the three faces that contain the nearest point,
-- then for each of those faces let diag be the point
-- that is diagonally opposite said nearest point, and
-- order by/draw those faces furthest diag away first.
-- (one or two of them may be completely obscured due
-- to the effects of the perspective projection.)
-- (you could of course draw all six faces, as long as
-- the 3 furthest are draw first/obliterated, which
-- is what that commented-out "else" would achieve.)
--
sequence faceset = {}
for i=1 to length(faces) do
sequence fi = faces[i]
integer k = find(np,fi) -- k:=2..5, or 0
if k then
integer diag = mod(k,4)+2 -- {2,3,4,5} --> {4,5,2,3}
-- aka swap 2<=>4 & 3<=>5
diag = fi[diag] -- 1..8, diagonally opp. np
faceset = append(faceset,{points[diag][Z],i})
-- else
-- faceset = append(faceset,{-9999,i})
end if
end for
faceset = sort(faceset)
for i=1 to length(faceset) do
sequence face = faces[faceset[i][2]]
cdCanvasSetForeground(cd_canvas,face[1])
-- first fill sides (with bresenham edges), then
-- redraw edges, but anti-aliased aka smoother
sequence modes = {CD_FILL,CD_CLOSED_LINES}
for m=1 to length(modes) do
cdCanvasBegin(cd_canvas,modes[m])
for fdx=2 to 5 do
sequence pt = points[face[fdx]]
cdCanvasVertex(cd_canvas,cx+pt[X],cy-pt[Y])
end for
cdCanvasEnd(cd_canvas)
end for
end for
end procedure
function canvas_action_cb(Ihandle canvas)
cdCanvasActivate(cd_canvas)
cdCanvasClear(cd_canvas)
integer {w, h} = IupGetIntInt(canvas, "DRAWSIZE")
draw_cube(floor(w/2),floor(h/2))
cdCanvasFlush(cd_canvas)
return IUP_DEFAULT
end function
function canvas_map_cb(Ihandle canvas)
IupGLMakeCurrent(canvas)
if platform()=JS then
cd_canvas = cdCreateCanvas(CD_IUP, canvas)
else
atom res = IupGetDouble(NULL, "SCREENDPI")/25.4
cd_canvas = cdCreateCanvas(CD_GL, "10x10 %g", {res})
end if
cdCanvasSetBackground(cd_canvas, CD_PARCHMENT)
return IUP_DEFAULT
end function
function canvas_resize_cb(Ihandle /*canvas*/)
integer {canvas_width, canvas_height} = IupGetIntInt(canvas, "DRAWSIZE")
atom res = IupGetDouble(NULL, "SCREENDPI")/25.4
cdCanvasSetAttribute(cd_canvas, "SIZE", "%dx%d %g", {canvas_width, canvas_height, res})
return IUP_DEFAULT
end function
function timer_cb(Ihandln /*ih*/)
-- (feel free to add a bit more randomness here, maybe)
rx = mod(rx+359,360)
ry = mod(ry+359,360)
rz = mod(rz+359,360)
IupRedraw(canvas)
return IUP_IGNORE
end function
procedure main()
IupOpen()
canvas = IupGLCanvas("RASTERSIZE=640x480")
IupSetCallbacks(canvas, {"ACTION", Icallback("canvas_action_cb"),
"MAP_CB", Icallback("canvas_map_cb"),
"RESIZE_CB", Icallback("canvas_resize_cb")})
dlg = IupDialog(canvas,`TITLE="%s"`,{title})
IupShow(dlg)
IupSetAttribute(canvas, "RASTERSIZE", NULL)
Ihandle hTimer = IupTimer(Icallback("timer_cb"), 30)
if platform()!=JS then
IupMainLoop()
IupClose()
end if
end procedure
main()