Tutorial: Ray casting and reflection

Tutorial: Ray casting and reflection

This tutorial discusses ray casting and reflection, achieved via the physics.rayCast() and physics.reflectRay() APIs respectively. It also follows along with a “real world” demo project which you can download from our GitHub repository.

What is ray casting?

At the most basic level, ray casting involves transmitting a ray — a straight line — from one point to another point, and using it to detect if one or more physics bodies resides in that path. Among other things, this can be used to detect if an object resides in the firing path of a gun, “the line-of-sight” of an NPC, etc. Essentially, it’s a quick and efficient way to query the physics world for the presence of other physics objects.

Corona also features a convenient API for reflecting a ray from any object it strikes. From any ray cast with a registered hit, physics.reflectRay() returns a vector representing the direction of the reflection, with a magnitude (length) of 1. Today’s demo shows this API in practice.

Room of mirrors

If you haven’t downloaded the demo project, please do so and follow along with the code.

An important step in the demo is configuring the “world,” in this case a room of mirrors with a laser turret in the center. You can set up your physics world however you wish, but this example loops through a series of tables containing x, y, and rotation values for each mirror and places them on the screen. The turret itself is a standard display object with a radial physics body — yes, ray casting reflection works on radial bodies too!

Next, we’ll start the turret rotating by setting its angular velocity to the turretSpeed variable located near the beginning of the sample code. Then, we’ll start a repeating timer to fire the laser every 2000 milliseconds.

Casting the ray

Casting the actual ray is simple. The first four arguments indicate an x and y starting position and an x and y destination. The fifth argument, while optional, is worth noting because it indicates the “type” of result(s) you want returned from the ray cast. The following options are currently available:

  • "any" — The first valid result, but not necessarily the closest to the starting point.
  • "closest" — The closest result from the starting point (default return value if none is specified).
  • "sorted"all of the results, sorted from closest to furthest from the starting point.
  • "unsorted" — all of the results without any sorting algorithm applied.

The physics.rayCast() API returns an array of tables describing each hit, but since we’re only interested in the first hit that the ray cast registers, we’ll only deal with the first table in the array.

From the table representing the specific hit, we get the following details:

  • object — The display object colliding with the ray.
  • position.x — The x collision position of object, in content space.
  • position.y — The y collision position of object, in content space.
  • normal.x — The x component of the normal of the object surface hit, in local space.
  • normal.y — The y component of the normal of the object surface hit, in local space.

If we register at least one hit — as in, physics.rayCast() doesn’t return nil — we can use the hit information to draw a line from the starting point to the hit point:

Reflecting the ray

If you wish to reflect the ray off a surface it hits, Corona provides the convenient physics.reflectRay() API. As stated above, this returns a vector representing the direction of the reflection, with a magnitude (length) of 1.

Calling this function requires three arguments.

  • from_x — the starting x position of the cast ray.
  • from_y — the starting y position of the cast ray.
  • hit — an entry in the “hits” array, as returned by the cast ray.

Notice that the from_x and from_y arguments are based on the core ray cast starting position, not the hit position x and y.

To extrude the reflected ray and set a destination point for it, just factor in a vector length of your choosing, sum it to the hit point, and then draw a new line from the hit point to the next destination point. In this example, we use 1600 for the vector length, but it can be set to whatever you need for the reflected vector.

In the demo project, the castRay() function repeats until there are no more hits or when the maxBeams value is exceeded. This is useful when a particular beam begins to bounce directly back and forth between two surfaces in such a similar pattern that the process would repeat almost indefinitely.

When either condition is satisfied and the process is terminated, we call a basic transition to fade out the parent display group and, upon completion, we call the resetBeams() function which clears/resets the group:

In summary

This essentially completes the demo walkthrough. If you tap on the screen, a laser beam is fired from the turret to the point where you tapped. A ray cast is done upon that line, and if it hits another object in the physics world, the ray reflection is calculated and extruded to another point. Based on that data, another ray cast is performed and the process continues until there are either no more hits or the beam threshold is exceeded.

