Rail3D*

   

 

More About Normals


 


If you want a bit more of an explanation of normals, then this is for you. It started originally as a message in the Rail 3D e-group. For a more general introduction to strips, see gettingstartedwithstrips.

 

 

1  Strips vs Fills

Before we start discussing normals, hopefully you can see the advantage that strips offer over Fill statements in most situations. Normals might seem a bit scary to learn, but it’s well worth the effort when you see the results.

Just to illustrate, consider the following two vertical cylinders:

 

Both cylinders have eight sides, yet you can see that the one on the left looks significantly more realistic than the one on the right. The left cylinder was created with one strip, and the right cylinder was created using eight Fill statements.

Frame rates are also an important consideration when making models, and strips also offer an advantage over Fills in this aspect. A curved wall of a coach created using Fills instead of strips can potentially have twice as many vertices to draw.

 

2  Some definitions

A normal is simply a vector that is perpendicular to (at right angles to) a surface. In other words, it’s an arrow that starts on a surface and points directly out of the surface. For a real-life analogy, you could say that a flagpole is normal to the ground, provided the ground is flat and the pole is straight!

For our purposes, a vector is essentially just a mathematical term for an arrow. All arrows have a direction and a length. In the same way, all vectors have a direction and a magnitude. In the flagpole analogy, the direction would be ‘up’ and the magnitude, or length, would be the height of the flagpole, say 10m.

If, for some reason, your flagpole was installed incorrectly so that it went below the ground instead of above it, you can represent this in “vector-speak” in one of two ways. You could just change the direction to ‘down’ (and leave the magnitude as 10), or you could leave the direction as ‘up’ and say the magnitude is now −10m. They both mean the same thing.

 

3  Normals in Rail 3D

Rail 3D uses normal vectors to decide how to light the panel. When you make a strip, you would know that the format requires you to enter a normal vector X/Y/Z at each W/L/H point, with optional u,v coordinates, like this:

 
 strip r.g.b [texture] [*gg]
 …
 W/L/H X/Y/Z 

By defining the normal, you are creating an imaginary arrow that tells R3D which way the surface is facing. If you would like to visualise this arrow, imagine it as a line pointing from 0/0/0 to X/Y/Z, where X/Y/Z is the normal information you have provided. This will be the direction that your “flagpole” is coming out of the surface, if you like.

This is where it might become a bit confusing. I said earlier that normals are always perpendicular to the surface, yet R3D is asking for a normal perpendicular to every single point. It never asks for a normal to the surface. Mathematically, you can’t have a normal to a point, since a point is just that - a point: you can’t tell which way a single point is facing! However, this usually isn’t a problem, and we’ll deal with it only when we have to.

 

3.1 A simple example

Consider a floor:

 
 strip
 −100/200/0   0/0/1
 −100/0/0     0/0/1
 100/200/0    0/0/1
 100/0/0      0/0/1
 endstrip

To show you what it looks like, I’ve included some diagrams. In all of these diagrams I’ve drawn in the positive axes too: red for W, green for L and blue for H.

 

This floor is at height H=0 and runs from W=−100 to W=100, and L=0 to L=200. It’s basically a 200 x 200 square lying at ground level. (If you want to try this in the editor then you’ll also need to add a colour to the strip line.) This is very basic, but you can see I have defined the normals to all be 0/0/1. From what I said previously, imagine this as an arrow pointing from 0/0/0 to 0/0/1. This is simply an arrow pointing straight up, and it is one unit long.

Fortunately for us, R3D only uses the direction of the normal, so it doesn’t care what the magnitude is. I could have done this instead:

 
 strip
 −100/200/0   0/0/180
 −100/0/0     0/0/999
 100/200/0    0/0/665
 100/0/0      0/0/352
 endstrip

and it would make absolutely no difference at all. They’re all vectors that point straight up, but it’s a lot easier on your brain when they’re all nice numbers like 0/0/1, especially when you’re trying to read over what you’ve done to find a mistake!

In case you’re still confused about what the normal might look like, I’ve drawn in a light blue normal to the surface in the following diagram. Remember that where the normal starts and ends is not important, as R3D cares only for its direction.

 

