Example #1
0
    protected override void OnUnload()
    {
      _cameraObject = null;

      // Remove terrain from scene.
      TerrainNode.Parent.Children.Remove(TerrainNode);

      TerrainNode.Dispose(false);
      TerrainNode = null;

      // We have to dispose the textures which were not loaded via the content manager.
      _terrainTile.HeightTexture.SafeDispose();
      _terrainTile.HeightTexture = null;
      _terrainTile.NormalTexture.SafeDispose();
      _terrainTile.NormalTexture = null;

      _rigidBody.Simulation.RigidBodies.Remove(_rigidBody);

      _terrainTile = null;
      _rigidBody = null;
    }
Example #2
0
    //--------------------------------------------------------------
    #region Methods
    //--------------------------------------------------------------

    protected override void OnLoad()
    {
      // Get common services and game objects.
      _graphicsService = _services.GetInstance<IGraphicsService>();
      var content = _services.GetInstance<ContentManager>();
      var scene = _services.GetInstance<IScene>();
      _simulation = _services.GetInstance<Simulation>();
      var gameObjectService = _services.GetInstance<IGameObjectService>();
      _cameraObject = gameObjectService.Objects.OfType<CameraObject>().First();
      _previousCameraFar = _cameraObject.CameraNode.Camera.Projection.Far;

      // Create a new terrain.
      var terrain = new Terrain();
      _terrainTile = new TerrainTile(_graphicsService)
      {
        CellSize = 2,
      };
      terrain.Tiles.Add(_terrainTile);

      var shadowMapEffect = content.Load<Effect>("DigitalRune/Terrain/TerrainShadowMap");
      var gBufferEffect = content.Load<Effect>("DigitalRune/Terrain/TerrainGBuffer");
      var materialEffect = content.Load<Effect>("DigitalRune/Terrain/TerrainMaterial");
      var material = new Material
      {
        { "ShadowMap", new EffectBinding(_graphicsService, shadowMapEffect, null, EffectParameterHint.Material) },
        { "GBuffer", new EffectBinding(_graphicsService, gBufferEffect, null, EffectParameterHint.Material) },
        { "Material", new EffectBinding(_graphicsService, materialEffect, null, EffectParameterHint.Material) }
      };
      TerrainNode = new TerrainNode(terrain, material)
      {
        BaseClipmap =
        {
          CellsPerLevel = 128,
          NumberOfLevels = 6,
        },
        DetailClipmap =
        {
          CellsPerLevel = 1364,
          NumberOfLevels = 9,
        },
      };
      scene.Children.Add(TerrainNode);

      // Create a rigid body with a height field for collision detection.
      var heightField = new HeightField
      {
        Depth = 1,
        UseFastCollisionApproximation = false,
      };
      _rigidBody = new RigidBody(heightField, new MassFrame(), null)
      {
        MotionType = MotionType.Static,
        UserData = _terrainTile,
      };
      _simulation.RigidBodies.Add(_rigidBody);

      InitializeHeightsAndNormals();

      InitializeClipmapCellSizes();

      InitializeTerrainLayers(content);

      // Enable mipmaps when using anisotropic filtering on AMD graphics cards:
      //TerrainNode.DetailClipmap.EnableMipMap = true;

      CreateGuiControls();
    }
