World Wide Guide | Knowledge BankKukushkin's Notebook | Design Fundamentals


Placing 3-D objects

Putting 3-D objects into the scenery is very easy. However, in some cases the limitations of the graphics engine lead to interferences or visibility problems between different objects. This section describes how to resolve such problems.

The basic problem that has to be solved when displaying 3D objects is to ensure that completely or partially covered objects do not show through objects that cover them.

Introducing PerspectiveCall

FS5 provides a simple solution to this problem. SDL has a special instruction called PerspectiveCall().

Normally, the subroutine that displays an object is passed to this instruction. PerspectiveCall() does not actually call the subroutine, but only notifies FS5 of its address. Later, when all normal and LayerCalled scenery drawing instructions have been processed, FS5 starts displaying 3-D objects by calling routines passed to PerspectiveCall()s. The idea here is, 3-D objects always cover 2-D objects (in particular, ground polygons), so they should be drawn after all 2-D objects. Also, FS5 has to resolve visibility problems between 3-D objects. This is done by sorting all PerspectiveCalled objects according to their distance from the viewpoint and displaying them beginning from the most distant object and ending with the least distant one.

SDL has no simple means to determine the distance to an object. Thus, the distance to its RefPoint is used instead. This substitution is normally justified and does not cause big problems.

When objects are being sorted according to the distance, FS5 has not yet executed any of the PerspectiveCalled routines. However, it already needs to know locations of RefPoints in corresponding objects. In order to resolve this problem, all PerspectiveCalled routines have to follow a convention that ensures that the RefPoint is defined in their very beginnings. Such a routine must begin like this:

:House
Perspective     ; Confirmation for a PerspectiveCalled object
RefPoint( ... ) ; RefPoint used for sorting
...

The Perspective() instruction should be the first instruction in the routine. It serves as a confirmation of a PerspectiveCalled routine. Nothing is drawn if it is omitted, and other problems can appear too.

Immediately following Perspective(), the RefPoint used for sorting should be defined. This ensures that FS5 can retrieve RefPoint coordinates without actually executing the subroutine. This RefPoint is normally also used for displaying the object when the code is actually called. The routine can also contain additional RefPoints. However, only the RefPoint defined in its very beginning is used for the sorting process.

This sorting algorithm resolves visibility problems only when distances between objects are big enough. More exactly, for every two objects, the imaginary plane that passes through the middle of the line segment connecting their RefPoints and is orthogonal to this segment, should completely separate these objects. This condition should be also satisfied for 3-D objects defined outside of the visual scenery -- like synth scenery mountains or dynamic scenery. If this condition is not satisfied for a certain pair of objects, they will be displayed in a wrong order from a certain view angle, thus causing visibility problems. This condition is very often not satisfied when two objects of very different sizes are drawn close to each other. The bigger objects would normally cross the imaginary plane.

For example, a building placed near a mountain often shows through the mountain because, even if the distance to the RefPoint of the mountain is greater than the distance to the house, some parts of the mountain are still between the house and the viewpoint. Because the sorting algorithm used by FS5 cannot detect such cases, the drawing order is often incorrect.

Some special configurations cannot be displayed correctly regardless of the drawing order chosen. For example, an aircraft in a hangar should be drawn after the far wall of the hangar but before its near wall is drawn.

Because the sorting only occurs between objects (aircraft and hanger), such drawing order is not possible.

Some tricks to fix visibility problems

There are some tricks to resolve visibility problems when the default sorting of FS5 fails.

The simplest of them is to adjust the position of RefPoints inside conflicting objects in order to move imaginary planes so that objects become entirely separated. 'Adjusting' actually means moving the RefPoint and adjusting the delta coordinates of the object, so that only the RefPoint has to move, not the object itself.

For example, the visibility problem for a (single) building placed near a mountain can be solved by moving the RefPoint of the mountain towards the building.

The visibility problem of multiple buildings placed near a mountain can sometimes be solved by moving their RefPoints away from the mountain. However, this can easily lead to conflicts between buildings.

The main disadvantage of this method is that it can easily create much more new visibility conflicts than resolve. Thus, it can only be helpful with simple configurations.

Another caveat is that in SCASM, automatic vector calculation relies on delta coordinates. Adjusting the RefPoint would cause delta coordinates to be altered, thus also causing visibility vectors to change their direction. All this can lead to sudden problems within objects.

