Home
JAQForum Ver 24.01
Log In or Join  
Active Topics
Local Time 07:35 06 Apr 2026 Privacy Policy
Jump to

Notice. New forum software under development. It's going to miss a few functions and look a bit ugly for a while, but I'm working on it full time now as the old forum was too unstable. Couple days, all good. If you notice any issues, please contact me.

Forum Index : Microcontroller and PC projects : PicoMiteVGA: Framework for ray casting using the DDA method

     Page 2 of 6    
Author Message
Volhout
Guru

Joined: 05/03/2018
Location: Netherlands
Posts: 5823
Posted: 11:30am 10 Feb 2026
Copy link to clipboard 
Print this post

Hi Mick,

Both you and Tom are right. If I look at (but .. who am I ?) my personal application domain, I would
- on RP2040 VGA have loved to trade RAY with STRUCTURES.
- on RP2040 microcontroller domain have loved to trade STEPPER for STRUCTURES.
- on any RP2040 would have dismissed TURTLE.

But that is me. I do not see the value in structures. But I know from the enthusiastic communication at the introduction of structures, that many do love it.
And TURTLE has it's own audience (my grand children are to grown up, but they may have loved it once). Maybe for my grand-grand children in 10 years.

Anyway. It is what it is.

Volhout
PicomiteVGA PETSCII ROBOTS
 
mozzie
Senior Member

Joined: 15/06/2020
Location: Australia
Posts: 251
Posted: 01:34pm 10 Feb 2026
Copy link to clipboard 
Print this post

G'day,
It probably shows the strength of the whole MMbasic / Picomite platform that with so many different users and use cases, it appears to satisfy 99% of them 99% of the time.
This is a huge ask from any system.

I too look at a MicroMite / PicoMite as an embedded system, but am amazed at what can be done with some of the extended functions we now have access to with HDMI / VGA graphics and so many LCD displays.

On top of this we have an enormous amount of device support without resorting to libraries etc.

Whilst my own abilities as a programmer are pretty poor, trying out the new commands / functions / systems as they are added and reading the posts of how others are using them has taught me a lot, and inspired many ideas, whether I will ever use some of it is another matter.

Also only my 5c

Regards,
Lyle.
 
Martin H.

Guru

Joined: 04/06/2022
Location: Germany
Posts: 1441
Posted: 02:38pm 10 Feb 2026
Copy link to clipboard 
Print this post

