2nd try to merge sim_physics with trunk rev 19825
[blender.git] / source / gameengine / Ketsji / KX_Scene.cpp
index a7e91e27df342626dd9d22c4138095ff28050fa2..96f2f3e8ed391210f1efeb0f7e737367b0fc183f 100644 (file)
 #pragma warning (disable : 4786)
 #endif //WIN32
 
-
 #include "KX_Scene.h"
 #include "MT_assert.h"
 
 #include "KX_KetsjiEngine.h"
+#include "KX_BlenderMaterial.h"
 #include "RAS_IPolygonMaterial.h"
 #include "ListValue.h"
 #include "SCA_LogicManager.h"
@@ -48,6 +48,7 @@
 #include "SCA_KeyboardManager.h"
 #include "SCA_MouseManager.h"
 #include "SCA_PropertyEventManager.h"
+#include "SCA_ActuatorEventManager.h"
 #include "KX_Camera.h"
 #include "SCA_JoystickManager.h"
 
@@ -65,6 +66,9 @@
 #include "SG_Controller.h"
 #include "SG_IObject.h"
 #include "SG_Tree.h"
+#include "DNA_group_types.h"
+#include "DNA_scene_types.h"
+#include "BKE_anim.h"
 
 #include "KX_SG_NodeRelationships.h"
 
@@ -72,7 +76,9 @@
 #include "NG_NetworkScene.h"
 #include "PHY_IPhysicsEnvironment.h"
 #include "KX_IPhysicsController.h"
+#include "PHY_IGraphicController.h"
 #include "KX_BlenderSceneConverter.h"
+#include "KX_MotionState.h"
 
 #include "BL_ShapeDeformer.h"
 #include "BL_DeformableGameObject.h"
 #include "CcdPhysicsController.h"
 #endif
 
+#include "KX_Light.h"
+
 void* KX_SceneReplicationFunc(SG_IObject* node,void* gameobj,void* scene)
 {
        KX_GameObject* replica = ((KX_Scene*)scene)->AddNodeReplicaObject(node,(KX_GameObject*)gameobj);
 
+       if(replica)
+               replica->Release();
+
        return (void*)replica;
 }
 
@@ -109,21 +120,25 @@ KX_Scene::KX_Scene(class SCA_IInputDevice* keyboarddevice,
                                   class SCA_IInputDevice* mousedevice,
                                   class NG_NetworkDeviceInterface *ndi,
                                   class SND_IAudioDevice* adi,
-                                  const STR_String& sceneName): 
+                                  const STR_String& sceneName,
+                                  Scene *scene): 
        PyObjectPlus(&KX_Scene::Type),
        m_keyboardmgr(NULL),
        m_mousemgr(NULL),
+       m_sceneConverter(NULL),
        m_physicsEnvironment(0),
        m_sceneName(sceneName),
        m_adi(adi),
        m_networkDeviceInterface(ndi),
        m_active_camera(NULL),
        m_ueberExecutionPriority(0),
