Join us on Discord!
You can help CodeWalrus stay online by donating here.

How to make a simple raycaster in TI-BASIC.

Started by _iPhoenix_, March 17, 2018, 06:54:14 PM

Previous topic - Next topic

0 Members and 1 Guest are viewing this topic.

_iPhoenix_

Some of you may remember my old TI-BASIC raycaster. It was made back when I wasn't that good at the language, and I think I broke it with some long-forgotten edit. I was reminded of this by a recent post on Cemetech by someone who was trying to understand my old code to try and make it work.

They weren't very successful. It was a crappy, unreadable script that completely ignored the points that make a good TI-BASIC program. In short, it was a prototype.

A few months ago, I happened to come back to this concept and completely rewrote my code for it. Here's my complete new code (import this with SC3):
StoreGDB 0
ZStandard
ZSquare
GridOff
AxesOff
LabelOff
BorderColor 4
BackgroundOff
{Black,DarkGray,Gray,MedGray,LtGray->L1
For(A,1,10
   For(B,1,10
      If [A](A,B
      Then
         For(C,1,5
            For(D,1,5
               Pxl-On(115+5A-C,5B-D,Black
            End
         End
      End
   End
End
122->A
5->B
DelVar DRepeat K=45
   For(theta,~3,3,.1
      Line(12+theta,3,12+theta,10,0
   End
   Circle(12,6,1.8,Black
   Line(10,6,14,6,1,Black,1
   Line(12,4,12,8,1,Black,1
   For(theta,~40+D,40+D
      If not(remainder(abs(theta-D),10
      Line(12+2.5cos(theta-2D),6+2.5sin(theta-2D),12+2cos(theta-2D),6+2sin(theta-2D),1,Red+(theta=D),1
      sin(theta->U
      cos(theta->V
      DelVar NRepeat Ans or N=10
         N+1->N
         int(A+NU->I
         int(B+NV->J
         pxl-Test(I,Ans
      End
      (theta-D)/5
      Line(Ans,5,Ans,~5,0
      If N!=10
      Line(Ans,(10-N)/2,Ans,~(10-N)/2,1,L1(1+min(int(N/2),dim(L1
   End
   If not(pxl-Test(A,B
   Pxl-On(A,B,Red
   getKey
   Repeat Ans
      getKey
   End
   Ans->K
   Pxl-Off(A,B
   D-5(K=11->D
   D+5(K=15->D
   D-2(K=12->D
   D+2(K=14->D
   int(A+(K=13)2sin(D)+(K=23)5sin(D->A
   int(B+(K=13)2cos(D)+(K=23)5cos(D->B
End
RecallGDB 0


In concept, this is essentially the same program, but I like it much better because it is more concise and readable. It also works. That's somewhat important.

To turn, you use the top 5 buttons on the keypad, namely [y=], [window], [zoom], [trace], and [graph]. To move forward, press [zoom]. To move forward faster, press [del].

This code is not the most optimized I could make it, for the sake of making a understandable tutorial. This tutorial/explanation assumes you understand at least a little trig and TI-BASIC.

Let's break this program down into manageable chunks, which I will explain one-by-one.

StoreGDB 0
ZStandard
ZSquare
GridOff
AxesOff
LabelOff
BorderColor 4
BackgroundOff
{Black,DarkGray,Gray,MedGray,LtGray->L1


This section is pretty much my standard "initialize the graphscreen" set of commands. I store the user's settings to GDB0 to retrieve them when the program is over.

The line storing colors of increasing brightness to L1 is important. It will be explained thoroughly later in this post.

For(A,1,10
   For(B,1,10
      If [A](A,B
      Then
         For(C,1,5
            For(D,1,5
               Pxl-On(115+5A-C,5B-D,Black
            End
         End
      End
   End
End


This code loops through matrix A, drawing a 5x5 "pixel" on the minimap in the bottom left corner of the screen if the value is truthy (i.e. not 0). We use this minimap to do the raycasting.

122->A
5->B
DelVar D


The variables A and B store the row and column (because we are using pixels, they are not the X and Y coordinates) of the player. The D variable stores our rotation. Because DelVar effectively sets it to 0 and 0° is directly left, the player starts out facing left.

Repeat K=45
   For(theta,~3,3,.1
      Line(12+theta,3,12+theta,10,0
   End


Here, we start the main loop. We clear the area where the compass will be redrawn each frame.

   Circle(12,6,1.8,Black
   Line(10,6,14,6,1,Black,1
   Line(12,4,12,8,1,Black,1


This draws a nice little compass in the top right corner of the screen, so that the player knows which direction he or she is facing.

   For(theta,~40+D,40+D
      If not(remainder(abs(theta-D),10
      Line(12+2.5cos(theta-2D),6+2.5sin(theta-2D),12+2cos(theta-2D),6+2sin(theta-2D),1,Red+(theta=D),1
      sin(theta->U
      cos(theta->V


Here is the start of the meat of the program.

We sweep through an 80° arc, centered around the player's rotation value.

The [mono]If not(remainder(abs(theta-D),10[/mono] part and the line following it draw markers on the compass to show the progress. The If statement makes sure that we only draw lines at 10-degree intervals.

As an extra layer of optimization, we store the sine and cosine values of our angle to U and V, respectively, so that we don't have to recalculate them in the next step.

      DelVar NRepeat Ans or N=10
         N+1->N
         int(A+NU->I
         int(B+NV->J
         pxl-Test(I,Ans
      End


This is where the actual raycasting happens.

I reset the variable N to 0. It stores the length of the ray.
We basically keep increasing the radius of an imaginary circle (centered on our player) by 1, calculating the point on this circle at our angle, θ, and checking if there is a pixel drawn there. To save time, if the circle has a radius equal to 10 and we still haven't hit anything, we stop calculating the ray. The value of 10 is essentially our "maximum render distance".

A raycaster basically casts a ray from the player's position at each angle in the player's FOV, measuring the distance the ray travels until it hits a wall.

      (theta-D)/5
      Line(Ans,5,Ans,~5,0
      If N!=10
      Line(Ans,(10-N)/2,Ans,~(10-N)/2,1,L1(1+min(int(N/2),dim(L1
   End


This is most of our drawing code, and also the ending of our drawing loop, where we were iterating through each of the angles.

We clear out anything on our "canvas" where the line is going to go. If we didn't prevent the ray from continuing to propagate, we draw a line on our graph. The X coordinate is reliant on the angle, while the vertical length of the line and the color of the line is solely influenced by the length of the ray. If the ray took longer to hit a wall, the line should be shorter and lighter in color, because it is further away.

   If not(pxl-Test(A,B
   Pxl-On(A,B,Red


You might have noticed that we never actually drew the player in on the minimap yet. This is because some rays would hit the player and register as there being a wall right where the player is, even if there isn't one. If we didn't stop the ray from continuing to propagate, we can safely draw this in now. I first do a check to make sure that we are not inside of a wall. I should probably add player collision checking, but it's a trivial thing in this scenario.

   getKey
   Repeat Ans
      getKey
   End
   Ans->K


This section of code heads our user input.

The first getKey prevents any keys that were pressed during the drawing from being registered in our movement-handling code. I don't fully understand how or why this happens, but it does.

We then wait for the user to press a key, and store the key code they pressed to K.

   Pxl-Off(A,B
   D-5(K=11->D
   D+5(K=15->D
   D-2(K=12->D
   D+2(K=14->D
   int(A+(K=13)2sin(D)+(K=23)5sin(D->A
   int(B+(K=13)2cos(D)+(K=23)5cos(D->B


Next, we turn off the pixel on the minimap representing the player. I have explained why we need to do this earlier in the post. The next section, which I intentionally left unoptimized, adjusts the rotation of the player. If the user presses the [y=] button, we assume they want to rotate more than if they pressed the [window] button. After this section of code, we move the player.

If the player pressed the [zoom] button, we move them forward by (approx.) 2 pixels. Because the rendering process is so slow, though, we also give them the option to move forward by 5 pixels by pressing [del].

End
RecallGDB 0


This last little bit of code closes up our main loop. When the user presses [clear], the loop stops (because of the "K=45" at the beginning), and we recall the user's graph settings back.

If you enjoyed reading this, it helped you, or you need help understanding something, please do not hesitate to reply below. If you do not have an account yet, they are free and only require a few minutes of your time to create.
  • Calculators owned: Two TI-84+ CE's
Please spam here: https://legend-of-iphoenix.github.io/spam/

"walruses are better than tuxedo chickens, all hail the great :walrii:" ~ me
Evolution of my avatar:

Powered by EzPortal