The freely scalable sprites certainly have their appeal.
They  would also be useful in other applications (such as Leon's Outrun port etc.)
ym2c
Martin
'no comment
 
gadgetjack
Senior Member

Joined: 15/07/2016
Location: United States
Posts: 206
Posted: 04:53pm 10 Feb 2026
Copy link to clipboard 
Print this post

Just putting in my 2 cents worth. I understand all the ones using this for control projects , I did that for years till I retired. But the graphics capabilities of this little machine have caught my eye from the beginning. I have followed now for a few years after finding this site and really enjoy the games and demo programs so many capable people on here write. I tip my hat to all of you. I really would like to see the RAY functions added , they would not eat that much memory up. Most designs have SD cards anyway for saving your code to. I vote for the RAY functions.
Jack
 
bfwolf
Senior Member

Joined: 03/01/2025
Location: Germany
Posts: 208
Posted: 09:20pm 10 Feb 2026
Copy link to clipboard 
Print this post

The discussion here about whether "ray casting" should be done via built-in commands, CSUBs, or directly in MMBasic, has once again shown me how beneficial it would be if MMBasic supported DLLs and their subs and functions. This would allow for the elegant and efficient implementation of highly specialized tasks suitable only for a selected user group and very specific use cases. And the MMBasic core wouldn't grow every time.

bfwolf.
 
matherp
Guru

Joined: 11/12/2012
Location: United Kingdom
Posts: 11142
Posted: 10:23pm 10 Feb 2026
Copy link to clipboard 
Print this post

  Quote  has once again shown me how beneficial it would be if MMBasic supported DLLs and their subs and functions.


Can't be done - won't be done
 
homa

Guru

Joined: 05/11/2021
Location: Germany
Posts: 555
Posted: 03:12pm 14 Feb 2026
Copy link to clipboard 
Print this post

@Martin H. that's impressive.  

@LeoNicolas thank you for the great explanatory video. I only knew the solution with the individual steps.

@matherp Peter, is it possible to get the graphics for your example? I can't find them anywhere. I'm currently testing the version PicoMiteHDMI MMBasic USB RP2350A Edition V6.02.01b2 - i it

Matthias
Edited 2026-02-15 01:14 by homa
 
Martin H.

Guru

Joined: 04/06/2022
Location: Germany
Posts: 1441
Posted: 05:52pm 14 Feb 2026
Copy link to clipboard 
Print this post

I have optimised this a little to keep the calculations within the DDA loop as low as possible.
' Raycaster 2 in MMBasic, DDA method (Digital Differential Analyser)
'V0.6
mmb4w=0
CLS
If mmb4w Then
   MODE -7 :CLS RGB(cyan)
PAGE WRITE 1
Else
   MODE 2: CLS RGB(cyan)
   FRAMEBUFFER create:FRAMEBUFFER write f
End If
CLS
mapS=64:mapy=24:mapx=24
' --- Labyrinth Definition ---
Dim integer mapW = 57,mapH = 51
Dim integer m(mapW, mapH),Col%(3,2),cll
Col%(1,0)=RGB(green):Col%(1,1)=RGB(0,170,0)
Col%(2,0)=RGB(0,170,255):Col%(2,1)=RGB(0,85,255)
Col%(3,0)=RGB(255,170,0):Col%(3,1)=RGB(255,85,0)
Restore MapData1
For y = 0 To mapH-1 :Read k$:k$=k$+"1"
For x=0 To Len(k$):m(x, y)=Val(Mid$(k$,x+1,1)):Next :Next
' --- Player Setup ---
px = 26.5 : py = 45.5
dx = 1 : dy = 0.5
planeX = 0 : planeY = 0.66
moveSpeed = 0.3
rotSpeed = 0.1
buffer = 0.2
' --- Grafic Setup ---
resStep = 3
scrW = MM.HRES*.75
scrH = MM.VRES*.75
' Flag to force the first frame
needsRedraw = 1
invScrW = 2 / scrW
halfScrH = scrH / 2
colWallA = RGB(green)
colWallB = RGB(0,128,0)
colSky = RGB(0,128,255)
colFloor = RGB(0,85,0)
Do
 ' Only paint when something has changed
 If needsRedraw = 1 Then
 tt=Timer
   ' Ceiling and floor
   Box 0, 0, scrW, halfScrH, 0, colSky, colSky
   Box 0, halfScrH, scrW, halfScrH, 0, colFloor, colFloor
   'DDA
   For x = 0 To scrW - 1 Step resStep
     cameraX = x * invScrW - 1
     rayDx = dx + planeX * cameraX
     rayDy = dy + planeY * cameraX
     mx = Int(px) : my = Int(py)
       If rayDx = 0 Then
       dDx = 1e30
       Else
       invRayDx = 1 / rayDx: dDx = Abs(invRayDx)
       End If
       If rayDy = 0 Then
         dDy = 1e30
         Else
         invRayDy = 1 / rayDy :dDy = Abs(invRayDy)
         End If  
     If rayDx < 0 Then
       stepX = -1 : sdX = (px - mx) * dDx
     Else
       stepX = 1 : sdX = (mx + 1 - px) * dDx
     End If
     If rayDy < 0 Then
       stepY = -1 : sdY = (py - my) * dDy
     Else
       stepY = 1 : sdY = (my + 1 - py) * dDy
     End If
     'cast one Ray
     hit = 0
     Do While hit = 0
       If sdX < sdY Then
         Inc sdX,dDx:Inc mx,stepX : side = 0
       Else
         Inc sdY,dDy:Inc my,stepY : side = 1
       End If
       If m(mx, my) Then hit = 1
     Loop
     cll=m(mx, my)
     If side = 0 Then pDist = (sdX - dDx) Else pDist = (sdY - dDy)
     'don't go through the wall
     If pDist < 0.1 Then pDist = 0.1
     lH = Int(scrH / pDist)
     yS = halfScrH - (lH/2)
    wallCol=col%(cll,side)
    Box x, yS, resStep, lH, , wallCol,wallCol
   Next x
   If mmb4w Then
   PAGE WRITE 0:Blit 0,0,0,0,scrW,scrH,1
   Text scrW,0,"X="+Str$(Int(px))+" "
   Text scrW,12,"Y="+Str$(Int(py))+" "
   Text scrW,24,Str$(Timer-tt)+"   ":tt=Timer
   PAGE WRITE 1
   Else
   FRAMEBUFFER write n
   Text scrW,0,"X="+Str$(Int(px))+" "
   Text scrW,12,"Y="+Str$(Int(py))+" "
   Text scrW,24,Str$(Timer-tt)+"   ":tt=Timer
   FRAMEBUFFER write f
   Blit framebuffer F,N,0,0,0,0,scrW,scrH
   End If
   needsRedraw = 0 ' Reset Redraw Flag
 End If
 ' Wait for button (does not block, but checks efficiently)
 k$ = UCase$(Inkey$)
 Do :Loop Until Inkey$=""
   If k$ <> "" Then
   ' Enable redraw when a movement key is pressed
   If Instr("WASD", k$) > 0 Then needsRedraw = 1
   Select Case k$
       Case "W"
         If m(Int(px + dx * buffer), Int(py)) = 0 Then px = px + dx * moveSpeed
         If m(Int(px), Int(py + dy * buffer)) = 0 Then py = py + dy * moveSpeed
       Case "S"
         If m(Int(px - dx * buffer), Int(py)) = 0 Then px = px - dx * moveSpeed
         If m(Int(px), Int(py - dy * buffer)) = 0 Then py = py - dy * moveSpeed
       Case "D"
         oldDx = dx : dx = dx * Cos(rotSpeed) - dy * Sin(rotSpeed)
         dy = oldDx * Sin(rotSpeed) + dy * Cos(rotSpeed)
         oldPx = planeX : planeX = planeX * Cos(rotSpeed) - planeY * Sin(rotSpeed)
         planeY = oldPx * Sin(rotSpeed) + planeY * Cos(rotSpeed)
       Case "A"
         oldDx = dx : dx = dx * Cos(-rotSpeed) - dy * Sin(-rotSpeed)
         dy = oldDx * Sin(-rotSpeed) + dy * Cos(-rotSpeed)
         oldPx = planeX : planeX = planeX * Cos(-rotSpeed) - planeY * Sin(-rotSpeed)
         planeY = oldPx * Sin(-rotSpeed) + planeY * Cos(-rotSpeed)
   End Select
 End If
Loop Until k$ = Chr$(27)
If mmb4w Then PAGE WRITE 0
MapData1: '
Data "11111111111111111111111133333333333331111111111111111111"
Data "11111111111111111111111133333333333331111111111111111111"
Data "11111111111111111111111130000000000333331111111111111111"
Data "11111000011111111111111130000000000333331111111111111111"
Data "11111000011111110000000000000000000300331111111111111111"
Data "11111000011111110000000030000000000300331111111111111111"
Data "11111110111111110011111130000000000333331111111111111111"
Data "11111000000001110011111130000000000333331111111111111111"
Data "11111000000001110011111133333003333331111111111111111111"
Data "11111000000001000000111133333003333331111111111111111111"
Data "11111000000000000000111111333003333331111111111111111111"
Data "11111000000001000000111111333003311111111111111111111111"
Data "11111000000001111111111333333003333111111111111111111111"
Data "10011111001111111111111333333003333111111111111111111111"
Data "10011111001111111111111300000000333111111111111111111111"
Data "11011111001111111111111300333003333111111111111111111111"
Data "10011111001111111111111300333003333111111111111111111111"
Data "10000000001111111111111300333003333111111111111111111111"
Data "10010000001111111111111333333003311111111111111111111111"
Data "10011111111111111111111111113003111111111111122222222222"
Data "10011000000001111111111111110000111111111111122222222222"
Data "10011000000001111111111100000000000011222222222000000022"
Data "10011000000001111111111000000000000002222222222000000022"
Data "10001000000001111111111000000000000000000000002000000022"
Data "10000000000001111111111000000000000000000000000000000022"
Data "10001000000001111111111000000000000002000000222000000022"
Data "10011000000001111111111000000000000001122002222000000022"
Data "10011000000001111111111111110000111111122002222000000022"
Data "10011111011111111111111111112202211111122002222222222222"
Data "10011111001111111111111111122002211111122002222222222222"
Data "10011111001111111111111111122002211111122002222222222222"
Data "10010001001111111111111111122002211111122002222222222211"
Data "10000001001111111111111111122002211111122002222020022211"
Data "10011111001111111111111111122002211111122000000000000211"
Data "10011111001111111111111111122002211111122000000000002211"
Data "10000000000000010000011111122002211111122022002020220211"
Data "10000000000000000000000111122002211111122222222222222211"
Data "10000000000000010000011111122002211111111111111111111111"
Data "11111111111111110111111122222002222221111111111111111111"
Data "11111111110010000111111222222202222221111111111111111111"
Data "11111111110000011111111200000000000021111111111111111111"
Data "11111111111011111111111200002002000021111111111111111111"
Data "11111111111001111111111200002002000021111111111111111111"
Data "11111111111001111111111222222002222221111111111111111111"
Data "11111111111111111111111200002002000021111111111111111111"
Data "11111111111111111111111200000000000021111111111111111111"
Data "11111111111111111111111200002002000021111111111111111111"
Data "11111111111111111111111222222002222221111111111111111111"
Data "11111111111111111111111200000000000021111111111111111111"
Data "11111111111111111111111200000000000021111111111111111111"
Data "11111111111111111111111222222222222221111111111111111111"


I have revised the map again. Now you get different values when you hit a wall. Here, I have planned to colour different areas differently (as in Wolf3d), which should help with orientation. The value found is stored in 'cll' but not yet evaluated.
Have Fun
Cheers
Martin
Edited 2026-02-16 22:06 by Martin H.
'no comment
 
Martin H.

Guru

Joined: 04/06/2022
Location: Germany
Posts: 1441
Posted: 12:08pm 16 Feb 2026
Copy link to clipboard 
Print this post

Addendum: I have adjusted the above listing. Different colours for areas are now taken into account by the programme.

'no comment
 
Volhout
Guru

Joined: 05/03/2018
Location: Netherlands
Posts: 5823
Posted: 01:25pm 16 Feb 2026
Copy link to clipboard 
Print this post

Hi Martin,

You may need a small fix. The use of floor and ceiling color is essential for the program to run. If I change them to rgb(black) and rgb(white) I cannot navigate anymore.

Volhout
PicomiteVGA PETSCII ROBOTS
 
Martin H.

Guru

Joined: 04/06/2022
Location: Germany
Posts: 1441
Posted: 01:45pm 16 Feb 2026
Copy link to clipboard 
Print this post

  Volhout said  Hi Martin,

You may need a small fix. The use of floor and ceiling color is essential for the program to run. If I change them to rgb(black) and rgb(white) I cannot navigate anymore.

Volhout

 Well, I don't understand that, because
colSky = RGB(White)
colFloor = RGB(Black)

only serve to give the two BOX commands a colour.
   
Box 0, 0, scrW, halfScrH, 0, colSky, colSky
   Box 0, halfScrH, scrW, halfScrH, 0, colFloor, colFloor
to draw the Background.
They are not taken into account further and have no influence on the calculation of the walls. They are purely visual.
What I can imagine is that it helps to clear the keyboard buffer after the query. So that you don't accidentally run into a wall. (It shouldn't happen, but it makes the controls more direct.)
k$ = UCase$(Inkey$)
Do :Loop Until Inkey$=""