-       m_sceneConverter(NULL)
+       m_blenderScene(scene)
 {
        m_suspendedtime = 0.0;
        m_suspendeddelta = 0.0;
 
+       m_dbvt_culling = false;
+       m_dbvt_occlusion_res = 0;
        m_activity_culling = false;
        m_suspend = false;
        m_isclearingZbuffer = true;
@@ -143,22 +158,32 @@ KX_Scene::KX_Scene(class SCA_IInputDevice* keyboarddevice,
        
        SCA_AlwaysEventManager* alwaysmgr = new SCA_AlwaysEventManager(m_logicmgr);
        SCA_PropertyEventManager* propmgr = new SCA_PropertyEventManager(m_logicmgr);
+       SCA_ActuatorEventManager* actmgr = new SCA_ActuatorEventManager(m_logicmgr);
        SCA_RandomEventManager* rndmgr = new SCA_RandomEventManager(m_logicmgr);
        KX_RayEventManager* raymgr = new KX_RayEventManager(m_logicmgr);
 
        KX_NetworkEventManager* netmgr = new KX_NetworkEventManager(m_logicmgr, ndi);
        
-       SCA_JoystickManager *joymgr     = new SCA_JoystickManager(m_logicmgr);
+       
 
        m_logicmgr->RegisterEventManager(alwaysmgr);
        m_logicmgr->RegisterEventManager(propmgr);
+       m_logicmgr->RegisterEventManager(actmgr);
        m_logicmgr->RegisterEventManager(m_keyboardmgr);
        m_logicmgr->RegisterEventManager(m_mousemgr);
        m_logicmgr->RegisterEventManager(m_timemgr);
        m_logicmgr->RegisterEventManager(rndmgr);
        m_logicmgr->RegisterEventManager(raymgr);
        m_logicmgr->RegisterEventManager(netmgr);
-       m_logicmgr->RegisterEventManager(joymgr);
+
+
+       SYS_SystemHandle hSystem = SYS_GetSystem();
+       bool nojoystick= SYS_GetCommandLineInt(hSystem,"nojoystick",0);
+       if (!nojoystick)
+       {
+               SCA_JoystickManager *joymgr     = new SCA_JoystickManager(m_logicmgr);
+               m_logicmgr->RegisterEventManager(joymgr);
+       }
 
        m_soundScene = new SND_Scene(adi);
        MT_assert (m_networkDeviceInterface != NULL);
@@ -171,7 +196,7 @@ KX_Scene::KX_Scene(class SCA_IInputDevice* keyboarddevice,
        m_canvasDesignWidth = 0;
        m_canvasDesignHeight = 0;
        
-       m_attrlist = PyDict_New(); /* new ref */
+       m_attr_dict = PyDict_New(); /* new ref */
 }
 
 
@@ -225,40 +250,10 @@ KX_Scene::~KX_Scene()
        {
                delete m_bucketmanager;
        }
-#ifdef USE_BULLET
-       // This is a fix for memory leaks in bullet: the collision shapes is not destroyed 
-       // when the physical controllers are destroyed. The reason is that shapes are shared
-       // between replicas of an object. There is no reference count in Bullet so the
-       // only workaround that does not involve changes in Bullet is to save in this array
-       // the list of shapes that are created when the scene is created (see KX_ConvertPhysicsObjects.cpp)
-       class btCollisionShape* shape;
-       class btTriangleMeshShape* meshShape;
-       vector<class btCollisionShape*>::iterator it = m_shapes.begin();
-       while (it != m_shapes.end()) {
-               shape = *it;
-               if (shape->getShapeType() == TRIANGLE_MESH_SHAPE_PROXYTYPE)
-               {
-                       meshShape = static_cast<btTriangleMeshShape*>(shape);
-                       // shapes based on meshes use an interface that contains the vertices.
-                       // Again the idea is to be able to share the interface between shapes but
-                       // this is not used in Blender: each base object will have its own interface 
-                       btStridingMeshInterface* meshInterface = meshShape->getMeshInterface();
-                       if (meshInterface)
-                               delete meshInterface;
-               }
-               delete shape;
-               it++;
-       }
-#endif
-       //Py_DECREF(m_attrlist);
-}
-
-void KX_Scene::AddShape(class btCollisionShape*shape)
-{
-       m_shapes.push_back(shape);
+       PyDict_Clear(m_attr_dict);
+       Py_DECREF(m_attr_dict);
 }
 
-
 void KX_Scene::SetProjectionMatrix(MT_CmMatrix4x4& pmat)
 {
        m_projectionmat = pmat;
@@ -419,6 +414,13 @@ void KX_Scene::RemoveNodeDestructObject(class SG_IObject* node,class CValue* gam
                // will in any case be deleted. This ensures that the object will not try to use the node
                // when it is finally deleted (see KX_GameObject destructor)
                orgobj->SetSGNode(NULL);
+               PHY_IGraphicController* ctrl = orgobj->GetGraphicController();
+               if (ctrl)
+               {
+                       // a graphic controller is set, we must delete it as the node will be deleted
+                       delete ctrl;
+                       orgobj->SetGraphicController(NULL);
+               }
        }
        if (node)
                delete node;
@@ -426,6 +428,11 @@ void KX_Scene::RemoveNodeDestructObject(class SG_IObject* node,class CValue* gam
 
 KX_GameObject* KX_Scene::AddNodeReplicaObject(class SG_IObject* node, class CValue* gameobj)
 {
+       // for group duplication, limit the duplication of the hierarchy to the
+       // objects that are part of the group. 
+       if (!IsObjectInGroup(gameobj))
+               return NULL;
+       
        KX_GameObject* orgobj = (KX_GameObject*)gameobj;
        KX_GameObject* newobj = (KX_GameObject*)orgobj->GetReplica();
        m_map_gameobject_to_replica.insert(orgobj, newobj);
@@ -470,7 +477,7 @@ KX_GameObject* KX_Scene::AddNodeReplicaObject(class SG_IObject* node, class CVal
 
        // this is the list of object that are send to the graphics pipeline
        m_objectlist->Add(newobj->AddRef());
-       newobj->Bucketize();
+       newobj->AddMeshUser();
 
        // logic cannot be replicated, until the whole hierarchy is replicated.
        m_logicHierarchicalGameObjects.push_back(newobj);
@@ -492,7 +499,14 @@ KX_GameObject* KX_Scene::AddNodeReplicaObject(class SG_IObject* node, class CVal
                        replicanode->AddSGController(replicacontroller);
                }
        }
-       
+       // replicate graphic controller
+       if (orgobj->GetGraphicController())
+       {
+               PHY_IMotionState* motionstate = new KX_MotionState(newobj->GetSGNode());
+               PHY_IGraphicController* newctrl = orgobj->GetGraphicController()->GetReplica(motionstate);
+               newctrl->setNewClientInfo(newobj->getClientInfo());
+               newobj->SetGraphicController(newctrl);
+       }
        return newobj;
 }
 
@@ -503,6 +517,11 @@ KX_GameObject* KX_Scene::AddNodeReplicaObject(class SG_IObject* node, class CVal
 // hierarchy that's because first ALL bricks must exist in the new
 // replica of the hierarchy in order to make cross-links work properly
 // !
+// It is VERY important that the order of sensors and actuators in
+// the replicated object is preserved: it is is used to reconnect the logic.
+// This method is more robust then using the bricks name in case of complex 
+// group replication. The replication of logic bricks is done in 
+// SCA_IObject::ReParentLogic(), make sure it preserves the order of the bricks.
 void KX_Scene::ReplicateLogic(KX_GameObject* newobj)
 {
        // also relink the controller to sensors/actuators
@@ -525,37 +544,38 @@ void KX_Scene::ReplicateLogic(KX_GameObject* newobj)
                for (vector<SCA_ISensor*>::iterator its = linkedsensors.begin();!(its==linkedsensors.end());its++)
                {
                        SCA_ISensor* oldsensor = (*its);
-                       STR_String name = oldsensor->GetName();
-                       //find this name in the list
-                       SCA_ISensor* newsensor = newobj->FindSensor(name);
+                       SCA_IObject* oldsensorobj = oldsensor->GetParent();
+                       SCA_IObject* newsensorobj = NULL;
                
-                       if (newsensor)
+                       // the original owner of the sensor has been replicated?
+                       void **h_obj = m_map_gameobject_to_replica[oldsensorobj];
+                       if (h_obj)
+                               newsensorobj = (SCA_IObject*)(*h_obj);
+                       if (!newsensorobj)
                        {
-                               // relink this newsensor to the controller
-                               m_logicmgr->RegisterToSensor(cont,newsensor);
+                               // no, then the sensor points outside the hierachy, keep it the same
+                               if (m_objectlist->SearchValue(oldsensorobj))
+                                       // only replicate links that points to active objects
+                                       m_logicmgr->RegisterToSensor(cont,oldsensor);
                        }
                        else
                        {
-                               // it can be linked somewhere in the hierarchy or...
-                               for (vector<KX_GameObject*>::iterator git = m_logicHierarchicalGameObjects.begin();
-                               !(git==m_logicHierarchicalGameObjects.end());++git)
-                               {
-                                       newsensor = (*git)->FindSensor(name);
-                                       if (newsensor)
-                                               break;
-                               } 
+                               // yes, then the new sensor has the same position
+                               SCA_SensorList& sensorlist = oldsensorobj->GetSensors();
+                               SCA_SensorList::iterator sit;
+                               SCA_ISensor* newsensor = NULL;
+                               int sensorpos;
 
-                               if (newsensor)
-                               {
-                                       // relink this newsensor to the controller somewhere else within this
-                                       // hierarchy
-                                       m_logicmgr->RegisterToSensor(cont,newsensor);
-                               }
-                               else
+                               for (sensorpos=0, sit=sensorlist.begin(); sit!=sensorlist.end(); sit++, sensorpos++)
                                {
-                                       // must be an external sensor, so...
-                                       m_logicmgr->RegisterToSensor(cont,oldsensor);
+                                       if ((*sit) == oldsensor) 
+                                       {
+                                               newsensor = newsensorobj->GetSensors().at(sensorpos);
+                                               break;
+                                       }
                                }
+                               assert(newsensor != NULL);
+                               m_logicmgr->RegisterToSensor(cont,newsensor);
                        }
                }
                
@@ -563,43 +583,195 @@ void KX_Scene::ReplicateLogic(KX_GameObject* newobj)
                for (vector<SCA_IActuator*>::iterator ita = linkedactuators.begin();!(ita==linkedactuators.end());ita++)
                {
                        SCA_IActuator* oldactuator = (*ita);
-                       STR_String name = oldactuator->GetName();
-                       //find this name in the list
-                       SCA_IActuator* newactuator = newobj->FindActuator(name);
-                       if (newactuator)
+                       SCA_IObject* oldactuatorobj = oldactuator->GetParent();
+                       SCA_IObject* newactuatorobj = NULL;
+
+                       // the original owner of the sensor has been replicated?
+                       void **h_obj = m_map_gameobject_to_replica[oldactuatorobj];
+                       if (h_obj)
+                               newactuatorobj = (SCA_IObject*)(*h_obj);
+
+                       if (!newactuatorobj)
                        {
-                               // relink this newsensor to the controller
-                               m_logicmgr->RegisterToActuator(cont,newactuator);
-                               newactuator->SetUeberExecutePriority(m_ueberExecutionPriority);
+                               // no, then the sensor points outside the hierachy, keep it the same
+                               if (m_objectlist->SearchValue(oldactuatorobj))
+                                       // only replicate links that points to active objects
+                                       m_logicmgr->RegisterToActuator(cont,oldactuator);
                        }
                        else
                        {
-                               // it can be linked somewhere in the hierarchy or...
-                               for (vector<KX_GameObject*>::iterator git = m_logicHierarchicalGameObjects.begin();
-                               !(git==m_logicHierarchicalGameObjects.end());++git)
-                               {
-                                       newactuator= (*git)->FindActuator(name);
-                                       if (newactuator)
-                                               break;
-                               } 
+                               // yes, then the new sensor has the same position
+                               SCA_ActuatorList& actuatorlist = oldactuatorobj->GetActuators();
+                               SCA_ActuatorList::iterator ait;
+                               SCA_IActuator* newactuator = NULL;
+                               int actuatorpos;
 
-                               if (newactuator)
+                               for (actuatorpos=0, ait=actuatorlist.begin(); ait!=actuatorlist.end(); ait++, actuatorpos++)
                                {
-                                       // relink this actuator to the controller somewhere else within this
-                                       // hierarchy
-                                       m_logicmgr->RegisterToActuator(cont,newactuator);
-                                       newactuator->SetUeberExecutePriority(m_ueberExecutionPriority);
-                               }
-                               else
-                               {
-                                       // must be an external actuator, so...
-                                       m_logicmgr->RegisterToActuator(cont,oldactuator);
+                                       if ((*ait) == oldactuator) 
+                                       {
+                                               newactuator = newactuatorobj->GetActuators().at(actuatorpos);
+                                               break;
+                                       }
                                }
+                               assert(newactuator != NULL);
+                               m_logicmgr->RegisterToActuator(cont,newactuator);
+                               newactuator->SetUeberExecutePriority(m_ueberExecutionPriority);
                        }
                }
        }
+       // ready to set initial state
+       newobj->ResetState();
 }
 
+void KX_Scene::DupliGroupRecurse(CValue* obj, int level)
+{
+       KX_GameObject* groupobj = (KX_GameObject*) obj;
+       KX_GameObject* replica;
+       KX_GameObject* gameobj;
+       Object* blgroupobj = groupobj->GetBlenderObject();
+       Group* group;
+       GroupObject *go;
+       vector<KX_GameObject*> duplilist;
+
+       if (!groupobj->GetSGNode() ||
+               !groupobj->IsDupliGroup() ||
+               level>MAX_DUPLI_RECUR)
+               return;
+
+       // we will add one group at a time
+       m_logicHierarchicalGameObjects.clear();
+       m_map_gameobject_to_replica.clear();
+       m_ueberExecutionPriority++;
+       // for groups will do something special: 
+       // we will force the creation of objects to those in the group only
+       // Again, this is match what Blender is doing (it doesn't care of parent relationship)
+       m_groupGameObjects.clear();
+
+       group = blgroupobj->dup_group;
+       for(go=(GroupObject*)group->gobject.first; go; go=(GroupObject*)go->next) 
+       {
+               Object* blenderobj = go->ob;
+               if (blgroupobj == blenderobj)
+                       // this check is also in group_duplilist()
+                       continue;
+
+               gameobj = (KX_GameObject*)m_logicmgr->FindGameObjByBlendObj(blenderobj);
+               if (gameobj == NULL) 
+               {
+                       // this object has not been converted!!!
+                       // Should not happen as dupli group are created automatically 
+                       continue;
+               }
+
+               gameobj->SetBlenderGroupObject(blgroupobj);
+
+               if ((blenderobj->lay & group->layer)==0)
+               {
+                       // object is not visible in the 3D view, will not be instantiated
+                       continue;
+               }
+               m_groupGameObjects.insert(gameobj);
+       }
+
+       set<CValue*>::iterator oit;
+       for (oit=m_groupGameObjects.begin(); oit != m_groupGameObjects.end(); oit++)
+       {
+               gameobj = (KX_GameObject*)(*oit);
+
+               KX_GameObject *parent = gameobj->GetParent();
+               if (parent != NULL)
+               {
+                       parent->Release(); // GetParent() increased the refcount
+
+                       // this object is not a top parent. Either it is the child of another
+                       // object in the group and it will be added automatically when the parent
+                       // is added. Or it is the child of an object outside the group and the group
+                       // is inconsistent, skip it anyway
+                       continue;
+               }
+               replica = (KX_GameObject*) AddNodeReplicaObject(NULL,gameobj);
+               // add to 'rootparent' list (this is the list of top hierarchy objects, updated each frame)
+               m_parentlist->Add(replica->AddRef());
+
+               // recurse replication into children nodes
+               NodeList& children = gameobj->GetSGNode()->GetSGChildren();
+
+               replica->GetSGNode()->ClearSGChildren();
+               for (NodeList::iterator childit = children.begin();!(childit==children.end());++childit)
+               {
+                       SG_Node* orgnode = (*childit);
+                       SG_Node* childreplicanode = orgnode->GetSGReplica();
+                       if (childreplicanode)
+                               replica->GetSGNode()->AddChild(childreplicanode);
+               }
+               // don't replicate logic now: we assume that the objects in the group can have
+               // logic relationship, even outside parent relationship
+               // In order to match 3D view, the position of groupobj is used as a 
+               // transformation matrix instead of the new position. This means that 
+               // the group reference point is 0,0,0
+
+               // get the rootnode's scale
+               MT_Vector3 newscale = groupobj->NodeGetWorldScaling();
+               // set the replica's relative scale with the rootnode's scale
+               replica->NodeSetRelativeScale(newscale);
+
+               MT_Matrix3x3 newori = groupobj->NodeGetWorldOrientation() * gameobj->NodeGetWorldOrientation();
+               replica->NodeSetLocalOrientation(newori);
+               MT_Point3 offset(group->dupli_ofs);
+               MT_Point3 newpos = groupobj->NodeGetWorldPosition() + 
+                       newscale*(groupobj->NodeGetWorldOrientation() * (gameobj->NodeGetWorldPosition()-offset));
+               replica->NodeSetLocalPosition(newpos);
+
+               replica->GetSGNode()->UpdateWorldData(0);
+               replica->GetSGNode()->SetBBox(gameobj->GetSGNode()->BBox());
+               replica->GetSGNode()->SetRadius(gameobj->GetSGNode()->Radius());
+               // done with replica
+               replica->Release();
+       }
+
+       // the logic must be replicated first because we need
+       // the new logic bricks before relinking
+       vector<KX_GameObject*>::iterator git;
+       for (git = m_logicHierarchicalGameObjects.begin();!(git==m_logicHierarchicalGameObjects.end());++git)
+       {
+               (*git)->ReParentLogic();
+       }
+       
+       //      relink any pointers as necessary, sort of a temporary solution
+       for (git = m_logicHierarchicalGameObjects.begin();!(git==m_logicHierarchicalGameObjects.end());++git)
+       {
+               // this will also relink the actuator to objects within the hierarchy
+               (*git)->Relink(&m_map_gameobject_to_replica);
+               // add the object in the layer of the parent
+               (*git)->SetLayer(groupobj->GetLayer());
+               // If the object was a light, we need to update it's RAS_LightObject as well
+               if ((*git)->IsLight())
+               {
+                       KX_LightObject* lightobj = static_cast<KX_LightObject*>(*git);
+                       lightobj->GetLightData()->m_layer = groupobj->GetLayer();
+               }
+       }
+
+       // replicate crosslinks etc. between logic bricks
+       for (git = m_logicHierarchicalGameObjects.begin();!(git==m_logicHierarchicalGameObjects.end());++git)
+       {
+               ReplicateLogic((*git));
+       }
+       
+       // now look if object in the hierarchy have dupli group and recurse
+       for (git = m_logicHierarchicalGameObjects.begin();!(git==m_logicHierarchicalGameObjects.end());++git)
+       {
+               if ((*git) != groupobj && (*git)->IsDupliGroup())
+                       // can't instantiate group immediately as it destroys m_logicHierarchicalGameObjects
+                       duplilist.push_back((*git));
+       }
+
+       for (git = duplilist.begin(); !(git == duplilist.end()); ++git)
+       {
+               DupliGroupRecurse((*git), level+1);
+       }
+}
 
 
 SCA_IObject* KX_Scene::AddReplicaObject(class CValue* originalobject,
@@ -609,6 +781,7 @@ SCA_IObject* KX_Scene::AddReplicaObject(class CValue* originalobject,
 
        m_logicHierarchicalGameObjects.clear();
        m_map_gameobject_to_replica.clear();
+       m_groupGameObjects.clear();
 
        // todo: place a timebomb in the object, for temporarily objects :)
        // lifespan of zero means 'this object lives forever'
@@ -642,56 +815,70 @@ SCA_IObject* KX_Scene::AddReplicaObject(class CValue* originalobject,
        {
                SG_Node* orgnode = (*childit);
                SG_Node* childreplicanode = orgnode->GetSGReplica();
-               replica->GetSGNode()->AddChild(childreplicanode);
+               if (childreplicanode)
+                       replica->GetSGNode()->AddChild(childreplicanode);
        }
 
-       //      relink any pointers as necessary, sort of a temporary solution
+       // At this stage all the objects in the hierarchy have been duplicated,
+       // we can update the scenegraph, we need it for the duplication of logic
+       MT_Point3 newpos = ((KX_GameObject*) parentobject)->NodeGetWorldPosition();
+       replica->NodeSetLocalPosition(newpos);
+
+       MT_Matrix3x3 newori = ((KX_GameObject*) parentobject)->NodeGetWorldOrientation();
+       replica->NodeSetLocalOrientation(newori);
+       
+       // get the rootnode's scale
+       MT_Vector3 newscale = parentobj->GetSGNode()->GetRootSGParent()->GetLocalScale();
+
+       // set the replica's relative scale with the rootnode's scale
+       replica->NodeSetRelativeScale(newscale);
+
+       replica->GetSGNode()->UpdateWorldData(0);
+       replica->GetSGNode()->SetBBox(originalobj->GetSGNode()->BBox());
+       replica->GetSGNode()->SetRadius(originalobj->GetSGNode()->Radius());
+
+       // now replicate logic
        vector<KX_GameObject*>::iterator git;
        for (git = m_logicHierarchicalGameObjects.begin();!(git==m_logicHierarchicalGameObjects.end());++git)
        {
+               (*git)->ReParentLogic();
+       }
+       
+       //      relink any pointers as necessary, sort of a temporary solution
+       for (git = m_logicHierarchicalGameObjects.begin();!(git==m_logicHierarchicalGameObjects.end());++git)
+       {
+               // this will also relink the actuators in the hierarchy
                (*git)->Relink(&m_map_gameobject_to_replica);
                // add the object in the layer of the parent
                (*git)->SetLayer(parentobj->GetLayer());
+               // If the object was a light, we need to update it's RAS_LightObject as well
+               if ((*git)->IsLight())
+               {
+                       KX_LightObject* lightobj = static_cast<KX_LightObject*>(*git);
+                       lightobj->GetLightData()->m_layer = parentobj->GetLayer();
+               }
        }
 
-       // now replicate logic
+       // replicate crosslinks etc. between logic bricks
        for (git = m_logicHierarchicalGameObjects.begin();!(git==m_logicHierarchicalGameObjects.end());++git)
        {
-               (*git)->ReParentLogic();
+               ReplicateLogic((*git));
        }
        
-       // replicate crosslinks etc. between logic bricks
+       // check if there are objects with dupligroup in the hierarchy
+       vector<KX_GameObject*> duplilist;
        for (git = m_logicHierarchicalGameObjects.begin();!(git==m_logicHierarchicalGameObjects.end());++git)
        {
-               ReplicateLogic((*git));
+               if ((*git)->IsDupliGroup())
+               {
+                       // separate list as m_logicHierarchicalGameObjects is also used by DupliGroupRecurse()
+                       duplilist.push_back(*git);
+               }
        }
-       
-       MT_Point3 newpos = ((KX_GameObject*) parentobject)->NodeGetWorldPosition();
-       replica->NodeSetLocalPosition(newpos);
-
-       MT_Matrix3x3 newori = ((KX_GameObject*) parentobject)->NodeGetWorldOrientation();
-       replica->NodeSetLocalOrientation(newori);
-       
-       // get the rootnode's scale
-       MT_Vector3 newscale = parentobj->GetSGNode()->GetRootSGParent()->GetLocalScale();
-
-       // set the replica's relative scale with the rootnode's scale
-       replica->NodeSetRelativeScale(newscale);
-
-       if (replica->GetPhysicsController())
+       for (git = duplilist.begin();!(git==duplilist.end());++git)
        {
-               replica->GetPhysicsController()->setPosition(newpos);
-               replica->GetPhysicsController()->setOrientation(newori.getRotation());
-               replica->GetPhysicsController()->setScaling(newscale);
+               DupliGroupRecurse(*git, 0);
        }
-
-       // here we want to set the relative scale: the rootnode's scale will override all other
-       // scalings, so lets better prepare for it
-
-
-       replica->GetSGNode()->UpdateWorldData(0);
-       replica->GetSGNode()->SetBBox(originalobj->GetSGNode()->BBox());
-       replica->GetSGNode()->SetRadius(originalobj->GetSGNode()->Radius());
        //      don't release replica here because we are returning it, not done with it...
        return replica;
 }
@@ -738,6 +925,12 @@ int KX_Scene::NewRemoveObject(class CValue* gameobj)
        int ret;
        KX_GameObject* newobj = (KX_GameObject*) gameobj;
 
+       // keep the blender->game object association up to date
+       // note that all the replicas of an object will have the same
+       // blender object, that's why we need to check the game object
+       // as only the deletion of the original object must be recorded
+       m_logicmgr->UnregisterGameObj(newobj->GetBlenderObject(), gameobj);
+
        //todo: look at this
        //GetPhysicsEnvironment()->RemovePhysicsController(gameobj->getPhysicsController());
 
@@ -779,6 +972,8 @@ int KX_Scene::NewRemoveObject(class CValue* gameobj)
        
        newobj->RemoveMeshes();
        ret = 1;
+       if (newobj->IsLight() && m_lightlist->RemoveValue(newobj))
+               ret = newobj->Release();
        if (m_objectlist->RemoveValue(newobj))
                ret = newobj->Release();
        if (m_tempObjectList->RemoveValue(newobj))
@@ -796,12 +991,14 @@ int KX_Scene::NewRemoveObject(class CValue* gameobj)
                //m_active_camera->Release();
                m_active_camera = NULL;
        }
+
        // in case this is a camera
        m_cameras.remove((KX_Camera*)newobj);
 
        if (m_sceneConverter)
                m_sceneConverter->UnregisterGameObject(newobj);
        // return value will be 0 if the object is actually deleted (all reference gone)
+       
        return ret;
 }
 
@@ -825,13 +1022,13 @@ void KX_Scene::ReplaceMesh(class CValue* obj,void* meshobj)
        {
                BL_DeformableGameObject* newobj = static_cast<BL_DeformableGameObject*>( gameobj );
                
-               if (newobj->m_pDeformer)
+               if (newobj->GetDeformer())
                {
-                       delete newobj->m_pDeformer;
-                       newobj->m_pDeformer = NULL;
+                       delete newobj->GetDeformer();
+                       newobj->SetDeformer(NULL);
                }
 
-               if (mesh->m_class == 1) 
+               if (mesh->IsDeformed())
                {
                        // we must create a new deformer but which one?
                        KX_GameObject* parentobj = newobj->GetParent();
@@ -853,6 +1050,12 @@ void KX_Scene::ReplaceMesh(class CValue* obj,void* meshobj)
                                blendmesh->dvert!=NULL;                                         // mesh has vertex group
                        bool releaseParent = true;
 
+                       
+                       if (oldblendobj==NULL) {
+                               std::cout << "warning: ReplaceMesh() new mesh is not used in an object from the current scene, you will get incorrect behavior" << std::endl;
+                               bHasShapeKey= bHasDvert= bHasArmature= false;
+                       }
+                       
                        if (bHasShapeKey)
                        {
                                BL_ShapeDeformer* shapeDeformer;
@@ -878,25 +1081,26 @@ void KX_Scene::ReplaceMesh(class CValue* obj,void* meshobj)
                                                NULL
                                        );
                                }
-                               newobj->m_pDeformer = shapeDeformer;
+                               newobj->SetDeformer( shapeDeformer);
                        }
                        else if (bHasArmature) 
                        {
                                BL_SkinDeformer* skinDeformer = new BL_SkinDeformer(
+                                       newobj,
                                        oldblendobj, blendobj,
                                        static_cast<BL_SkinMeshObject*>(mesh),
                                        true,
                                        static_cast<BL_ArmatureObject*>( parentobj )
                                );
                                releaseParent= false;
-                               newobj->m_pDeformer = skinDeformer;
+                               newobj->SetDeformer(skinDeformer);
                        }
                        else if (bHasDvert)
                        {
                                BL_MeshDeformer* meshdeformer = new BL_MeshDeformer(
-                                       oldblendobj, static_cast<BL_SkinMeshObject*>(mesh)
+                                       newobj, oldblendobj, static_cast<BL_SkinMeshObject*>(mesh)
                                );
-                               newobj->m_pDeformer = meshdeformer;
+                               newobj->SetDeformer(meshdeformer);
                        }
 
                        // release parent reference if its not being used 
@@ -904,7 +1108,8 @@ void KX_Scene::ReplaceMesh(class CValue* obj,void* meshobj)
                                parentobj->Release();
                }
        }
-       gameobj->Bucketize();
+
+       gameobj->AddMeshUser();
 }
 
 
