diff options
Diffstat (limited to 'source')
34 files changed, 771 insertions, 78 deletions
diff --git a/source/blender/blenkernel/intern/world.c b/source/blender/blenkernel/intern/world.c index 594d18f1ca7..6635ef29d51 100644 --- a/source/blender/blenkernel/intern/world.c +++ b/source/blender/blenkernel/intern/world.c @@ -106,7 +106,8 @@ World *add_world(char *name) wrld->ao_approx_error= 0.25f; wrld->physicsEngine= WOPHY_BULLET;//WOPHY_SUMO; Bullet by default - wrld->mode = WO_DBVT_CAMERA_CULLING; // DBVT culling by default + wrld->mode = WO_DBVT_CULLING; // DBVT culling by default + wrld->occlusionRes = 128; wrld->preview = NULL; return wrld; diff --git a/source/blender/blenloader/intern/readfile.c b/source/blender/blenloader/intern/readfile.c index 63dd1e8e6cb..c3a355160e0 100644 --- a/source/blender/blenloader/intern/readfile.c +++ b/source/blender/blenloader/intern/readfile.c @@ -8057,7 +8057,8 @@ static void do_versions(FileData *fd, Library *lib, Main *main) } /* DBVT culling by default */ for(wrld=main->world.first; wrld; wrld= wrld->id.next) { - wrld->mode |= WO_DBVT_CAMERA_CULLING; + wrld->mode |= WO_DBVT_CULLING; + wrld->occlusionRes = 128; } } diff --git a/source/blender/makesdna/DNA_actuator_types.h b/source/blender/makesdna/DNA_actuator_types.h index 2252126b46c..aeabae42adf 100644 --- a/source/blender/makesdna/DNA_actuator_types.h +++ b/source/blender/makesdna/DNA_actuator_types.h @@ -195,7 +195,8 @@ typedef struct bGameActuator { typedef struct bVisibilityActuator { /** bit 0: Is this object visible? - ** bit 1: Apply recursively */ + ** bit 1: Apply recursively + ** bit 2: Is this object an occluder? */ int flag; } bVisibilityActuator; @@ -458,6 +459,7 @@ typedef struct FreeCamera { /* Set means the object will become invisible */ #define ACT_VISIBILITY_INVISIBLE (1 << 0) #define ACT_VISIBILITY_RECURSIVE (1 << 1) +#define ACT_VISIBILITY_OCCLUSION (1 << 2) /* twodfilter->type */ #define ACT_2DFILTER_ENABLED -2 diff --git a/source/blender/makesdna/DNA_object_types.h b/source/blender/makesdna/DNA_object_types.h index b17896aec70..3a408404b0b 100644 --- a/source/blender/makesdna/DNA_object_types.h +++ b/source/blender/makesdna/DNA_object_types.h @@ -437,6 +437,7 @@ extern Object workob; #define OB_COLLISION 65536 #define OB_SOFT_BODY 0x20000 +#define OB_OCCLUDER 0x40000 /* ob->gameflag2 */ #define OB_NEVER_DO_ACTIVITY_CULLING 1 @@ -455,6 +456,7 @@ extern Object workob; #define OB_BODY_TYPE_DYNAMIC 2 #define OB_BODY_TYPE_RIGID 3 #define OB_BODY_TYPE_SOFT 4 +#define OB_BODY_TYPE_OCCLUDER 5 /* ob->scavisflag */ #define OB_VIS_SENS 1 diff --git a/source/blender/makesdna/DNA_world_types.h b/source/blender/makesdna/DNA_world_types.h index a51e9704be2..f599364ed66 100644 --- a/source/blender/makesdna/DNA_world_types.h +++ b/source/blender/makesdna/DNA_world_types.h @@ -88,7 +88,8 @@ typedef struct World { * bit 5: (gameengine) : enable Bullet DBVT tree for view frustrum culling */ short mode; - int physicsEngine; /* here it's aligned */ + short occlusionRes; /* resolution of occlusion Z buffer in pixel */ + short physicsEngine; /* here it's aligned */ float misi, miststa, mistdist, misthi; @@ -135,7 +136,7 @@ typedef struct World { #define WO_DOF 4 #define WO_ACTIVITY_CULLING 8 #define WO_AMB_OCC 16 -#define WO_DBVT_CAMERA_CULLING 32 +#define WO_DBVT_CULLING 32 /* aomix */ #define WO_AOADD 0 diff --git a/source/blender/python/api2_2x/Object.c b/source/blender/python/api2_2x/Object.c index fd2301bfae4..2de2906335f 100644 --- a/source/blender/python/api2_2x/Object.c +++ b/source/blender/python/api2_2x/Object.c @@ -3561,7 +3561,7 @@ static int Object_setRBMass( BPy_Object * self, PyObject * args ) /* this is too low level, possible to add helper methods */ -#define GAMEFLAG_MASK ( OB_COLLISION | OB_DYNAMIC | OB_CHILD | OB_ACTOR | OB_DO_FH | \ +#define GAMEFLAG_MASK ( OB_OCCLUDER | OB_COLLISION | OB_DYNAMIC | OB_CHILD | OB_ACTOR | OB_DO_FH | \ OB_ROT_FH | OB_ANISOTROPIC_FRICTION | OB_GHOST | OB_RIGID_BODY | OB_SOFT_BODY | \ OB_BOUNDS | OB_COLLISION_RESPONSE | OB_SECTOR | OB_PROP | \ OB_MAINACTOR ) @@ -5542,6 +5542,7 @@ static PyObject *M_Object_RBFlagsDict( void ) if( M ) { BPy_constant *d = ( BPy_constant * ) M; + PyConstant_Insert( d, "OCCLUDER", PyInt_FromLong( OB_OCCLUDER ) ); PyConstant_Insert( d, "COLLISION", PyInt_FromLong( OB_COLLISION ) ); PyConstant_Insert( d, "DYNAMIC", PyInt_FromLong( OB_DYNAMIC ) ); PyConstant_Insert( d, "CHILD", PyInt_FromLong( OB_CHILD ) ); diff --git a/source/blender/src/buttons_logic.c b/source/blender/src/buttons_logic.c index 97f07c7553c..2f68720acb0 100644 --- a/source/blender/src/buttons_logic.c +++ b/source/blender/src/buttons_logic.c @@ -2455,18 +2455,18 @@ static short draw_actuatorbuttons(Object *ob, bActuator *act, uiBlock *block, sh xco + 10, yco - 20, (width - 20)/3, 19, &visAct->flag, 0.0, 0.0, 0, 0, "Set the objects visible. Initialized from the objects render restriction toggle (access in the outliner)"); - uiDefButBitI(block, TOG, ACT_VISIBILITY_INVISIBLE, B_REDR, - "Invisible", + uiDefButBitI(block, TOG, ACT_VISIBILITY_OCCLUSION, B_REDR, + "Occlusion", xco + 10 + ((width - 20)/3), yco - 20, (width - 20)/3, 19, &visAct->flag, 0.0, 0.0, 0, 0, - "Set the object invisible. Initialized from the objects render restriction toggle (access in the outliner)"); + "Set the object to occlude objects behind it. Initialized from the object type in physics button"); uiBlockEndAlign(block); uiDefButBitI(block, TOG, ACT_VISIBILITY_RECURSIVE, B_NOP, "Children", xco + 10 + (((width - 20)/3)*2)+10, yco - 20, ((width - 20)/3)-10, 19, &visAct->flag, 0.0, 0.0, 0, 0, - "Sets all the children of this object to the same visibility recursively"); + "Sets all the children of this object to the same visibility/occlusion recursively"); yco-= ysize; @@ -3033,25 +3033,29 @@ static void check_body_type(void *arg1_but, void *arg2_object) Object *ob = arg2_object; switch (ob->body_type) { + case OB_BODY_TYPE_OCCLUDER: + ob->gameflag |= OB_OCCLUDER; + ob->gameflag &= ~(OB_COLLISION|OB_DYNAMIC); + break; case OB_BODY_TYPE_NO_COLLISION: - ob->gameflag &= ~OB_COLLISION; + ob->gameflag &= ~(OB_COLLISION|OB_OCCLUDER|OB_DYNAMIC); break; case OB_BODY_TYPE_STATIC: ob->gameflag |= OB_COLLISION; - ob->gameflag &= ~(OB_DYNAMIC|OB_RIGID_BODY|OB_SOFT_BODY); + ob->gameflag &= ~(OB_DYNAMIC|OB_RIGID_BODY|OB_SOFT_BODY|OB_OCCLUDER); break; case OB_BODY_TYPE_DYNAMIC: ob->gameflag |= OB_COLLISION|OB_DYNAMIC|OB_ACTOR; - ob->gameflag &= ~(OB_RIGID_BODY|OB_SOFT_BODY); + ob->gameflag &= ~(OB_RIGID_BODY|OB_SOFT_BODY|OB_OCCLUDER); break; case OB_BODY_TYPE_RIGID: ob->gameflag |= OB_COLLISION|OB_DYNAMIC|OB_RIGID_BODY|OB_ACTOR; - ob->gameflag &= ~(OB_SOFT_BODY); + ob->gameflag &= ~(OB_SOFT_BODY|OB_OCCLUDER); break; default: case OB_BODY_TYPE_SOFT: ob->gameflag |= OB_COLLISION|OB_DYNAMIC|OB_SOFT_BODY|OB_ACTOR; - ob->gameflag &= ~(OB_RIGID_BODY); + ob->gameflag &= ~(OB_RIGID_BODY|OB_OCCLUDER); /* assume triangle mesh, if no bounds chosen for soft body */ if ((ob->gameflag & OB_BOUNDS) && (ob->boundtype<OB_BOUND_POLYH)) @@ -3216,7 +3220,7 @@ static void buttons_bullet(uiBlock *block, Object *ob) /* determine the body_type setting based on flags */ if (!(ob->gameflag & OB_COLLISION)) - ob->body_type = OB_BODY_TYPE_NO_COLLISION; + ob->body_type = (ob->gameflag & OB_OCCLUDER) ? OB_BODY_TYPE_OCCLUDER : OB_BODY_TYPE_NO_COLLISION; else if (!(ob->gameflag & OB_DYNAMIC)) ob->body_type = OB_BODY_TYPE_STATIC; else if (!(ob->gameflag & (OB_RIGID_BODY|OB_SOFT_BODY))) @@ -3234,7 +3238,7 @@ static void buttons_bullet(uiBlock *block, Object *ob) //only enable game soft body if Blender Soft Body exists but = uiDefButS(block, MENU, REDRAWVIEW3D, - "Object type%t|No collision%x0|Static%x1|Dynamic%x2|Rigid body%x3|Soft body%x4", + "Object type%t|Occluder%x5|No collision%x0|Static%x1|Dynamic%x2|Rigid body%x3|Soft body%x4", 10, 205, 100, 19, &ob->body_type, 0, 0, 0, 0, "Selects the type of physical representation"); uiButSetFunc(but, check_body_type, but, ob); diff --git a/source/blender/src/buttons_shading.c b/source/blender/src/buttons_shading.c index e68c86349ce..a129698cce3 100644 --- a/source/blender/src/buttons_shading.c +++ b/source/blender/src/buttons_shading.c @@ -2181,7 +2181,7 @@ static void world_panel_mistaph(World *wrld) uiSetButLock(wrld->id.lib!=0, ERROR_LIBDATA_MESSAGE); #if GAMEBLENDER == 1 - uiDefButI(block, MENU, B_REDR, + uiDefButS(block, MENU, B_REDR, #ifdef USE_ODE "Physics %t|None %x0|Sumo %x2|Ode %x4 |Bullet %x5", #else @@ -2198,8 +2198,12 @@ static void world_panel_mistaph(World *wrld) /* Gravitation for the game worlds */ uiDefButF(block, NUMSLI,0, "Grav ", 150,180,150,19, &(wrld->gravity), 0.0, 25.0, 0, 0, "Sets the gravitation constant of the game world"); - if (wrld->physicsEngine == WOPHY_BULLET) - uiDefButBitS(block, TOG, WO_DBVT_CAMERA_CULLING, 0, "DBVT culling", 10,160,140,19, &wrld->mode, 0, 0, 0, 0, "Toggles use of optimized Bullet DBVT tree for camera culling"); + if (wrld->physicsEngine == WOPHY_BULLET) { + uiDefButBitS(block, TOG, WO_DBVT_CULLING, B_REDR, "DBVT culling", 10,160,140,19, &wrld->mode, 0, 0, 0, 0, "Toggles use of optimized Bullet DBVT tree for view frustrum and occlusion culling"); + if (wrld->mode & WO_DBVT_CULLING) + uiDefButS(block, NUM, B_REDR, "Occlu Res:", + 150, 160, 150, 19, &wrld->occlusionRes, 128.0, 1024.0, 0, 0, "Sets the size of the occlusion buffer in pixel, use higher value for better precsion (slower)"); + } #endif uiBlockSetCol(block, TH_BUT_SETTING1); diff --git a/source/gameengine/Converter/BL_BlenderDataConversion.cpp b/source/gameengine/Converter/BL_BlenderDataConversion.cpp index 50a660e77c9..5de2c4a2fe7 100644 --- a/source/gameengine/Converter/BL_BlenderDataConversion.cpp +++ b/source/gameengine/Converter/BL_BlenderDataConversion.cpp @@ -843,6 +843,7 @@ RAS_MeshObject* BL_ConvertMesh(Mesh* mesh, Object* blenderobj, RAS_IRenderTools* { bool visible = true; + bool twoside = false; RAS_IPolyMaterial* polymat = NULL; BL_Material *bl_mat = NULL; @@ -859,6 +860,7 @@ RAS_MeshObject* BL_ConvertMesh(Mesh* mesh, Object* blenderobj, RAS_IRenderTools* visible = ((bl_mat->ras_mode & POLY_VIS)!=0); collider = ((bl_mat->ras_mode & COLLIDER)!=0); + twoside = ((bl_mat->mode & TF_TWOSIDE)!=0); /* vertex colors and uv's were stored in bl_mat temporarily */ bl_mat->GetConversionRGB(rgb); @@ -899,6 +901,7 @@ RAS_MeshObject* BL_ConvertMesh(Mesh* mesh, Object* blenderobj, RAS_IRenderTools* mode = tface->mode; visible = !((mface->flag & ME_HIDE)||(tface->mode & TF_INVISIBLE)); + twoside = ((tface->mode & TF_TWOSIDE)!=0); uv0.setValue(tface->uv[0]); uv1.setValue(tface->uv[1]); @@ -999,6 +1002,7 @@ RAS_MeshObject* BL_ConvertMesh(Mesh* mesh, Object* blenderobj, RAS_IRenderTools* poly->SetVisible(visible); poly->SetCollider(collider); + poly->SetTwoside(twoside); //poly->SetEdgeCode(mface->edcode); meshobj->AddVertex(poly,0,pt0,uv0,uv20,tan0,rgb0,no0,flat,mface->v1); @@ -1677,6 +1681,7 @@ static KX_GameObject *gameobject_from_blenderobject( bool ignoreActivityCulling = ((ob->gameflag2 & OB_NEVER_DO_ACTIVITY_CULLING)!=0); gameobj->SetIgnoreActivityCulling(ignoreActivityCulling); + gameobj->SetOccluder((ob->gameflag & OB_OCCLUDER) != 0, false); // two options exists for deform: shape keys and armature // only support relative shape key @@ -1894,12 +1899,14 @@ void BL_ConvertBlenderObjects(struct Main* maggie, if (blenderscene->world) { kxscene->SetActivityCulling( (blenderscene->world->mode & WO_ACTIVITY_CULLING) != 0); kxscene->SetActivityCullingRadius(blenderscene->world->activityBoxRadius); - kxscene->SetDbvtCameraCulling((blenderscene->world->mode & WO_DBVT_CAMERA_CULLING) != 0); + kxscene->SetDbvtCulling((blenderscene->world->mode & WO_DBVT_CULLING) != 0); } else { kxscene->SetActivityCulling(false); - kxscene->SetDbvtCameraCulling(false); + kxscene->SetDbvtCulling(false); } - + // no occlusion culling by default + kxscene->SetDbvtOcclusionRes(0); + int activeLayerBitInfo = blenderscene->lay; // templist to find Root Parents (object with no parents) @@ -2452,8 +2459,9 @@ void BL_ConvertBlenderObjects(struct Main* maggie, } // create graphic controller for culling - if (kxscene->GetDbvtCameraCulling()) + if (kxscene->GetDbvtCulling()) { + bool occlusion = false; for (i=0; i<sumolist->GetCount();i++) { KX_GameObject* gameobj = (KX_GameObject*) sumolist->GetValue(i); @@ -2464,8 +2472,12 @@ void BL_ConvertBlenderObjects(struct Main* maggie, // box[0] is the min, box[1] is the max bool isactive = objectlist->SearchValue(gameobj); BL_CreateGraphicObjectNew(gameobj,box[0],box[1],kxscene,isactive,physics_engine); + if (gameobj->GetOccluder()) + occlusion = true; } } + if (occlusion) + kxscene->SetDbvtOcclusionRes(blenderscene->world->occlusionRes); } //set ini linearVel and int angularVel //rcruiz diff --git a/source/gameengine/Converter/KX_BlenderSceneConverter.cpp b/source/gameengine/Converter/KX_BlenderSceneConverter.cpp index b0c676a410d..190f9dbb472 100644 --- a/source/gameengine/Converter/KX_BlenderSceneConverter.cpp +++ b/source/gameengine/Converter/KX_BlenderSceneConverter.cpp @@ -283,7 +283,7 @@ void KX_BlenderSceneConverter::ConvertScene(const STR_String& scenename, case WOPHY_BULLET: { physics_engine = UseBullet; - useDbvtCulling = (blenderscene->world->mode & WO_DBVT_CAMERA_CULLING) != 0; + useDbvtCulling = (blenderscene->world->mode & WO_DBVT_CULLING) != 0; break; } diff --git a/source/gameengine/Converter/KX_ConvertActuators.cpp b/source/gameengine/Converter/KX_ConvertActuators.cpp index f7635856ad9..eb2d0a1c4b1 100644 --- a/source/gameengine/Converter/KX_ConvertActuators.cpp +++ b/source/gameengine/Converter/KX_ConvertActuators.cpp @@ -1008,9 +1008,10 @@ void BL_ConvertActuators(char* maggiename, bVisibilityActuator *vis_act = (bVisibilityActuator *) bact->data; KX_VisibilityActuator * tmp_vis_act = NULL; bool v = ((vis_act->flag & ACT_VISIBILITY_INVISIBLE) != 0); + bool o = ((vis_act->flag & ACT_VISIBILITY_OCCLUSION) != 0); bool recursive = ((vis_act->flag & ACT_VISIBILITY_RECURSIVE) != 0); - tmp_vis_act = new KX_VisibilityActuator(gameobj, !v, recursive); + tmp_vis_act = new KX_VisibilityActuator(gameobj, !v, o, recursive); baseact = tmp_vis_act; } diff --git a/source/gameengine/Ketsji/KX_GameObject.cpp b/source/gameengine/Ketsji/KX_GameObject.cpp index 817afedd205..16cf3d9ae32 100644 --- a/source/gameengine/Ketsji/KX_GameObject.cpp +++ b/source/gameengine/Ketsji/KX_GameObject.cpp @@ -92,6 +92,7 @@ KX_GameObject::KX_GameObject( m_bIsNegativeScaling(false), m_bVisible(true), m_bCulled(true), + m_bOccluder(false), m_pPhysicsController1(NULL), m_pGraphicController(NULL), m_pPhysicsEnvironment(NULL), @@ -146,7 +147,12 @@ KX_GameObject::~KX_GameObject() } } - +KX_GameObject* KX_GameObject::GetClientObject(KX_ClientObjectInfo* info) +{ + if (!info) + return NULL; + return info->m_gameobject; +} CValue* KX_GameObject:: Calc(VALUE_OPERATOR op, CValue *val) { @@ -435,7 +441,7 @@ static void UpdateBuckets_recursive(SG_Node* node) void KX_GameObject::UpdateBuckets( bool recursive ) { - double* fl = GetOpenGLMatrix(); + double* fl = GetOpenGLMatrixPtr()->getPointer(); for (size_t i=0;i<m_meshes.size();i++) m_meshes[i]->UpdateBuckets(this, fl, m_bUseObjectColor, m_objectColor, m_bVisible, m_bCulled); @@ -597,23 +603,34 @@ KX_GameObject::SetVisible( setVisible_recursive(m_pSGNode, v); } -bool -KX_GameObject::GetCulled( - void - ) +static void setOccluder_recursive(SG_Node* node, bool v) { - return m_bCulled; + NodeList& children = node->GetSGChildren(); + + for (NodeList::iterator childit = children.begin();!(childit==children.end());++childit) + { + SG_Node* childnode = (*childit); + KX_GameObject *clientgameobj = static_cast<KX_GameObject*>( (*childit)->GetSGClientObject()); + if (clientgameobj != NULL) // This is a GameObject + clientgameobj->SetOccluder(v, false); + + // if the childobj is NULL then this may be an inverse parent link + // so a non recursive search should still look down this node. + setOccluder_recursive(childnode, v); + } } void -KX_GameObject::SetCulled( - bool c +KX_GameObject::SetOccluder( + bool v, + bool recursive ) { - m_bCulled = c; + m_bOccluder = v; + if (recursive) + setOccluder_recursive(m_pSGNode, v); } - void KX_GameObject::SetLayer( int l @@ -1036,6 +1053,7 @@ PyMethodDef KX_GameObject::Methods[] = { {"setCollisionMargin", (PyCFunction) KX_GameObject::sPySetCollisionMargin, METH_O}, {"setParent", (PyCFunction)KX_GameObject::sPySetParent,METH_O}, {"setVisible",(PyCFunction) KX_GameObject::sPySetVisible, METH_VARARGS}, + {"setOcclusion",(PyCFunction) KX_GameObject::sPySetOcclusion, METH_VARARGS}, {"removeParent", (PyCFunction)KX_GameObject::sPyRemoveParent,METH_NOARGS}, {"getChildren", (PyCFunction)KX_GameObject::sPyGetChildren,METH_NOARGS}, {"getChildrenRecursive", (PyCFunction)KX_GameObject::sPyGetChildrenRecursive,METH_NOARGS}, @@ -1069,6 +1087,7 @@ PyAttributeDef KX_GameObject::Attributes[] = { KX_PYATTRIBUTE_RO_FUNCTION("parent", KX_GameObject, pyattr_get_parent), KX_PYATTRIBUTE_RW_FUNCTION("mass", KX_GameObject, pyattr_get_mass, pyattr_set_mass), KX_PYATTRIBUTE_RW_FUNCTION("visible", KX_GameObject, pyattr_get_visible, pyattr_set_visible), + KX_PYATTRIBUTE_BOOL_RW ("occlusion", KX_GameObject, m_bOccluder), KX_PYATTRIBUTE_RW_FUNCTION("position", KX_GameObject, pyattr_get_position, pyattr_set_position), KX_PYATTRIBUTE_RO_FUNCTION("localInertia", KX_GameObject, pyattr_get_localInertia), KX_PYATTRIBUTE_RW_FUNCTION("orientation",KX_GameObject,pyattr_get_orientation,pyattr_set_orientation), @@ -1746,6 +1765,16 @@ PyObject* KX_GameObject::PySetVisible(PyObject* self, PyObject* args) } +PyObject* KX_GameObject::PySetOcclusion(PyObject* self, PyObject* args) +{ + int occlusion, recursive = 0; + if (!PyArg_ParseTuple(args,"i|i:setOcclusion",&occlusion, &recursive)) + return NULL; + + SetOccluder(occlusion ? true:false, recursive ? true:false); + Py_RETURN_NONE; +} + PyObject* KX_GameObject::PyGetVisible(PyObject* self) { ShowDeprecationWarning("getVisible()", "the visible property"); diff --git a/source/gameengine/Ketsji/KX_GameObject.h b/source/gameengine/Ketsji/KX_GameObject.h index 172272150c0..c389d6cc776 100644 --- a/source/gameengine/Ketsji/KX_GameObject.h +++ b/source/gameengine/Ketsji/KX_GameObject.h @@ -87,6 +87,7 @@ protected: // culled = while rendering, depending on camera bool m_bVisible; bool m_bCulled; + bool m_bOccluder; KX_IPhysicsController* m_pPhysicsController1; PHY_IGraphicController* m_pGraphicController; @@ -103,6 +104,11 @@ protected: public: bool m_isDeformable; + /** + * Helper function for modules that can't include KX_ClientObjectInfo.h + */ + static KX_GameObject* GetClientObject(KX_ClientObjectInfo* info); + // Python attributes that wont convert into CValue // // there are 2 places attributes can be stored, in the CValue, @@ -118,11 +124,8 @@ public: // * if CValue conversion fails, use a PyObject in "m_attrlist" // * when assigning a value, first see if it can be a CValue, if it can remove the "m_attrlist" and set the CValue // - PyObject* m_attrlist; - - virtual void /* This function should be virtual - derived classed override it */ Relink( GEN_Map<GEN_HashedPtr, void*> *map @@ -698,20 +701,37 @@ public: /** * Was this object culled? */ - bool + inline bool GetCulled( void - ); + ) { return m_bCulled; } /** * Set culled flag of this object */ - void + inline void SetCulled( bool c - ); + ) { m_bCulled = c; } + + /** + * Is this object an occluder? + */ + inline bool + GetOccluder( + void + ) { return m_bOccluder; } /** + * Set occluder flag of this object + */ + void + SetOccluder( + bool v, + bool recursive + ); + + /** * Change the layer of the object (when it is added in another layer * than the original layer) */ @@ -908,6 +928,7 @@ public: KX_PYMETHOD_O(KX_GameObject,SetOrientation); KX_PYMETHOD_NOARGS(KX_GameObject,GetVisible); KX_PYMETHOD_VARARGS(KX_GameObject,SetVisible); + KX_PYMETHOD_VARARGS(KX_GameObject,SetOcclusion); KX_PYMETHOD_NOARGS(KX_GameObject,GetState); KX_PYMETHOD_O(KX_GameObject,SetState); KX_PYMETHOD_VARARGS(KX_GameObject,AlignAxisToVect); diff --git a/source/gameengine/Ketsji/KX_KetsjiEngine.cpp b/source/gameengine/Ketsji/KX_KetsjiEngine.cpp index aeb80806f6c..83a2fa8a448 100644 --- a/source/gameengine/Ketsji/KX_KetsjiEngine.cpp +++ b/source/gameengine/Ketsji/KX_KetsjiEngine.cpp @@ -294,8 +294,14 @@ void KX_KetsjiEngine::RenderDome() if (!BeginFrame()) return; - int n_renders=m_dome->GetNumberRenders();// usually 4 or 6 KX_SceneList::iterator sceneit; + for (sceneit = m_scenes.begin();sceneit != m_scenes.end(); sceneit++) + { + // do this only once per scene + (*sceneit)->UpdateMeshTransformations(); + } + + int n_renders=m_dome->GetNumberRenders();// usually 4 or 6 for (int i=0;i<n_renders;i++){ m_canvas->ClearBuffer(RAS_ICanvas::COLOR_BUFFER|RAS_ICanvas::DEPTH_BUFFER); for (sceneit = m_scenes.begin();sceneit != m_scenes.end(); sceneit++) @@ -311,7 +317,6 @@ void KX_KetsjiEngine::RenderDome() // shadow buffers if (i == 0){ RenderShadowBuffers(scene); - scene->UpdateMeshTransformations();//I need to run it somewherelse, otherwise Im overrunning it } // Avoid drawing the scene with the active camera twice when it's viewport is enabled if(cam && !cam->GetViewport()) @@ -812,6 +817,9 @@ void KX_KetsjiEngine::Render() // pass the scene's worldsettings to the rasterizer SetWorldSettings(scene->GetWorldInfo()); + // do this only once per scene + scene->UpdateMeshTransformations(); + // shadow buffers RenderShadowBuffers(scene); @@ -1140,7 +1148,6 @@ void KX_KetsjiEngine::RenderShadowBuffers(KX_Scene *scene) light->BindShadowBuffer(m_rasterizer, cam, camtrans); /* update scene */ - scene->UpdateMeshTransformations(); scene->CalculateVisibleMeshes(m_rasterizer, cam, light->GetShadowLayer()); /* render */ @@ -1245,7 +1252,7 @@ void KX_KetsjiEngine::RenderFrame(KX_Scene* scene, KX_Camera* cam) cam->GetCameraLocation(), cam->GetCameraOrientation()); cam->SetModelviewMatrix(viewmat); - //redundant, already done in + //redundant, already done in Render() //scene->UpdateMeshTransformations(); // The following actually reschedules all vertices to be diff --git a/source/gameengine/Ketsji/KX_Scene.cpp b/source/gameengine/Ketsji/KX_Scene.cpp index 2f7c1b77794..98c129ebca5 100644 --- a/source/gameengine/Ketsji/KX_Scene.cpp +++ b/source/gameengine/Ketsji/KX_Scene.cpp @@ -138,6 +138,7 @@ KX_Scene::KX_Scene(class SCA_IInputDevice* keyboarddevice, m_suspendeddelta = 0.0; m_dbvt_culling = false; + m_dbvt_occlusion_res = 0; m_activity_culling = false; m_suspend = false; m_isclearingZbuffer = true; @@ -1352,17 +1353,18 @@ void KX_Scene::CalculateVisibleMeshes(RAS_IRasterizer* rasty,KX_Camera* cam, int if (m_dbvt_culling) { // test culling through Bullet - PHY__Vector4 planes[5]; + PHY__Vector4 planes[6]; // get the clip planes MT_Vector4* cplanes = cam->GetNormalizedClipPlanes(); // and convert - planes[0].setValue(cplanes[0].getValue()); - planes[1].setValue(cplanes[1].getValue()); - planes[2].setValue(cplanes[2].getValue()); - planes[3].setValue(cplanes[3].getValue()); - planes[4].setValue(cplanes[5].getValue()); + planes[0].setValue(cplanes[4].getValue()); // near + planes[1].setValue(cplanes[5].getValue()); // far + planes[2].setValue(cplanes[0].getValue()); // left + planes[3].setValue(cplanes[1].getValue()); // right + planes[4].setValue(cplanes[2].getValue()); // top + planes[5].setValue(cplanes[3].getValue()); // bottom CullingInfo info(layer); - dbvt_culling = m_physicsEnvironment->cullingTest(PhysicsCullingCallback,&info,planes,5); + dbvt_culling = m_physicsEnvironment->cullingTest(PhysicsCullingCallback,&info,planes,5,m_dbvt_occlusion_res); } if (!dbvt_culling) { // the physics engine couldn't help us, do it the hard way diff --git a/source/gameengine/Ketsji/KX_Scene.h b/source/gameengine/Ketsji/KX_Scene.h index 55e7afa4957..e1e89e253ed 100644 --- a/source/gameengine/Ketsji/KX_Scene.h +++ b/source/gameengine/Ketsji/KX_Scene.h @@ -265,6 +265,11 @@ protected: bool m_dbvt_culling; /** + * Occlusion culling resolution + */ + int m_dbvt_occlusion_res; + + /** * The framing settings used by this scene */ @@ -545,8 +550,10 @@ public: bool IsClearingZBuffer(); void EnableZBufferClearing(bool isclearingZbuffer); // use of DBVT tree for camera culling - void SetDbvtCameraCulling(bool b) { m_dbvt_culling = b; }; - bool GetDbvtCameraCulling() { return m_dbvt_culling; }; + void SetDbvtCulling(bool b) { m_dbvt_culling = b; }; + bool GetDbvtCulling() { return m_dbvt_culling; }; + void SetDbvtOcclusionRes(int i) { m_dbvt_occlusion_res = i; }; + int GetDbvtOcclusionRes() { return m_dbvt_occlusion_res; }; void SetSceneConverter(class KX_BlenderSceneConverter* sceneConverter); diff --git a/source/gameengine/Ketsji/KX_VisibilityActuator.cpp b/source/gameengine/Ketsji/KX_VisibilityActuator.cpp index 1ee2169adc4..fceb0b5922c 100644 --- a/source/gameengine/Ketsji/KX_VisibilityActuator.cpp +++ b/source/gameengine/Ketsji/KX_VisibilityActuator.cpp @@ -38,11 +38,13 @@ KX_VisibilityActuator::KX_VisibilityActuator( SCA_IObject* gameobj, bool visible, + bool occlusion, bool recursive, PyTypeObject* T ) : SCA_IActuator(gameobj,T), m_visible(visible), + m_occlusion(occlusion), m_recursive(recursive) { // intentionally empty @@ -78,6 +80,7 @@ KX_VisibilityActuator::Update() KX_GameObject *obj = (KX_GameObject*) GetParent(); obj->SetVisible(m_visible, m_recursive); + obj->SetOccluder(m_occlusion, m_recursive); obj->UpdateBuckets(m_recursive); return false; @@ -130,6 +133,7 @@ KX_VisibilityActuator::Methods[] = { PyAttributeDef KX_VisibilityActuator::Attributes[] = { KX_PYATTRIBUTE_BOOL_RW("visibility", KX_VisibilityActuator, m_visible), + KX_PYATTRIBUTE_BOOL_RW("occlusion", KX_VisibilityActuator, m_occlusion), KX_PYATTRIBUTE_BOOL_RW("recursion", KX_VisibilityActuator, m_recursive), { NULL } //Sentinel }; diff --git a/source/gameengine/Ketsji/KX_VisibilityActuator.h b/source/gameengine/Ketsji/KX_VisibilityActuator.h index fca37500915..4269258f862 100644 --- a/source/gameengine/Ketsji/KX_VisibilityActuator.h +++ b/source/gameengine/Ketsji/KX_VisibilityActuator.h @@ -39,6 +39,7 @@ class KX_VisibilityActuator : public SCA_IActuator /** Make visible? */ bool m_visible; + bool m_occlusion; bool m_recursive; public: @@ -46,6 +47,7 @@ class KX_VisibilityActuator : public SCA_IActuator KX_VisibilityActuator( SCA_IObject* gameobj, bool visible, + bool occlusion, bool recursive, PyTypeObject* T=&Type ); diff --git a/source/gameengine/Physics/BlOde/OdePhysicsEnvironment.h b/source/gameengine/Physics/BlOde/OdePhysicsEnvironment.h index e4aaef1803d..2e4709cf420 100644 --- a/source/gameengine/Physics/BlOde/OdePhysicsEnvironment.h +++ b/source/gameengine/Physics/BlOde/OdePhysicsEnvironment.h @@ -55,7 +55,7 @@ public: virtual void removeConstraint(void * constraintid); virtual PHY_IPhysicsController* rayTest(PHY_IRayCastFilterCallback &filterCallback,float fromX,float fromY,float fromZ, float toX,float toY,float toZ); - virtual bool cullingTest(PHY_CullingCallback callback, void* userData, PHY__Vector4* planes, int nplanes) { return false; } + virtual bool cullingTest(PHY_CullingCallback callback, void* userData, PHY__Vector4* planes, int nplanes, int occlusionRes) { return false; } //gamelogic callbacks diff --git a/source/gameengine/Physics/Bullet/CMakeLists.txt b/source/gameengine/Physics/Bullet/CMakeLists.txt index 6bab9858011..2cb2a540d97 100644 --- a/source/gameengine/Physics/Bullet/CMakeLists.txt +++ b/source/gameengine/Physics/Bullet/CMakeLists.txt @@ -30,11 +30,18 @@ SET(INC . ../common ../../../../extern/bullet2/src + ../../../../extern/glew/include ../../../../intern/moto/include ../../../kernel/gen_system ../../../../intern/string + ../../../intern/SoundSystem ../../Rasterizer + ../../Ketsji + ../../Expressions + ../../GameLogic + ../../SceneGraph ../../../../source/blender/makesdna + ${PYTHON_INC} ) BLENDERLIB(bf_bullet "${SRC}" "${INC}") diff --git a/source/gameengine/Physics/Bullet/CcdPhysicsEnvironment.cpp b/source/gameengine/Physics/Bullet/CcdPhysicsEnvironment.cpp index faf1ca42766..858416bae6a 100644 --- a/source/gameengine/Physics/Bullet/CcdPhysicsEnvironment.cpp +++ b/source/gameengine/Physics/Bullet/CcdPhysicsEnvironment.cpp @@ -33,6 +33,10 @@ subject to the following restrictions: #include "PHY_IMotionState.h" +#include "KX_GameObject.h" +#include "RAS_MeshObject.h" +#include "RAS_Polygon.h" +#include "RAS_TexVert.h" #define CCD_CONSTRAINT_DISABLE_LINKED_COLLISION 0x80 @@ -47,7 +51,9 @@ btRaycastVehicle::btVehicleTuning gTuning; #endif //NEW_BULLET_VEHICLE_SUPPORT #include "LinearMath/btAabbUtil2.h" - +#include "MT_Matrix4x4.h" +#include "MT_Vector3.h" +#include "GL/glew.h" #ifdef WIN32 void DrawRasterizerLine(const float* from,const float* to,int color); @@ -1189,17 +1195,492 @@ PHY_IPhysicsController* CcdPhysicsEnvironment::rayTest(PHY_IRayCastFilterCallbac return result.m_controller; } +// Handles occlusion culling. +// The implementation is based on the CDTestFramework +struct OcclusionBuffer +{ + struct WriteOCL + { + static inline bool Process(btScalar& q,btScalar v) { if(q<v) q=v;return(false); } + static inline void Occlusion(bool& flag) { flag = true; } + }; + struct QueryOCL + { + static inline bool Process(btScalar& q,btScalar v) { return(q<=v); } + static inline void Occlusion(bool& flag) { } + }; + btScalar* m_buffer; + size_t m_bufferSize; + bool m_initialized; + bool m_occlusion; + int m_sizes[2]; + btScalar m_scales[2]; + btScalar m_offsets[2]; + btScalar m_wtc[16]; // world to clip transform + btScalar m_mtc[16]; // model to clip transform + // constructor: size=largest dimension of the buffer. + // Buffer size depends on aspect ratio + OcclusionBuffer() + { + m_initialized=false; + m_occlusion = false; + m_buffer == NULL; + m_bufferSize = 0; + } + // multiplication of column major matrices: m=m1*m2 + template<typename T1, typename T2> + void CMmat4mul(btScalar* m, const T1* m1, const T2* m2) + { + m[ 0] = btScalar(m1[ 0]*m2[ 0]+m1[ 4]*m2[ 1]+m1[ 8]*m2[ 2]+m1[12]*m2[ 3]); + m[ 1] = btScalar(m1[ 1]*m2[ 0]+m1[ 5]*m2[ 1]+m1[ 9]*m2[ 2]+m1[13]*m2[ 3]); + m[ 2] = btScalar(m1[ 2]*m2[ 0]+m1[ 6]*m2[ 1]+m1[10]*m2[ 2]+m1[14]*m2[ 3]); + m[ 3] = btScalar(m1[ 3]*m2[ 0]+m1[ 7]*m2[ 1]+m1[11]*m2[ 2]+m1[15]*m2[ 3]); + + m[ 4] = btScalar(m1[ 0]*m2[ 4]+m1[ 4]*m2[ 5]+m1[ 8]*m2[ 6]+m1[12]*m2[ 7]); + m[ 5] = btScalar(m1[ 1]*m2[ 4]+m1[ 5]*m2[ 5]+m1[ 9]*m2[ 6]+m1[13]*m2[ 7]); + m[ 6] = btScalar(m1[ 2]*m2[ 4]+m1[ 6]*m2[ 5]+m1[10]*m2[ 6]+m1[14]*m2[ 7]); + m[ 7] = btScalar(m1[ 3]*m2[ 4]+m1[ 7]*m2[ 5]+m1[11]*m2[ 6]+m1[15]*m2[ 7]); + + m[ 8] = btScalar(m1[ 0]*m2[ 8]+m1[ 4]*m2[ 9]+m1[ 8]*m2[10]+m1[12]*m2[11]); + m[ 9] = btScalar(m1[ 1]*m2[ 8]+m1[ 5]*m2[ 9]+m1[ 9]*m2[10]+m1[13]*m2[11]); + m[10] = btScalar(m1[ 2]*m2[ 8]+m1[ 6]*m2[ 9]+m1[10]*m2[10]+m1[14]*m2[11]); + m[11] = btScalar(m1[ 3]*m2[ 8]+m1[ 7]*m2[ 9]+m1[11]*m2[10]+m1[15]*m2[11]); + + m[12] = btScalar(m1[ 0]*m2[12]+m1[ 4]*m2[13]+m1[ 8]*m2[14]+m1[12]*m2[15]); + m[13] = btScalar(m1[ 1]*m2[12]+m1[ 5]*m2[13]+m1[ 9]*m2[14]+m1[13]*m2[15]); + m[14] = btScalar(m1[ 2]*m2[12]+m1[ 6]*m2[13]+m1[10]*m2[14]+m1[14]*m2[15]); + m[15] = btScalar(m1[ 3]*m2[12]+m1[ 7]*m2[13]+m1[11]*m2[14]+m1[15]*m2[15]); + } + void setup(int size) + { + m_initialized=false; + m_occlusion=false; + // compute the size of the buffer + GLint v[4]; + GLdouble m[16],p[16]; + int maxsize; + double ratio; + glGetIntegerv(GL_VIEWPORT,v); + maxsize = (v[2] > v[3]) ? v[2] : v[3]; + assert(maxsize > 0); + ratio = 1.0/(2*maxsize); + // ensure even number + m_sizes[0] = 2*((int)(size*v[2]*ratio+0.5)); + m_sizes[1] = 2*((int)(size*v[3]*ratio+0.5)); + m_scales[0]=btScalar(m_sizes[0]/2); + m_scales[1]=btScalar(m_sizes[1]/2); + m_offsets[0]=m_scales[0]+0.5f; + m_offsets[1]=m_scales[1]+0.5f; + // prepare matrix + // at this time of the rendering, the modelview matrix is the + // world to camera transformation and the projection matrix is + // camera to clip transformation. combine both so that + glGetDoublev(GL_MODELVIEW_MATRIX,m); + glGetDoublev(GL_PROJECTION_MATRIX,p); + CMmat4mul(m_wtc,p,m); + } + void initialize() + { + size_t newsize = (m_sizes[0]*m_sizes[1])*sizeof(btScalar); + if (m_buffer) + { + // see if we can reuse + if (newsize > m_bufferSize) + { + free(m_buffer); + m_buffer = NULL; + m_bufferSize = 0; + } + } + if (!m_buffer) + { + m_buffer = (btScalar*)calloc(1, newsize); + m_bufferSize = newsize; + } else + { + // buffer exists already, just clears it + memset(m_buffer, 0, newsize); + } + // memory allocate must succeed + assert(m_buffer != NULL); + m_initialized = true; + m_occlusion = false; + } + void SetModelMatrix(double *fl) + { + CMmat4mul(m_mtc,m_wtc,fl); + if (!m_initialized) + initialize(); + } + + // transform a segment in world coordinate to clip coordinate + void transformW(const btVector3& x, btVector4& t) + { + t[0] = x[0]*m_wtc[0]+x[1]*m_wtc[4]+x[2]*m_wtc[8]+m_wtc[12]; + t[1] = x[0]*m_wtc[1]+x[1]*m_wtc[5]+x[2]*m_wtc[9]+m_wtc[13]; + t[2] = x[0]*m_wtc[2]+x[1]*m_wtc[6]+x[2]*m_wtc[10]+m_wtc[14]; + t[3] = x[0]*m_wtc[3]+x[1]*m_wtc[7]+x[2]*m_wtc[11]+m_wtc[15]; + } + void transformM(const float* x, btVector4& t) + { + t[0] = x[0]*m_mtc[0]+x[1]*m_mtc[4]+x[2]*m_mtc[8]+m_mtc[12]; + t[1] = x[0]*m_mtc[1]+x[1]*m_mtc[5]+x[2]*m_mtc[9]+m_mtc[13]; + t[2] = x[0]*m_mtc[2]+x[1]*m_mtc[6]+x[2]*m_mtc[10]+m_mtc[14]; + t[3] = x[0]*m_mtc[3]+x[1]*m_mtc[7]+x[2]*m_mtc[11]+m_mtc[15]; + } + // convert polygon to device coordinates + static bool project(btVector4* p,int n) + { + for(int i=0;i<n;++i) + { + const btScalar iw=1/p[i][3]; + p[i][2]=1/p[i][3]; + p[i][0]*=p[i][2]; + p[i][1]*=p[i][2]; + } + return(true); + } + // pi: closed polygon in clip coordinate, NP = number of segments + // po: same polygon with clipped segments removed + template <const int NP> + static int clip(const btVector4* pi,btVector4* po) + { + btScalar s[2*NP]; + btVector4 pn[2*NP], *p; + int i, j, m, n, ni; + // deal with near clipping + for(i=0, m=0;i<NP;++i) + { + s[i]=pi[i][2]+pi[i][3]; + if(s[i]<0) m+=1<<i; + } + if(m==((1<<NP)-1)) + return(0); + if(m!=0) + { + for(i=NP-1,j=0,n=0;j<NP;i=j++) + { + const btVector4& a=pi[i]; + const btVector4& b=pi[j]; + const btScalar t=s[i]/(a[3]+a[2]-b[3]-b[2]); + if((t>0)&&(t<1)) + { + pn[n][0] = a[0]+(b[0]-a[0])*t; + pn[n][1] = a[1]+(b[1]-a[1])*t; + pn[n][2] = a[2]+(b[2]-a[2])*t; + pn[n][3] = a[3]+(b[3]-a[3])*t; + ++n; + } + if(s[j]>0) pn[n++]=b; + } + // ready to test far clipping, start from the modified polygon + pi = pn; + ni = n; + } else + { + // no clipping on the near plane, keep same vector + ni = NP; + } + // now deal with far clipping + for(i=0, m=0;i<ni;++i) + { + s[i]=pi[i][2]-pi[i][3]; + if(s[i]>0) m+=1<<i; + } + if(m==((1<<ni)-1)) + return(0); + if(m!=0) + { + for(i=ni-1,j=0,n=0;j<ni;i=j++) + { + const btVector4& a=pi[i]; + const btVector4& b=pi[j]; + const btScalar t=s[i]/(a[2]-a[3]-b[2]+b[3]); + if((t>0)&&(t<1)) + { + po[n][0] = a[0]+(b[0]-a[0])*t; + po[n][1] = a[1]+(b[1]-a[1])*t; + po[n][2] = a[2]+(b[2]-a[2])*t; + po[n][3] = a[3]+(b[3]-a[3])*t; + ++n; + } + if(s[j]<0) po[n++]=b; + } + return(n); + } + for(int i=0;i<ni;++i) po[i]=pi[i]; + return(ni); + } + // write or check a triangle to buffer. a,b,c in device coordinates (-1,+1) + template <typename POLICY> + inline bool draw( const btVector4& a, + const btVector4& b, + const btVector4& c, + const float face, + const btScalar minarea) + { + const btScalar a2=cross(b-a,c-a)[2]; + if((face*a2)<0.f || btFabs(a2)<minarea) + return false; + // further down we are normally going to write to the Zbuffer, mark it so + POLICY::Occlusion(m_occlusion); + + int x[3], y[3], ib=1, ic=2; + btScalar z[3]; + x[0]=(int)(a.x()*m_scales[0]+m_offsets[0]); + y[0]=(int)(a.y()*m_scales[1]+m_offsets[1]); + z[0]=a.z(); + if (a2 < 0.f) + { + // negative aire is possible with double face => must + // change the order of b and c otherwise the algorithm doesn't work + ib=2; + ic=1; + } + x[ib]=(int)(b.x()*m_scales[0]+m_offsets[0]); + x[ic]=(int)(c.x()*m_scales[0]+m_offsets[0]); + y[ib]=(int)(b.y()*m_scales[1]+m_offsets[1]); + y[ic]=(int)(c.y()*m_scales[1]+m_offsets[1]); + z[ib]=b.z(); + z[ic]=c.z(); + const int mix=btMax(0,btMin(x[0],btMin(x[1],x[2]))); + const int mxx=btMin(m_sizes[0],1+btMax(x[0],btMax(x[1],x[2]))); + const int miy=btMax(0,btMin(y[0],btMin(y[1],y[2]))); + const int mxy=btMin(m_sizes[1],1+btMax(y[0],btMax(y[1],y[2]))); + const int width=mxx-mix; + const int height=mxy-miy; + if ((width*height) <= 1) + { + // degenerated in at most one single pixel + btScalar* scan=&m_buffer[miy*m_sizes[0]+mix]; + // use for loop to detect the case where width or height == 0 + for(int iy=miy;iy<mxy;++iy) + { + for(int ix=mix;ix<mxx;++ix) + { + if(POLICY::Process(*scan,z[0])) + return(true); + if(POLICY::Process(*scan,z[1])) + return(true); + if(POLICY::Process(*scan,z[2])) + return(true); + } + } + } else if (width == 1) + { + // Degenerated in at least 2 vertical lines + // The algorithm below doesn't work when face has a single pixel width + // We cannot use general formulas because the plane is degenerated. + // We have to interpolate along the 3 edges that overlaps and process each pixel. + // sort the y coord to make formula simpler + int ytmp; + btScalar ztmp; + if (y[0] > y[1]) { ytmp=y[1];y[1]=y[0];y[0]=ytmp;ztmp=z[1];z[1]=z[0];z[0]=ztmp; } + if (y[0] > y[2]) { ytmp=y[2];y[2]=y[0];y[0]=ytmp;ztmp=z[2];z[2]=z[0];z[0]=ztmp; } + if (y[1] > y[2]) { ytmp=y[2];y[2]=y[1];y[1]=ytmp;ztmp=z[2];z[2]=z[1];z[1]=ztmp; } + int dy[]={ y[0]-y[1], + y[1]-y[2], + y[2]-y[0]}; + btScalar dzy[3]; + dzy[0] = (dy[0]) ? (z[0]-z[1])/dy[0] : btScalar(0.f); + dzy[1] = (dy[1]) ? (z[1]-z[2])/dy[1] : btScalar(0.f); + dzy[2] = (dy[2]) ? (z[2]-z[0])/dy[2] : btScalar(0.f); + btScalar v[3] = { dzy[0]*(miy-y[0])+z[0], + dzy[1]*(miy-y[1])+z[1], + dzy[2]*(miy-y[2])+z[2] }; + dy[0] = y[1]-y[0]; + dy[1] = y[0]-y[1]; + dy[2] = y[2]-y[0]; + btScalar* scan=&m_buffer[miy*m_sizes[0]+mix]; + for(int iy=miy;iy<mxy;++iy) + { + if(dy[0] >= 0 && POLICY::Process(*scan,v[0])) + return(true); + if(dy[1] >= 0 && POLICY::Process(*scan,v[1])) + return(true); + if(dy[2] >= 0 && POLICY::Process(*scan,v[2])) + return(true); + scan+=m_sizes[0]; + v[0] += dzy[0]; v[1] += dzy[1]; v[2] += dzy[2]; + dy[0]--; dy[1]++, dy[2]--; + } + } else if (height == 1) + { + // Degenerated in at least 2 horizontal lines + // The algorithm below doesn't work when face has a single pixel width + // We cannot use general formulas because the plane is degenerated. + // We have to interpolate along the 3 edges that overlaps and process each pixel. + int xtmp; + btScalar ztmp; + if (x[0] > x[1]) { xtmp=x[1];x[1]=x[0];x[0]=xtmp;ztmp=z[1];z[1]=z[0];z[0]=ztmp; } + if (x[0] > x[2]) { xtmp=x[2];x[2]=x[0];x[0]=xtmp;ztmp=z[2];z[2]=z[0];z[0]=ztmp; } + if (x[1] > x[2]) { xtmp=x[2];x[2]=x[1];x[1]=xtmp;ztmp=z[2];z[2]=z[1];z[1]=ztmp; } + int dx[]={ x[0]-x[1], + x[1]-x[2], + x[2]-x[0]}; + btScalar dzx[3]; + dzx[0] = (dx[0]) ? (z[0]-z[1])/dx[0] : btScalar(0.f); + dzx[1] = (dx[1]) ? (z[1]-z[2])/dx[1] : btScalar(0.f); + dzx[2] = (dx[2]) ? (z[2]-z[0])/dx[2] : btScalar(0.f); + btScalar v[3] = { dzx[0]*(mix-x[0])+z[0], + dzx[1]*(mix-x[1])+z[1], + dzx[2]*(mix-x[2])+z[2] }; + dx[0] = x[1]-x[0]; + dx[1] = x[0]-x[1]; + dx[2] = x[2]-x[0]; + btScalar* scan=&m_buffer[miy*m_sizes[0]+mix]; + for(int ix=mix;ix<mxx;++ix) + { + if(dx[0] >= 0 && POLICY::Process(*scan,v[0])) + return(true); + if(dx[1] >= 0 && POLICY::Process(*scan,v[1])) + return(true); + if(dx[2] >= 0 && POLICY::Process(*scan,v[2])) + return(true); + scan++; + v[0] += dzx[0]; v[1] += dzx[1]; v[2] += dzx[2]; + dx[0]--; dx[1]++, dx[2]--; + } + } else + { + // general case + const int dx[]={ y[0]-y[1], + y[1]-y[2], + y[2]-y[0]}; + const int dy[]={ x[1]-x[0]-dx[0]*width, + x[2]-x[1]-dx[1]*width, + x[0]-x[2]-dx[2]*width}; + const int a=x[2]*y[0]+x[0]*y[1]-x[2]*y[1]-x[0]*y[2]+x[1]*y[2]-x[1]*y[0]; + const btScalar ia=1/(btScalar)a; + const btScalar dzx=ia*(y[2]*(z[1]-z[0])+y[1]*(z[0]-z[2])+y[0]*(z[2]-z[1])); + const btScalar dzy=ia*(x[2]*(z[0]-z[1])+x[0]*(z[1]-z[2])+x[1]*(z[2]-z[0]))-(dzx*width); + int c[]={ miy*x[1]+mix*y[0]-x[1]*y[0]-mix*y[1]+x[0]*y[1]-miy*x[0], + miy*x[2]+mix*y[1]-x[2]*y[1]-mix*y[2]+x[1]*y[2]-miy*x[1], + miy*x[0]+mix*y[2]-x[0]*y[2]-mix*y[0]+x[2]*y[0]-miy*x[2]}; + btScalar v=ia*((z[2]*c[0])+(z[0]*c[1])+(z[1]*c[2])); + btScalar* scan=&m_buffer[miy*m_sizes[0]]; + for(int iy=miy;iy<mxy;++iy) + { + for(int ix=mix;ix<mxx;++ix) + { + if((c[0]>=0)&&(c[1]>=0)&&(c[2]>=0)) + { + if(POLICY::Process(scan[ix],v)) + return(true); + } + c[0]+=dx[0];c[1]+=dx[1];c[2]+=dx[2];v+=dzx; + } + c[0]+=dy[0];c[1]+=dy[1];c[2]+=dy[2];v+=dzy; + scan+=m_sizes[0]; + } + } + return(false); + } + // clip than write or check a polygon + template <const int NP,typename POLICY> + inline bool clipDraw( const btVector4* p, + const float face, + btScalar minarea) + { + btVector4 o[NP*2]; + int n=clip<NP>(p,o); + bool earlyexit=false; + if (n) + { + project(o,n); + for(int i=2;i<n && !earlyexit;++i) + { + earlyexit|=draw<POLICY>(o[0],o[i-1],o[i],face,minarea); + } + } + return(earlyexit); + } + // add a triangle (in model coordinate) + // face = 0.f if face is double side, + // = 1.f if face is single sided and scale is positive + // = -1.f if face is single sided and scale is negative + void appendOccluderM(const float* a, + const float* b, + const float* c, + const float face) + { + btVector4 p[3]; + transformM(a,p[0]); + transformM(b,p[1]); + transformM(c,p[2]); + clipDraw<3,WriteOCL>(p,face,btScalar(0.f)); + } + // add a quad (in model coordinate) + void appendOccluderM(const float* a, + const float* b, + const float* c, + const float* d, + const float face) + { + btVector4 p[4]; + transformM(a,p[0]); + transformM(b,p[1]); + transformM(c,p[2]); + transformM(d,p[3]); + clipDraw<4,WriteOCL>(p,face,btScalar(0.f)); + } + // query occluder for a box (c=center, e=extend) in world coordinate + inline bool queryOccluderW( const btVector3& c, + const btVector3& e) + { + if (!m_occlusion) + // no occlusion yet, no need to check + return true; + btVector4 x[8]; + transformW(btVector3(c[0]-e[0],c[1]-e[1],c[2]-e[2]),x[0]); + transformW(btVector3(c[0]+e[0],c[1]-e[1],c[2]-e[2]),x[1]); + transformW(btVector3(c[0]+e[0],c[1]+e[1],c[2]-e[2]),x[2]); + transformW(btVector3(c[0]-e[0],c[1]+e[1],c[2]-e[2]),x[3]); + transformW(btVector3(c[0]-e[0],c[1]-e[1],c[2]+e[2]),x[4]); + transformW(btVector3(c[0]+e[0],c[1]-e[1],c[2]+e[2]),x[5]); + transformW(btVector3(c[0]+e[0],c[1]+e[1],c[2]+e[2]),x[6]); + transformW(btVector3(c[0]-e[0],c[1]+e[1],c[2]+e[2]),x[7]); + for(int i=0;i<8;++i) + { + // the box is clipped, it's probably a large box, don't waste our time to check + if((x[i][2]+x[i][3])<=0) return(true); + } + static const int d[]={ 1,0,3,2, + 4,5,6,7, + 4,7,3,0, + 6,5,1,2, + 7,6,2,3, + 5,4,0,1}; + for(int i=0;i<(sizeof(d)/sizeof(d[0]));) + { + const btVector4 p[]={ x[d[i++]], + x[d[i++]], + x[d[i++]], + x[d[i++]]}; + if(clipDraw<4,QueryOCL>(p,1.f,0.f)) + return(true); + } + return(false); + } +}; + + struct DbvtCullingCallback : btDbvt::ICollide { PHY_CullingCallback m_clientCallback; void* m_userData; + OcclusionBuffer *m_ocb; DbvtCullingCallback(PHY_CullingCallback clientCallback, void* userData) { m_clientCallback = clientCallback; m_userData = userData; + m_ocb = NULL; + } + bool Descent(const btDbvtNode* node) + { + return(m_ocb->queryOccluderW(node->volume.Center(),node->volume.Extents())); } - void Process(const btDbvtNode* node,btScalar depth) { Process(node); @@ -1210,31 +1691,83 @@ struct DbvtCullingCallback : btDbvt::ICollide // the client object is a graphic controller CcdGraphicController* ctrl = static_cast<CcdGraphicController*>(proxy->m_clientObject); KX_ClientObjectInfo* info = (KX_ClientObjectInfo*)ctrl->getNewClientInfo(); + if (m_ocb) + { + // means we are doing occlusion culling. Check if this object is an occluders + KX_GameObject* gameobj = KX_GameObject::GetClientObject(info); + if (gameobj && gameobj->GetOccluder()) + { + double* fl = gameobj->GetOpenGLMatrixPtr()->getPointer(); + // this will create the occlusion buffer if not already done + // and compute the transformation from model local space to clip space + m_ocb->SetModelMatrix(fl); + float face = (gameobj->IsNegativeScaling()) ? -1.0f : 1.0f; + // walk through the meshes and for each add to buffer + for (int i=0; i<gameobj->GetMeshCount(); i++) + { + RAS_MeshObject* meshobj = gameobj->GetMesh(i); + const float *v1, *v2, *v3, *v4; + + int polycount = meshobj->NumPolygons(); + for (int j=0; j<polycount; j++) + { + RAS_Polygon* poly = meshobj->GetPolygon(j); + switch (poly->VertexCount()) + { + case 3: + v1 = poly->GetVertex(0)->getXYZ(); + v2 = poly->GetVertex(1)->getXYZ(); + v3 = poly->GetVertex(2)->getXYZ(); + m_ocb->appendOccluderM(v1,v2,v3,((poly->IsTwoside())?0.f:face)); + break; + case 4: + v1 = poly->GetVertex(0)->getXYZ(); + v2 = poly->GetVertex(1)->getXYZ(); + v3 = poly->GetVertex(2)->getXYZ(); + v4 = poly->GetVertex(3)->getXYZ(); + m_ocb->appendOccluderM(v1,v2,v3,v4,((poly->IsTwoside())?0.f:face)); + break; + } + } + } + } + } if (info) (*m_clientCallback)(info, m_userData); } }; -bool CcdPhysicsEnvironment::cullingTest(PHY_CullingCallback callback, void* userData, PHY__Vector4 *planes, int nplanes) +static OcclusionBuffer gOcb; +bool CcdPhysicsEnvironment::cullingTest(PHY_CullingCallback callback, void* userData, PHY__Vector4 *planes, int nplanes, int occlusionRes) { if (!m_cullingTree) return false; DbvtCullingCallback dispatcher(callback, userData); - btVector3 planes_n[5]; - btScalar planes_o[5]; - if (nplanes > 5) - nplanes = 5; + btVector3 planes_n[6]; + btScalar planes_o[6]; + if (nplanes > 6) + nplanes = 6; for (int i=0; i<nplanes; i++) { planes_n[i].setValue(planes[i][0], planes[i][1], planes[i][2]); planes_o[i] = planes[i][3]; } - btDbvt::collideKDOP(m_cullingTree->m_sets[1].m_root,planes_n,planes_o,nplanes,dispatcher); - btDbvt::collideKDOP(m_cullingTree->m_sets[0].m_root,planes_n,planes_o,nplanes,dispatcher); + // if occlusionRes != 0 => occlusion culling + if (occlusionRes) + { + gOcb.setup(occlusionRes); + dispatcher.m_ocb = &gOcb; + // occlusion culling, the direction of the view is taken from the first plan which MUST be the near plane + btDbvt::collideOCL(m_cullingTree->m_sets[1].m_root,planes_n,planes_o,planes_n[0],nplanes,dispatcher); + btDbvt::collideOCL(m_cullingTree->m_sets[0].m_root,planes_n,planes_o,planes_n[0],nplanes,dispatcher); + }else + { + btDbvt::collideKDOP(m_cullingTree->m_sets[1].m_root,planes_n,planes_o,nplanes,dispatcher); + btDbvt::collideKDOP(m_cullingTree->m_sets[0].m_root,planes_n,planes_o,nplanes,dispatcher); + } return true; } - int CcdPhysicsEnvironment::getNumContactPoints() { return 0; diff --git a/source/gameengine/Physics/Bullet/CcdPhysicsEnvironment.h b/source/gameengine/Physics/Bullet/CcdPhysicsEnvironment.h index ddbcbe6b4d6..f861621ae37 100644 --- a/source/gameengine/Physics/Bullet/CcdPhysicsEnvironment.h +++ b/source/gameengine/Physics/Bullet/CcdPhysicsEnvironment.h @@ -172,7 +172,7 @@ protected: btTypedConstraint* getConstraintById(int constraintId); virtual PHY_IPhysicsController* rayTest(PHY_IRayCastFilterCallback &filterCallback, float fromX,float fromY,float fromZ, float toX,float toY,float toZ); - virtual bool cullingTest(PHY_CullingCallback callback, void* userData, PHY__Vector4* planes, int nplanes); + virtual bool cullingTest(PHY_CullingCallback callback, void* userData, PHY__Vector4* planes, int nplanes, int occlusionRes); //Methods for gamelogic collision/physics callbacks diff --git a/source/gameengine/Physics/Bullet/Makefile b/source/gameengine/Physics/Bullet/Makefile index bf3573138f7..48e537bb6a3 100644 --- a/source/gameengine/Physics/Bullet/Makefile +++ b/source/gameengine/Physics/Bullet/Makefile @@ -39,9 +39,16 @@ CPPFLAGS += -I$(NAN_BULLET2)/include CPPFLAGS += -I$(NAN_GUARDEDALLOC)/include CPPFLAGS += -I$(NAN_STRING)/include CPPFLAGS += -I$(NAN_MOTO)/include +CPPFLAGS += -I$(NAN_GLEW)/include +CPPFLAGS += -I$(NAN_PYTHON)/include/python$(NAN_PYTHON_VERSION) +CPPFLAGS += -I$(NAN_SOUNDSYSTEM)/include CPPFLAGS += -I../../../kernel/gen_system CPPFLAGS += -I../../Physics/common CPPFLAGS += -I../../Physics/Dummy CPPFLAGS += -I../../Rasterizer +CPPFLAGS += -I../../Ketsji +CPPFLAGS += -I../../Expressions +CPPFLAGS += -I../../GameLogic +CPPFLAGS += -I../../SceneGraph CPPFLAGS += -I../../../../source/blender/makesdna diff --git a/source/gameengine/Physics/Bullet/SConscript b/source/gameengine/Physics/Bullet/SConscript index f3b6549089d..115ab8bf730 100644 --- a/source/gameengine/Physics/Bullet/SConscript +++ b/source/gameengine/Physics/Bullet/SConscript @@ -3,9 +3,21 @@ Import ('env') sources = 'CcdPhysicsEnvironment.cpp CcdPhysicsController.cpp CcdGraphicController.cpp' -incs = '. ../common #source/kernel/gen_system #intern/string #intern/moto/include #source/gameengine/Rasterizer #source/blender/makesdna' +incs = '. ../common' +incs += ' #source/kernel/gen_system' +incs += ' #intern/string' +incs += ' #intern/moto/include' +incs += ' #extern/glew/include' +incs += ' #source/gameengine/Rasterizer' +incs += ' #source/gameengine/Ketsji' +incs += ' #source/gameengine/Expressions' +incs += ' #source/gameengine/GameLogic' +incs += ' #source/gameengine/SceneGraph' +incs += ' #source/blender/makesdna' +incs += ' #intern/SoundSystem' incs += ' ' + env['BF_BULLET_INC'] +incs += ' ' + env['BF_PYTHON_INC'] cxxflags = [] if env['OURPLATFORM']=='win32-vc': diff --git a/source/gameengine/Physics/Dummy/DummyPhysicsEnvironment.h b/source/gameengine/Physics/Dummy/DummyPhysicsEnvironment.h index fae1844d505..4e15e6ec130 100644 --- a/source/gameengine/Physics/Dummy/DummyPhysicsEnvironment.h +++ b/source/gameengine/Physics/Dummy/DummyPhysicsEnvironment.h @@ -70,7 +70,7 @@ public: } virtual PHY_IPhysicsController* rayTest(PHY_IRayCastFilterCallback &filterCallback, float fromX,float fromY,float fromZ, float toX,float toY,float toZ); - virtual bool cullingTest(PHY_CullingCallback callback, void* userData, PHY__Vector4* planes, int nplanes) { return false; } + virtual bool cullingTest(PHY_CullingCallback callback, void* userData, PHY__Vector4* planes, int nplanes, int occlusionRes) { return false; } //gamelogic callbacks diff --git a/source/gameengine/Physics/Sumo/SumoPhysicsEnvironment.h b/source/gameengine/Physics/Sumo/SumoPhysicsEnvironment.h index 9942a367451..418a361a065 100644 --- a/source/gameengine/Physics/Sumo/SumoPhysicsEnvironment.h +++ b/source/gameengine/Physics/Sumo/SumoPhysicsEnvironment.h @@ -76,7 +76,7 @@ public: } virtual PHY_IPhysicsController* rayTest(PHY_IRayCastFilterCallback &filterCallback,float fromX,float fromY,float fromZ, float toX,float toY,float toZ); - virtual bool cullingTest(PHY_CullingCallback callback, void* userData, PHY__Vector4 *planes, int nplanes) { return false; } + virtual bool cullingTest(PHY_CullingCallback callback, void* userData, PHY__Vector4 *planes, int nplanes, int occlusionRes) { return false; } //gamelogic callbacks diff --git a/source/gameengine/Physics/common/PHY_IPhysicsEnvironment.h b/source/gameengine/Physics/common/PHY_IPhysicsEnvironment.h index 5edafe6b51e..9a4500c3214 100644 --- a/source/gameengine/Physics/common/PHY_IPhysicsEnvironment.h +++ b/source/gameengine/Physics/common/PHY_IPhysicsEnvironment.h @@ -143,7 +143,9 @@ class PHY_IPhysicsEnvironment virtual PHY_IPhysicsController* rayTest(PHY_IRayCastFilterCallback &filterCallback, float fromX,float fromY,float fromZ, float toX,float toY,float toZ)=0; //culling based on physical broad phase - virtual bool cullingTest(PHY_CullingCallback callback, void *userData, PHY__Vector4* planeNormals, int planeNumber) = 0; + // the plane number must be set as follow: near, far, left, right, top, botton + // the near plane must be the first one and must always be present, it is used to get the direction of the view + virtual bool cullingTest(PHY_CullingCallback callback, void *userData, PHY__Vector4* planeNormals, int planeNumber, int occlusionRes) = 0; //Methods for gamelogic collision/physics callbacks //todo: diff --git a/source/gameengine/PyDoc/KX_GameObject.py b/source/gameengine/PyDoc/KX_GameObject.py index fb1e099e575..fa4641c3e2f 100644 --- a/source/gameengine/PyDoc/KX_GameObject.py +++ b/source/gameengine/PyDoc/KX_GameObject.py @@ -25,6 +25,8 @@ class KX_GameObject: # (SCA_IObject) @ivar visible: visibility flag. - note: Game logic will still run for invisible objects. @type visible: boolean + @ivar occlusion: occlusion capability flag. + @type occlusion: boolean @ivar position: The object's position. @type position: list [x, y, z] @ivar orientation: The object's orientation. 3x3 Matrix. You can also write a Quaternion or Euler vector. @@ -76,6 +78,14 @@ class KX_GameObject: # (SCA_IObject) @type recursive: boolean @param recursive: optional argument to set all childrens visibility flag too. """ + def setOcclusion(occlusion, recursive): + """ + Sets the game object's occlusion capability. + + @type visible: boolean + @type recursive: boolean + @param recursive: optional argument to set all childrens occlusion flag too. + """ def getState(): """ Gets the game object's state bitmask. (B{deprecated}) diff --git a/source/gameengine/PyDoc/KX_VisibilityActuator.py b/source/gameengine/PyDoc/KX_VisibilityActuator.py index aca8d1ce243..36f25b2423c 100644 --- a/source/gameengine/PyDoc/KX_VisibilityActuator.py +++ b/source/gameengine/PyDoc/KX_VisibilityActuator.py @@ -7,7 +7,9 @@ class KX_VisibilityActuator(SCA_IActuator): Visibility Actuator. @ivar visibility: whether the actuator makes its parent object visible or invisible @type visibility: boolean - @ivar recursion: whether the visibility/invisibility should be propagated to all children of the object + @ivar occlusion: whether the actuator makes its parent object an occluder or not + @type occlusion: boolean + @ivar recursion: whether the visibility/occlusion should be propagated to all children of the object @type recursion: boolean """ def set(visible): diff --git a/source/gameengine/Rasterizer/RAS_MaterialBucket.cpp b/source/gameengine/Rasterizer/RAS_MaterialBucket.cpp index 69f73c2ee25..5ddcdd310b0 100644 --- a/source/gameengine/Rasterizer/RAS_MaterialBucket.cpp +++ b/source/gameengine/Rasterizer/RAS_MaterialBucket.cpp @@ -62,10 +62,12 @@ RAS_MeshSlot::~RAS_MeshSlot() { vector<RAS_DisplayArray*>::iterator it; +#ifdef USE_SPLIT Split(true); while(m_joinedSlots.size()) m_joinedSlots.front()->Split(true); +#endif for(it=m_displayArrays.begin(); it!=m_displayArrays.end(); it++) { (*it)->m_users--; @@ -428,11 +430,11 @@ bool RAS_MeshSlot::IsCulled() return true; if(!m_bCulled) return false; - +#ifdef USE_SPLIT for(it=m_joinedSlots.begin(); it!=m_joinedSlots.end(); it++) if(!(*it)->m_bCulled) return false; - +#endif return true; } diff --git a/source/gameengine/Rasterizer/RAS_MeshObject.cpp b/source/gameengine/Rasterizer/RAS_MeshObject.cpp index a907994bf57..162f9a81335 100644 --- a/source/gameengine/Rasterizer/RAS_MeshObject.cpp +++ b/source/gameengine/Rasterizer/RAS_MeshObject.cpp @@ -406,7 +406,9 @@ void RAS_MeshObject::UpdateBuckets(void* clientobj, ms->m_bCulled = culled || !visible; /* split if necessary */ +#ifdef USE_SPLIT ms->Split(); +#endif } } diff --git a/source/gameengine/Rasterizer/RAS_Polygon.cpp b/source/gameengine/Rasterizer/RAS_Polygon.cpp index 66b14bb60b0..eacc1285166 100644 --- a/source/gameengine/Rasterizer/RAS_Polygon.cpp +++ b/source/gameengine/Rasterizer/RAS_Polygon.cpp @@ -97,6 +97,17 @@ void RAS_Polygon::SetCollider(bool visible) else m_polyflags &= ~COLLIDER; } +bool RAS_Polygon::IsTwoside() +{ + return (m_polyflags & TWOSIDE) != 0; +} + +void RAS_Polygon::SetTwoside(bool twoside) +{ + if(twoside) m_polyflags |= TWOSIDE; + else m_polyflags &= ~TWOSIDE; +} + RAS_MaterialBucket* RAS_Polygon::GetMaterial() { return m_bucket; diff --git a/source/gameengine/Rasterizer/RAS_Polygon.h b/source/gameengine/Rasterizer/RAS_Polygon.h index 224a7e0eed2..41eaa6bdd4a 100644 --- a/source/gameengine/Rasterizer/RAS_Polygon.h +++ b/source/gameengine/Rasterizer/RAS_Polygon.h @@ -56,7 +56,8 @@ class RAS_Polygon public: enum { VISIBLE = 1, - COLLIDER = 2 + COLLIDER = 2, + TWOSIDE = 4 }; RAS_Polygon(RAS_MaterialBucket* bucket, RAS_DisplayArray* darray, int numvert); @@ -79,6 +80,9 @@ public: bool IsCollider(); void SetCollider(bool collider); + bool IsTwoside(); + void SetTwoside(bool twoside); + RAS_MaterialBucket* GetMaterial(); RAS_DisplayArray* GetDisplayArray(); }; diff --git a/source/gameengine/VideoTexture/ImageRender.cpp b/source/gameengine/VideoTexture/ImageRender.cpp index 58697ed3cc7..6ef62f64d3f 100644 --- a/source/gameengine/VideoTexture/ImageRender.cpp +++ b/source/gameengine/VideoTexture/ImageRender.cpp @@ -249,7 +249,7 @@ void ImageRender::Render() // restore the stereo mode now that the matrix is computed m_rasterizer->SetStereoMode(stereomode); - // do not update the mesh, we don't want to do it more than once per frame + // do not update the mesh transform, we don't want to do it more than once per frame //m_scene->UpdateMeshTransformations(); m_scene->CalculateVisibleMeshes(m_rasterizer,m_camera); |