A Starry turtleSpaces Logo Introduction Part Three: Starwarp Improvements
In the previous episode, we created a progressively-generated starfield we moved through with the camera turtle, creating a cool flying-through-space effect.
But it has a few issues we should address. Firstly, it is possible for a star to end up flying through the windscreen of our spaceship! Which is cool, but looks a bit strange. Second, as the program runs it piles up all of these stars behind us, which can slow everything down. We need to get rid of those. Finally, it would be pretty neat if we could have the stars ‘pop’ into view, so we’ll explore how we can do that.
Improvement One: Spaced Out Stars
So, firstly we want to ensure that stars aren’t placed too near to the center, where they could possibly fly through our windshield. Ideally we want to detect if we’re going to place a star within the narrow barrel our ship is flying through, and if so don’t place it there, place it somewhere else.
There are a few new tools we can use to accomplish this:
distance – takes two lists of coordinates, eg [x1 y1 z1] [x2 y2 z2], and returns the distance between them. This will be useful to us, because we are going to provide the prospective new spot position as one list, and {0 0 zpos} as the second list, giving us the distance between the new spot and the XY center of the space at Myrtle’s current Z position.
dountil – repeats a list of commands until it gets the desired results. In this case, we’re going to want to have dountil pick a random position, and then use the distance function to check if that position is outside of our no-go range. dountil executes the list of instructions it is provided before checking the condition it needs to stop executing, while its cousin until checks first.
So, to ensure we don’t place a star within that ‘barrel’, instead of the existing setposition command, we do the following:
dountil 100 < distance position {0 0 zpos} [ setposition {-1500 + random 3000 -500 + random 1000 zpos} ]
So, until 100 is less than the distance between the turtle’s position and the XY center of the turtle’s current Z position, keep choosing a new position — and choose a new position before doing the first check.
Note: because of the way Logo’s parser works, if you do a comparison operation (<, >, = etc) where one side is a single parameter (eg a number) you’re best to put that FIRST and the complex parameter second.
Why? Because Logo collects things up right to left, and while Logo will evaluate the stuff to the right of the operator correctly, passing that to the operator, the parser will give the operator the first ‘complete’ thing it sees to the left of it, which if you switched things around would be {0 0 zpos} and is not what we want!
So to solve this you would need to put round brackets () around distance position {0 0 zpos} to ensure that Logo evaluated and gave < what we really wanted to give it. It’s easier in this case just to put the single parameter on the left.
This solves our problem! Stars will keep out of our way. However, this method also moves the turtle every time we try a new position, and all of these false jumps will stay in the turtle’s ‘turtle track’ or list of things the turtle has done.
There are a few methods we could use to stop this from happening, but this one is probably the simplest:
dountil 100 > distance :position {0 0 zpos) [ make "position {-1500 + random 3000 -500 + random 1000 zpos} ] setposition :position
Rather than set the position every time we try, we make a container (variable) called position containing the prospective coordinates, and test that instead. Then, once we have a good set of coordinates, we set that :position using the setposition command.
A colon before a word indicates to the parser that it is meant to pass the contents of a container with that name to the next command or function in the chain.
The colon is shorthand for thing, which returns the value of the container named passed to it. So, for example, you could have used setposition thing “position instead. Note that the name of the variable is preceded by a quote, not a colon. If you used a colon, thing would return the value of the container with the name CONTAINED inside of the container you referenced with the colon!
The mind boggles, doesn’t it?
This is also why you generally make “container rather than make :container — if you did the second, you would make a container with the name contained inside of the container you referenced, which does have practical applications but can be a bit confusing at first.
For now, just remember you make with a quote, retrieve with a colon.
Okay, moving on…
Improvement Two: Galactic Janitorial
This procedure creates a lot of stars. Like, a lot. While you can have a lot of things in turtleSpaces (so many things!) they can start to gum up the works if they get to extreme numbers. A computer can only remember so much you know! And so, we should clean out the stars behind us, because they don’t matter to us anymore anyway.
But the stars are part of Myrtle’s ‘turtle track’, and so we can’t just clean out some of them, can we? There are the clean and clearscreen commands but they get rid of everything!
Never fear, tag is here!
tag allows you to wrap one or more turtle track entries so that you can reference them later, to replace, copy or delete them. We’re going to put tags around stars so we can delete them. We do this with begintag and endtag.
We need to give each tag a name, and so we need to give begintag a name to use. endtag doesn’t take a name, since we can only close the last opened tag. If you create a tag within a tag, that tag has to be closed first, before you can close the tag above it.
What we’re going to do is every 2000 stars, we’re going to close off the existing tag (the stars still in front of the camera turtle), then create a new tag for the next batch of stars, and erase the tag that came before the one that we just closed off (the stars now behind the camera turtle). It’s a real slight-of-hand magic trick!
To set up our tags, first we have to add the following before the forever loop:
begintag 0 endtag ;dummy 'first' tag (numbered 0) make "tag 1 ;create tag container begintag :tag ;create second tag using the tag ;container value
Because we’re erasing the tag before the previous tag, we need to create a dummy 0 tag to erase when we get started. Then we create a tag container, which contains the value (number) of the current tag, and then we create a tag with a name of that value (1).
We’re all ready to go! Now, inside of the forever loop, we need to add:
if divisorp 2000 loopcount [ ;every 2000 loops: endtag erasetag :tag - 1 ;erase the tag BEFORE the tag we just closed inc "tag ;increment the tag container begintag :tag ;start a new tag ]
if is sort of like dountil, except that it checks if the condition is true first and then executes the list of instructions provided to it, but it only does it once and only if the condition is true.
divisorp is a boolean, it returns only true or false. Because of booleans, unlike in other programming languages if does not require a comparison operator. Comparison operators (=, >, < etc) are themselves boolean operators — they return either true or false to if, until, dountil or whatever other command needs a boolean parameter passed to them.
divisorp returns true if the first value passed to it divides equally into the second value. divisorp in this case returns true if 2000 divides equally into loopcount, which is the total count of the number of loops executed — for example, the number of times we’ve been through the forever loop.
So every 2000 times through the forever loop, divisorp returns a true value and that causes if to execute the contents of the list. Which is:
endtag – end the current tag
erasetag :tag – 1 – erase the tag before the tag we just closed
inc “tag – increment the tag counter
begintag :tag – start a new tag
And that’s all there is to it. The janitor will come every so often and sweep out those old stars, keeping things running smoothly!
Improvement Three: View Screen On
I’m kind of torn on which is cooler, having the galaxy wink into existence around us or approaching it as if we’ve arrived from the intergalactic void, but if you want to try the winking-into-existence option, here’s how to do it:
First, before the forever loop, we put in a norender command. This stops turtleSpaces from updating the ‘render’, or representation of items in 3D space.
Second, inside the forever loop, before everything else, we need to put the following:
if loopcount = 1500 [ render print |Engage!| ]
which as you may remember from the previous section, after 1500 loops will cause if to execute the render command, which turns rendering back on. This ensures there are stars for us to see before we start to see them.
That’s all there is to it!
Bonus Improvement: Spinning Through Space
As a final bonus improvement, after the cam:forward command, insert the following:
cam:rollright 1/10
This will simulate the gentle roll of the spacecraft as it travels through the stars, causing the stars to spin slightly around the spacecraft.
Congratulations! You’ve graduated from starwarp academy! Good job. Welcome to turtleSpaces.