Similarly, if you were making a flat exterior wall for the right hand side of a coach, your normal vector would need to be pointing straight out to the right hand side. This would correspond a normal of 1/0/0. What about the left hand wall? Easy - it’s the opposite direction, so all you need to do is change the direction, which in this case means going in the negative W direction instead of the positive W direction, and it becomes −1/0/0.

 

3.2 A more complicated example

Let’s say we tilted this 200×200 square floor up on one side so that it was at 45° to the ground. The normal vector is not going to be 0/0/1 any longer. Let’s make the left hand side (so the negative W side) of the floor to be at H=200 instead of H=0.

 
 strip
 −100/200/200   1/0/1
 −100/0/200     1/0/1
 100/200/0      1/0/1
 100/0/0        1/0/1
 endstrip
 

The normals are now 1/0/1, and again I’ve drawn a normal to the surface so you can see a visual representation of it. The strip is facing both up and right now, and the new normals reflect this. Because the surface is at 45°, the normal is now pointing in equal amounts up and right.

You may have noticed that the colour of the panel appears to have been changed. This is because the normal is now facing in a different direction, and R3D has worked out that the panel should receive less light than before, hence the darker colour.

 

3.3 More complex angles and stuff

If we had only tilted the surface a little bit, maybe by putting one side of it at H=20, then the surface would be nearly flat on the floor, but not quite.

 

The question is, how do you work out the normal now? You can either take a guess, or work it out mathematically.

Take a guess. Another good thing with R3D normals is that they rarely have to be exactly ‘correct’. A good guess will usually be good enough. There are also two ways to take a guess:

 
  1. We know that when the surface was flat on the ground, the normal was 0/0/1. We also know that if we tilt it by 45° like we did earlier, the normal becomes 1/0/1, and that if we tilt it right over so it becomes a wall instead of a floor, it becomes 1/0/0. So there is a trend as we go from flat to 45°… 0/0/1 to 1/0/1. It’s obvious that any tilting between 0° and 45° will be x/0/1, where x is a number between zero and one. So because we haven’t tilted it very far, x is probably going to be closer to 0 than it is to 1. Let’s guess now and say it’s going to be 0.1. We could write 0.1/0/1, but remember that R3D doesn’t care how big the normal is, so it’ll look nicer if we multiply it by 10 and make it 1/0/10. I’m not sure if R3D accepts decimals in normals anyway.
     
  2. The other way to guess is to look at it like this. Because it’s not exactly flat, we say the surface can’t be facing straight up. In this case it must be facing to the right just a little bit too. But even though it is pointing a little bit to the right, it’s mainly pointing straight up, so we’ll make the ‘up’ part of the normal bigger than the ‘right’ part, and guess 1/0/10.

You could think of it like making cordial. The instructions might say “mix 1 part cordial concentrate to 5 parts water”. These normal vectors are the same. By saying 1/0/10, we mean “1 part right for every 10 parts up”. This is why the length of the normal isn’t important, because saying “2 parts right for every 20 parts up” is exactly the same thing.

A more mathematical approach. This method requires more thought, but it should yield more accurate results. The components of the normal (i.e. X, Y and Z) essentially reflect the ratio of the differences between the respective W, L and H coordinates of the surface. With our slightly-tilted surface, there isn’t any L component in our normal. (Why? Because the surface faces neither the positive L direction nor the negative L direction.)

To calculate the ratio between the components of the vector, we have to go from one corner of the surface to another adjacent corner, and work out over what distance each of the W, L and H components changes while another of the W, L or H components is changing. It sounds confusing, so I’ll do it now.

 
  • As we move from a corner of the surface where W=−100 to an adjacent corner of the surface where W=+100, note that H changes also. Because something else is changing as well, we’ll let ΔW=200, which is the difference between the two W values.
  • Now as we move from a corner of the surface where H=20 to an adjacent corner where H=0, note that W changes also. (We just found that H changes with W anyway, but I’m doing it from each perspective for the sake of demonstration.) So because something else is changing as we change H, we’ll let ΔH=−20.
  • Finally, moving from a corner where L=0 to an adjacent corner where L=200, we note that neither of the H and W components change at all. This indicates that no matter how long we make the surface in the L direction, the normal will not change. In turn, this means that the surface normal must be perpendicular to the L axis, i.e. there is no L component. Even though L changes by 200, nothing else changes with it, so we’re calling ΔL=∞. This means that you’d have to go to L=∞ (infinity) before you could get any of the other components to change while changing L.