Edited 2026-02-17 00:03 by Martin H.
'no comment
 
Volhout
Guru

Joined: 05/03/2018
Location: Netherlands
Posts: 5823
Posted: 02:12pm 16 Feb 2026
Copy link to clipboard 
Print this post

Yeah,

works now.

Volhout
PicomiteVGA PETSCII ROBOTS
 
dddns
Guru

Joined: 20/09/2024
Location: Germany
Posts: 816
Posted: 08:52pm 16 Feb 2026
Copy link to clipboard 
Print this post

This is really nice, many thanks for sharing!
I've tested it with an ILI9341 LCD and it runs fast and smooth.

Regards
 
Martin H.

Guru

Joined: 04/06/2022
Location: Germany
Posts: 1441
Posted: 10:38am 19 Feb 2026
Copy link to clipboard 
Print this post

@dddns Thanks for the feedback. I don't have an LCD Pico myself at the moment, but it's good to hear that it works there too.

Here I have further optimised the routines and now pre-calculate the angles in lookup tables.
On an RP2040 @225, image rendering now takes 40-85 ms.
I don't see any further optimisation possibilities at this time.
If you can think of any further optimisation possibilities, please let me know.

' Raycaster 2 in MMBasic, DDA method (Digital Differential Analyser)
'V0.8.. added Lookuptables
mmb4w=0
CLS
If mmb4w Then
   MODE -7 :CLS RGB(cyan)
