The idea is that by default everything simply uses BoundingBox, and that is the only method that you really have to override. You do not have to (in fact, usually you should not) call "inherited" when overriding collision methods mentioned above.
Height of a point above the 3D model. This checks ray collision, from Position along the negated GravityUp vector. Measures distance to the nearest scene item (called "ground" here).
Height above the ground. One height unit equals one GravityUp vector. Always use normalized GravityUp vector if you expect to receive here a normal distance.
AboveHeight is always set to MaxSingle when returned result is False (this guarantee simplifies some code).
Pointer to P3DTriangle representing the ground. Must be Nil if returned result is False. May be Nil even if we returned True (not all 3D objects may be able to generate P3DTriangle information about collision).
This may be useful for example to make a footsteps sound dependent on texture of the ground. Or to decrease player life points for walking on hot lava. See "The Castle" game for examples.
If the 3D scene is hit. False means that Position floats above an empty space. That is, if you turn gravity on, it will fall down forever, as far as this 3D scene is concerned.
Can other 3D object (maybe a player) move without colliding with this object.
If IsRadius, then you should prefer to perform exact collision with sphere of given radius (must be > 0). At the very least, this checks that the line segment between OldPos and NewPos doesn't collide, and that sphere with given Radius centered around NewPos doesn't collide.
If not IsRadius, or if checking for collisions with sphere is not possible for some reasons, then you can check for collisions with boxes. OldBox should usually be ignored (it can be useful when collision-checking has to be approximate in some corner cases, see TCreature.MoveCollision). NewBox plays the same role as "sphere centered around NewPos" in paragraph above.
Overloaded version with separate ProposedNewPos and NewPos parameters allows you to accept the move, but for NewPos (that should be some slightly modified version of ProposedNewPos). This allows to implement wall-sliding: when camera tries to walk into the wall, we will change movement to move alongside the wall (instead of just completely blocking the move). When this version returns False, it's undefined what is the NewPos.
If non-nil, these are automatically filled with the details about the collision. If the result is False, the Details contents are untouched. If the result is True, the Details contents are set to describe the 3D objects hierarchy that caused this collision.
Check collision with a point in 2D (which is an infinite line along the Z axis in 3D).
Note that PointCollision2D and SphereCollision2Ddo not work reliably on objects that have 3D rotations, that is: rotations that change the direction of Z axis! This applies to all ways of rotating – using the T3DCustomTransform descendants (like T3DTransform) or using the X3D node TTransformNode (within a TCastleSce).
The reason: we transform the point (or sphere center) to the local coordinates, and we should also transform the Z axis to the local coordinates, to be always correct. Right now, we don't do the latter.
And we don't want to do it (at least not in all cases)! The simple 2D point collision check would then actually perform a 3D line collision check, thus PointCollision2D would lose all the speed benefits over LineCollision. PointCollision2D would become a simple shortcut to perform LineCollision with a line parallel to Z axis.
And in case of 2D games, or mostly 2D games, this speed loss would not be justified. Often you know that your objects have no 3D rotations, for example if your animations are done in Spine.
In the future, we may overcome this limitation. To do this, we will detect whether the transformation is "only 2D" (actually this part is easy, you can detect it by looking at the matrix even, so check whether appropriate numbers are zero). And then PointCollision2D will change to LineCollision, and SphereCollision2D will change to something like ExtrudedCirleCollision, only when necessary.
Check collision with a ray, building a TRayCollision result. Returns a collision as TRayCollision instance, or Nil if no collision. Caller is responsible for freeing the returned TRayCollision instance.
Contrary to other collision routines, this should ignore the Collides property. The Collides property specifies whether item collides with camera. And this method is used for picking (pointing) 3D stuff — everything visible can be picked, collidable or not. Instead, this looks at Pickable property (actually, at GetPickable method result).
This always returns the first collision with the 3D world, that is the one with smallest TRayCollision.Distance. For example, when implemented in T3DList, this checks collisions for all list items, and chooses the closest one.
constructor Create(AOwner: TComponent); override;
destructor Destroy; override;
function GetExists: boolean; virtual;
Does item really exist, see Exists and Enable, Disable. It T3D class, returns True if Exists and not disabled. May be modified in subclasses, to return something more complicated.
function GetCollides: boolean; virtual;
Does item really collide, see Collides. It T3D class, returns Collides and GetExists. May be modified in subclasses, to return something more complicated.
function GetPickable: boolean; virtual;
Is item really pickable, see Pickable. It T3D class, returns Pickable and GetExists. May be modified in subclasses, to return something more complicated.
Items that are at least once disabled are treated like not existing. Every Disable call should always be paired with Enable call (usually using try ... finally .... end block). Internally, we keep a counter of how many times the object is disabled, and if this counter is <> 0 then GetExists returns False. Using this is useful for taming collisions, especially to avoid self-collisions (when a creature moves, it doesn't want to collide with other creatures, but obviously it doesn't collide with it's own bounding volume).
Render given object. Should check and immediately exit when Exists is False. Should render only parts with matching Params.Transparency and Params.ShadowVolumesReceivers values (it may be called more than once to render frame).
May be used to optimize rendering, to not render the parts outside the Frustum.
It does shadow volumes culling inside (so ShadowVolumeRenderer should have FrustumCullingInit already initialized).
ParentTransform and ParentTransformIsIdentity describe the transformation of this object in the 3D world. T3D objects may be organized in a hierarchy when parent transforms it's children. When ParentTransformIsIdentity, ParentTransform must be IdentityMatrix4Single (it's not guaranteed that when ParentTransformIsIdentity = True, Transform value will be ignored !).
Implementation note: In Render, it is usually possible to implement ParentTransform* by glPush/PopMatrix and Frustum.Move tricks. But RenderShadowVolume needs actual transformation explicitly: ShadowMaybeVisible needs actual box position in world coordinates, so bounding box has to be transformed by ParentTransform. And TCastleScene.RenderShadowVolumeCore needs explicit ParentTransform to correctly detect front/back sides (for silhouette edges and volume capping).
Prepare resources, making various methods (like rendering and such) to execute fast.
This requires OpenGL to be initailized for most 3D objects. If not, some parts of preparations will be aborted.
This makes sure that appropriate methods execute as fast as possible. It's never required to call this method — everything will be prepared "as needed" anyway. But if you allow everything to be prepared "as needed", then e.g. the first Render call may take a long time because it may have to prepare resources that will be reused in next Render calls. This is bad, as your program will seem very slow at the beginning (when rendering resources are prepared, so a first frame, or a couple of first frames, if it's something like a precalculated animation). To avoid this, call this method, showing the user something like "now we're preparing the resources — please wait".
For OpenGL rendered objects, this method ties this object to the current OpenGL context. But it doesn't change any OpenGL state or buffers contents (at most, it allocates some texture and display list names).
function PointingDeviceActivate(const Active: boolean; const Distance: Single): boolean; virtual;
Pointing device (usually mouse) events. Return True if you handled the event.
PointingDeviceActivate signals that the picking button (usually, left mouse button) is pressed or released (depending on Active parameter).
Note that the exact key or mouse responsible for this is configurable in our engine by Input_Interact. By default it's the left mouse button, as is usual for VRML/X3D browsers. But it can be configured to be other mouse button or a key, for example most 3D games use "e" key to interact.
PointingDeviceMove receives Pick information about what exactly is hit by the 3D ray corresponding to the current mouse position. It contains the detailed information about 3D point, triangle and ray (all in local coordinate system) that are indicated by the mouse. PointingDeviceActivate does not receive this information now (because it may happen in obscure situations when ray direction is not known; this is all related to our "fallback to MainScene" mechanism).
The pointing device event (activation, deactivation or move) is send first to the innermost 3D object. That is, we first send this event to the first item on TRayCollision list corresponding to the current ray. This way, the innermost ("most local") 3D object has the chance to handle this event first. If the event is not handled, it is passed to other 3D objects (we simply iterate over the TRayCollision list). If nothing on TRayCollision list handled the item, it is eventually passed to main 3D scene (TCastleSceneManager.MainScene), if it wasn't already present on TRayCollision list.
function PointingDeviceMove(const Pick: TRayCollisionNode; const Distance: Single): boolean; virtual;
procedure Update(const SecondsPassed: Single; var RemoveMe: TRemoveType); virtual;
Continously occuring event, for various tasks.
Set this to rtRemove or rtRemoveAndFree to remove this item from 3D world (parent list) after Update finished. rtRemoveAndFree additionally will free this item. Initially it's rtNone when this method is called.
Something visible changed inside this 3D object. This is usually called by implementation of this 3D object, to notify others that it changed.
Changes is a set describing what changes occurred. See TVisibleChange docs for more information. It must specify all things that possibly changed.
Changes can be , meaning "something tells us to redraw, but no visible change happened yet, maybe something will happen during a redraw" (this is used when e.g. possibly LOD level changed). We still broadcast VisibleChangeNotification, even when Changes=.
Something visible changed in the 3D world. This is usually called by our container (like TCastleSceneManager), to allow this 3D object to react (e.g. by regenerating mirror textures) to changes in the 3D world (not necessarily in this 3D object, maybe in some other T3D instance).
If you want to react to visibility changes, you should override this.
Are we in the middle of dragging something by moving the mouse.
This should be set to True to disable camera navigation methods that also use mouse move. In practice, to disable TExamineCamera view rotation/movement by moving the mouse, as it makes (comfortable) dragging practically impossible (at each mouse move, view changes...).
In particular, when you operate on active X3D pointing-device sensors (like drag sensors, e.g. PlaneSensor, but also TouchSensor may use it).
Middle point, usually "eye point", of the 3D model. This is used for sphere center (if overriden Sphere returns True) and is the central point from which collisions of this object are checked (Move, MoveAllowed, Height, LineOfSight). For 3D things like level scene this is mostly useless (as you will leave Sphere at default False then, and the scene itself doesn't move), but it's crucial for dynamic 3D things like player and moving creatures.
In short, it's usually most comfortable to think about this as a position of the eye, or the middle of the creature's head.
In an ideal situation, it should not be based on anything dynamic. For example, when this is based on the current bounding box of the animation, there is a risk that a large and sudden change in animation box could make the Middle point to jump to the other side of the wall (breaking collisions, as it changes Middle without a chance to check for collisions by MoveAllowed). Ideally, it should remain constant even when the shape of the object changes, and be possible to change only when MoveAllowed is checked (so only when T3DOrient.Position or T3DTransform.Translation can change).
Sector where the middle of this 3D object is. Used for AI. Nil if none (maybe because we're not part of any world, maybe because sectors of the world were not initialized, or maybe simply because we're outside of all sectors).
function Sphere(out Radius: Single): boolean; virtual;
Can the approximate sphere (around Middle point) be used for some collision-detection tasks. If True then Radius (and Middle point) determine the approximate sphere surrounding the 3D object (it does not have to be a perfect bounding sphere around the object), and it may be used for some collisions instead of BoundingBox. See CollidesWithMoving and MoveAllowed for when it may happen.
By default, in T3D class, this always returns False and Sphere is undefined.
The advantages of using a sphere, that does not have to be a perfect bounding sphere (it may be smaller than necessary, and only account e.g. for upper body part of the creature), are:
It can have constant radius, even though the actual creature animates. This allows us to perfectly, reliably guarantee that sphere absolutely never collides with level and such.
In case of a tight bounding volume (box or sphere) that animates, this guarantee is not really possible. Simply increasing time changes the animation to the next frame, which may be slightly larger in one dimension because e.g. creature moves a hand in this direction. This means that simply increasing time may change the non-collidable creature into a collidable one, if creature stands close to a wall/other creature and such. And we cannot simply stop/reverse an arbitrary animation at an arbitrary time (to avoid collision), this would look weird for some animations and would require some additional work at preparing animations and designing AI (as then "every action can be interrupted").
Also using a bounding volume large enough to account for all possible positions is not doable, as it would be too large. Consider that for humanoid creatures, walking animation usually has tall and thin bounding box (creature stands) but dead/lying animation usually has flat and wide bounding box.
So, only a bounding volume (like a sphere) that may be smaller than bounding volume can remain constant and easily guarantee the assertion "it never collides".
This means that using such sphere results in simpler collision detection routines, as they may assume that collision doesn't occur. In contrast, detection routines looking at our (possibly animated) BoundingBox must take into account that collision may already be happening, and they must incorporate code to allow creatures/players to "get unstruck".
Using smaller sphere also allows to naturally ascend the stairs and upward slopes. Sphere can move forward slightly, and then creature may raise up, to reach it's preferred height. Then sphere can move further forward, and so on. This alllows to allow stair climbing for creatures without any extra effort in the code.
The downside is that creature legs will temporarily "sink into the floor" when climbing up the stairs. But it's not noticeable if "growing up" mechanism works fast enough.
Sphere is far from perfect as a bounding volume — it's too small, sometimes also too large, sometimes both at the same time...
Since the Sphere radius remains always the same, it must be good for many creature animation frames. In cases where the sphere isn't suitable, and you don't need advantages above — you can make Sphere return False. E.g. a dead creature may be stuck in a wall, and it doesn't have to climb stairs. So you don't really need sphere advantages listed above, and Sphere may return False when creature is in dying state.
But still it may be a problem sometimes, if some creature states have entirely different animations and bounding boxes. Then you will be forced to choose one universal Radius for all creature states. And you need constant radius to keep the advantage above of "guarantee".
1. Obviously you can't set radius too small, because if it's much smaller than actual creature's geometry then the creature will noticeably collide with level geometry and other creatures.
2. On the other hand, you can't set radius too large (or move sphere center, Middle, much lower). This would block stair climbing.
function Height(const MyPosition: TVector3Single; out AboveHeight: Single): boolean;
Get height of my point above the rest of the 3D world.
This ignores the geometry of this 3D object (to not accidentaly collide with your own geometry), and checks collisions with the rest of the world.
Is the move from OldPos to ProposedNewPos possible for me. Returns true and sets NewPos if some move is allowed. Overloaded version without ProposedNewPos doesn't do wall-sliding, and only answers if exactly this move is allowed.
If this 3D object allows to use sphere as the bounding volume (see Sphere), then this sphere must be centered around OldPos, not some other point. That is, we assume that Sphere returns Center that is equal to OldPos.
This ignores the geometry of this 3D object (to not accidentaly collide with your own geometry), and checks collisions with the rest of the world.
function MoveAllowed(const OldPos, NewPos: TVector3Single; const BecauseOfGravity: boolean): boolean;
In case this scene shares lights with other scenes, this is the source scene. In usual circumstances, this method simply returns Self, which means "no sharing". In case of scenes that are children of TCastlePrecalculatedAnimation, their Shared methods all point to the 1st animation scene.
Setting this to False pretty much turns everything of this 3D object to "off". This is useful for objects that disappear completely from the level when something happens. You could just as well remove this object from TCastleSceneManager.Items tree, but sometimes it's more comfortable to simply turn this property to False.
The only exception are the collisions with T3DMoving instances (movable world parts like elevators and doors) that have their own detection routines and look at CollidesWithMoving property of other objects. That is, the T3DMoving instance itself must still have Collides = True, but it interacts with other objects if and only if they have CollidesWithMoving = True (ignoring their Collides value). This allows items to be moved by elevators, but still player and creatures can pass through them.
Note that if not Exists then this doesn't matter (not existing objects never participate in collision detection).
Descendants may also override GetCollides method. Sometimes it's more comfortable than changing the property value.
World containing this 3D object. In other words, the root of 3D objects tree containing this object. In practice, the world instance is always 1-1 corresponding to a particular TCastleSceneManager instance (each scene manager has it's world instance in TCastleSceneManager.Items).
Can this object be pushed by (or block movement of) doors, elevators and other moving level parts (T3DMoving instances).
Some 3D moving objects may try to avoid crushing this item. Like an automatic door that stops it's closing animation to not crush things standing in the doorway.
Some other 3D moving objects may push this object. Like elevators (vertical, or horizontal moving platforms). We may use sphere (see T3D.Sphere) for checking collisions, or bounding box (T3D.BoundingBox), depending on need. The item is moved using T3D.Translate, so make sure it actually does something (for example, by descending from T3DTransform, that provides natural T3D.Translate implementation).
If this 3D object is rendered as part of TCastleSceneManager, and TCastleSceneManager.UseGlobalLights is True, then this property allows to make an exception for this 3D object: even though TCastleSceneManager.UseGlobalLights is True, do not use global lights for this 3D object.
Note that this is not applied recursively. Instead, it is checked at each T3D instance that checks TRenderParams.BaseLights. In practice, it is only checked at TCastleScene, unless you do custom rendering on your own.