...
You can turn on similar shadow volumes on the debug panel from inside NWN:EE. You can also turn on a cross-hair marker for lights using the "renderlights" checkbox.
So far this all seems very intuitive because it stems from very simple geometry. However, shadow engines need to consider things like hollow spaces inside these shapes.
...
Another simple method often said to correct shadows is to reduce the complexity of the model. This only works for barely concave meshes to convex meshes and only if the pivot is inside or nearly touching the mesh to begin with. Let's take a look at a sphere produced in GMax. By default the sphere can be very detailed, but the pivot will bet be at the very bottom of there sphere touching the bottommost bottom-most vertex. The following image shows a bad sphere and a good sphere, but both cast good shadows. The bad sphere has the pivot technically outside the sphere, and the good sphere has the pivot inside the sphere. However, both spheres cast good shadows because the light is above the sphere, and the error draws back to the pivot, which is touching the sphere, so you never see it.
...
However, if we draw the pivot away from the mesh body and definitely place it outside the mesh, then we start to see really strange effects. As an example, I am standing inside this tube with my approximate light position marked by the cyan cross-hairs. The red-circled pivot belongs to the tube. Notice the shadow is now thrown to the other side and off the tile. Instead of projecting the shape of the outside of the tube, the shape of the hole in the tube seems to be projected. This shadow is wrong in every way except luminance.
If the light source is place placed behind just one more face, there are instead two projections, both of which are wrong.
...
What even is happening in these cases? Let's look at the shadow volume mesh to find out.
It looks like the shadow volume is projecting not only the pivot outside the mesh (circled in red) but also projecting backward from the pivot into space, then coming back through the floor to project somewhere else entirely? So very wrong.
In the case of a sphere, the fix is simple: just move the pivot inside the mesh if it causes an issue. However, the problem with a hulled tube is that there is no position inside the tube mesh which is behind all faces. In fact, there's a good chance that any position could only exist behind about a third or a quarter of the total faces.
To fix objects like this, they need to be split.
Think about the lowest number of meshes we would need to split this shape into so that there were X number of pivots that could exist behind all faces of the split. We could split out the inside, the outside, and the caps into four sections. However that leaves the inside as a concave tube which any point can only see half of. Likewise, the ends and the outside can be one mesh with a shared center.
So what we can do is split the tube into outside plus ends, and then split the remainder inside into two or three radial sections. This tube has 18 radial faces, so if we split it into three parts of 6 then we should be very safe. Each part is color coded in the image below (red, green, blue, and white).
...
--Returns 1 if vNormalA faces the same direction as vNormalB, otherwise returns 0fn0
fn GetIsFacingSameGeneralDirection vNormalA vNormalB = (
vNormalA = normalize vNormalA vNormalA
vNormalB = normalize vNormalB vNormalB
(dot vNormalA vNormalB) > 0
)
Determine if the origin is behind a face
...
--Returns 1 if origin of oNode is behind face iFace of oNode
fn GetIsOriginBehindFace oNode iFace = (
local vFaceCenter = meshop.getfacecenter oNode iFace iFace
local vDiff = vFaceCenter - oNode.position position
local vNormalA = normalize vDiff vDiff
local vNormalB = getfacenormal oNode iFace iFace
(dot vNormalA vNormalB) > 0
)
Simple First Test: Set the pivot to the average of all face centers minus their face normal
...
--Does a simple check to see if shadow pivot position is behind all faces
--Does NOT check if there are overlapped faces from that position
--Returns 1 if all faces on node oNode are in front of the pivot, or 0 if any one face is not
--another position value can be specified in vPos, but if left empty will use the position of oNode
fn CheckShadowPivotBCheckShadowPivot oNode vPos:undefined = (
if vPos == undefined then vPos = oNode.position
local keepIt = true
local f = 1
while f <= oNode.numfaces and keepIt do (
local vFaceCenter = meshop.getfacecenter oNode f
local vPointToFace = normalize (vFaceCenter-vPos)
local vFaceNormal = getfacenormal oNode f
local fFaceDotNormal = dot vFaceNormal vPointToFace
if fFaceDotNormal < 0 then (
keepIt = false
)
f+=1
)
return keepIt
)
...




