3d turtle graphics: Difference between revisions
m (→{{header|Wren}}: Removed superfluous import.) |
(→{{header|Wren}}: Improved the look of the roof by making a rough allowance for perspective.) |
||
Line 368: | Line 368: | ||
// draw roof |
// draw roof |
||
_t.left( |
_t.left(10) |
||
_t.goto(_w/8, _h/2) |
_t.goto(_w/8, _h/2) |
||
_t.walk(size) |
_t.walk(size) |
||
_t.dir = |
_t.dir = 63 |
||
_t.walk(size) |
_t.walk((size*15/16).floor) |
||
_t.back(size) |
_t.back((size*15/16).floor) |
||
_t.left( |
_t.left(44) |
||
_t.walk(size) |
_t.walk((size*31/32).floor) |
||
_t.back(size) |
_t.back((size*31/32).floor) |
||
_t.dir = |
_t.dir = 102 |
||
_t.walk((size |
_t.walk((size/3).floor) |
||
// draw door |
// draw door |
Revision as of 18:25, 10 March 2022
Create a 3d representation of a house and bar chart, similar to that of Simple turtle graphics, but
- (you can) use a cube for the house and an equilateral square pyramid for the roof, and
- (likewise if it helps) use columns with a square cross section for the bar chart.
J
Assumes jqt and a "recent" (2022) version of J. <lang J>NB. pre-requisites: NB. ;install each cut 'gl2 gles github:zerowords/tgsjo' load'zerowords/tgsjo' stop clearscreen createTurtle 0 0 0
EYE_tgsjo_=: 0 0 _90 rotR 180+94 10 0
column=: {{
4 repeats {{'height width'=. y 2 repeats {{'height width'=. y forward width pitch 90 forward height pitch 90 }} y forward width right 90 }} y
}}
cube=: Template:Column y,y
codihedral=: 180p_1*_3 o.%%:2 pyramid=: {{
4 repeats {{ roll codihedral pitch triangle y roll-codihedral forward y left 90 }} y
}}
NB. in 3d, we want to know which turning mechanism to use, when drawing a plane figure triangle=: {{
3 repeats (u {{ forward y u 120 }}) y
}}
house=: {{
cube y roll 180 pyramid y
}}
barchart=: {{'lst size'=. y
if.#lst do. scale=. size%>./lst width=. size%#lst for_j. lst do. column (j * scale),width forward width end. back size end.
}}
penColor Red house 150 pen 0 back 30 right 180 pen 1 penColor Blue barchart 0.5 0.3333 2 1.3 0.5; 200 pen 0 left 180 forward 30 roll 180</lang>
Phix
Fairly straightforward extension of Simple_turtle_graphics.exw, apart from the time spent staring at my own hand figuring out the sequences of angles.
Reuses turtle.e, however the code I really wanted to squirrel away has ended up in turtle_projection.e.
You can run this online here.
-- -- demo\rosetta\3D_turtle_graphics.exw -- =================================== -- -- Fairly straightforward extension of Simple_turtle_graphics.exw, apart from -- the time spent staring at my own hand figuring out the sequences of angles. -- -- The turtle itself always draws in normal space, ie with the y axis vertical, -- the x axis horizontal, and the z axis effectively a point, with any and all -- 3D effects generated by projection onto a camera plane, which can be moved -- about with the usual four arrow and +/- keys. Note the camera handling was -- cribbed from demo/pGUI/rubik.e, less than perfect here but will have to do. -- -- Invoking turn() (aka yaw) does pretty much what you would expect, whereas -- roll() rotates about the direction of travel. To simulate pitch() (aka to -- climb or nosedive) perform a roll(90), turn(pitch) sequence, avoiding any -- temptation to "level off" before moving, as that just complicates angles. -- with javascript_semantics include turtle.e -- (common code for 2D and 3D versions) include turtle_projection.e -- (final 3D projection stuff) atom px = 0, py = 0, pz = 0, -- position hx = 1, hy = 0, hz = 0, -- heading nx = 0, ny = 0, nz = 1 -- normal procedure walk3D(atom d) // // Move forward by distance d pixels. // // Aside: not entirely sure why rounding to 4dp (and normalising // to 8dp) helps, but without it errors seem to build quickly.. // Of course in my opinion regularly teleporting the turtle back // to {0,0,0} would just be cheating whereas this makes it exact // sequence p1 = {px,py,pz} px = round(px+d*hx,10000) py = round(py+d*hy,10000) pz = round(pz+d*hz,10000) if pen_down then -- (not entirely sure why it's "{y,x} =" either, but it is.) atom {{y1,x1},{y2,x2}} = rotate_and_project({p1,{px,py,pz}}) cdCanvasLine(cdcanvas,x1,y1,x2,y2) end if end procedure function left_unit_vector() return {ny*hz-nz*hy,nz*hx-nx*hz,nx*hy-ny*hx} end function function normalize(sequence v) v = sq_round(sq_mul(v,1/sqrt(sum(sq_power(v,2)))),100000000) return v end function enum ROLL,TURN procedure rot3D(atom angle, integer tp) angle *= CD_DEG2RAD atom cos_a = cos(angle), sin_a = sin(angle), {ux,uy,uz} = left_unit_vector() if tp=ROLL then -- aka rotation about the heading {nx,ny,nz} = normalize({nx*cos_a-ux*sin_a, ny*cos_a-uy*sin_a, nz*cos_a-uz*sin_a}) else -- tp=TURN -- aka rotation about the normal {hx,hy,hz} = normalize({hx*cos_a+ux*sin_a, hy*cos_a+uy*sin_a, hz*cos_a+uz*sin_a}) end if end procedure procedure rtm3D(sequence s) -- s is a list of {roll,turn,dist}, any of which can be 0. for i=1 to length(s) do atom {roll,turn,dist} = s[i] if roll then rot3D(roll,ROLL) end if if turn then rot3D(turn,TURN) end if if dist then walk3D(dist) end if end for end procedure procedure rectangle(atom width, height, depth=width) rtm3D({{ 0, 0,height},{ 0,90,width},{0,90,height},{0,90,width}, {90,-90,depth},{-90,90,height},{0,90,depth},{0,90,height},{0,90,depth}, {90,-90,width},{-90,90,height},{0,90,width},{0,90,height},{0,90,width}, {90,-90,depth},{-90,90,height},{0,90,depth},{0,90,height},{0,90,depth}, {-90,90,width},{ 90,90,0}}) end procedure procedure draw_house(atom width, height) // // Draw a house at the current position // heading must be {1,0,0} for house to be upright // // house walls pendown() rectangle(width, height) // door penup() rtm3D({{0,90,width/7},{0,-90,0}}) pendown(CD_GREEN) rectangle(width/8,height/2.5,0) penup() rtm3D({{0,-90,width/7},{0,90,0}}) // roof walk3D(height) pendown(CD_ORANGE) rtm3D({{-45,45,width},{0,90,width}}) penup() rtm3D({{0,-45,0},{90,-135,width},{0,45,0}}) pendown(CD_ORANGE) rtm3D({{90,135,width},{0,90,width}}) // return to original position and direction penup() rtm3D({{0,-45,0},{90,135,width},{90,90,-height}}) end procedure procedure draw_barchart(sequence nums, atom w, h) // draw a barchart occupying the middle 60% of w,h // nums can contain +ve and/or -ve values. integer n = length(nums) atom mx = max(max(nums),0), mn = min(min(nums),0), r = mx-mn, -- range zl = abs(mn)/r*h*0.6+h/5, -- zero line bw = w*0.6/n -- bar width rtm3D({{0,90,w/5},{0,-90,zl}}) pendown() for i=1 to n do atom ni = nums[i]/r*h*0.6 pendown(iff(ni<0?CD_ORANGE:CD_NAVY)) rectangle(bw,ni) rtm3D({{0,90,bw},{0,-90,0}}) end for penup() // return to origin({w/2,0}) and direction 0: rtm3D({{0,180,zl},{0,90,w/5+bw*n},{0,90,0}}) end procedure function redraw_cb(Ihandle /*ih*/) integer {width, height} = IupGetIntInt(canvas, "DRAWSIZE") atom hw = width/2, qw = width/4, qh = height/4 cdCanvasActivate(cdcanvas) cdCanvasClear(cdcanvas) rtm3D({{0,0,qh},{0,90,qw},{0,-90,0}}) draw_house(qw,qh) -- house in the left half rtm3D({{0,180,qh},{0,90,qw},{0,90,0}}) -- return to {0,0} rtm3D({{0,90,hw},{0,-90,0}}) -- barchart in the right half draw_barchart({0.5, -4/3, 2, 1.3, 0.5},width/2,height) rtm3D({{0,-90,hw},{0,90,0}}) -- return to {0,0} -- sanity checks (but I got a 0.0002 under pwa/p2js when dev tools open...) if platform()!=JS then assert({px,py,pz}={0,0,0}) assert({hx,hy,hz}={1,0,0}) assert({nx,ny,nz}={0,0,1}) end if cdCanvasFlush(cdcanvas) return IUP_DEFAULT end function function key_cb(Ihandle ih, atom c) if c=K_ESC then return IUP_CLOSE end if integer axis = find(c,{K_RIGHT,K_DOWN,'+',K_LEFT,K_UP,'-'}) if axis then integer angle = 1 if axis>3 then angle = 359 axis -= 3 end if if length(view_rotations) and view_rotations[$][1] = axis then view_rotations[$][2] = mod(view_rotations[$][2]+angle,360) else view_rotations = append(view_rotations,{axis,angle}) end if IupRedraw(canvas) elsif c='0' then view_rotations = {} IupRedraw(canvas) end if return IUP_CONTINUE end function IupOpen() canvas = IupCanvas(Icallback("redraw_cb"),"RASTERSIZE=600x400") dlg = IupDialog(canvas,`TITLE="3D turtle graphics"`) IupMap(dlg) cdcanvas = cdCreateCanvas(CD_IUP, canvas) IupShow(dlg) IupSetAttribute(canvas, "RASTERSIZE", NULL) -- release minimum limit IupSetCallback(dlg, "KEY_CB", Icallback("key_cb")) IupSetAttributeHandle(NULL, "PARENTDIALOG", dlg) if platform()!=JS then IupMainLoop() IupClose() end if
Wren
<lang ecmascript>import "dome" for Window import "graphics" for Canvas, Color import "math" for Math import "./turtle" for Turtle
class Main {
construct new(width, height) { Window.resize(width, height) Canvas.resize(width, height) Window.title = "3d turtle graphics" _w = width _h = height }
init() { Canvas.cls(Color.white) _t = Turtle.new() drawHouse3D(_w/4) barChart([15, 10, 50, 35, 20], _w/3) }
drawRectCuboid(x, y, hsize, vsize) { // draw front rectangle _t.drawRect(x, y, hsize, vsize)
// draw back rectangle _t.drawRect(x + hsize/2, y - hsize/2, hsize, vsize)
var side = ((2 * hsize * hsize).sqrt/2).floor
// turn right 45 degrees _t.right(45) // goto bottom left front _t.goto(x, y + vsize) _t.walk(side)
// goto top left front _t.goto(x, y) _t.walk(side)
// goto bottom right front _t.goto(x + hsize, y + vsize) _t.walk(side)
// goto top right front _t.goto(x + hsize, y) _t.walk(side) }
drawHouse3D(size) { // save initial turtle position and direction var saveX = _t.x var saveY = _t.y var saveD = _t.dir
_t.pen.width = 2
// draw house drawRectCuboid(_w/8, _h/2, size, size)
// draw roof _t.left(10) _t.goto(_w/8, _h/2) _t.walk(size)
_t.dir = 63 _t.walk((size*15/16).floor) _t.back((size*15/16).floor)
_t.left(44) _t.walk((size*31/32).floor) _t.back((size*31/32).floor) _t.dir = 102 _t.walk((size/3).floor)
// draw door var doorWidth = (size/4).floor var doorHeight = (size/2.5).floor _t.drawRect(_w/4 + doorWidth/2, _h/2 + (size/2).floor - doorHeight, doorWidth, doorHeight)
// draw window var windWidth = (size/3).floor var windHeight = (size/4).floor _t.drawRect(_w/4 + size/1.8, _h/2 + (size/2.8).floor - windHeight, windWidth, windHeight)
// restore initial turtle position and direction _t.x = saveX _t.y = saveY _t.dir = saveD } // nums assumed to be all non-negative barChart(nums, size) { _t.pen.color = Color.brown
// save intial turtle position and direction var saveX = _t.x var saveY = _t.y var saveD = _t.dir
// find maximum var max = 0 for (n in nums) if (n > max) max = n
// scale to fit within a square with sides 'size' and draw chart var barWidth = (size / nums.count).floor var startX = _w / 2 + 20 var startY = _h / 2 for (i in 0...nums.count) { // nums.count) { var barHeight = (nums[i] * size / max).round drawRectCuboid(startX, startY - barHeight, barWidth, barHeight) startX = startX + barWidth }
// restore intial turtle position and direction _t.x = saveX _t.y = saveY _t.dir = saveD }
update() {}
draw(alpha) {}
}
var Game = Main.new(800, 800)</lang>