Example: Plane Trapped in a Torus

This example demonstrates the use of various camera-related functions, shape inversion, the premodel primitive and others to create this cool animation of a plane trapped in a torus!

The procedure first sets the turtle model to the built-in plane model, before creating a tag that sets the color of the torus. Then it creates an inverted torus (one whose inside is rendered instead of its outslde) because we’re going to fly inside it!

We then position the turtle to prepare it for its orbital flight path, position the camera behind it and set up its light. Then we begin orbiting, and while we do so we use setpremodel to move the turtle relative to the camera, creating the drifting motions of the plane as it flies.

Every so often, we change the color of the torus by replacing the contents of the color tag. Cool stuff!

Read through the source code below and take note of the comments, which explain what various parts of the trapped procedure do.

TO trapped
  reset
  setmodel "plane
  ;sets Myrtle's model to the built-in plane model
  
  begintag "col
  setfillcolor 1 + random 15
  endtag
  ;the contents of tags can be used to create models, or
  ;they can be disabled or replaced. We're going to replace
  ;the contents of this tag later in our procedure, to change
  ;the color of the torus 'on the fly' (ba-dum)
  
  torus 30 -50 20 20
  ;creates an inverted torus by inverting the radius parameter.
  ;Closed shapes such as the torus don't ordinarily have an 'inside'
  ;unless we create them inverted.
  
  ;Why do we want to invert it?
  
  ;We're going to switch the camera turtle to Myrtle, and so
  ;we want to see inside the torus, not the outside. To do this,
  ;we invert it, as demonstrated above.
  
  penup
  ;don't forget, the turtle draws a line by default
  
  dropanchor
  tether
  pullout 50
  left 90
  ;We're going to use the orbit primitive to move Myrtle
  ;inside of the torus. So we 'dropanchor' at Myrtle's position,
  ;to set the 'anchorpoint' that the orbit primitives rotate around,
  ;'tether' to keep the anchor point static (because otherwise
  ;when Myrtle turns the anchor point moves to stay in front of
  ;her) 'pullout' 50 turtle units from the anchor point, and then
  ;turn left 90 degrees (which we can do because we called tether)
  
  ;To demonstrate the need for tether, try:
  ;reset repeat 36 [repeat 90 [orbitleft 4] right 10]
  ;As you can see, the point Myrtle orbit arounds moves when she
  ;turns right. Put a 'tether' primitive before the first repeat
  ;and notice the difference!
  
  setpremodel [rt 10]
  ;'setpremodel' allows us to put commands between the 'turtle track'
  ;that contains everything the turtle draws and the turtle model
  ;itself.
  
  ;When we attach the camera to a turtle, its position is
  ;set before premodel (and the turtle model) and so we can use
  ;setpremodel to change the position and orientation of the turtle
  ;as seen from the camera. And so we will see the model pointing
  ;slightly to the right
  
  setview "myrtle
  ;set the camera to show Myrtle's point of view
  
  snappy:setlight 0
  ;turn Snappy (the normal camera turtle)'s light off.
  ;Snappy has a light on by defalt.
  
  setlight 2
  ;turn Myrtle's light on. The value 2 is a point light,
  ;which casts light around Myrtle
  
  setdiffuse [40 40 40 100]
  setambient [0 0 0 0]
  ;these set paramets related to the light.
  ;see their help entries for more information
  
  setviewpoint [5 10 -50]
  ;sets the position of the camera relative to the turtle
  ;as a list of [x y z]. So in this case, to the right, above
  ;and behind the turtle
  
  forever [
    
    setpremodel {
    "right 20 - 30 * (sin 0.5 * loopcount)
    "slideright -5 + 10 * (sin 0.5 * loopcount)
    "up 10 * (sin loopcount)
    "raise 2.5 - 10 * (0.5 * sin loopcount)
    }
    
    ;curly braces indicate a 'softlist', a list that is
    ;evaluated at the time of execution (the point at
    ;which the interpreter interprets the list).
    ;They allow us to use functions and containers to
    ;assemble a list dynamically.
    
    ;In this case, we're using the sin function and loopcount
    ;to move the turtle model relative to its position and
    ;simulate the motion of an aircraft in flight.
    
    ;loopcount is similar to repcount, but is used in non-repeat
    ;loops such as forever, while, until, dowhile, dountil and
    ;foreach.
    
    ;it counts the number of times the loop has been executed
    
    orbitleft 0.5
    ;orbit to the left half a degree. In this case, 'to the left'
    ;causes the turtle to appear to move forward. But remember,
    ;we turned the turtle to the left earlier in the procedure!
    
    if divp 300 loopcount [
      replacetag "col {
      "setfillcolor 1 + random 15
      "setfs -15 + random 30
      }
    ]
    ;divp (or divisorp) returns true if the numerator (the first
    ;number) divides equally into the denominator (the second
    ;number, in this case the loopcount.)
    
    ;if divp returns true, we replace the "col tag we created
    ;earlier in the procedure with new contents setting a random
    ;color and shade. We need to use curly braces so that the
    ;random numbers are generated at the time we call replacetag.
    
    ;note that the primitives need to have a " in front of them
    ;so they are not themselves executed when the softlist is
    ;evaluated! By putting a quotation mark in front of them,
    ;they are evaluated as words and passed verbatim to
    ;the replacetag primitive.
    
    ;there are other ways to do this that you can do in other
    ;Logo environments but they are more cumbersome.
    
    sleep 5
    ;sleep for 5 milliseconds
    
  ]
  ;do this forever, or until we press escape or the stop button
  
