Lasers and Mirrors

Recently, someone asked me for tips on how to bounce a simulated ray of light around reflective surfaces. I thought it might be fun to post my answer here in case other folks find it useful.

In Bobo Explores Light, there are a couple of interactive pages that show rays of light bouncing dynamically around the screen:

Laser1 Laser2

To achieve a similar effect in your game, you need to do the following:

  1. Figure out all the reflection points for your ray of light
  2. String an image along those points

Figuring Out Reflection Points

The first part is pretty straight forward. Simply follow these steps.reflectionVectors

  1. Figure out the initial position and direction of your light ray. Let’s call that ray L with a starting position at point P.
  2. Figure out the width, position, and orientation of your mirror. Let’s call that segment M and the normal vector to it N.
  3. You test whether L crosses through M. You can use simple linear algebra equations to get your answer and those are discussed in detail here. If the two indeed intersect, let’s call the intersection point P’.
  4. Your reflected ray L’ begins at P’, in the direction of L reflected along N. Here is the equation to figure out the direction of the reflected vector.
  5. Let P = P’, L = L’ and repeat steps 1-5 as many times as you need.

In the process, you will create a series of points P, P’, P”, P”’, etc. The next thing you have to do is draw a line connecting those points

Stringing an Image Along

You have several options on how to proceed here.

Option 1: Use glDrawLine()

You can draw some low level lines in OpenGL using a single call to glDrawLine(). The problem you might run into is line aliasing. Instead of the image on the LEFT, you get the image on the RIGHT:

lineAntiAlias lineAlias

Option 2: Use Core Graphics

To be perfectly honest, I haven’t played with the Core Graphics libraries on iOS. However, I hear they are pretty powerful. Ray Wenderlich has some cool Core Graphics tutorials on his site, such as this one by Brian Moakley, which might come in handy if this option is available to you. Some people swear by it.

Option 3: Stretch an image along each line segment

For this option you can use images (using UIKit’s UIImage for example) or sprites (using the built-in SpriteKit framework or external libraries similar to Cocos2D) or something different yet. Basically you position the bottom of stretchable image at point A and adjust its length so that it reaches all the way to point B. The cool thing is that this technique allows you to add custom glow effects and such which can be quite neat. It’s also pretty simple to setup. You’re just stretching and rotating images. You will run into problems at the reflection points, however, especially when you are using wide and / or transparent images:

stretchedSprites

For thin lines, you might be able to get away with this glitch. But if not, I’d suggest…

Option 4: Draw a custom polygon using OpenGL

This is by far the most labor intensive option, but it will yield sharp results. You want to draw a triangle strip using OpenGL, tracing points P, P’, P”, … thusly:

PolyStrip2

In the image above, the triangle strip traces points 1, 2, 3, 4, 5, 6, 7, 8 in that order. Note that in order for this method to work, your light ray image needs to be symmetrical. Otherwise you might get incorrect-looking image flipping at the reflection points.

This is essentially the method I ended up implementing in Bobo. The trick is in reflecting the polygon along the reflection points smoothly. I utilized two methods to get that part done.

Method A:

This method of folding the polygon onto itself works well when the angle of incidence is < 45º, but it becomes increasingly poor as you approach 90º at which point the folding edge (points 3-4 in the images below) becomes infinitely long.

hSplit2_2

hSplit2_1

Method B:

This method of reflecting the polygon, on the other hand, works well when the angle of incidence is > 45º, but, again, the folding edge approaches infinity as the angle gets closer and closer to 0º.

 vSplit2_1

vSplit2_2

As you can see, in this case the image of the light ray penetrates the reflecting surface, but generally speaking the ray image is much thinner than the reflection surface, so it’s not really a problem. The extra ray width often comes from the glow part of the image and if that part spills over the reflecting surface, it still visually works.

So, since both of the methods have their shortcomings, you combine them. You use Method A when the angle of incidence is <  45º and Method B when the angle of incidence is >  45º. Note that Method A actually reverses or reflects the order of your vertices. So if your light ray is dynamic (ie. it changes with time and context) and one reflection point switches from using Method A to using Method B or vice-versa, you will need to follow your triangle strip down and reverse the order of vertex points from that point onward (ie. switch the left and right vertices at each “kink”).

Another thing to note is that when you switch from Method A to Method B for a given point, the reflection fold switches from being perpendicular to being parallel with the surface normal. That change produces a visible jump akin to a glitch. To avoid  drawing attention to it, you can overlay each reflection point with a glowing ball thusly:

ballExample

That’s it! Definitely a lot of work, but the technique works very well in practice. I hope it works for you as well!


, , , ,

Comments are closed.