I imagine this could be quite confusing still, especially since I have no diagrams to help illustrate.

So now I’ve found ΔW=200, ΔL=∞ and ΔH=−20. Finally we can get the normal to the surface!

Remember the normal is defined like X/Y/Z, so:

 
 X = 1/ΔW
 Y = 1/ΔL
 Z = 1/ΔH

Substituting in the values will give us the normal, albeit in small decimal numbers. We can just multiply the whole normal by some factor that will give us nice numbers, remembering that Rail 3D cares only for the direction of the normal, not the length. The result:

 
 Normal                 Result       Result      “Nice numbers”
 component   Formula  (fraction)    (decimal)       x by 200
    X          1/ΔW     1/200         0.005             1
    Y          1/ΔL     1/∞           0                 0
    Z          1/ΔH     1/20          0.05             10

So finally we find that the normal is 1,0,10. This is also what we found using the guessing method, but I knew then what it was going to be anyway. :-) Our final strip:

 
 strip
 −100/200/20    1/0/10
 −100/0/20      1/0/10
 100/200/0      1/0/10
 100/0/0        1/0/10
 endstrip

This may seem a long winded method, but I have explained the whole thing out in all its gory detail and in reality you could just look at it and say “well, W changes by 200 and H by 20, and L is not involved, so the ratio will be 10:1 or 1:10, and the surface obviously faces ‘up’ more than it does ‘right’, so the normal will be 1,0,10.” You start needing this method if you have a surface on a weird angle involving all of W, L and H and you want to get the correct normal.

 

3.4 What about curving strips with more vertices?

Admittedly, everything thus far has been about a four-cornered flat panel tilted at some angle. What about if we added some more vertices so we had a curved surface?

While we’re at it, the notion of a curved surface is not entirely correct because the surface isn’t curved. All we’re doing in Rail 3D is trying to approximate a curve by using many smaller flat surfaces. The curved effect is then achieved by two means: a) changing the angle of the surface gradually, and b) R3D’s use of normals and lighting to smooth over the changes of angle.

Returning to the examples, let’s add another two vertices onto the end of the strip we were using last:

 
 strip
 −100/200/20    1/0/10
 −100/0/20      1/0/10
 100/200/0      1/0/10
 100/0/0        1/0/10
 200/200/0      0/0/1
 200/0/0        0/0/1
 endstrip
 

The last two vertices add the flat section at ground level onto the right-hand side of our tilted surface. I’ve drawn in a blue line between the flat and tilted sections so you can see where they intersect. Because this new section of the strip is flat on the ground, the normal is facing straight up, so I have put those normals in as 0/0/1.

(Technical note: Rail 3D strips actually work with triangles, so a triangle is filled in between the first three vertices, and then another triangle between vertices 2–4, then between vertices 3–5, and so on until the last vertex is reached. For our purposes we only need to consider the rectangles formed.)

Recall that I mentioned right at the start how Rail 3D wants normals at points, not surfaces. This is where it becomes important. Notice how the tilted surface is has vertices 1 - 4 at its corners, and the flat surface has vertices 3 - 6 at its corners. The two surfaces therefore share vertices 3 and 4, even though the two surfaces have different normals because of their different angles. Rail 3D wants one normal per point, yet we have a dilemma because the point is shared by surfaces with different normals. Which normal do we assign to the shared vertices?

The answer is both! You can add both of the normals together in a certain way to produce a resultant normal, which will be a combination of both normals. Despite this resultant normal not being perpendicular to either of the surfaces, remember that we are trying to approximate a curved surface with smaller, flat surfaces. This resultant normal is then perpendicular to the curved surface we’re trying to imitate. (Note: the normal is perpendicular to the surface at that point only.)

At the moment, these shared vertices are using the normal for the tilted surface. We want the lighting to create the impression that the tilted surface curves into the flat surface. If you look carefully, you can see that this curved impression created by the lighting happens solely on the flat surface. We can fix this by calculating the resultant normals.

 

3.5 Calculating resultant normals

This is almost straightforward addition, with the exception that each of the normal vectors you’re going to add together have to be the same length.