PAGE WRITE 1
Else
   MODE 2: CLS RGB(cyan)
   FRAMEBUFFER create:FRAMEBUFFER write f
End If
CLS
'sin / cos Look Up Table
Const LUTSIZE=360,MULT=128
Dim integer sinLUT(LUTSIZE), cosLUT(LUTSIZE)
Dim integer angleIndex = 0 ' direction of view
Dim integer rotStep = 5
For i = 0 To LUTSIZE - 1
   angle = i * (2 * Pi / LUTSIZE)
   sinLUT(i) = Int(Sin(angle)*MULT)
   cosLUT(i) = Int(Cos(angle)*MULT)
Next
mapS=64:mapy=24:mapx=24
' --- Labyrinth Definition ---
Dim integer mapW = 57,mapH = 51,lH,scrW,scrH
Dim integer m(mapW, mapH),Col(3,2),lw,hit
Col(1,0)=RGB(green):Col(1,1)=RGB(0,170,0)
Col(2,0)=RGB(0,170,255):Col(2,1)=RGB(0,85,255)
Col(3,0)=RGB(255,170,0):Col(3,1)=RGB(255,85,0)
Restore MapData1
For y = 0 To mapH-1 :Read k$:k$=k$+"1"
For x=0 To Len(k$):m(x, y)=Val(Mid$(k$,x+1,1)):Next :Next
' --- Player Setup ---
px = 26.5 : py = 45.5
dx = 1 : dy = 0.5
planeX = 0 : planeY = 0.66
moveSpeed = 0.3
rotSpeed = 0.1
buffer = 0.2
' --- Grafic Setup ---
resStep = 3
scrW = MM.HRES*.75
scrH = MM.VRES*.75
' Flag to force the first frame
needsRedraw = 1
invScrW = 2 / scrW
halfScrH = scrH / 2
colSky = RGB(255,255,255)
colFloor = RGB(0,85,0)
lw=Int((resStep+1)/2)
Do
 ' Only paint when something has changed
 dx = cosLUT(angleIndex)/MULT
 dy = sinLUT(angleIndex)/MULT
 planeX = -dy * 0.66
 planeY =  dx * 0.66
 If needsRedraw Then
 tt=Timer
   ' Ceiling and floor
   Box 0, 0, scrW, halfScrH, , colSky, colSky
   Box 0, halfScrH, scrW, halfScrH, , colFloor, colFloor
   'DDA
   For x = 0 To scrW - 1 Step resStep
     cameraX = x * invScrW - 1
     rayDx = dx + planeX * cameraX
     rayDy = dy + planeY * cameraX
     mx = Int(px) : my = Int(py)
   If Not rayDx  Then dDx = 1000 Else invRayDx = 1 / rayDx: dDx = Abs(invRayDx)
   If Not rayDy  Then dDy = 1000 Else invRayDy = 1 / rayDy :dDy = Abs(invRayDy)
   If rayDx < 0 Then
     stepX=-1:sdX=(px - mx)*dDx
   Else
     stepX=1:sdX=(mx+1-px)*dDx
     End If
   If rayDy < 0 Then
       stepY = -1 : sdY = (py - my) * dDy
   Else
       stepY = 1 : sdY = (my + 1 - py) * dDy
   End If
     'cast one Ray
     hit = 0
     Do While Not hit
       If sdX < sdY Then
         Inc sdX,dDx:Inc mx,stepX : side = 0
       Else
         Inc sdY,dDy:Inc my,stepY : side = 1
       End If
       hit=m(mx, my)
     Loop
     If side = 0 Then pDist = sdX - dDx Else pDist = sdY - dDy
     'don't go through the wall
     If pDist < 0.1 Then pDist = 0.1
     lH = Int(scrH / pDist)
     yS = halfScrH - (lH>>1)
     Box x, yS, resStep, lH, LW, Col(hit,side)
   Next x
   If mmb4w Then
   PAGE WRITE 0:Blit 0,0,0,0,scrW,scrH,1
   Text scrW,0,"X="+Str$(Int(px))+" "
   Text scrW,12,"Y="+Str$(Int(py))+" "
   Text scrW,24,Str$(Timer-tt)+"   ":tt=Timer
   PAGE WRITE 1
   Else
   FRAMEBUFFER write n
   Text scrW,0,"X="+Str$(Int(px))+" "
   Text scrW,12,"Y="+Str$(Int(py))+" "
   Text scrW,24,Str$(Timer-tt)+"   ":tt=Timer
   FRAMEBUFFER write f
   Blit framebuffer F,N,0,0,0,0,scrW,scrH
   End If
   needsRedraw = 0 ' Reset Redraw Flag
 End If
 ' Wait for button (does not block, but checks efficiently)
 k$ = UCase$(Inkey$)