Example #3
0
    //--------------------------------------------------------------
    #region Methods
    //--------------------------------------------------------------

    protected override void OnLoad()
    {
      // Get common services and game objects.
      _graphicsService = _services.GetInstance<IGraphicsService>();
      _graphicsScreen = _graphicsService.Screens.OfType<DeferredGraphicsScreen>().First();
      var content = _services.GetInstance<ContentManager>();
      var scene = _services.GetInstance<IScene>();
      _simulation = _services.GetInstance<Simulation>();
      var gameObjectService = _services.GetInstance<IGameObjectService>();
      _cameraObject = gameObjectService.Objects.OfType<CameraObject>().First();
      _previousCameraFar = _cameraObject.CameraNode.Camera.Projection.Far;

      // Create a new terrain.
      var terrain = new Terrain();

      // The terrain is made up of terrain tiles which can be loaded independently. 
      // Each terrain tile consists of height and normal textures which define the terrain
      // geometry and terrain layers which define the material (detail textures).
      // In this sample we create 2x2 tiles.
      _tiles = new Tile[2, 2];
      for (int row = 0; row < 2; row++)
      {
        for (int column = 0; column < 2; column++)
        {
          // Create a tile and add it to the terrain.
          // (The tile content is loaded later.)
          var terrainTile = new TerrainTile(_graphicsService)
          {
            CellSize = 1,   // The terrain has a resolution of 1 height sample per world space unit.
          };
          terrain.Tiles.Add(terrainTile);

          // Create a rigid body with a height field for collision detection and add
          // it to the simulation. (The height data is loaded later.)
          var heightField = new HeightField
          {
            Depth = 1,
            UseFastCollisionApproximation = false,
          };
          var rigidBody = new RigidBody(heightField, new MassFrame(), null)
          {
            MotionType = MotionType.Static,
            UserData = terrainTile,
          };
          _simulation.RigidBodies.Add(rigidBody);

          // Store the tile for use later in this sample.
          _tiles[row, column] = new Tile
          {
            TerrainTile = terrainTile,
            RigidBody = rigidBody,
          };
        }
      }

      // Create a terrain node which represents the terrain in the scene graph.
      // The terrain node is rendered by the TerrainRenderer (see DeferredGraphicsScreen).
      // The material used to render the terrain is customizable.  The material must specify 
      // the effects for the different render passes which we use in the DeferredGraphicsScreen 
      // ("ShadowMap", "GBuffer", "Material").
      // The prebuilt DigitalRune content contains standard terrain effects. However, you could
      // change the effects to change how the material is rendered.
      // We can create the material by loading a .drmat file. Or we can create the material in
      // code like this:
      var shadowMapEffect = content.Load<Effect>("DigitalRune/Terrain/TerrainShadowMap");
      var gBufferEffect = content.Load<Effect>("DigitalRune/Terrain/TerrainGBuffer");
      var materialEffect = content.Load<Effect>("DigitalRune/Terrain/TerrainMaterial");
      var material = new Material
      {
        { "ShadowMap", new EffectBinding(_graphicsService, shadowMapEffect, null, EffectParameterHint.Material) },
        { "GBuffer", new EffectBinding(_graphicsService, gBufferEffect, null, EffectParameterHint.Material) },
        { "Material", new EffectBinding(_graphicsService, materialEffect, null, EffectParameterHint.Material) }
      };
      TerrainNode = new TerrainNode(terrain, material)
      {
        // The terrain rendering uses clipmaps.
        // The clipmaps are updated by the TerrainClipmapRenderer (see DeferredGraphicsScreen)
        // when the camera moves.
        // The base clipmap contains the basic geometry info (height, normals, hole info).
        // It also determines the terrain mesh resolution.
        BaseClipmap =
        {
          CellsPerLevel = 128,
          NumberOfLevels = 6
        },
        // The detail clipmap contains the splatted detail textures (e.g. grass, rock, ...).
        // (The max texture size in XNA is 4096x4096. That means we can fit 9 clipmap levels
        // into a single texture.)
        DetailClipmap =
        {
          CellsPerLevel = 1365,
          NumberOfLevels = 9,
        },
      };
      scene.Children.Add(TerrainNode);

      // Load the height and normal maps which define the terrain geometry.
      InitializeHeightsAndNormals();

      // Set the clipmap cell sizes.
      InitializeClipmapCellSizes();

      // Create the terrain layers which define the detail textures (e.g. grass, rock, ...)
      InitializeTerrainLayers(content);

      // Special note for AMD GPUs:
      // If we want anisotropic filtering for the terrain, then we need to enable mipmaps for
      // AMD GPUs. NVIDIA and Intel can do anisotropic filtering without mipmaps.
      //TerrainNode.DetailClipmap.EnableMipMap = true;

      CreateGuiControls();
    }
        private void ProcessClipmap(TerrainNode node, TerrainClipmap clipmap, RenderContext context)
        {
            var graphicsDevice = context.GraphicsService.GraphicsDevice;
              var lodCameraNode = context.LodCameraNode ?? context.CameraNode;

              bool isBaseClipmap = (node.BaseClipmap == clipmap);

              // Update the clipmap render targets if necessary.
              InitializeClipmapTextures(graphicsDevice, clipmap);

              // Update other clipmap data (origins, offsets, ...). No rendering.
              // (Data is stored in TerrainClipmap class.)
              UpdateClipmapData(node, clipmap, lodCameraNode, isBaseClipmap);

              // Compute which rectangular regions need to be updated.
              // (Data is stored in TerrainClipmap class.)
              ComputeInvalidRegions(node, clipmap, isBaseClipmap);

              // Abort if there are no invalid regions.
              int numberOfInvalidRegions = 0;
              for (int level = 0; level < clipmap.NumberOfLevels; level++)
            numberOfInvalidRegions += clipmap.InvalidRegions[level].Count;

              Debug.Assert(numberOfInvalidRegions > 0 || clipmap.UseIncrementalUpdate,
            "If the clipmap update is not incremental, there must be at least one invalid region.");

              if (numberOfInvalidRegions == 0)
            return;

              // Set render target binding to render into all clipmap textures at once.
              int numberOfTextures = clipmap.Textures.Length;
              if (_renderTargetBindings[numberOfTextures] == null)
            _renderTargetBindings[numberOfTextures] = new RenderTargetBinding[numberOfTextures];

              for (int i = 0; i < numberOfTextures; i++)
            _renderTargetBindings[numberOfTextures][i] = new RenderTargetBinding((RenderTarget2D)clipmap.Textures[i]);

              switch (numberOfTextures)
              {
            case 1: context.Technique = "RenderTargets1"; break;
            case 2: context.Technique = "RenderTargets2"; break;
            case 3: context.Technique = "RenderTargets3"; break;
            case 4: context.Technique = "RenderTargets4"; break;
            default: context.Technique = null; break;
              }

              graphicsDevice.SetRenderTargets(_renderTargetBindings[numberOfTextures]);

              // The viewport covers the whole texture atlas.
              var viewport = graphicsDevice.Viewport;
              context.Viewport = viewport;

              Debug.Assert(_previousMaterialBinding == null);

              // Loop over all layers. Render each layer into all levels (if there is an invalid region).
              Aabb tileAabb = new Aabb(new Vector3F(-Terrain.TerrainLimit), new Vector3F(Terrain.TerrainLimit));
              ProcessLayer(graphicsDevice, context, clipmap, isBaseClipmap, _clearLayer, tileAabb);
              foreach (var tile in node.Terrain.Tiles)
              {
            tileAabb = tile.Aabb;
            context.Object = tile;
            ProcessLayer(graphicsDevice, context, clipmap, isBaseClipmap, tile, tileAabb);

            foreach (var layer in tile.Layers)
              ProcessLayer(graphicsDevice, context, clipmap, isBaseClipmap, layer, tileAabb);

            context.Object = null;
              }

              _previousMaterialBinding = null;

              ClearFlags(_clearLayer, context);
              foreach (var tile in node.Terrain.Tiles)
              {
            ClearFlags(tile, context);
            foreach (var layer in tile.Layers)
              ClearFlags(layer, context);
              }

              // All invalid regions handled.
              for (int i = 0; i < clipmap.NumberOfLevels; i++)
            clipmap.InvalidRegions[i].Clear();

              // The next time we can update incrementally.
              clipmap.UseIncrementalUpdate = true;
        }
        private void ComputeInvalidRegions(TerrainNode node, TerrainClipmap clipmap, bool isBaseClipmap)
        {
            // Note:
              // This method computes the AABBs of terrain parts that need to be updated.
              // Important: AABBs must not overlap because the materials can use alpha blending and they
              // must not blend twice into the same area.

              var terrain = node.Terrain;

              var invalidRegions = isBaseClipmap ? terrain.InvalidBaseRegions : terrain.InvalidDetailRegions;
              var areInvalidRegionsClipped = isBaseClipmap ? terrain.AreInvalidBaseRegionsClipped : terrain.AreInvalidDetailRegionsClipped;

              float border = isBaseClipmap ? 0 : Border;
              for (int level = 0; level < clipmap.NumberOfLevels; level++)
              {
            if (clipmap.InvalidRegions[level] == null)
              clipmap.InvalidRegions[level] = new List<Aabb>();

            // Compute AABB of whole level.
            float borderWorld = border * clipmap.ActualCellSizes[level];
            Vector2F newOrigin = clipmap.Origins[level];
            var aabb = new Aabb
            {
              Minimum =
              {
            X = newOrigin.X - borderWorld,
            Y = float.MinValue,
            Z = newOrigin.Y - borderWorld
              },
              Maximum =
              {
            X = newOrigin.X + clipmap.LevelSizes[level] + borderWorld,
            Y = float.MaxValue,
            Z = newOrigin.Y + clipmap.LevelSizes[level] + borderWorld
              }
            };

            // Store AABB of whole clipmap.
            if (level == clipmap.NumberOfLevels - 1)
              clipmap.Aabb = aabb;

            // For debugging:
            //var oldOrigin = clipmap.OldOrigins[level];
            //if (oldOrigin != newOrigin)
            //  clipmap.InvalidRegions[level].Add(aabb);

            if (!clipmap.UseIncrementalUpdate)
            {
              // Incremental update not possible. --> The whole clipmap is the invalid region.
              clipmap.InvalidRegions[level].Add(aabb);
            }
            else
            {
              // The clipmap contains info from the last frame and the layers where not changed.
              // We can use a toroidal update.

              Vector2F oldOrigin = clipmap.OldOrigins[level];
              float levelSize = clipmap.LevelSizes[level];
              if (oldOrigin != newOrigin)
              {
            // The camera or the clipmap level origin has moved.

            if (oldOrigin.X >= newOrigin.X + levelSize
                || oldOrigin.Y >= newOrigin.Y + levelSize
                || newOrigin.X >= oldOrigin.X + levelSize
                || newOrigin.Y >= oldOrigin.Y + levelSize
                || isBaseClipmap)   // Base clipmap does not (yet) use toroidal wrap.
            {
              // We moved a lot and we cannot reuse anything from the last frame.
              clipmap.InvalidRegions[level].Clear();
              clipmap.InvalidRegions[level].Add(aabb);
              clipmap.Offsets[level] = new Vector2F(0);
            }
            else
            {
              // The origin has moved. An L shape has to be updated. --> We need two AABBs.
              // To avoid an unnecessary overlap: The horizontal AABB covers the whole width.
              // The vertical AABB is shorter to avoid overlap with the horizontal AABB.
              Aabb horizontalAabb = new Aabb();
              horizontalAabb.Minimum.X = newOrigin.X;
              horizontalAabb.Maximum.X = newOrigin.X + levelSize;
              horizontalAabb.Minimum.Y = float.MinValue;
              horizontalAabb.Maximum.Y = float.MaxValue;
              if (oldOrigin.Y <= newOrigin.Y)
              {
                // Origin moved down.
                horizontalAabb.Minimum.Z = oldOrigin.Y + levelSize;
                horizontalAabb.Maximum.Z = newOrigin.Y + levelSize;
              }
              else
              {
                // Origin moved up.
                horizontalAabb.Minimum.Z = newOrigin.Y;
                horizontalAabb.Maximum.Z = oldOrigin.Y;
              }

              Aabb verticalAabb = new Aabb();
              verticalAabb.Minimum.Y = float.MinValue;
              verticalAabb.Maximum.Y = float.MaxValue;
              verticalAabb.Minimum.Z = newOrigin.Y;
              verticalAabb.Maximum.Z = newOrigin.Y + levelSize;
              if (oldOrigin.X <= newOrigin.X)
              {
                // Origin moved right.
                verticalAabb.Minimum.X = oldOrigin.X + levelSize;
                verticalAabb.Maximum.X = newOrigin.X + levelSize;
              }
              else
              {
                // Origin moved left.
                verticalAabb.Minimum.X = newOrigin.X;
                verticalAabb.Maximum.X = oldOrigin.X;
              }

              Debug.Assert(horizontalAabb.Minimum <= horizontalAabb.Maximum);
              Debug.Assert(verticalAabb.Minimum <= verticalAabb.Maximum);
              Debug.Assert(Numeric.AreEqual(horizontalAabb.Extent.X, levelSize));

              // (Assertions need larger epsilon.)
              //Debug.Assert(Numeric.AreEqual(horizontalAabb.Extent.Z, Math.Abs(oldOrigin.Y - newOrigin.Y)));
              //Debug.Assert(Numeric.AreEqual(verticalAabb.Extent.X, Math.Abs(oldOrigin.X - newOrigin.X)));

              // Add the border for texture filtering.
              horizontalAabb.Minimum.X -= borderWorld;
              horizontalAabb.Minimum.Z -= borderWorld;
              horizontalAabb.Maximum.X += borderWorld;
              horizontalAabb.Maximum.Z += borderWorld;
              verticalAabb.Minimum.X -= borderWorld;
              verticalAabb.Minimum.Z -= borderWorld;
              verticalAabb.Maximum.X += borderWorld;
              verticalAabb.Maximum.Z += borderWorld;

              // AABBs must not overlap.
              if (oldOrigin.Y <= newOrigin.Y)
                verticalAabb.Maximum.Z = horizontalAabb.Minimum.Z;
              else
                verticalAabb.Minimum.Z = horizontalAabb.Maximum.Z;

              Debug.Assert(horizontalAabb.Minimum.X >= verticalAabb.Maximum.X
                           || horizontalAabb.Maximum.X <= verticalAabb.Minimum.X
                           || horizontalAabb.Minimum.Y >= verticalAabb.Maximum.Y
                           || horizontalAabb.Maximum.Y <= verticalAabb.Minimum.Y
                           || horizontalAabb.Minimum.Z >= verticalAabb.Maximum.Z
                           || horizontalAabb.Maximum.Z <= verticalAabb.Minimum.Z,
                           "Invalid region AABBs must not overlap.");

              if (clipmap.InvalidRegions[level].Count == 0)
              {
                clipmap.InvalidRegions[level].Add(horizontalAabb);
                clipmap.InvalidRegions[level].Add(verticalAabb);
              }
              else
              {
                AddAabbWithClipping(clipmap.InvalidRegions[level], ref horizontalAabb);
                AddAabbWithClipping(clipmap.InvalidRegions[level], ref verticalAabb);
              }
            }
              }

              if (!areInvalidRegionsClipped && invalidRegions.Count > 1)
              {
            // Clip the invalid regions stored in the Terrain once.
            _aabbList.Clear();
            for (int i = 0; i < invalidRegions.Count; i++)
            {
              var invalidAabb = invalidRegions[i];
              AddAabbWithClipping(_aabbList, ref invalidAabb);
            }

            invalidRegions.Clear();
            invalidRegions.AddRange(_aabbList);

            // Update flag (also in terrain in case other TerrainNodes use the same Terrain).
            areInvalidRegionsClipped = true;
            if (isBaseClipmap)
              terrain.AreInvalidBaseRegionsClipped = true;
            else
              terrain.AreInvalidDetailRegionsClipped = true;
              }

              if (invalidRegions.Count > 0)
              {
            // Add all invalid regions - without overlap.
            for (int i = 0; i < invalidRegions.Count; i++)
            {
              var invalidAabb = invalidRegions[i];
              AddAabbWithClipping(clipmap.InvalidRegions[level], ref invalidAabb);
            }
              }
            }

            #if DEBUG
            // Assert: Invalid regions do not overlap.
            for (int i = 0; i < clipmap.InvalidRegions[level].Count; i++)
            {
              var a = clipmap.InvalidRegions[level][i];
              for (int j = i + 1; j < clipmap.InvalidRegions[level].Count; j++)
              {
            var b = clipmap.InvalidRegions[level][j];
            Debug.Assert(!HaveContactXZ(ref a, ref b));
              }
            }
            #endif

            // Compute the union of all invalid regions. We can use this to early out if a layer
            // does not overlap this combined region.
            var numberOfInvalidRegions = clipmap.InvalidRegions[level].Count;
            if (numberOfInvalidRegions == 0)
            {
              clipmap.CombinedInvalidRegionsAabbs[level] = new Aabb(new Vector3F(float.NaN), new Vector3F(float.NaN));
            }
            else
            {
              clipmap.CombinedInvalidRegionsAabbs[level] = clipmap.InvalidRegions[level][0];
              for (int i = 1; i < numberOfInvalidRegions; i++)
            clipmap.CombinedInvalidRegionsAabbs[level].Grow(clipmap.InvalidRegions[level][i]);
            }

            // For debugging:
            //clipmap.CombinedInvalidRegionsAabbs[level] = new Aabb(new Vector3F(float.MinValue), new Vector3F(float.MaxValue));
              }
        }
        private static void UpdateClipmapData(TerrainNode node, TerrainClipmap clipmap, CameraNode lodCameraNode, bool isBaseClipmap)
        {
            int border = isBaseClipmap ? 0 : Border;

              var terrain = node.Terrain;
              var terrainAabb = terrain.Aabb;
              Vector2F terrainAabbMin = new Vector2F(terrainAabb.Minimum.X, terrainAabb.Minimum.Z);
              Vector2F terrainAabbMax = new Vector2F(terrainAabb.Maximum.X, terrainAabb.Maximum.Z);

              for (int level = clipmap.NumberOfLevels - 1; level >= 0; level--)
              {
            // Compute new origins.
            int texelsPerLevel = clipmap.CellsPerLevel - 2 * border;
            Vector3F referencePosition3D = lodCameraNode.PoseWorld.Position;
            Vector2F referencePosition2D = new Vector2F(referencePosition3D.X, referencePosition3D.Z);
            clipmap.LevelSizes[level] = clipmap.ActualCellSizes[level] * texelsPerLevel;
            Vector2F levelOrigin = new Vector2F(
              referencePosition2D.X - clipmap.LevelSizes[level] / 2,
              referencePosition2D.Y - clipmap.LevelSizes[level] / 2);

            // Do not move detail clipmap outside the terrain. This would only waste resources.
            if (!isBaseClipmap)
            {
              // The fade range is hardcoded to 15% of the radius in Terrain.fxh.
              float fadeRange = clipmap.LevelSizes[level] * node.DetailFadeRange / 2; // / 2 because its relative to the radius.

              // The last level does not need a fade range.
              if (level == clipmap.NumberOfLevels - 1)
            fadeRange = 0;

              levelOrigin.X = Math.Min(levelOrigin.X, terrainAabbMax.X - clipmap.LevelSizes[level] + fadeRange);
              levelOrigin.Y = Math.Min(levelOrigin.Y, terrainAabbMax.Y - clipmap.LevelSizes[level] + fadeRange);
              levelOrigin.X = Math.Max(levelOrigin.X, terrainAabbMin.X - fadeRange);
              levelOrigin.Y = Math.Max(levelOrigin.Y, terrainAabbMin.Y - fadeRange);
            }

            // Snap to grid.
            levelOrigin = RoundToGrid(levelOrigin, clipmap.ActualCellSizes[level]);

            if (isBaseClipmap)
            {
              // To align the base clipmap with the terrain tile cell raster, we have to move it by
              // half a cell size. E.g. if the tile origin is (0, 0), then the first mesh vertex
              // is over (0, 0). The vertices should sample the clipmap texel centers.
              levelOrigin.X -= clipmap.ActualCellSizes[0] / 2;
              levelOrigin.Y -= clipmap.ActualCellSizes[0] / 2;
            }

            // Simple movement threshold for debugging: Only update clipmaps if origin
            // has moved more than 10 units.
            //if ((levelOrigin - clipmap.OldOrigins[level]).Length < 10)
            //  continue;

            if (levelOrigin == clipmap.OldOrigins[level] && clipmap.UseIncrementalUpdate)
              continue;

            if (level < clipmap.MinLevel)
            {
              // We do not update this level.
              // Note: As long as the level is within the region of the next level we could still
              // draw it - but we ignore this here:
              // Set to invalid origin, to make the shader ignore this level. (The value is still used
              // in the shader, so we must not set it to a totally extreme value.)
              clipmap.Origins[level] = referencePosition2D - new Vector2F(10000);
              clipmap.OldOrigins[level] = clipmap.Origins[level];
              continue;
            }

            clipmap.OldOrigins[level] = clipmap.Origins[level];
            clipmap.OldOffsets[level] = clipmap.Offsets[level];

            // Compute new toroidal wrap offset.
            if (!clipmap.UseIncrementalUpdate || isBaseClipmap)
            {
              // Full clipmap update needed or it is a base clipmap.

              clipmap.Origins[level] = levelOrigin;

              // Base clipmaps are usually super small (< 256²) and are updated infrequently.
              // Therefore we do not make a toroidal update for the base clipmap.
              clipmap.Offsets[level] = new Vector2F(0, 0);
            }
            else
            {
              clipmap.Origins[level] = levelOrigin;

              Vector2F levelSize = new Vector2F(clipmap.LevelSizes[level]);
              Vector2F newOffset = clipmap.OldOffsets[level] + (levelOrigin - clipmap.OldOrigins[level]) / levelSize;

              float cellsPerLevelWithoutBorder = clipmap.CellsPerLevel - 2 * border;
              //Debug.Assert(
              //  Numeric.AreEqual(newOffset.X * cellsPerLevelWithoutBorder,
              //    (float)Math.Floor(newOffset.X * cellsPerLevelWithoutBorder + 0.5f), 0.01f),
              //  "New clipmap offset is not snapped to texel grid.");
              //Debug.Assert(
              //  Numeric.AreEqual(newOffset.Y * cellsPerLevelWithoutBorder,
              //    (float)Math.Floor(newOffset.Y * cellsPerLevelWithoutBorder + 0.5f), 0.01f),
              //  "New clipmap offset is not snapped to texel grid.");

              // The offset should always correspond to clipmap texels, but if we compute the new offset
              // from the old offset then we accumulate errors. --> Snap to texels to remove error.
              newOffset.X = (float)Math.Floor(newOffset.X * cellsPerLevelWithoutBorder + 0.5f) / cellsPerLevelWithoutBorder;
              newOffset.Y = (float)Math.Floor(newOffset.Y * cellsPerLevelWithoutBorder + 0.5f) / cellsPerLevelWithoutBorder;

              // Use "positive modulo" to wrap to [0, 1].
              newOffset.X = ((newOffset.X % 1) + 1) % 1;
              newOffset.Y = ((newOffset.Y % 1) + 1) % 1;

              Debug.Assert(Numeric.IsGreaterOrEqual(newOffset.X, 0), "New offset is not in [0,1]");
              Debug.Assert(Numeric.IsLessOrEqual(newOffset.X, 1), "New offset is not in [0,1]");
              Debug.Assert(Numeric.IsGreaterOrEqual(newOffset.Y, 0), "New offset is not in [0,1]");
              Debug.Assert(Numeric.IsLessOrEqual(newOffset.Y, 1), "New offset is not in [0,1]");

              // Note: clipmap.Offsets stores the offsets as if border is 0.
              clipmap.Offsets[level].X = newOffset.X;
              clipmap.Offsets[level].Y = newOffset.Y;
            }
              }
        }