Why is this necessary? Well, if you add 0/0/1 and 1/0/1 then you’ll get 1/0/2, and it looks like it could be a reasonable resultant vector. However, if we had 0/0/50 and 1/0/1 then adding them together will give you 1/0/51, which is clearly not the same thing. All we did was make the first vector longer, but this has made it far too influential in the resultant vector.

To overcome this, we convert all the normal vectors to unit vectors before adding them. A unit vector is simply the vector in question divided by its own length, so that its new length is 1. To work out the length of a vector, we take the square root of the sum of the squares of the vector components. Since that sounds like a mouthful, I’ll write it out. If n is the normal vector, then the length, | n |, is:

 
 | n | = √(X2 + Y2 + Z2)

By dividing the normal vector by its own length, we get a vector that is of length 1. If we add vectors that all have length = 1 then we ensure that each vector has an even “weight” in the addition.

Going back to our 6-vertex strip with the tilted and flat surfaces, we were wanting to calculate the normals for vertices 3 and 4. These normals will be a combination of the normals associated with each of the two surfaces. We need to combine 1/0/10 and 0/0/1 to get a resultant normal for these vertices.

First, we need to make unit vectors. For 1/0/10, we find the length is:

 
    √(X2 + Y2 + Z2)
  = √(12 + 0 + 102)
  = √(101) 
  ≈ 10.05 units

And the vector then becomes:

 
 n = (1/√(101))(1,0,10)
   ≈ (0.1,0,1)
   = 0.1/0/1 …in Rail3D syntax.

The normal 0/0/1 is much easier because its length is already equal to 1. This is already a unit vector. Note that the first vector has length approximately equal to 10.05. In reality you would further approximate this to 10 – the decimal makes hardly any difference on this scale, and it’s not worth worrying about.

So our two unit length vectors are 0.1/0/1 and 0/0/1. We can now add them to find the resultant vector, which will be 0.1/0/2. Multiplying this by 10 to get nice numbers gives your final resultant vector as 1/0/20. And that’s it!

So our final strip looks like this:

 
 strip
 −100/200/20    1/0/10
 −100/0/20      1/0/10
 100/200/0      1/0/20
 100/0/0        1/0/20
 200/200/0      0/0/1
 200/0/0        0/0/1
 endstrip
 

We end up with three different normals being used here. The normal on vertices 1 and 2 is a normal to the tilted surface, and the normal on vertices 5 and 6 is a normal to the flat ground-level surface. The normal on vertices 3 and 4 is what we have just calculated and is a combination of the normals on both of the surfaces.

This is a bit of work to go to if you want to get it exactly right, and most of the time it’s not necessary and the guessing method will work ok. However, if you’re a pedant or you have something that is absolutely critical you get right, then the mathematical method might be best. It would also be useful for writing into a program to calculate strip normals automatically, something I’m thinking of doing myself sometime when I get the chance.

 

3.6 Calculating normals when there are adjacent, unparallel strips

Let’s pretend that the 6-vertex strip we were just dealing with is actually part of bowl, and that we want to put a new strip next to the original strip so that they share a side with each other. The only problem here is that the new strip will have to curve up a bit somewhere, and as a result will have different surface normals to our original strip.

We can go ahead as before and calculate these separately, although if the difference in angle is great, you may notice lines of different shading/lightness where the two strips join, especially if a directional light source (like a headlight) is lighting them. These two strips may have vertices at the same points along their common edge, but the strips won’t be using the same actual vertices since they are defining in separate strip statements. If you want to get the normals exactly right, you’re going to have to take into account all surfaces in all strips that have a vertex at that point.

With two adjacent strips, you’ll probably have four surfaces having or sharing a vertex at that point, two from each strip. All you have to do here is find the unit vectors for each of these four surfaces in exactly the same way we did before, and add them together to find the resultant normal. You can then use this normal for that particular vertex in each of the strips.

The only thing now is that you might wonder where on earth you came up with that normal, should you forget you were taking into account a neighbouring surface!

 

3.7 Notes on using cylinder tool normals

[There was a question about using the normals generated for a cylinder by the cylinder tool - this section is the response to that question.]