'  Do :Loop Until Inkey$=""
   If k$ <> "" Then
   ' Enable redraw when a movement key is pressed
   If Instr("WASD", k$) Then needsRedraw = 1
   Select Case k$
       Case "W"
         If Not (m(Int(px + dx * moveSpeed), Int(py))) Then px = px + dx * moveSpeed
         If Not (m(Int(px), Int(py + dy * moveSpeed))) Then py = py + dy * moveSpeed
       Case "S"
         If Not (m(Int(px - dx * moveSpeed), Int(py))) Then px = px - dx * moveSpeed
         If Not (m(Int(px), Int(py - dy * moveSpeed))) Then py = py - dy * moveSpeed
      Case "D"   ' turn right
         angleIndex = (angleIndex + rotStep) Mod LUTSIZE
      Case "A"   ' turn left
         angleIndex = (angleIndex - rotStep + LUTSIZE) Mod LUTSIZE
   End Select
 End If
Loop Until k$ = Chr$(27)
If mmb4w Then PAGE WRITE 0
MapData1: '
Data "11111111111111111111111133333333333331111111111111111111"
Data "11111111111111111111111133333333333331111111111111111111"
Data "11111111111111111111111130000000000333331111111111111111"
Data "11111000011111111111111130000000000333331111111111111111"
Data "11111000011111110000000000000000000300331111111111111111"
Data "11111000011111110000000030000000000300331111111111111111"
Data "11111110111111110011111130000000000333331111111111111111"
Data "11111000000001110011111130000000000333331111111111111111"
Data "11111000000001110011111133333003333331111111111111111111"
Data "11111000000001000000111133333003333331111111111111111111"
Data "11111000000000000000111111333003333331111111111111111111"
Data "11111000000001000000111111333003311111111111111111111111"
Data "11111000000001111111111333333003333111111111111111111111"
Data "10011111001111111111111333333003333111111111111111111111"
Data "10011111001111111111111300000000333111111111111111111111"
Data "11011111001111111111111300333003333111111111111111111111"
Data "10011111001111111111111300333003333111111111111111111111"
Data "10000000001111111111111300333003333111111111111111111111"
Data "10010000001111111111111333333003311111111111111111111111"
Data "10011111111111111111111111113003111111111111122222222222"
Data "10011000000001111111111111110000111111111111122222222222"
Data "10011000000001111111111100000000000011222222222000000022"
Data "10011000000001111111111000000000000002222222222000000022"
Data "10001000000001111111111000000000000000000000002000000022"
Data "10000000000001111111111000000000000000000000000000000022"
Data "10001000000001111111111000000000000002000000222000000022"
Data "10011000000001111111111000000000000001122002222000000022"
Data "10011000000001111111111111110000111111122002222000000022"
Data "10011111011111111111111111112202211111122002222222222222"
Data "10011111001111111111111111122002211111122002222222222222"
Data "10011111001111111111111111122002211111122002222222222222"
Data "10010001001111111111111111122002211111122002222222222211"
Data "10000001001111111111111111122002211111122002222020022211"
Data "10011111001111111111111111122002211111122000000000000211"
Data "10011111001111111111111111122002211111122000000000002211"
Data "10000000000000010000011111122002211111122022002020220211"
Data "10000000000000000000000111122002211111122222222222222211"
Data "10000000000000010000011111122002211111111111111111111111"
Data "11111111111111110111111122222002222221111111111111111111"
Data "11111111110010000111111222222202222221111111111111111111"
Data "11111111110000011111111200000000000021111111111111111111"
Data "11111111111011111111111200002002000021111111111111111111"
Data "11111111111001111111111200002002000021111111111111111111"
Data "11111111111001111111111222222002222221111111111111111111"
Data "11111111111111111111111200002002000021111111111111111111"
Data "11111111111111111111111200000000000021111111111111111111"
Data "11111111111111111111111200002002000021111111111111111111"
Data "11111111111111111111111222222002222221111111111111111111"
Data "11111111111111111111111200000000000021111111111111111111"
Data "11111111111111111111111200000000000021111111111111111111"
Data "11111111111111111111111222222222222221111111111111111111"

The next thing I will try is to replace the box commands with Blit to save a few ms.
Additionally, it would be possible to draw the depth darker using dithering.
Cheers
Martin
Edited 2026-02-19 20:42 by Martin H.
'no comment
 
Volhout
Guru

Joined: 05/03/2018
Location: Netherlands
Posts: 5823
Posted: 10:53am 19 Feb 2026
Copy link to clipboard 
Print this post

Hi Martin,

I am surprized that using a lookup table for sine and cosine is actually faster than calculating the sine and the cosine. This should be so fast, especially in 2350.

Volhout
PicomiteVGA PETSCII ROBOTS
 
Martin H.

Guru