@@ -995,16 +1200,16 @@ void KX_Scene::UpdateMeshTransformations()
        {
                KX_GameObject* gameobj = (KX_GameObject*)m_objectlist->GetValue(i);
                gameobj->GetOpenGLMatrix();
-//             gameobj->UpdateNonDynas();
        }
 }
 
-void KX_Scene::MarkVisible(SG_Tree *node, RAS_IRasterizer* rasty, KX_Camera* cam)
+void KX_Scene::MarkVisible(SG_Tree *node, RAS_IRasterizer* rasty, KX_Camera* cam, int layer)
 {
        int intersect = KX_Camera::INTERSECT;
        KX_GameObject *gameobj = node->Client()?(KX_GameObject*) node->Client()->GetSGClientObject():NULL;
-       bool dotest = (gameobj && gameobj->GetVisible()) || node->Left() || node->Right();
-       
+       bool visible = (gameobj && gameobj->GetVisible() && (!layer || (gameobj->GetLayer() & layer)));
+       bool dotest = visible || node->Left() || node->Right();
+
        /* If the camera is inside the box, assume intersect. */
        if (dotest && !node->inside( cam->NodeGetWorldPosition()))
        {
@@ -1028,19 +1233,19 @@ void KX_Scene::MarkVisible(SG_Tree *node, RAS_IRasterizer* rasty, KX_Camera* cam
                        break;
                case KX_Camera::INTERSECT:
                        if (gameobj)
-                               MarkVisible(rasty, gameobj,cam);
+                               MarkVisible(rasty, gameobj, cam, layer);
                        if (node->Left())
-                               MarkVisible(node->Left(), rasty,cam);
+                               MarkVisible(node->Left(), rasty, cam, layer);
                        if (node->Right())
-                               MarkVisible(node->Right(), rasty,cam);
+                               MarkVisible(node->Right(), rasty, cam, layer);
                        break;
                case KX_Camera::INSIDE:
-                       MarkSubTreeVisible(node, rasty, true,cam);
+                       MarkSubTreeVisible(node, rasty, true, cam, layer);
                        break;
        }
 }
 
-void KX_Scene::MarkSubTreeVisible(SG_Tree *node, RAS_IRasterizer* rasty, bool visible,KX_Camera* cam)
+void KX_Scene::MarkSubTreeVisible(SG_Tree *node, RAS_IRasterizer* rasty, bool visible, KX_Camera* cam, int layer)
 {
        if (node->Client())
        {
@@ -1050,36 +1255,42 @@ void KX_Scene::MarkSubTreeVisible(SG_Tree *node, RAS_IRasterizer* rasty, bool vi
                        if (visible)
                        {
                                int nummeshes = gameobj->GetMeshCount();
-                               MT_Transform t( cam->GetWorldToCamera() * gameobj->GetSGNode()->GetWorldTransform());
-       
                                
+                               // this adds the vertices to the display list
                                for (int m=0;m<nummeshes;m++)
-                               {
-                                       // this adds the vertices to the display list
-                                       (gameobj->GetMesh(m))->SchedulePolygons(t, rasty->GetDrawingMode());
-                               }
+                                       (gameobj->GetMesh(m))->SchedulePolygons(rasty->GetDrawingMode());
                        }
-                       gameobj->MarkVisible(visible);
+
+                       gameobj->SetCulled(!visible);
+                       gameobj->UpdateBuckets(false);
                }
        }
        if (node->Left())
-               MarkSubTreeVisible(node->Left(), rasty, visible,cam);
+               MarkSubTreeVisible(node->Left(), rasty, visible, cam, layer);
        if (node->Right())
-               MarkSubTreeVisible(node->Right(), rasty, visible,cam);
+               MarkSubTreeVisible(node->Right(), rasty, visible, cam, layer);
 }
 
-void KX_Scene::MarkVisible(RAS_IRasterizer* rasty, KX_GameObject* gameobj,KX_Camera*  cam)
+void KX_Scene::MarkVisible(RAS_IRasterizer* rasty, KX_GameObject* gameobj,KX_Camera*  cam,int layer)
 {
        // User (Python/Actuator) has forced object invisible...
-       if (!gameobj->GetVisible())
+       if (!gameobj->GetSGNode() || !gameobj->GetVisible())
+               return;
+       
+       // Shadow lamp layers
+       if(layer && !(gameobj->GetLayer() & layer)) {
+               gameobj->SetCulled(true);
+               gameobj->UpdateBuckets(false);
                return;
+       }
+
        // If Frustum culling is off, the object is always visible.
        bool vis = !cam->GetFrustumCulling();
        
        // If the camera is inside this node, then the object is visible.
        if (!vis)
        {
-               vis = gameobj->GetSGNode()->inside( GetActiveCamera()->GetCameraLocation() );
+               vis = gameobj->GetSGNode()->inside( cam->GetCameraLocation() );
        }
                
        // Test the object's bound sphere against the view frustum.
@@ -1107,36 +1318,63 @@ void KX_Scene::MarkVisible(RAS_IRasterizer* rasty, KX_GameObject* gameobj,KX_Cam
        if (vis)
        {
                int nummeshes = gameobj->GetMeshCount();
-               MT_Transform t(cam->GetWorldToCamera() * gameobj->GetSGNode()->GetWorldTransform());
                
                for (int m=0;m<nummeshes;m++)
                {
                        // this adds the vertices to the display list
-                       (gameobj->GetMesh(m))->SchedulePolygons(t, rasty->GetDrawingMode());
+                       (gameobj->GetMesh(m))->SchedulePolygons(rasty->GetDrawingMode());
                }
                // Visibility/ non-visibility are marked
                // elsewhere now.
-               gameobj->MarkVisible();
+               gameobj->SetCulled(false);
+               gameobj->UpdateBuckets(false);
        } else {
-               gameobj->MarkVisible(false);
+               gameobj->SetCulled(true);
+               gameobj->UpdateBuckets(false);
        }
 }
 
-void KX_Scene::CalculateVisibleMeshes(RAS_IRasterizer* rasty,KX_Camera* cam)
+void KX_Scene::PhysicsCullingCallback(KX_ClientObjectInfo* objectInfo, void* cullingInfo)
 {
-// FIXME: When tree is operational
-#if 1
-       // do this incrementally in the future
-       for (int i = 0; i < m_objectlist->GetCount(); i++)
+       KX_GameObject* gameobj = objectInfo->m_gameobject;
+       if (!gameobj->GetVisible())
+               // ideally, invisible objects should be removed from the culling tree temporarily
+               return;
+       if(((CullingInfo*)cullingInfo)->m_layer && !(gameobj->GetLayer() & ((CullingInfo*)cullingInfo)->m_layer))
+               // used for shadow: object is not in shadow layer
+               return;
+
+       // make object visible
+       gameobj->SetCulled(false);
+       gameobj->UpdateBuckets(false);
+}
+
+void KX_Scene::CalculateVisibleMeshes(RAS_IRasterizer* rasty,KX_Camera* cam, int layer)
+{
+       bool dbvt_culling = false;
+       if (m_dbvt_culling) 
        {
-               MarkVisible(rasty, static_cast<KX_GameObject*>(m_objectlist->GetValue(i)), cam);
+               // test culling through Bullet
+               PHY__Vector4 planes[6];
+               // get the clip planes
+               MT_Vector4* cplanes = cam->GetNormalizedClipPlanes();
+               // and convert
+               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,m_dbvt_occlusion_res);
+       }
+       if (!dbvt_culling) {
+               // the physics engine couldn't help us, do it the hard way
+               for (int i = 0; i < m_objectlist->GetCount(); i++)
+               {
+                       MarkVisible(rasty, static_cast<KX_GameObject*>(m_objectlist->GetValue(i)), cam, layer);
+               }
        }
-#else
-       if (cam->GetFrustumCulling())
-               MarkVisible(m_objecttree, rasty, cam);
-       else
-               MarkSubTreeVisible(m_objecttree, rasty, true, cam);
-#endif
 }
 
 // logic stuff
@@ -1217,7 +1455,7 @@ void KX_Scene::UpdateParents(double curtime)
        for (int i=0; i<GetRootParentList()->GetCount(); i++)
        {
                KX_GameObject* parentobj = (KX_GameObject*)GetRootParentList()->GetValue(i);
-               parentobj->NodeUpdateGS(curtime,true);
+               parentobj->NodeUpdateGS(curtime);
        }
 }
 
