/////////////////////////////////////////////////////
/////////////////////////////////////////////////////
//
// ObjectBallSlider
//
/////////////////////////////////////////////////////
/////////////////////////////////////////////////////
#include "CKAll.h"
#include "A_CalcCollision.h"
#define MINVERTICES_FORCLASSIFY 64
CKObjectDeclaration *FillBehaviorObjectBallSliderDecl();
CKERROR CreateObjectBallSliderProto(CKBehaviorPrototype **);
int ObjectBallSlider(const CKBehaviorContext& behcontext);
CKERROR ObjectBallSliderCallBackObject(const CKBehaviorContext& behcontext);
// Inner struct
typedef struct {
VxVector Old_Pos;
} A_OBS;
//---
CKObjectDeclaration *FillBehaviorObjectBallSliderDecl()
{
CKObjectDeclaration *od = CreateCKObjectDeclaration("Object Slider");
od->SetDescription("Object Ball Slider ; Impede the 3d object from penetrating a 3dobject in the specified group.");
/* rem:
On: activates the process.
Off: deactivates the process.
Contact: activate each time there's a contact.
No Contact: activate each time there's no contact.
Radius: radius to impede object penetration (expressed in world absolute value).
Group: group in which other objects are to be concidered (the object [ball] will not penetrate those objects).
Reaction Vector: 3d vector response (not normalized).
Accuracy: maximum accuracy for object collision.
Touched Objects Group: group filled with all the touched objects when a contact occurs.
Place Optimization: if TRUE, the process will be optimized for levels designed with Places.
Instead of parsing all the objects of the group, and then performing a bounding box collision test for each of them,
we parse instead all the places and perform a hierarchical bounding box collision test with those places.
If the test is TRUE, then we parse the place hierarchy in a recursive way, and for each child of the place,
we perform again a hierarchical bounding box collision test ... and so on.
This method is very efficient if your level is splitted into places, and if each place contain - on average - more than one
collision object defined in the input group. Because in that way, just a few objects from the group will be tested.
*/
/* warning:
- the "Touched Objects Group" (in settings), is cleared and filled again at each contact events. It isn't clear if no collision occured.
Therefore you can use this group to retrieve all the touched object, even if the collision occured 5 or 10 frames ago.
- you can change the "radius" and the "group" at run-time.
- you can use the 'Reaction' output parameter to get information about the normal of the collision (don't forget to normalize if you need a normalized vector),
or to get the point of collision on the sphere [ contact_point = -Radius * Normalize( Reaction ) ].
- unlike the 'Layer Slider' building block, the radius isn't proportionnal to the object's bounding sphere. Radius is expressed in absolute world values.
- all object in group must be 3d Ojects ( no Groups, no Places no Data Arrays ... etc, only 3d Objects or derived types ).
- all object in group must be 1x1x1 scaled.
- If you want to teleport your object from place to an other, you should deactivate the building block ( triggers OFF, or deactivate the script ).
- Do not check "Place Optimization" setting if you have more places in your level than objects in the group (it would result in a slower process).
*/
od->SetType( CKDLL_BEHAVIORPROTOTYPE );
od->SetCategory("Collisions/3D Entity");
od->SetGuid(CKGUID(0x13603db0,0x16bc321a));
od->SetAuthorGuid(VIRTOOLS_GUID);
od->SetAuthorName("Virtools");
od->SetVersion(0x00010000);
od->SetCreationFunction(CreateObjectBallSliderProto);
od->SetCompatibleClassId(CKCID_3DENTITY);
return od;
}
CKERROR CreateObjectBallSliderProto(CKBehaviorPrototype **pproto)
{
CKBehaviorPrototype *proto = CreateCKBehaviorPrototype("Object Slider");
if(!proto) return CKERR_OUTOFMEMORY;
proto->DeclareInput("On");
proto->DeclareInput("Off");
proto->DeclareOutput("Contact");
proto->DeclareOutput("No Contact");
proto->DeclareInParameter("Radius", CKPGUID_FLOAT, "1");
proto->DeclareInParameter("Group", CKPGUID_GROUP);
proto->DeclareOutParameter("Reaction", CKPGUID_VECTOR);
proto->DeclareSetting("Accuracy", CKPGUID_INT, "0");
proto->DeclareLocalParameter(NULL, CKPGUID_VOIDBUF ); // inner struct OBS
proto->DeclareSetting("Touched Objects Group", CKPGUID_GROUP);
proto->DeclareSetting("Place Optimization", CKPGUID_BOOL, FALSE);
proto->SetFlags(CK_BEHAVIORPROTOTYPE_NORMAL);
proto->SetFunction(ObjectBallSlider);
proto->SetBehaviorFlags(CKBEHAVIOR_TARGETABLE);
proto->SetBehaviorCallbackFct( ObjectBallSliderCallBackObject );
*pproto = proto;
return CK_OK;
}
/************************************************/
/* CallBack */
/************************************************/
CKERROR ObjectBallSliderCallBackObject(const CKBehaviorContext& behcontext)
{
CKBehavior *beh = behcontext.Behavior;
switch( behcontext.CallbackMessage ){
case CKM_BEHAVIORCREATE:
case CKM_BEHAVIORLOAD:
{
A_OBS *obs = new A_OBS;
beh->SetLocalParameterValue(1, &obs, sizeof(obs) );
} break;
case CKM_BEHAVIORDELETE:
{
A_OBS *obs=NULL;
beh->GetLocalParameterValue(1, &obs);
if( obs ){
delete obs;
obs=NULL;
beh->SetLocalParameterValue(1, &obs);
}
} break;
}
return CKBR_OK;
}
/***********************************************/
/* Thread Object */
/* convenient function to thread */
/* the ball/object possible collision tests */
/***********************************************/
inline void ThreadObjectOBS( CK3dEntity *const CurrentEntity, CK3dEntity *const ball, \
const VxVector &ball_pos, A_CollBallFace &collballface,\
const float radius, int &precis_tmp, CKGroup *touched_group,\
CKBOOL &touched ){
//________________________________/ Rejection if = Ball
if( !CurrentEntity || CurrentEntity==ball ) return;
//______________________________/ Rejection by Bounding Cube (in world)
const VxBbox &box = CurrentEntity->GetBoundingBox();
if( box.Min.x-radius <= ball_pos.x && box.Max.x+radius >= ball_pos.x &&
box.Min.y-radius <= ball_pos.y && box.Max.y+radius >= ball_pos.y &&
box.Min.z-radius <= ball_pos.z && box.Max.z+radius >= ball_pos.z )
{
CKMesh *CurrentMesh = CurrentEntity->GetCurrentMesh();
//________________________________/ Rejection if no mesh
if( !CurrentMesh ) return;
CKBOOL current_object_touched = FALSE;
VxVector ball_pos_local,dif;
collballface.UpdateValues( radius, &ball_pos_local, CurrentMesh );
ball->GetPosition( &ball_pos_local, CurrentEntity );
VxVector contact;
collballface.contact = &contact;
CKVINDEX* faceIndices = CurrentMesh->GetFacesIndices();
int vcount = CurrentMesh->GetVertexCount();
int fcount = CurrentMesh->GetFaceCount();
if (vcount > MINVERTICES_FORCLASSIFY) { // we need to classify the vertices
// Get the vertices
CKDWORD vStride = 0;
BYTE* vPos = (BYTE*)collballface.mesh->GetPositionsPtr(&vStride);
// Ask CK for a block of preallocated memory for our flags
CKContext* ctx = collballface.mesh->GetCKContext();
VxScratch mempool(vcount*sizeof(float));
DWORD* vFlags = (DWORD *)mempool.Mem();
// we create the box of the sphere, locally to the object
VxBbox spherebox;
spherebox.SetCenter(ball_pos_local,VxVector(radius,radius,radius));
/////////////////////////
// The classification
////////////////////////
if (vcount > fcount*2) { // Single axis classification
// we select the maximum axis
const VxBbox& localBox = CurrentMesh->GetLocalBox();
VxVector dif = localBox.Max-localBox.Min;
int axis = 0;
float max = dif.x;
if (max < dif.y) {
max = dif.y;
axis = 1;
}
if (max < dif.z) {
max = dif.z;
axis = 2;
}
spherebox.ClassifyVerticesOneAxis(vcount,vPos, vStride,axis,vFlags);
} else { // 3 axis classification
spherebox.ClassifyVertices(vcount,vPos, vStride, vFlags);
}
// now we only need to consider the faces not enterely
// culled by one faces of the spherebox
for( int f=0 ; fTranslate( &dif, CurrentEntity );
ball_pos_local += dif;
precis_tmp = 0;
current_object_touched = TRUE;
}
}
} else { // no need to bother the classification
for( int f=0 ; fTranslate( &dif, CurrentEntity );
ball_pos_local += dif;
precis_tmp = 0;
current_object_touched = TRUE;
}
}
}
if( current_object_touched ){
if( touched_group ){
if( !touched ) touched_group->Clear();
touched_group->AddObject(CurrentEntity);
}
touched=TRUE;
}
}
}
/***********************************************/
/* Parse Hierarchy Child OBS */
/* convenient function to parse the hierarchy */
/* and call ThreadObjectOBS if necessary. */
/* Recursive function */
/***********************************************/
void ParseHierarchyChildOBS( CK3dEntity *const CurrentEntity, CK3dEntity *const ball, \
const VxVector &ball_pos, A_CollBallFace &collballface,\
const float radius, int &precis_tmp, CKGroup *touched_group,\
CKBOOL &touched, CKGroup *const group ){
//______________________________/ Rejection by Hierarchical Bounding Cube (in world)
const VxBbox &box = CurrentEntity->GetHierarchicalBox();
if( box.Min.x-radius <= ball_pos.x && box.Max.x+radius >= ball_pos.x &&
box.Min.y-radius <= ball_pos.y && box.Max.y+radius >= ball_pos.y &&
box.Min.z-radius <= ball_pos.z && box.Max.z+radius >= ball_pos.z )
{
int childCount = CurrentEntity->GetChildrenCount();
CK3dEntity *child;
while( childCount ){
child = CurrentEntity->GetChild(--childCount);
if( !child ) continue;
if( child->IsInGroup( group )){
ThreadObjectOBS( child, ball, ball_pos, collballface,\
radius, precis_tmp, touched_group, touched );
}
if( child->GetChildrenCount() ){
ParseHierarchyChildOBS( child, ball, ball_pos, collballface,\
radius, precis_tmp, touched_group, touched, group );
}
}
}
}
/***********************************************/
/* Main Function */
/***********************************************/
int ObjectBallSlider(const CKBehaviorContext& behcontext)
{
CKBehavior* beh = behcontext.Behavior;
if( beh->IsInputActive(1) ){ // enter by OFF
beh->ActivateInput(1, FALSE);
return CKBR_OK;
}
CK3dEntity *ball = (CK3dEntity *) beh->GetTarget();
if( !ball ) {
if( beh->IsInputActive(0) )
beh->ActivateInput(0, FALSE);
beh->ActivateOutput(1);
return CKBR_OWNERERROR;
}
A_OBS *obs = NULL; // get local param
beh->GetLocalParameterValue(1, &obs);
VxVector init_pos;
ball->GetPosition( &init_pos );
if( beh->IsInputActive(0) ){ // enter by ON
beh->ActivateInput(0, FALSE);
obs->Old_Pos = init_pos;
}
float radius = 1.0f; // get radius
beh->GetInputParameterValue( 0, &radius );
//---
CKBOOL placeOptim = FALSE; // use portal optimization ?
beh->GetLocalParameterValue(3, &placeOptim);
CKGroup *group = (CKGroup *)beh->GetInputParameterObject( 1 );
if( !group ) return CKBR_OK;
CKGroup *touched_group = (CKGroup *)beh->GetLocalParameterObject( 2 );
VxVector contact_world;
VxVector ball_pos, pos;
CK3dEntity *CurrentEntity;
A_CollBallFace collballface;
int precis = 0; // get accuracy level
beh->GetLocalParameterValue(0, &precis);
if( precis < 0 ) precis = 0;
int precis_tmp = precis;
VxVector ball_step;
if( precis ){
precis_tmp = (int)(Magnitude( init_pos - obs->Old_Pos ) / (0.9f*radius));
if( precis_tmp > precis ) precis_tmp = precis;
ball_step = ( init_pos - obs->Old_Pos ) / (precis_tmp + 1.0f);
ball_pos = obs->Old_Pos;
} else {
ball_pos = init_pos;
}
CKBOOL touched = FALSE;
//________________________________________
//--- if we parse a group of object
if( !placeOptim ){
do{
if( precis ){
ball_pos += ball_step;
ball->SetPosition( &ball_pos );
}
int objCount = group->GetObjectCount();
for( int i=0 ; iGetObject(i);
ThreadObjectOBS( CurrentEntity, ball, ball_pos, collballface,\
radius, precis_tmp, touched_group, touched );
}
} while( precis_tmp-- );
//________________________________________
//--- if we parse places & hierarchy
} else {
CKContext *ctx = behcontext.Context;
// get places count (if placeOptim)
CK_ID *placeID;
CKPlace *currentPlace;
int placeCount = ctx->GetObjectsCountByClassID(CKCID_PLACE);
placeID = ctx->GetObjectsListByClassID(CKCID_PLACE);
do{
if( precis ){
ball_pos += ball_step;
ball->SetPosition( &ball_pos );
}
for( int a=0 ; aGetObject(placeID[a]);
ParseHierarchyChildOBS( currentPlace, ball, ball_pos, collballface,\
radius, precis_tmp, touched_group, touched, group );
}
} while( precis_tmp-- );
}
//--- responce
VxVector responce;
ball->GetPosition( &responce );
responce -= ball_pos;
beh->SetOutputParameterValue(0, &responce);
if( precis ){
ball->GetPosition( &obs->Old_Pos );
}
if( touched ) beh->ActivateOutput( 0 );
else beh->ActivateOutput( 1 );
return CKBR_ACTIVATENEXTFRAME;
}