Joined: 04/06/2022
Location: Germany
Posts: 1441
Posted: 12:13pm 19 Feb 2026
Copy link to clipboard 
Print this post

  Volhout said  I am surprized that using a lookup table for sine and cosine is actually faster than calculating the sine and the cosine. This should be so fast, especially in 2350.

Volhout
Well, I've tweaked a lot of things to remove "expensive" calculations from the cast-loop. The lookup tables are just one of them, although they are only calculated once per frame.
Cheers
Martin
Edited 2026-02-19 22:14 by Martin H.
'no comment
 
Martin H.

Guru

Joined: 04/06/2022
Location: Germany
Posts: 1441
Posted: 04:36pm 22 Feb 2026
Copy link to clipboard 
Print this post

Because it's Sunday. Version 0.86, some calculations have been sped up again, and it is now possible with the dither%-switch to darken the walls depending on
'the distance (selectable dithering).
Everyone is free to use and improve the routines.

' Raycaster 2 in MMBasic, DDA method (Digital Differential Analyser)
'V0.86.. added Lookuptables,changed to Blit vs Box,compressed math
'added possible dithering
'By Martin H. for https://www.thebackshed.com
'The software is subject to the terms of the GNU General Public Licence (GPL)
'and may be freely used, copied and updated.
'Individuals are encouraged to freely use and modify the software.

dim integer dither%=1,mmb4w=0
CLS
If mmb4w Then
   MODE -7 :CLS RGB(cyan)
PAGE WRITE 1
Else
   MODE 2: CLS RGB(cyan)
   FRAMEBUFFER create:FRAMEBUFFER write f
End If
CLS
'sin / cos LookUpTable
Const LUTSIZE=360,MULT=128
Dim integer sinLUT(LUTSIZE), cosLUT(LUTSIZE)
Dim integer angleIndex = 0 ' direction of view
Dim integer rotStep = 5,lh,ys
For i = 0 To LUTSIZE - 1
   angle = i * (2 * Pi / LUTSIZE)
   sinLUT(i) = Int(Sin(angle)*MULT)
   cosLUT(i) = Int(Cos(angle)*MULT)
Next
' --- Color scheme Definition ---
Dim integer Col(3,2)
Col(1,0)=RGB(green):Col(1,1)=RGB(0,170,0)
Col(2,0)=RGB(0,170,255):Col(2,1)=RGB(0,85,255)
Col(3,0)=RGB(255,170,0):Col(3,1)=RGB(255,85,0)
colSky = RGB(0,0,0)
colFloor = RGB(0,85,0)
' --- Labyrinth Definition ---
mapS=64:mapy=24:mapx=24
Dim integer mapW = 57,mapH = 51,scrW,scrH
Dim integer m(mapW, mapH),lw,mx,my',hit
Restore MapData1
For y = 0 To mapH-1 :Read k$:k$=k$+"1"
For x=0 To Len(k$):m(x, y)=Val(Mid$(k$,x+1,1)):Next :Next
' --- Player Setup ---
px = 26.5 : py = 45.5
dx = 1 : dy = 0.5
planeX = 0 : planeY = 0.66
moveSpeed = 0.3
rotSpeed = 0.1
buffer = 0.2
' --- Grafic Setup ---
resStep = 2
scrW = MM.HRES-96
scrH = MM.VRES*.75
precalc_shading
needsRedraw = 1 ' Flag to force the first frame
invScrW = 2 / scrW
halfScrH = scrH / 2
lw=Int((resStep+1)/2)
Do
 ' Only paint when something has changed
 If needsRedraw Then
 dx = cosLUT(angleIndex)/MULT
 dy = sinLUT(angleIndex)/MULT
 planeX = -dy * 0.66
 planeY =  dx * 0.66
 tt=Timer
   ' Ceiling and floor
   Box 0, 0, scrW, halfScrH, , colSky, colSky
   Box 0, halfScrH, scrW, halfScrH, , colFloor, colFloor
   'DDAA cast field of view
   For x = 0 To scrW - 1 Step resStep
     cameraX = x * invScrW - 1
     rayDx = dx + planeX * cameraX
     rayDy = dy + planeY * cameraX
     mx = Int(px) : my = Int(py)
   If Not rayDx  Then dDx = 1000 Else dDx = Abs(1 / rayDx)
   If Not rayDy  Then dDy = 1000 Else dDy = Abs( 1 / rayDy) 
   If rayDx < 0 Then
     stepX=-1:sdX=(px - mx)*dDx
   Else
     stepX=1:sdX=(mx+1-px)*dDx
   End If
   If rayDy < 0 Then
       stepY = -1: sdY = (py - my) * dDy
   Else
       stepY = 1 : sdY = (my + 1 - py) * dDy
   End If
     'cast one Ray
     Do While Not m(mx,my)
      If sdX < sdY Then
         Inc sdX,dDx:Inc mx,stepX : side = 0
       Else
         Inc sdY,dDy:Inc my,stepY : side = 1
       End If
    Loop
     pDist=Max(0.1,(sdX - dDx)*Not(side)+(sdY-dDy)*side)
     lH = scrH / pDist
     shade = min (int(pDist>>3)*24,72)*dither%  