An easier method of adjusting the RefPoint is to define the second RefPoint immediately after the first one. The first RefPoint would only be used for sorting and can thus be (almost) freely moved. The second RefPoint would be used for drawing. Having two RefPoints instead of one requires, however, more computations and thus can reduce the frame rate if many objects are implemented this way.

Merging and splitting

Two more general approaches to solving visibility problems are merging objects and splitting them.

Merging means combining two or more 3-D objects into one and then using VectorJump()s to resolve visibility problems within this object. VectorJump()s are described in another section of this file. The resulting object is then drawn using only one PerspectiveCall().

Merging allows implementing almost any landscape. However, it also has its problems.

The main problem is potential visibility conflicts with other objects. The "object" produced by merging is normally much larger than its components. It can easily have visibility conflicts with nearby 3-D objects that are not part of it.

Also, merged objects can have problems with 3-D objects that do not come from the visual scenery, like dynamic scenery. Because the merged object is drawn as a whole, other objects can only be drawn either before or after it has been drawn. However, for a correct drawing order it is sometimes required that other objects are drawn between parts of a merged objects.

For example, when two nearby buildings are merged into one object, dynamic aircraft located between these buildings cannot be displayed correctly because from some viewing angles, the aircraft should be drawn after one building and before the other.

Some configurations, like buildings on an elevated ground, can only be displayed correctly by merging objects.

Merged objects also require slightly less computations than the same objects would when displayed using separate PerspectiveCall()s. The sorting of PerspectiveCalled objects is a slow operation, much slower than comparing these objects with VectorJump(). Thus, reducing the number of PerspectiveCalls can significally improve the frame rate.

An alternative approach to merging is splitting objects into smaller parts that can be displayed correctly with the default sorting process. Each of this parts should have its own PerspectiveCall() and RefPoint. For example, the Golden Gate bridge in the default scenery consists of multiple PerspectiveCalled parts.

The main problem when using splitting is to determine locations of RefPoints used for object parts and to ensure these parts are correctly 'attached' to each other. The second problem can be resolved by defining a second RefPoint with the same coordinates for all object parts. The first RefPoint in this case would only be used for sorting.

The first problem is a much more difficult one and no general solution exists. One of possible solutions is to split the object into rectangular pieces and put their RefPoints into their centers. This approach is used internally for synth scenery mountains.

Splitting requires multiple PerspectiveCall()s for each object. The total number of PerspectiveCalls per frame is limited in FS5, and too many PerspectiveCalls can easily reach this limit. Reaching this limit can cause some objects not to appear. This can be very unpleasant if, for example, parts of elevated ground do not appear. Split objects also require more computations, because of having to sort more PerspectiveCalls and the overhead of defining multiple RefPoints for a single object. This can reduce the frame rate significantly.

Splitting greatly reduces the possibility of visibility conflicts with other 3-D objects. Thus, it should be used mainly for large objects, like elevated ground. It can also be applied to merged objects in order to reduce visibility conflicts.

For example, the only way to display buildings on an elevated ground without visibility conflicts with the ground is to merge them and the ground into one object. However, an object that large would have visibility conflicts even with distant 3-D objects. Splitting this (merged) object into rectangular pieces containing individual buildings would reduce the size of PerspectiveCalled pieces, thus reducing the risk. It would also reduce visibility problems with the dynamic scenery, which would otherwise show through, or be completely covered by, the buildings.

Merged objects can easily run out of the 16K limit of an Area() block [FSASM: Group?R]. This problem can too be solved by splitting such an object into smaller pieces and putting them into separate Area() blocks.

No perfect solution

As can be seen, there is no perfect solution for resolving conflicts between 3-D objects. However, there are many small tricks that can resolve most problems.

PerspectiveCall() can often be confused with other ...Call() instructions. It is NOT a call instruction, because it does not execute the routine. For example, a sequence

RotatedCall( :CallHouse 0 0 45 ); Draw a rotated house... or not?
...
:CallHouse
PerspectiveCall( :House )
Return
...

would not work as could be expected. The coordinate system for PerspectiveCall() would indeed be rotated.

But the routine would not be immediately called and the coordinate system would be rotated back by the Return instruction. Later, the subroutine would be called with a non-rotated coordinate system. (Having a RefPoint in a RotatedCalled routine would cancel the rotation anyway). Similarly, the sequence

Smoothing( 1 )
PerspectiveCall( :Building )
Smoothing( 0 )

...

would display a building with image smoothing OFF.


Last updated 19 October 1996 by Gene Kraybill.