As you can see, ray casting can be a useful addition to your physics toolset. From querying the physics world to calculating a potential path of a moving object, ray casting is fast, simple, and can be accomplished in just a few lines of code.


Brent Sorrentino
brent@coronalabs.com

Brent Sorrentino is a full-time Developer Evangelist and technical writer who assists others with game development, code, and in overcoming other challenges to help them bring their app dreams to life.

16 Comments
  • IcySpark
    Posted at 16:10h, 07 May

    There is a bug in the rayCast api in that it won’t always return the nearest collision intersection first. I think it’s more a problem with box2D rather than corona though after having searched google. I have managed to code around that issue however.

    You can check out an image from my upcoming app using raycasting in a similar vein.
    It’s got mirrors, splitters, portals, color changers etc.

    http://forums.coronalabs.com/topic/34551-new-game-using-raycasting-coming-soon/

    • Brent Sorrentino
      Posted at 16:14h, 07 May

      Hi @IcySpark,
      Can you briefly explain what your workaround is? This would be useful to those implementing serious raycasting.

      Looking forward to your game! My demo is quite basic, so I’m eager to see what you’ve done with raycasting.

      Brent

  • IcySpark
    Posted at 16:34h, 07 May

    Hi Brent,

    I had to calculate the length of each hit point of the ray (if it hit more than one object) which is quite easy as you have both the start and end points for every hit. Then put them in a table, and looped over them to find the smallest.

    For my game it works out well, for example to flag certain intersections as I may not want the closest intersection to reflect, depending on the objects type.

  • J. A. Whye
    Posted at 17:00h, 07 May

    Raycasting is really cool, but I have a small problem — is there a way to add a filter to determine which objects will cause a hit or not?

    For example, I’m using raycasting to determine whether the player can see a monster. They turn the corner and the monster appears. But when I fire a missile at the monster, the raycast picks up on the missile, says there’s an object in the way, and the monster blinks out.

    I can set a flag to not raycast if a missile is on screen, but there are many pitfalls with that method so I’m hoping there’s something clever I can do. 🙂

    Jay

  • IcySpark
    Posted at 17:14h, 07 May

    Yes sure I’m using filters in my upcoming game.

    The best way would be to set

    local hits = physics.rayCast( startX, startY, destX, destY, 0 )

    to receive all hits.

    then just check each of those hits to see if any equals a monster:

    eg

    for i =1,#hits do
    if hits[i].object.type == “monster” then
    print(“Run Awwwayyy!”)
    end
    end

    • J. A. Whye
      Posted at 18:06h, 07 May

      I’ve been reluctant to grab all hits and do any kinds of loops inside the raycasting function because I was worried about performance — but I never did test to determine whether it was actually a problem or not. 🙂

      Not sure that will help anyway, unless I check to determine what every hit is. When I check for a single hit I know if #hit == 1 the monster can see the player. If I check for all hits, the monster will be there, but so will any walls between them and the player, a missile, etc.

      Maybe instead of actually looking at which objects I can tag physical stuff with .obscures = true and if that shows up in the loop, you can’t see him. Missiles, bullets, etc. would all be .obscures = false. Hmmm…I’ll try that.

      Thanks!

      Jay

  • Dean Hodge
    Posted at 18:21h, 07 May

    Of course your monster might be standing in front of the wall, in which case you could see it, though the wall.obscure would still be in the loop.

    What you might need to do (due to raycast not always returning the closest object first) is calculate the distance from you to the monster and the .obscures. If monster is closest the you can see him otherwise the walls etc are in the way.

    • J. A. Whye
      Posted at 18:50h, 07 May

      Yeah, that sounds like what I might have to do. Grab all hits, then cycle through and find the closest (ignoring all obscures = false) — if the closest one is a monster, fire!

      Corona makes things so easy that I always think there should be a 1-line solution to every problem. 🙂

      Jay

  • Vasant
    Posted at 19:44h, 08 May

    The API documentation for physics.rayCast() in 2013.1101 build says…..
    >>>>
    This function is used to find the objects that collide with a line, and the collision points along that line.

    The positions returned are in content-space. The positions returned are ordered from closest to farthest from the starting point.

    The normals returned are in local-space.
    >>>>
    When I try the sample example and play with the code, I don’t think the positions are NOT closest to farthest from the starting point. Not sure what order they are in. Is that a bug or is there a specific way to access positions to get closest to farthest from the starting point? If it worked as documented, the code could be very efficient.

    Thanks for the code sample. I have few ideas where using these features makes the puzzle game very simple to code but very elegant to play.

    Vasant

  • Jacques
    Posted at 04:14h, 10 May

    @Vasant, you’re right they are not in any order we would understand, use icy sparks little bit of code and you’ll be able to have a perfect list of closest to furthest or however you want. I’ve been using raycast heavily in my upcoming game War of the Zombie. Raycast is a blessing but also a curse! The most important thing you do need to remember is that it’s still part of the box2d physics world AND that means it’s very picky about physics objects you alter during a collision. You shouldn’t have any problem if you’re just firing off a few raycasts now and then but if you’re ray casting very often for multiple objects then you really need to be sure nothing is removed OR more importantly nothing gets ‘targetted’ by the ray cast the moment it’s being altered or corona will crash (as will your device). I found this out the hard way and for now the only solution i have is to pause any new raycast for a few milliseconds to be sure that any objects i’m removing (for example after being hit by a ‘bullet’) are finished being removed before another raycast might possibly target it.

    So far raycast seems to be darned fast (thankfully), but i only have on average about 20 objects casting rays constantly at more then 100+ other objects.

  • Brent Sorrentino
    Posted at 19:55h, 23 May

    Hi Jacques, Vasant,

    Good news! As of Daily Build #1125, the “hit order” issue of physics.rayCast (inherent to Box2D) has been fixed. Instead of supplying the maximum number of hits that you wish to detect (as the fifth argument), you now have 3 options to return exactly the hit(s) you need.

    This property can be one of the following options:
    1) “closest” (default if unspecified) : Return only the closest hit from the starting point, if any.
    2) “any” : Return one result, not necessarily the closest one. This is the “least expensive” behavior (and works as before this update).
    3) “sorted” : Return all results, sorted from closest to farthest. This is the “most expensive” behavior.

    Here is the Daily Build documentation on it.
    http://docs.coronalabs.com/daily/api/library/physics/rayCast.html

    The raycasting tutorial and demo project have also been updated accordingly.

    Hopefully this is useful for you!
    Brent Sorrentino

    • Jacques
      Posted at 04:32h, 30 May

      Yeah thanks however, i think it was actually working better before unless there is a way to still detect more then one hit without using what you describe as “most expensive”(“sorted”) behaviour. Perhaps you can tell me if this “most expensive” behaviour is still LESS expensive then sorting out our own closest to further list as we had been doing?
      Reason I’m asking is there are situations where I have objects that a bullet can pass through but with the NEW default method it wouldn’t be able to detect beyond that object unless i use your “most expensive” behaviour (ie; “sorted”)

      • Albert
        Posted at 15:51h, 30 May

        The most expensive behavior (“sorted”) is still less expensive than doing the sorting Lua-side.

        Note: “sorted” isn’t very expensive. It’s just the most expensive of all the behaviors.

  • Jacques
    Posted at 04:37h, 30 May

    Actually, what would be great is the ability for the rayCast not to detect physics objects set to be sensors, or at least give us the chance to decide if we want them detected or not. Again, this is in regards to developers perhaps having physics objects on stage that should not be consdered ‘hits’ by the rayCast. Does this make any sense as not sure if explaining it clearly. 🙂

  • Tony
    Posted at 08:32h, 16 September

    Hi I was just wondering if I could use ray casting and reflection in creating a game that uses lasers to shoot monsters, in which the lasers can also be reflected to hit my target? Thanks.

    • Brent Sorrentino
      Posted at 15:23h, 16 September

      Hi Tony,
      Yes, that is a great use-case for raycasting. The demo in this tutorial should get you started with implementing it in your own game.

      Brent