(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()