In relation to your thoughts about the normals produced by the cylinder tool, I can’t tell what dimensions and position the cylinders you’ve created have. I take it you were alluding to the similarities between the W/L/H coordinate and the X/Y/Z normal, and wondering why there is a pattern here and why we don’t make better use of it.

If you have created a cylinder that lies along any of the coordinate axes (i.e. W, L or H axes), then you’re going to get normals that look similar to the vertex coordinates themselves. Did you know that you can think about each W/L/H coordinate you specify as a vector? A W/L/H coordinate is really just an “arrow” that tells R3D how to get from the origin to the point you’re talking about. It just so happens that if you build a shape with radial symmetry (like a cylinder) around one of the axes, then the W/L/H vector for some point will be the same as the normal except with one component zero. The component that is zero will be the axis that the shape is around.

If you made a cylinder of radius 10 around the L axis, then you’re going to get normals like W/L/H points like 0/0/10 on top of the cylinder, 0/0/−10 underneath, 10/0/0 on the right and −10/0/0 on the left. On the angled bits everywhere else you’ll get values inbetween these vectors, and I can’t be bothered working these out now!

But it should make sense, and you should be able to see, that the normals to these points will be the same as the W/L/H coordinate, but the Y part of the normal will be zero. For example, at the top of the cylinder where the W/L/H coordinate is 0/0/10, you’ll want a normal that points straight up, and 0/0/10 will do the trick. My guess is that the cylinder tool doesn’t bother reducing the normals to smaller, nicer numbers, otherwise it might have worked out that 0/0/1 would be just as good and then the similarities between the W/L/H coordinate and the normal would not have been apparent.

As soon as you move this cylinder off the axis, say move it 100 units in the +W direction, you’ll have the top of the cylinder as 100/0/10 now, but the normal will still be 0/0/10. If you made the normal the same as the W/L/H coordinate at that point (i.e. 100/0/10) then you’d be saying that the surface at that point is facing mostly to the right and a little bit ‘up’. This would not be correct.

You would also get incorrect normals if the object around the axis was not radially symmetrical. By radial symmetry, I mean that the object is the same at some radius r from the centre no matter which direction out from the middle you go. Another example of a raidally symmetrical object is a sphere, this time about a single point. Anyway, in our case, the middle/centre is the L-axis, and the object is the surface of the cylinder, which is always the same distance out from the L-axis (I think we said it was 10). If we changed the object to a squashed cylinder (which is not radially symmetrical) and used the W/L/H coordinates as normals again, then we’d actually be defining normals as if the shape was a cylinder, and we’d get lighting effects as if it were a cylinder. For all intents and purposes, this probably wouldn’t be noticeable unless the cylinder was quite squashed, then you’d probably start to get shadows appearing on relatively flat parts of the surface.

It would be possible to use the cylinder tool to do the normals for you, but it would involve some extra manipulation of the cylinder strips. Assuming you wanted to make the curved front of a loco or something, you’d have to do the following:

 
  1. Decide what radius the curve has. Most curves on the front aren’t all that much, so the radius is probably going to be quite big, like the turning circle of a truck or something!
  2. Make a cylinder with one end at the origin (0/0/0) and set the radius to the same as what you determined in (1).
  3. Find the parts of the cylinder that represent the vertices on your panels at the front of the loco the closest.
  4. Use the corresponding normals from the cylinders on your front panels.

You can do it this way, but it’s a bit of an effort, especially properly approximating the radius of the curve on the loco and extracting the relevant parts of the cylinder. Then again, it doesn’t need to be exact. You could always guess the normals. If you get better at guessing reasonably ok normals then you’ll be able to do more complex things more easily without completely random guessing.

 

4  Summary

So that’s about it for working out normals. I have mentioned it a couple of times, but I’ll say it again - most of the time you won’t need to go anywhere this much detail in working out normals. A lot of this detail was to give an introduction to normals and then describe some ways to accurately work out normals, should you ever want to do that. There are, of course, other ways of mathematically calculating the normal, but they’re not covered here at the moment.

If you can remember that normal is like a flagpole out of the ground, and that all you’re trying to do is express that with some numbers that roughly tell Rail 3D which direction the ground is facing and the flagpole pointing, then you should be right.


Created: 09 August 2005 Revised: 12 August 2005


 


MRG 13/12/2013 14:27:35