@@ -1225,7 +1463,7 @@ void KX_Scene::UpdateParents(double curtime)
 
 RAS_MaterialBucket* KX_Scene::FindBucket(class RAS_IPolyMaterial* polymat, bool &bucketCreated)
 {
-       return m_bucketmanager->RAS_BucketManagerFindBucket(polymat, bucketCreated);
+       return m_bucketmanager->FindBucket(polymat, bucketCreated);
 }
 
 
@@ -1235,10 +1473,9 @@ void KX_Scene::RenderBuckets(const MT_Transform & cameratransform,
                                                         class RAS_IRenderTools* rendertools)
 {
        m_bucketmanager->Renderbuckets(cameratransform,rasty,rendertools);
+       KX_BlenderMaterial::EndFrame();
 }
 
-
-
 void KX_Scene::UpdateObjectActivity(void) 
 {
        if (m_activity_culling) {
@@ -1341,31 +1578,23 @@ double KX_Scene::getSuspendedDelta()
 //----------------------------------------------------------------------------
 //Python
 
-PyMethodDef KX_Scene::Methods[] = {
-       KX_PYMETHODTABLE(KX_Scene, getLightList),
-       KX_PYMETHODTABLE(KX_Scene, getObjectList),
-       KX_PYMETHODTABLE(KX_Scene, getName),
-       
-       {NULL,NULL} //Sentinel
-};
-
 PyTypeObject KX_Scene::Type = {
-       PyObject_HEAD_INIT(&PyType_Type)
+       PyObject_HEAD_INIT(NULL)
                0,
                "KX_Scene",
-               sizeof(KX_Scene),
+               sizeof(PyObjectPlus_Proxy),
                0,
-               PyDestructor,
+               py_base_dealloc,
                0,
-               __getattr,
-               __setattr,
-               0, //&MyPyCompare,
-               __repr,
-               0, //&cvalue_as_number,
                0,
                0,
                0,
-               0, 0, 0, 0, 0, 0
+               py_base_repr,
+               0,0,0,0,0,0,
+               py_base_getattro,
+               py_base_setattro,
+               0,0,0,0,0,0,0,0,0,
+               Methods
 };
 
 PyParentObject KX_Scene::Parents[] = {
@@ -1374,74 +1603,155 @@ PyParentObject KX_Scene::Parents[] = {
                NULL
 };
 
-PyObject* KX_Scene::_getattr(const STR_String& attr)
-{
-       if (attr == "name")
-               return PyString_FromString(GetName());
-       
-       if (attr == "active_camera")
-       {
-               KX_Camera *camera = GetActiveCamera();
-               camera->AddRef();
-               return (PyObject*) camera;
-       }
-       
-       if (attr == "suspended")
-               return PyInt_FromLong(m_suspend);
+PyMethodDef KX_Scene::Methods[] = {
+       KX_PYMETHODTABLE(KX_Scene, getLightList),
+       KX_PYMETHODTABLE(KX_Scene, getObjectList),
+       KX_PYMETHODTABLE(KX_Scene, getName),
+       KX_PYMETHODTABLE(KX_Scene, addObject),
        
-       if (attr == "activity_culling")
-               return PyInt_FromLong(m_activity_culling);
+       {NULL,NULL} //Sentinel
+};
+
+PyObject* KX_Scene::pyattr_get_name(void *self_v, const KX_PYATTRIBUTE_DEF *attrdef)
+{
+       KX_Scene* self= static_cast<KX_Scene*>(self_v);
+       return PyString_FromString(self->GetName().ReadPtr());
+}
+
+PyObject* KX_Scene::pyattr_get_objects(void *self_v, const KX_PYATTRIBUTE_DEF *attrdef)
+{
+       KX_Scene* self= static_cast<KX_Scene*>(self_v);
+       return self->GetObjectList()->GetProxy();
+}
+
+PyObject* KX_Scene::pyattr_get_active_camera(void *self_v, const KX_PYATTRIBUTE_DEF *attrdef)
+{
+       KX_Scene* self= static_cast<KX_Scene*>(self_v);
+       return self->GetActiveCamera()->GetProxy();
+}
+
+/* __dict__ only for the purpose of giving useful dir() results */
+PyObject* KX_Scene::pyattr_get_dir_dict(void *self_v, const KX_PYATTRIBUTE_DEF *attrdef)
+{
+       KX_Scene* self= static_cast<KX_Scene*>(self_v);
+       /* Useually done by py_getattro_up but in this case we want to include m_attr_dict dict */
+       PyObject *dict_str= PyString_FromString("__dict__");
+       PyObject *dict= py_getattr_dict(self->PyObjectPlus::py_getattro(dict_str), Type.tp_dict);
+       Py_DECREF(dict_str);
        
-       if (attr == "activity_culling_radius")
-               return PyFloat_FromDouble(m_activity_box_radius);
+       PyDict_Update(dict, self->m_attr_dict);
+       return dict;
+}
+
+PyAttributeDef KX_Scene::Attributes[] = {
+       KX_PYATTRIBUTE_RO_FUNCTION("name",                      KX_Scene, pyattr_get_name),
+       KX_PYATTRIBUTE_RO_FUNCTION("objects",           KX_Scene, pyattr_get_objects),
+       KX_PYATTRIBUTE_RO_FUNCTION("active_camera",     KX_Scene, pyattr_get_active_camera),
+       KX_PYATTRIBUTE_BOOL_RO("suspended",                     KX_Scene, m_suspend),
+       KX_PYATTRIBUTE_BOOL_RO("activity_culling",      KX_Scene, m_activity_culling),
+       KX_PYATTRIBUTE_FLOAT_RW("activity_culling_radius", 0.5f, FLT_MAX, KX_Scene, m_activity_box_radius),
+       KX_PYATTRIBUTE_BOOL_RO("dbvt_culling",          KX_Scene, m_dbvt_culling),
+       KX_PYATTRIBUTE_RO_FUNCTION("__dict__",          KX_Scene, pyattr_get_dir_dict),
+       { NULL }        //Sentinel
+};
+
+
+PyObject* KX_Scene::py_getattro__internal(PyObject *attr)
+{      
+       py_getattro_up(PyObjectPlus);
+}
+
+int KX_Scene::py_setattro__internal(PyObject *attr, PyObject *pyvalue)
+{
+       return PyObjectPlus::py_setattro(attr, pyvalue);
+}
+
+PyObject* KX_Scene::py_getattro(PyObject *attr)
+{
+       PyObject *object = py_getattro__internal(attr);
        
-       PyObject* value = PyDict_GetItemString(m_attrlist, const_cast<char *>(attr.ReadPtr()));
-       if (value)
+       if (object==NULL)
        {
-               Py_INCREF(value);
-               return value;
+               PyErr_Clear();
+               object = PyDict_GetItem(m_attr_dict, attr);
+               if(object) {
+                       Py_INCREF(object);
+               }
+               else {
+                       PyErr_Format(PyExc_AttributeError, "KX_Scene attribute \"%s\" not found", PyString_AsString(attr));
+               }
        }
        
-       _getattr_up(PyObjectPlus);
+       return object;
 }
 
-int KX_Scene::_delattr(const STR_String &attr)
+
+int KX_Scene::py_setattro(PyObject *attr, PyObject *value)
 {
-       PyDict_DelItemString(m_attrlist, const_cast<char *>(attr.ReadPtr()));
-       return 0;
+       int ret= py_setattro__internal(attr, value);
+       
+       if (ret==PY_SET_ATTR_MISSING) {
+               if (PyDict_SetItem(m_attr_dict, attr, value)==0) {
+                       PyErr_Clear();
+                       ret= PY_SET_ATTR_SUCCESS;
+               }
+               else {
+                       PyErr_SetString(PyExc_AttributeError, "failed assigning value to KX_Scenes internal dictionary");
+                       ret= PY_SET_ATTR_FAIL;
+               }
+       }
+       
+       return ret;
 }
 
-int KX_Scene::_setattr(const STR_String &attr, PyObject *pyvalue)
+int KX_Scene::py_delattro(PyObject *attr)
 {
-
-       if (!PyDict_SetItemString(m_attrlist, const_cast<char *>(attr.ReadPtr()), pyvalue))
-               return 0;
-
-       return PyObjectPlus::_setattr(attr, pyvalue);
+       PyDict_DelItem(m_attr_dict, attr);
+       return 0;
 }
 
-KX_PYMETHODDEF_DOC(KX_Scene, getLightList,
+KX_PYMETHODDEF_DOC_NOARGS(KX_Scene, getLightList,
 "getLightList() -> list [KX_Light]\n"
 "Returns a list of all lights in the scene.\n"
 )
 {
-       m_lightlist->AddRef();
-       return (PyObject*) m_lightlist;
+       return m_lightlist->GetProxy();
 }
 
-KX_PYMETHODDEF_DOC(KX_Scene, getObjectList,
+KX_PYMETHODDEF_DOC_NOARGS(KX_Scene, getObjectList,
 "getObjectList() -> list [KX_GameObject]\n"
 "Returns a list of all game objects in the scene.\n"
 )
 {
-       m_objectlist->AddRef();
-       return (PyObject*) m_objectlist;
+       // ShowDeprecationWarning("getObjectList()", "the objects property"); // XXX Grr, why doesnt this work?
+       return m_objectlist->GetProxy();
 }
 
-KX_PYMETHODDEF_DOC(KX_Scene, getName,
+KX_PYMETHODDEF_DOC_NOARGS(KX_Scene, getName,
 "getName() -> string\n"
 "Returns the name of the scene.\n"
 )
 {
        return PyString_FromString(GetName());
 }
+
+KX_PYMETHODDEF_DOC(KX_Scene, addObject,
+"addObject(object, other, time=0)\n"
+"Returns the added object.\n")
+{
+       PyObject *pyob, *pyother;
+       KX_GameObject *ob, *other;
+
+       int time = 0;
+
+       if (!PyArg_ParseTuple(args, "OO|i:addObject", &pyob, &pyother, &time))
+               return NULL;
+
+       if (!ConvertPythonToGameObject(pyob, &ob, false)
+               || !ConvertPythonToGameObject(pyother, &other, false))
+               return NULL;
+
+
+       SCA_IObject* replica = AddReplicaObject((SCA_IObject*)ob, other, time);
+       return replica->GetProxy();
+}
\ No newline at end of file