mailto: blog -at- heyrick -dot- eu

Navi: Previous entry Display calendar Next entry
Switch to desktop version

FYI! Last read at 19:05 on 2024/12/27.

Merry Christmas

It is, perhaps, a White Christmas if one horribly abuses the definition of "white". In this case, there is much fog, and everything is drenched.
Fog photo
A White Christmas?

Anyway, I'd like to wish everybody a happy holiday.

 

Freezer

My freezer is almost full. The section at the top with the door has various ready meals in it. And at the bottom? My favourite drawer. I have over the past few weekends baked various cakes and such. Then, if I feel like something cakey, I just need to take out one and let it defrost.
Cake pieces in the freezer
For when you're feeling peckish for something sweet.

 

Wages

As is increasingly happening, when the government raises the minimum wage, our wage doesn't go up in step. Instead we get straight minimum, as that's the legal minimum. In France there are various categories of worker. The lowest is called OE1, somebody that needs their hands held. Next up is OE2, the production line workers are this - they have line managers to tell them what to do. My category is OE3 because as a washer-upper we manage ourselves and handle our own organisation. We don't have a line manager. And yet, making €11,88 an hour (brut), we're being paid OE1 wages and expected, I presume, to do the rest for free.
The only reason I'm not doing more than whinging on my blog is because I have been there long enough that I qualify for a thirteenth month which adds to the overall take-home (albeit in a lump sum once a year).

I am mostly writing this in response to an article I saw about a delivery driver in America who was fed up and stressed and a few days ago dumped a load of Amazon parcels in the woods. They were quickly discovered and, I think, returned to Amazon (sucks to be you if those were your presents...).
Being America, it's that never-ending battle between unions and employers, possible because the delivery drivers are a third party and thus avoided Amazon's infamous anti-union action; but then this sort of thing seems to be all too common in the United States.

One of the reasons they are upset is that Amazon apparently pays $15/hour and some organisation says that a living wage in the US is $22.20/hour. I suppose one would need more money in the US because the number of things we take for granted like social care, access to medical facilities that won't backrupt us, and suchlike are absolutely not assured in the US. However, to put it into context, my wage equates to $12.30 (brut, that's not what I get in my pocket).

For what it is worth, I have no sympathy and hope this driver gets told to walk. Why? Because if they were stressed out, the correct action would have been to take the parcels back to the depot. They are things that people paid for and are depending upon that person to deliver. Yes, delivery drivers get a crap deal, we all do these days, plus Amazon is notorious for this - it's something that often turns up in newspapers. To just dump everything like that? No, that's crossing a moral line.

 

Right-wing madness

Immediately following the recent tragic attack at the Christmas market in Germany, the Associated Press went with this headline:
A car has driven into a group of people at a Christmas market in Germany.

Immediately a number of right-wing Americans, like J.D. Vance, took shot at the Associated Press with questions like "Who was driving the car?".
Clearly they fail to understand that the AP's wording is deliberately vague because while it sure looked and felt like an act of terrorism, at that point it could have been a horrible tragedy due to the driver suffering, perhaps, a heart attack or somesuch. Sure, it is a remote possibility, but a decent press will report on the facts as they become available. This does not mean that they're trying to downplay an Islamist act of terror by "implying" that the car drove itself into the crowd...something that wouldn't happen on this side of the ocean anyway as we're not dumb enough to use hapless civilians as crash-test dummies for self-driving cars.

Of course, things get decidedly murkier when it turns out that the man, a Saudi doctor, was a right-wing supporter who apparently did this atrocity because he was angry about the Islamification of the country. Like, huh? Talk about throwing a spanner in the works of the usual racist right-wing bollocks.
Oh, and in case you missed it, this guy was "known to authorities" and they had received warnings about him. You see, the powers that be don't need extra powers to snoop in everybody's lives, they just need to pay attention to the information that they already have.

So if it is bad for the non-right-wing press to be cautious in what they report, let's take a quick look at what the right-wing press were up to. Fair's fair, right? While we currently know that five people have died, on the night the news unfolded it was known that there were two deaths. GB News, however, went with this story.

GB News reporting falsehoods
Because accuracy matters...

