// If called with no mapInfo for the terrain, this will create a new mapInfo and terrain // based on the passed information. The 'id' should be either the terrain id or // BSScene.CHILDTERRAIN_ID. If the latter, a new child terrain ID will be allocated and used. // The latter feature is for creating child terrains for mega-regions. // If called with a mapInfo in m_heightMaps but the terrain has no body yet (mapInfo.terrainBody.Ptr == 0) // then a new body and shape is created and the mapInfo is filled. // This call is used for doing the initial terrain creation. // If called with a mapInfo in m_heightMaps and there is an existing terrain body, a new // terrain shape is created and added to the body. // This call is most often used to update the heightMap and parameters of the terrain. // The 'doNow' boolean says whether to do all the unmanaged activities right now (like when // calling this routine from initialization or taint-time routines) or whether to delay // all the unmanaged activities to taint-time. private void UpdateOrCreateTerrain(uint id, float[] heightMap, Vector3 minCoords, Vector3 maxCoords, bool atTaintTime) { DetailLog("{0},BSTerrainManager.UpdateOrCreateTerrain,call,minC={1},maxC={2},atTaintTime={3}", BSScene.DetailLogZero, minCoords, maxCoords, atTaintTime); float minZ = float.MaxValue; float maxZ = float.MinValue; Vector2 terrainRegionBase = new Vector2(minCoords.X, minCoords.Y); int heightMapSize = heightMap.Length; for (int ii = 0; ii < heightMapSize; ii++) { float height = heightMap[ii]; if (height < minZ) { minZ = height; } if (height > maxZ) { maxZ = height; } } // The shape of the terrain is from its base to its extents. minCoords.Z = minZ; maxCoords.Z = maxZ; BulletHeightMapInfo mapInfo; if (m_heightMaps.TryGetValue(terrainRegionBase, out mapInfo)) { // If this is terrain we know about, it's easy to update mapInfo.heightMap = heightMap; mapInfo.minCoords = minCoords; mapInfo.maxCoords = maxCoords; mapInfo.minZ = minZ; mapInfo.maxZ = maxZ; mapInfo.sizeX = maxCoords.X - minCoords.X; mapInfo.sizeY = maxCoords.Y - minCoords.Y; DetailLog("{0},UpdateOrCreateTerrain:UpdateExisting,call,terrainBase={1},minC={2}, maxC={3}, szX={4}, szY={5}", BSScene.DetailLogZero, terrainRegionBase, mapInfo.minCoords, mapInfo.maxCoords, mapInfo.sizeX, mapInfo.sizeY); BSScene.TaintCallback rebuildOperation = delegate() { if (MegaRegionParentPhysicsScene != null) { // It's possible that Combine() was called after this code was queued. // If we are a child of combined regions, we don't create any terrain for us. DetailLog("{0},UpdateOrCreateTerrain:AmACombineChild,taint", BSScene.DetailLogZero); // Get rid of any terrain that may have been allocated for us. ReleaseGroundPlaneAndTerrain(); // I hate doing this, but just bail return; } if (mapInfo.terrainBody.ptr != IntPtr.Zero) { // Updating an existing terrain. DetailLog("{0},UpdateOrCreateTerrain:UpdateExisting,taint,terrainBase={1},minC={2}, maxC={3}, szX={4}, szY={5}", BSScene.DetailLogZero, terrainRegionBase, mapInfo.minCoords, mapInfo.maxCoords, mapInfo.sizeX, mapInfo.sizeY); // Remove from the dynamics world because we're going to mangle this object BulletSimAPI.RemoveObjectFromWorld2(PhysicsScene.World.ptr, mapInfo.terrainBody.ptr); // Get rid of the old terrain BulletSimAPI.DestroyObject2(PhysicsScene.World.ptr, mapInfo.terrainBody.ptr); BulletSimAPI.ReleaseHeightMapInfo2(mapInfo.Ptr); mapInfo.Ptr = IntPtr.Zero; /* * // NOTE: This routine is half here because I can't get the terrain shape replacement * // to work. In the short term, the above three lines completely delete the old * // terrain and the code below recreates one from scratch. * // Hopefully the Bullet community will help me out on this one. * * // First, release the old collision shape (there is only one terrain) * BulletSimAPI.DeleteCollisionShape2(m_physicsScene.World.Ptr, mapInfo.terrainShape.Ptr); * * // Fill the existing height map info with the new location and size information * BulletSimAPI.FillHeightMapInfo2(m_physicsScene.World.Ptr, mapInfo.Ptr, mapInfo.ID, * mapInfo.minCoords, mapInfo.maxCoords, mapInfo.heightMap, TERRAIN_COLLISION_MARGIN); * * // Create a terrain shape based on the new info * mapInfo.terrainShape = new BulletShape(BulletSimAPI.CreateTerrainShape2(mapInfo.Ptr)); * * // Stuff the shape into the existing terrain body * BulletSimAPI.SetBodyShape2(m_physicsScene.World.Ptr, mapInfo.terrainBody.Ptr, mapInfo.terrainShape.Ptr); */ } // else { // Creating a new terrain. DetailLog("{0},UpdateOrCreateTerrain:CreateNewTerrain,taint,baseX={1},baseY={2},minZ={3},maxZ={4}", BSScene.DetailLogZero, mapInfo.minCoords.X, mapInfo.minCoords.Y, minZ, maxZ); mapInfo.ID = id; mapInfo.Ptr = BulletSimAPI.CreateHeightMapInfo2(PhysicsScene.World.ptr, mapInfo.ID, mapInfo.minCoords, mapInfo.maxCoords, mapInfo.heightMap, TERRAIN_COLLISION_MARGIN); // The terrain object initial position is at the center of the object Vector3 centerPos; centerPos.X = minCoords.X + (mapInfo.sizeX / 2f); centerPos.Y = minCoords.Y + (mapInfo.sizeY / 2f); centerPos.Z = minZ + ((maxZ - minZ) / 2f); // Create the terrain shape from the mapInfo mapInfo.terrainShape = new BulletShape(BulletSimAPI.CreateTerrainShape2(mapInfo.Ptr), ShapeData.PhysicsShapeType.SHAPE_TERRAIN); mapInfo.terrainBody = new BulletBody(mapInfo.ID, BulletSimAPI.CreateBodyWithDefaultMotionState2(mapInfo.terrainShape.ptr, id, centerPos, Quaternion.Identity)); } // Make sure the entry is in the heightmap table m_heightMaps[terrainRegionBase] = mapInfo; // Set current terrain attributes BulletSimAPI.SetFriction2(mapInfo.terrainBody.ptr, PhysicsScene.Params.terrainFriction); BulletSimAPI.SetHitFraction2(mapInfo.terrainBody.ptr, PhysicsScene.Params.terrainHitFraction); BulletSimAPI.SetRestitution2(mapInfo.terrainBody.ptr, PhysicsScene.Params.terrainRestitution); BulletSimAPI.SetCollisionFlags2(mapInfo.terrainBody.ptr, CollisionFlags.CF_STATIC_OBJECT); BulletSimAPI.SetMassProps2(mapInfo.terrainBody.ptr, 0f, Vector3.Zero); BulletSimAPI.UpdateInertiaTensor2(mapInfo.terrainBody.ptr); // Return the new terrain to the world of physical objects BulletSimAPI.AddObjectToWorld2(PhysicsScene.World.ptr, mapInfo.terrainBody.ptr); // redo its bounding box now that it is in the world BulletSimAPI.UpdateSingleAabb2(PhysicsScene.World.ptr, mapInfo.terrainBody.ptr); BulletSimAPI.SetCollisionFilterMask2(mapInfo.terrainBody.ptr, (uint)CollisionFilterGroups.TerrainFilter, (uint)CollisionFilterGroups.TerrainMask); // Make sure the new shape is processed. // BulletSimAPI.Activate2(mapInfo.terrainBody.ptr, true); BulletSimAPI.ForceActivationState2(mapInfo.terrainBody.ptr, ActivationState.DISABLE_SIMULATION); m_terrainModified = true; }; // There is the option to do the changes now (we're already in 'taint time'), or // to do the Bullet operations later. if (atTaintTime) { rebuildOperation(); } else { PhysicsScene.TaintedObject("BSScene.UpdateOrCreateTerrain:UpdateExisting", rebuildOperation); } } else { // We don't know about this terrain so either we are creating a new terrain or // our mega-prim child is giving us a new terrain to add to the phys world // if this is a child terrain, calculate a unique terrain id uint newTerrainID = id; if (newTerrainID >= BSScene.CHILDTERRAIN_ID) { newTerrainID = ++m_terrainCount; } float[] heightMapX = heightMap; Vector3 minCoordsX = minCoords; Vector3 maxCoordsX = maxCoords; DetailLog("{0},UpdateOrCreateTerrain:NewTerrain,call,id={1}, minC={2}, maxC={3}", BSScene.DetailLogZero, newTerrainID, minCoords, minCoords); // Code that must happen at taint-time BSScene.TaintCallback createOperation = delegate() { DetailLog("{0},UpdateOrCreateTerrain:NewTerrain,taint,baseX={1},baseY={2}", BSScene.DetailLogZero, minCoords.X, minCoords.Y); // Create a new mapInfo that will be filled with the new info mapInfo = new BulletHeightMapInfo(id, heightMapX, BulletSimAPI.CreateHeightMapInfo2(PhysicsScene.World.ptr, newTerrainID, minCoordsX, maxCoordsX, heightMapX, TERRAIN_COLLISION_MARGIN)); // Put the unfilled heightmap info into the collection of same m_heightMaps.Add(terrainRegionBase, mapInfo); // Build the terrain UpdateOrCreateTerrain(newTerrainID, heightMap, minCoords, maxCoords, true); m_terrainModified = true; }; // If already in taint-time, just call Bullet. Otherwise queue the operations for the safe time. if (atTaintTime) { createOperation(); } else { PhysicsScene.TaintedObject("BSScene.UpdateOrCreateTerrain:NewTerrain", createOperation); } } }