Example #7
0
    public TerrainTextureSample(Microsoft.Xna.Framework.Game game)
      : base(game)
    {
      SampleFramework.IsMouseVisible = false;

      _graphicsScreen = new DeferredGraphicsScreen(Services);
      _graphicsScreen.DrawReticle = true;
      GraphicsService.Screens.Insert(0, _graphicsScreen);
      GameObjectService.Objects.Add(new DeferredGraphicsOptionsObject(Services));

      Services.Register(typeof(DebugRenderer), null, _graphicsScreen.DebugRenderer);

      var scene = _graphicsScreen.Scene;
      Services.Register(typeof(IScene), null, scene);

      // Add gravity and damping to the physics simulation.
      Simulation.ForceEffects.Add(new Gravity());
      Simulation.ForceEffects.Add(new Damping());

      // Add a custom game object which controls the camera.
      var cameraGameObject = new CameraObject(Services, 60);
      cameraGameObject.ResetPose(new Vector3F(0, 1.8f, 0), 0, 0);
      GameObjectService.Objects.Add(cameraGameObject);
      _graphicsScreen.ActiveCameraNode = cameraGameObject.CameraNode;

      for (int i = 0; i < 10; i++)
        GameObjectService.Objects.Add(new DynamicObject(Services, 1));

      GameObjectService.Objects.Add(new DynamicSkyObject(Services, true, false, true));

      // Create a simple flat terrain.
      var terrain = new Terrain();
      _terrainTile = new TerrainTile(GraphicsService)
      {
        OriginX = -100,
        OriginZ = -100,
        CellSize = 1,
      };
      terrain.Tiles.Add(_terrainTile);

      // Create a flat dummy height texture.
      float[] heights = new float[200 * 200];
      Texture2D heightTexture = null;
      TerrainHelper.CreateHeightTexture(
        GraphicsService.GraphicsDevice,
        heights,
        200,
        200,
        false,
        ref heightTexture);
      _terrainTile.HeightTexture = heightTexture;
      
      var shadowMapEffect = ContentManager.Load<Effect>("DigitalRune/Terrain/TerrainShadowMap");
      var gBufferEffect = ContentManager.Load<Effect>("DigitalRune/Terrain/TerrainGBuffer");
      var materialEffect = ContentManager.Load<Effect>("DigitalRune/Terrain/TerrainMaterial");
      var material = new Material
      {
        { "ShadowMap", new EffectBinding(GraphicsService, shadowMapEffect, null, EffectParameterHint.Material) },
        { "GBuffer", new EffectBinding(GraphicsService, gBufferEffect, null, EffectParameterHint.Material) },
        { "Material", new EffectBinding(GraphicsService, materialEffect, null, EffectParameterHint.Material) }
      };
      var terrainNode = new TerrainNode(terrain, material)
      {
        DetailClipmap =
        {
          CellsPerLevel = 1364,
          NumberOfLevels = 9,
          EnableMipMap = true,
        },
      };
      scene.Children.Add(terrainNode);

      // Add 3 detail textures layers: gravel, rock, snow.
      float detailCellSize = terrainNode.DetailClipmap.CellSizes[0];
      var materialGravel = new TerrainMaterialLayer(GraphicsService)
      {
        DiffuseTexture = ContentManager.Load<Texture2D>("Terrain/Gravel-Diffuse"),
        NormalTexture = ContentManager.Load<Texture2D>("Terrain/Gravel-Normal"),
        SpecularTexture = ContentManager.Load<Texture2D>("Terrain/Gravel-Specular"),
        TileSize = detailCellSize * 512,
        BlendRange = 0.1f,
      };
      _terrainTile.Layers.Add(materialGravel);

      var noiseTexture = NoiseHelper.GetNoiseTexture(GraphicsService, 128, 60);

      var materialRock = new TerrainMaterialLayer(GraphicsService)
      {
        DiffuseTexture = ContentManager.Load<Texture2D>("Terrain/Rock-02-Diffuse"),
        NormalTexture = ContentManager.Load<Texture2D>("Terrain/Rock-02-Normal"),
        SpecularTexture = ContentManager.Load<Texture2D>("Terrain/Rock-02-Specular"),
        HeightTexture = ContentManager.Load<Texture2D>("Terrain/Rock-02-Height"),
        TileSize = detailCellSize * 1024,
        DiffuseColor = new Vector3F(1 / 0.702f),
        BlendTexture = noiseTexture,
        BlendTextureChannel = 0,
        BlendRange = 0.1f,
        TerrainHeightBlendRange = 0.1f,
      };
      _terrainTile.Layers.Add(materialRock);

      var materialSnow = new TerrainMaterialLayer(GraphicsService)
      {
        DiffuseTexture = ContentManager.Load<Texture2D>("Terrain/Snow-Diffuse"),
        NormalTexture = ContentManager.Load<Texture2D>("Terrain/Snow-Normal"),
        SpecularTexture = ContentManager.Load<Texture2D>("Terrain/Snow-Specular"),
        TileSize = detailCellSize * 512,
        BlendTexture = noiseTexture,
        BlendTextureChannel = 1,
        BlendRange = 0.1f,
      };
      _terrainTile.Layers.Add(materialSnow);

      // Create a flat plane for collision detection.
      var rigidBody = new RigidBody(new PlaneShape(), new MassFrame(), null)
      {
        MotionType = MotionType.Static,
      };
      Simulation.RigidBodies.Add(rigidBody);

      CreateGuiControls();
    }