Why? Around two hundred people have been injured, some of them seriously, and there had been two (now five) deaths. Why did they run an article claiming eleven? Could it possibly be that it was for additional 'shock value' (as if the event wasn't shocking enough) to permit opening a dialogue that basically boils down to "Islam is evil"?

I'd like to link to the article, but all I have is the screenshot that I was prescient enough to take, because it appears as if the article no longer exists. That's the mark of quality journalism right there - make up bollocks and erase it once reality proves it wrong. But, then, the right wingers are like that. Look at just about everything said about Brexit as an example. Or what the next four years of POTUS will bestow upon humanity.

 

Dora Advent Calendar

I have just watched an advert for a "Dora Advent Calendar" with new episodes of Dora on the channel Nick Jr. Why am I commenting on this, especially given that I can't even receive that channel? Because it popped up on C5 during an advert break in The Railway Children Return...on Christmas Day, the day after advent calendars are over.

<shrug>

 

The parcel scam

On the 9th of December, a friend in the UK got me some Betty Crocker cake mix. Royal Mail wanted some obscene amount to send it because it passed the 2kg limit, so the woman at the Post Office offered a cheaper option - a carrier called Evri. He declared it correctly and sent it after he was assured that there wouldn't be a problem.
He phoned me with the tracking number, and the status said "Error". Nothing else. After a few days of this, I made a report using a dumb bot called Ezra. Evri never replied to me, but the status was updated to say that the parcel was being held in Basildon following an error at LHR, return to sender.
Given how much was paid for that parcel, it is ridiculous that it took until Friday 20th to make it back. Actually, it took until the 21st because Evri omitted a line from the return address and ended up sending the parcel to a completely different property, and luckily the person there brought it to the right place.
I have never, not once, heard anything good about Evri. Count this as another data point.

He took one of the cake mixes out to put it under the 2kg weight limit and sent it by Royal Mail. It has already made it to France, and has cleared customs. However, since there is no customs agreement with the UK and the EU, I'm being scammed for a "handling" charge. Luckily this time I was able to pay online (unlike with the Tilley Lamp parts), so it was only €7 rather than the usual €11.

It may come in the next few days. It'll be interesting to see what happens when she hands me a bill for the full amount and I'll reply with "Nope, I've already paid that". It seems that left hand and right hand barely talk to each other. It's almost as if nobody ever imports stuff by post and so La Poste doesn't really know how to handle any of this stuff, isn't it?

But, really, being scammed for a "handling fee" for them doing what they do anyway. Yet another reason to say F██k Brexit.

 

The maths of raycasting - part two

In the previous article I looked at the grimy maths of tracing a ray through a grid to determine when it hit something.
This time, we're going to start to turn it into something vaguely useful - walls plotted on the screen. But before we can do that, we need to know how far away these walls are.

 

Calculating the distance

As it happens, we already have the distance as a result of tracing the ray. It's xside or yside. However, we do need to subtract the appropriate delta as that will have been added by the DDA, so undoing that gives us our proper distance.

IF (side% = 0) THEN
  walldist = xside - xdelta
ELSE
  walldist = yside - ydelta
ENDIF

If we want to be cheap'n'cheerful then that is it. The height of the wall is inversely propertional to the distance - that means closer walls are taller than ones farther away, so it's pretty simple, also, to calculate how big the wall should be. Simply divide the height of the screen by the wall distance.

Here is some really naff code to bung that onto the screen in MODE 28. We simply step each ray in turn and draw ten pixels on the screen as a result of it. This is only for demonstration purposes.

ON ERROR PRINT REPORT$+" at "+STR$(ERL) : END
playx = 4.5
playy = 1.5

MODE 28

DIM map%(8,8)
FOR x% = 3 TO 6 : map%(x%, 7) = 1 : NEXT
map%(2, 6) = 1 : map%(7, 6) = 1
FOR y% = 1 TO 5 : map%(1, y%) = 1 : map%(8, y%) = 1 : NEXT

col% = 0
FOR angdeg = 120 TO 60 STEP -1
  angle = RAD(angdeg)

  xpos = playx : ypos = playy
  xmap = INT(xpos) : ymap = INT(ypos)
  xdir = COS(angle) : ydir = SIN(angle)

  IF ( xdir = 0 ) THEN xdelta = 123456 ELSE xdelta = ABS(1 / xdir)
  IF ( ydir = 0 ) THEN ydelta = 123456 ELSE ydelta = ABS(1 / ydir)

  IF ( xdir < 0 ) THEN
    xstep = -1 : xside = (xpos - xmap) * xdelta
  ELSE
    xstep = 1 : xside = ((xmap + 1) - xpos) * xdelta
  ENDIF

  IF ( ydir < 0 ) THEN
    ystep = -1 : yside = (ypos - ymap) * ydelta
  ELSE
    ystep = 1 : yside = ((ymap + 1) - ypos) * ydelta
  ENDIF

  wallhit% = FALSE
  REPEAT
    IF ( xside < yside ) THEN
      xside += xdelta : xmap += xstep : side% = 0
    ELSE
      yside += ydelta : ymap += ystep : side% = 1
    ENDIF

    IF ( (xmap > 0 ) AND ( xmap < 9 ) ) THEN
      IF ( (ymap > 0 ) AND ( ymap < 9 ) ) THEN
        IF ( map%(xmap, ymap) = 1 ) THEN wallhit% = TRUE
      ENDIF
    ENDIF
  UNTIL ( wallhit% = TRUE )

  IF (side% = 0) THEN
    walldist = xside - xdelta
  ELSE
    walldist = yside - ydelta
  ENDIF

  wallhei = 480 / walldist

  IF side% = 1 THEN GCOL 255, 255, 255 ELSE GCOL 127, 127, 127

  RECTANGLE FILL col% << 1, 240 - (wallhei / 2) << 1, 10 << 1, wallhei << 1

  col% += 10
NEXT

As you can see, it's a remarkably small amount of code. It creates the following display.

Drawing walls in raycasting

 

Correcting fish-eye distortion

As you no doubt noticed, the walls were not drawn straight. The big wall directly in front bulges slightly. The reason for this is that rays cast to either side of the direction the player is facing, that is to say anything not directly in front, travel a slightly longer distance even if they are visually the same distance.

The way to correct this goes back to trigonometry. What we are ending up with after the ray stepping algorithm is the hypotenuse which is the ray length. If we multiply this with the cosine of the angle difference, we end up with the adjacent, which is the perceptual distance as the player would perceive it. To better understand this, you'll need to dig into that school stuff about right-angle triangles, I'm not going to explain it here. What I will say is that we calculate the difference between the direction the player is actually facing and the ray angle because as you can imagine the closer we are to the sides of the screen, the more correction will be necessary.

In place of the line wallhei = 480 / walldist, write this:

   diff% = ABS(90 - angdeg)
   corrdist = walldist * COS(RAD(diff%))
   wallhei = 480 / corrdist

This simple, but FP heavy, calculation now provides a good correction so all of the walls look as they should.

Drawing walls in raycasting, better

 

What's with the weird angles?

Facing 'up' in the grid that we have created is 90°. The degrees move anticlockwise, so counting backwards. There may be ways to resolve this such as running the grid as a mirror image or something, but to be honest it isn't that hard to turn the angle that we use internally into a suitable angle for display that has 'up' as zero degrees and the angles turning clockwise as would be expected.

  virtang% = 360 - (angdeg - 90)
  IF virtang < 0 THEN virtang += 360
  IF virtang > 359 THEN virtang -= 360

Clearly it is simpler if what is shown on screen is the same as what happens internally, but the reason we use computers is because one can hide away such calculations. The user doesn't need to know what's going on inside.

 

Rotation

For any form of movement, we're going to have to do quite a number of changes to the program in order to support proper redrawing. We can't simply dump data to the screen because it may take longer to draw than the screen update and it will flicker horribly. In order to resolve this, let's introduce the concept of buffering - that is to say we draw frames not to the screen itself, but to some memory and then switch what we're actually displaying once the frame has been drawn.

Here is an updated program that displays the screen in a never-ending loop (press Esc to quit).

ON ERROR PROCerror
@% = "+G10.3"

playx  = 4.5
playy  = 2.5
playa% = 90    : REM Actual angle used here
fov%   = 60    : REM Field of view

MODE 28
screenwidth% = VDU(11)
screenheight% = VDU(12)

SYS "XOS_ReadModeVariable", -1, 7 TO ,, scrsize%
scrwant% = scrsize% * 3
SYS "XOS_ReadDynamicArea", 2 TO , scrcurr%
screxpa% = scrwant% - scrcurr% : REM How much to enlarge by
IF ( screxpa% > 0 ) THEN
  SYS "XOS_ChangeDynamicArea", 2, screxpa% TO ; f%
  IF (f% AND 1) THEN ERROR 123, "Not enough screen memory."
ENDIF

DIM map%(8,8)
FOR x% = 3 TO 6 : map%(x%, 7) = 1 : NEXT
map%(2, 6) = 1 : map%(7, 6) = 1
FOR y% = 1 TO 5 : map%(1, y%) = 1 : map%(8, y%) = 1 : NEXT
FOR x% = 1 TO 8 : map%(x%, 1) = 1 : NEXT

MOUSE OFF
SYS "OS_RemoveCursors"
SYS "OS_Byte", 114, 0 : REM Shadow modes
bank% = 1
PROCswitchbank

timeout% = TIME + 100
fcnt% = 0
fps% = 0

REPEAT
  PROCswitchbank
  CLS

  GCOL 0, 127, 127
  RECTANGLE FILL 0, (screenheight% / 2) << 1, screenwidth% << 1, (screenheight% / 2) << 1
  GCOL 0, 127, 0
  RECTANGLE FILL 0, 0, screenwidth% << 1, (screenheight% / 2) << 1

  FOR col% = 0 TO screenwidth% STEP 2
    colang = fov% / (screenwidth% + 1) : REM Degrees per screen column
    angdeg = playa% + ( -(col% - ((screenwidth% + 1) / 2)) ) * colang

    angle = RAD(angdeg)

    xpos = playx : ypos = playy
    xmap = INT(xpos) : ymap = INT(ypos)
    xdir = COS(angle) : ydir = SIN(angle)

    IF ( xdir = 0 ) THEN xdelta = 123456 ELSE xdelta = ABS(1 / xdir)
    IF ( ydir = 0 ) THEN ydelta = 123456 ELSE ydelta = ABS(1 / ydir)

    IF ( xdir < 0 ) THEN
      xstep = -1 : xside = (xpos - xmap) * xdelta
    ELSE
      xstep = 1 : xside = ((xmap + 1) - xpos) * xdelta
    ENDIF

    IF ( ydir < 0 ) THEN
      ystep = -1 : yside = (ypos - ymap) * ydelta
    ELSE
      ystep = 1 : yside = ((ymap + 1) - ypos) * ydelta
    ENDIF

    wallhit% = FALSE
    REPEAT
      IF ( xside < yside ) THEN
        xside += xdelta : xmap += xstep : side% = 0
      ELSE
        yside += ydelta : ymap += ystep : side% = 1
      ENDIF

      IF ( (xmap > 0 ) AND ( xmap < 9 ) ) THEN
        IF ( (ymap > 0 ) AND ( ymap < 9 ) ) THEN
          IF ( map%(xmap, ymap) = 1 ) THEN wallhit% = TRUE
        ENDIF
      ENDIF
    UNTIL ( wallhit% = TRUE )

    IF (side% = 0) THEN
      walldist = xside - xdelta
    ELSE
      walldist = yside - ydelta
    ENDIF

    virtang = 360 - (angdeg - 90)
    IF virtang < 0 THEN virtang+=360
    IF virtang > 359 THEN virtang-=360

    diff% = ABS(playa% - angdeg)
    corrdist = walldist * COS(RAD(diff%))
    wallhei = (screenheight% + 1) / corrdist

    IF side% = 1 THEN GCOL 255, 255, 0 ELSE GCOL 127, 127, 0

    RECTANGLE FILL col% << 1, 240 - (wallhei / 2) << 1, 2 << 1, wallhei << 1
  NEXT

  REM Movement here!

  PRINTTAB(0,0);"X="+STR$(playx)+", Y="+STR$(playy)+", A="+STR$(playa%)+"° ("+STR$(fps%)+" fps)"

  fcnt% += 1
  IF ( TIME >= timeout% ) THEN
    timeout% = TIME + 100
    fps% = fcnt%
    fcnt% = 0
  ENDIF

UNTIL FALSE

END

:

DEFPROCerror
  REM Sanitise the display, report the error, go byebye.
  PROCdrawbank(0)
  PROCshowbank(0)
  SYS "OS_Byte", 114, 1
  PRINTTAB(0,0);REPORT$+" at "+STR$(ERL)
  END
ENDPROC

DEFPROCdrawbank(w%)
  REM What screen bank are we drawing to?
  SYS "XOS_Byte", 112, w%
ENDPROC

DEFPROCshowbank(w%)
  REM What screen bank is the user looking at?
  SYS "XOS_Byte", 113, w%
ENDPROC

DEFPROCswitchbank
  REM Furtle with the screen banks
  WAIT
  PROCshowbank(bank%)
  bank% = (bank% MOD 3) + 1
  PROCdrawbank(bank%)
ENDPROC

DEFFNkey(k%)
  REM Returns TRUE if the nominated key is pressed, else FALSE
  SYS "OS_Byte", 121, (k% EOR amp;&80) TO , r%
  IF r% = &FF THEN =TRUE
=FALSE

You will notice, as an optimisation, we draw every two screen columns. On my Pi 3B+, this changes the framerate reported from ~19fps to ~38fps while barely changing how it looks on-screen, so it's a worthwhile optimisation.
You'll also notice that it looks better with some colours. ☺

Better looking raycasting

 

Now to add rotation. Unlike the series that I wrote a couple of years ago that used planes, this raycaster works with angles and translates those on-the-fly. So all we need to do is increment or decrement the angle that the player is facing.

Replace the part that says "REM Movement here!" with this:

  REM Rotation (cursor left/right)
  IF ( FNkey(25) ) OR ( FNkey(121) ) THEN
    speed% = 1
    IF FNkey(0) THEN speed% = 2
    IF FNkey(25) THEN playa% += speed% ELSE playa% -= speed%
    IF ( playa% < 0 ) THEN playa% += 360
    IF ( playa% > 359 ) THEN playa% -= 360
  ENDIF

If you press Left, you turn to the left. If you press Right, you turn to the right. If Shift is held down, you'll turn twice as fast.

I'll remind you, the angles here run anticlockwise which is why one adds to turn to the left.

 

Forwards and backwards

This is harder, because we need to perform two actions here. The first is to perform the movement. For this we need to 'follow a ray' in the forward direction for a tiny step. This will give us a new X and Y position. Guess what, it's more of the sine and cosine functions.
Once this has been done, the second thing to do is to look at the new map coordinate to see what is there. If it's not a zero, not an empty square, then the movement will be reverted to avoid a collision with a wall.

Put this after the rotation code.

  REM Movement (cursor up/down)
  IF ( FNkey(57) ) OR ( FNkey(41) ) THEN
    oldplayx = playx
    oldplayy = playy
    IF ( FNkey(57) ) THEN
      playx += COS(angle) * 0.1
      playy += SIN(angle) * 0.1
    ELSE
      playx -= COS(angle) * 0.1
      playy -= SIN(angle) * 0.1
    ENDIF

    IF map%(playx, playy) <> 0 THEN
      playx = oldplayx
      playy = oldplayy
    ENDIF
  ENDIF

 

After the map setup at the top, you could add this to have something to walk around.

map%(4, 4) = 1

Have fun playing with this.

Raycast animation

 

Download raycast2024.zip (4.66K)
For RISC OS machines.

 

 

Your comments:

Rob, 26th December 2024, 00:36
I was listening to Heart Xmas last night, well about 2am this morning, whilst wrapping presents, and almost every other advert was (to summarise) saying to get your last minute gifts at BP/M&S forecourt shops because they were open late. I checked. The nearest couple had all closed at 11pm. Do you think companies just book ads without checking how long they are going to run for?
jgh in Japan, 27th December 2024, 02:07
Sh1t! I'm barely on more than you as an IT Enggggg ennnnn (cough!) technician. (checks spreadsheet) £12.54ph

Add a comment (v0.11) [help?]
Your name:

 
Your email (optional):

 
Validation:
Please type 10630 backwards.

 
Your comment:

 

Navi: Previous entry Display calendar Next entry
Switch to desktop version

Search:

See the rest of HeyRick :-)