Blit shade+scrw-8+8*m(mx,my)+4*side,0,x,Max(0,halfScrH-(lH>>1)),resStep,Min(lH,scrH)
   Next x
   If mmb4w Then
   PAGE WRITE 0:Blit 0,0,0,0,scrW,scrH,1
   Text scrW,0,"X="+Str$(Int(px))+" "
   Text scrW,12,"Y="+Str$(Int(py))+" "
   Text scrW,24,Str$(Timer-tt)+"   ":tt=Timer
   PAGE WRITE 1
   Else
   FRAMEBUFFER write n
   Text scrW,0,"X="+Str$(Int(px))+" "
   Text scrW,12,"Y="+Str$(Int(py))+" "
   Text scrW,24,Str$(Timer-tt)+"   ":tt=Timer
   FRAMEBUFFER write f
   Blit framebuffer F,N,0,0,0,0,scrW,scrH
   End If
   needsRedraw = 0 ' Reset Redraw Flag
 End If
 ' Wait for button (does not block, but checks efficiently)
 k$ = UCase$(Inkey$)
 If k$ <> "" Then
  Do :Loop Until Inkey$=""
 ' Enable redraw when a movement key is pressed
   'If Instr("WASD", k$) Then
   needsRedraw = 1
   Select Case k$
     Case "W"
       dmx=dx * moveSpeed:dmy=dy * moveSpeed
       'don't go through the wall
        If Not (m(Int(px + dmx), Int(py))) Then Inc px,dmx
        If Not (m(Int(px), Int(py + dmy))) Then Inc py,dmy
     Case "S"
       dmx=dx * moveSpeed:dmy=dy * moveSpeed
       'don't go through the wall
        If Not (m(Int(px - dmx), Int(py))) Then Inc px ,- dmx
        If Not (m(Int(px), Int(py - dmy))) Then Inc py ,- dmy
     Case "D"   ' Rechts drehen
       angleIndex = (angleIndex + rotStep) Mod LUTSIZE
     Case "A"   ' Links drehen
       angleIndex = (angleIndex - rotStep + LUTSIZE) Mod LUTSIZE
   End Select
 End If
Loop Until k$ = Chr$(27)
If mmb4w Then PAGE WRITE 0
sub precalc_shading
local integer d75%(3,3) = (1,0,1,0, 1,1,1,1, 0,1,0,1, 1,1,1,1)
local integer d50%(3,3) = (1,0,1,0, 0,1,0,1, 1,0,1,0, 0,1,0,1)
local integer d25%(3,3) = (1,0,1,0, 0,0,0,0, 0,1,0,1, 0,0,0,0)
For s = 1 To 3   ' 1=75%, 2=50%, 3=25%
 shadeBase = scrw+24+ (s-1)*24   ' 24 Pixel pro Block
 For n = 1 To 3
   For f = 0 To 1
     idx = (n-1)*2 + f        ' 0..5
     baseX = shadeBase + idx*4
     For y = 0 To MM.VRES-1
       For x = 0 To 3
         Select Case s
           Case 1: p = d75%(y And 3, x)
           Case 2: p = d50%(y And 3, x)
           Case 3: p = d25%(y And 3, x)
         End Select
         If p Then
           Pixel baseX + x, y, col(n,f)
         Else
           Pixel baseX + x, y, 0
         End If
       Next x
     Next y
   Next f
 Next n
Next s
For n=1 To 3:For f=0 To 1
box scrw+8*(n-1)+4*f,0,4,MM.VRES,,col(n,f),col(n,f):Next :Next
end Sub

MapData1: '
Data "11111111111111111111111133333333333331111111111111111111"
Data "11111111111111111111111133333333333331111111111111111111"
Data "11111111111111111111111130000000000333331111111111111111"
Data "11111000011111111111111130000000000333331111111111111111"
Data "11111000011111110000000000000000000300331111111111111111"
Data "11111000011111110000000030000000000300331111111111111111"
Data "11111110111111110011111130000000000333331111111111111111"
Data "11111000000001110011111130000000000333331111111111111111"
Data "11111000000001110011111133333003333331111111111111111111"
Data "11111000000001000000111133333003333331111111111111111111"
Data "11111000000000000000111111333003333331111111111111111111"
Data "11111000000001000000111111333003311111111111111111111111"
Data "11111000000001111111111333333003333111111111111111111111"
Data "10011111001111111111111333333003333111111111111111111111"
Data "10011111001111111111111300000000333111111111111111111111"
Data "11011111001111111111111300333003333111111111111111111111"
Data "10011111001111111111111300333003333111111111111111111111"
Data "10000000001111111111111300333003333111111111111111111111"
Data "10010000001111111111111333333003311111111111111111111111"
Data "10011111111111111111111111113003111111111111122222222222"
Data "10011000000001111111111111110000111111111111122222222222"
Data "10011000000001111111111100000000000011222222222000000022"
Data "10011000000001111111111000000000000002222222222000000022"
Data "10001000000001111111111000000000000000000000002000000022"
Data "10000000000001111111111000000000000000000000000000000022"
Data "10001000000001111111111000000000000002000000222000000022"
Data "10011000000001111111111000000000000001122002222000000022"
Data "10011000000001111111111111110000111111122002222000000022"
Data "10011111011111111111111111112202211111122002222222222222"
Data "10011111001111111111111111122002211111122002222222222222"
Data "10011111001111111111111111122002211111122002222222222222"
Data "10010001001111111111111111122002211111122002222222222211"
Data "10000001001111111111111111122002211111122002222020022211"
Data "10011111001111111111111111122002211111122000000000000211"
Data "10011111001111111111111111122002211111122000000000002211"
Data "10000000000000010000011111122002211111122022002020220211"
Data "10000000000000000000000111122002211111122222222222222211"
Data "10000000000000010000011111122002211111111111111111111111"
Data "11111111111111110111111122222002222221111111111111111111"
Data "11111111110010000111111222222202222221111111111111111111"
Data "11111111110000011111111200000000000021111111111111111111"
Data "11111111111011111111111200002002000021111111111111111111"
Data "11111111111001111111111200002002000021111111111111111111"
Data "11111111111001111111111222222002222221111111111111111111"
Data "11111111111111111111111200002002000021111111111111111111"
Data "11111111111111111111111200000000000021111111111111111111"
Data "11111111111111111111111200002002000021111111111111111111"
Data "11111111111111111111111222222002222221111111111111111111"
Data "11111111111111111111111200000000000021111111111111111111"
Data "11111111111111111111111200000000000021111111111111111111"
Data "11111111111111111111111222222222222221111111111111111111"