END

 

 

 

One-a-Day: Space Tube Ride using TUBE and TUBEARC

Myrtle creates a 3D space tube ride for her to fly through…

Sometimes Myrtle needs to take a break and have a little fun! One of the things she can do is make a space tube and fly through it, using the TUBE and TUBEARC primitives:

TUBE makes a straight tube, and takes the parameters <radius> <depth> and <sides>. So, tube 20 10 10 makes a tube with a radius of 20 turtle units, a depth of 10 turtle units and 10 sides.

TUBEARC creates a curved tube, and takes the parameters <thickness> (the radius of the tube itself) <radius> (the radius of the arc itself) <sides> <totalsegments> and <arcsegments> – the last two are the total segments that would make up a complete torus, or donut, and the number of segments we actually want to create. So values of 20 and 10 will create a half-donut: tubearc 10 20 20 20 10

There are four procedures to our space tube program: createcourse, drawcourse, followcourse and spacetube.

createcourse generates the data that represents the tube’s course. It creates a course made up of 100 segments, represented by a word made up of a series of numbers between 0 and 4, where 0 represents a straight section of tube, and 1 to 4 represent four directions of turn. We use dountil to ensure we don’t repeat the same direction of turn more than once and end up looping back on ourself. Although, createcourse is not perfect and it is still possible for the course to cross over itself – but it’s a good thing tube walls aren’t solid!

drawcourse creates the graphical representation of the contents of the :course container we generated using createcourse. We use the twosided primitive to generate reflective “normals” on both sides of the shapes we generate from that point forward, and we use randfc to set a random ‘fill color’ or shape color for each segment as we create them.

We use foreach to step through each segment listed in the :course container, creating either a tube or a tubearc where appropriate.

While tubes are created directly under Myrtle, tubearcs are created around her, and so this means we need to do a bit of calisthenics to position the tubearcs where they need to go. Follow them through one command at a time to see how they work! We wait 2 / 60ths of a second between each segment creation so we can follow it.

followcourse causes Myrtle to fly through the created course, once again iterating through the :course container using foreach. fluid causes Myrtle to move forward smoothly, and to turn through the arcs we use a repeat. sleep is another form of wait, which takes milliseconds as a parameter instead of wait‘s 60ths of a second.

setpremodel allows us to ‘prepend’ commands to the turtle’s model, allowing us to make Myrtle look like she’s turning the appropriate way as she moves through the course.

Finally, spacetube puts it all together and is the procedure we execute to create the maze and fly through it. spacetube performs a reset, turns off the text layer, tells Snappy (the camera turtle) to pull away from Myrtle 1000 turtle units, tells Snappy to ‘follow’ Myrtle (turn to face her when she disappears from his view), then executes the createcourse procedure, the drawcourse procedure, sets the view (camera) turtle to Myrtle, turns Myrtle’s ‘light’ on (so we can see properly inside the tubes), turns Snappy’s light off, sets the camera ‘view point’ of Myrtle a bit up and behind her, and then executes followcourse.

TO spacetube
  reset
  fullscreen
  snappy:pullout 1000
  snappy:follow "myrtle
  createcourse
  drawcourse
  setview "myrtle
  setlight 1
  snappy:setlight 0
  setviewpoint [0 10 -30]
  followcourse
END

TO createcourse
  make "course empty
  make "last 5
  repeat 100 [
    
    dountil :last != :segment [make "segment random 5]
    
    make "course word :course :segment
    if :segment != 0 [make "last :segment]
  ]
END

TO drawcourse
  nofluid
  cs
  pu twosided
  foreach "segment :course [
    randfc
    if :segment = 0 [tube 50 200 20 lo 200]
    else [rt :segment * 90
      dn 90 rr 90 sl 100 tubearc 50 100 20 20 5 fd 100 rr 90 lt 180 + :segment * 90]
    wait 2
  ]
  home
END

TO followcourse
  dn 90
  fluid
  foreach "segment :course [
    if :segment = 0 [fd 200]
    if :segment = 1 [setpremodel [rt 22.5] repeat 90 [fd 1.75 rt 1 sleep 1] setpremodel []]
    if :segment = 2 [setpremodel [dn 22.5] repeat 90 [fd 1.75 dn 1 sleep 1] setpremodel []]
    if :segment = 3 [setpremodel [lt 22.5] repeat 90 [fd 1.75 lt 1 sleep 1] setpremodel []]
    if :segment = 4 [setpremodel [up 22.5] repeat 90 [fd 1.75 up 1 sleep 1] setpremodel []]
  ]
END