cheers
 Martin
Edited 2026-02-23 02:43 by Martin H.
'no comment
 
dddns
Guru

Joined: 20/09/2024
Location: Germany
Posts: 816
Posted: 06:06pm 22 Feb 2026
Copy link to clipboard 
Print this post

Even though I don't understand a word, that's terrific. Super fast and (almost) less code than with the ray command itself.
 
Martin H.

Guru

Joined: 04/06/2022
Location: Germany
Posts: 1441
Posted: 01:53pm 23 Feb 2026
Copy link to clipboard 
Print this post

  dddns said  Even though I don't understand a word

That's not your fault; I wouldn't be able to read the formulas anymore either.
I removed the calculations from the loop as best I could.
I replaced many “if queries” with calculations using true and false.
Testing line by line to find out which type of calculation is currently faster.
This speeds up execution considerably, but does not contribute to readability.
To understand the calculation and how it works, it's best to watch this Video .

Then compare the function from the original routine

  For x = 0 To scrW - 1 Step resStep
    cameraX = 2 * x / scrW - 1
    rayDx = dx + planeX * cameraX
    rayDy = dy + planeY * cameraX
    mx = Int(px) : my = Int(py)

    If rayDx = 0 Then dDx = 1e30 Else dDx = Abs(1 / rayDx)
    If rayDy = 0 Then dDy = 1e30 Else dDy = Abs(1 / rayDy)

    If rayDx < 0 Then
      stepX = -1 : sdX = (px - mx) * dDx
    Else
      stepX = 1 : sdX = (mx + 1.0 - px) * dDx
    End If
    If rayDy < 0 Then
      stepY = -1 : sdY = (py - my) * dDy
    Else
      stepY = 1 : sdY = (my + 1.0 - py) * dDy
    End If
    hit = 0
    Do While hit = 0
      If sdX < sdY Then
        sdX = sdX + dDx : mx = mx + stepX : side = 0
      Else
        sdY = sdY + dDy : my = my + stepY : side = 1
      EndIf
      If m(mx, my) > 0 Then hit = 1
    Loop
    If side = 0 Then pDist = (sdX - dDx) Else pDist = (sdY - dDy)
    If pDist < 0.1 Then pDist = 0.1

    lH = Int(scrH / pDist)
    yS = (scrH / 2) - (lH / 2)
    wallCol = RGB(GREEN)
    If side = 1 Then wallCol = RGB(0, 128, 0)
    Box x, yS, resStep, lH, , wallCol
  Next x



with the current one; that will help you understand.

  For x = 0 To scrW - 1 Step resStep
    cameraX = x * invScrW - 1
    rayDx = dx + planeX * cameraX
    rayDy = dy + planeY * cameraX
    mx = Int(px) : my = Int(py)
  If Not rayDx  Then dDx = 1000 Else dDx = Abs(1 / rayDx)
  If Not rayDy  Then dDy = 1000 Else dDy = Abs( 1 / rayDy)
  If rayDx < 0 Then
    stepX=-1:sdX=(px - mx)*dDx
  Else
    stepX=1:sdX=(mx+1-px)*dDx
  End If
  If rayDy < 0 Then
      stepY = -1: sdY = (py - my) * dDy
  Else
      stepY = 1 : sdY = (my + 1 - py) * dDy
  End If
    'cast one Ray
    Do While Not m(mx,my)
     If sdX < sdY Then
        Inc sdX,dDx:Inc mx,stepX : side = 0
      Else
        Inc sdY,dDy:Inc my,stepY : side = 1
      End If
   Loop
    pDist=Max(0.1,(sdX - dDx)*Not(side)+(sdY-dDy)*side)
    lH = scrH / pDist
    shade = min (int(pDist>>3)*24,72)*dither%  
Blit shade+scrw-8+8*m(mx,my)+4*side,0,x,Max(0,halfScrH-(lH>>1)),resStep,Min(lH,scrH)
  Next x

If I find further ways to optimize it, it will certainly become even more unreadable.
Have Fun
Martin
'no comment
 
dddns
Guru

Joined: 20/09/2024
Location: Germany
Posts: 816
Posted: 05:30pm 23 Feb 2026
Copy link to clipboard 
Print this post

Amazing, optimize it as much as you can :) ..is there a way to place a (3D) icon on the floor?
Best success!
 
     Page 2 of 6    
Print this page
The Back Shed's forum code is written, and hosted, in Australia.
© JAQ Software 2026