Esempio n. 1
0
        /// <summary>
        /// Sets the decoration for a specific cell
        /// </summary>
        /// <param name="cell">The cell to set for</param>
        /// <param name="cellMappedData">The cell's mapped data</param>
        /// <param name="mesh">The decoration mesh</param>
        private void SetCellDecoration(Cell cell, CellMappedData cellMappedData, string decoration)
        {
            if (cellMappedData.Decoration.Key == decoration)
            {
                return;
            }

            // Free old decoration
            if (cellMappedData.Decoration.Key != null)
            {
                this.decorations[cellMappedData.Decoration.Key].FreeIndex(cellMappedData.Decoration.Value);
            }

            // Set new decoration
            if (decoration != null && decoration.Length > 0)
            {
                var index = this.decorations[decoration].GetFreeIndex();
                cellMappedData.Decoration = new KeyValuePair <string, int>(decoration, index);
                this.decorations[decoration].SetPosition(index, new Vector3(TopographyHelper.HexToWorld(cell.Coordinates), (float)cell.Elevation));
            }
            else
            {
                cellMappedData.Decoration = new KeyValuePair <string, int>(null, 0);
            }
        }
Esempio n. 2
0
        public void GenerateWorld(int radius)
        {
            double statLowestPoint     = double.MaxValue;
            double statHighestMountain = double.MinValue;
            int    statSeaTiles        = 0;

            Perlin perlin  = new Perlin(5);
            float  boundsX = TopographyHelper.HexToWorld(new Point(radius, 0)).X;
            float  boundsY = TopographyHelper.HexToWorld(new Point(0, radius)).Y;
            var    perlinZ = random.NextDouble();

            scene.WorldMap = new Map(radius);

            for (int p = -radius; p <= radius; p++)
            {
                for (int q = -radius; q <= radius; q++)
                {
                    var currentPoint = new Point(p, q);
                    var cell         = scene.WorldMap.GetCell(p, q);
                    if (cell == null)
                    {
                        continue;
                    }

                    // TODO: Remove Magic Numbers, put them in WorldSettings

                    // Set cell neighbors
                    cell.Neighbours = scene.WorldMap.GetNeighbours(currentPoint);

                    // Set cell height
                    var worldPoint  = TopographyHelper.HexToWorld(currentPoint);
                    var perlinPoint = (worldPoint + new Vector2(boundsX, boundsY)) * 0.01f;
                    var elevation   = perlin.OctavePerlin(perlinPoint.X, perlinPoint.Y, perlinZ, 4, 0.7);
                    // Flat valleys
                    elevation = Math.Pow(elevation, scene.Settings.ElevationPower);

                    // Actually multiply to max height
                    elevation *= scene.Settings.ElevationMultiplier;

                    // Give cells closer to the edge of the map a negative bias. This means the entire map will be surrounded by oceans.
                    var distanceFromEdge = (radius - TopographyHelper.Distance(new Point(0, 0), currentPoint)) / radius;
                    if (distanceFromEdge < 0 || distanceFromEdge > 1)
                    {
                        throw new Exception("Value out of range, check math");
                    }
                    cell.Elevation = elevation * Math.Pow(distanceFromEdge, scene.Settings.EdgeDistancePower);

                    // Update stats
                    if (cell.Elevation > statHighestMountain)
                    {
                        statHighestMountain = cell.Elevation;
                    }
                    if (cell.Elevation < statLowestPoint)
                    {
                        // Now all data has been set, calculate the modifiers
                        statLowestPoint = cell.Elevation;
                    }

                    // Set wetness by rain
                    var rain = perlin.OctavePerlin(perlinPoint.X, perlinPoint.Y, perlinZ * 100, 2, 0.7) * scene.Settings.RainMultiplier;
                    cell.Wetness = rain;
                }
            }

            // Creates oceans by flood filling from map edge
            FloodFillOceans();

            // Do final touchups and modifier calculation
            // TODO: This can contain NULL cells, this is bad
            var cells = scene.WorldMap.Cells;

            foreach (var cell in cells)
            {
                if (cell == null)
                {
                    continue;
                }

                // Remove single-cell 'seas' and islands
                if (cell.IsWater)
                {
                    bool canBeWater = false;
                    foreach (var n in cell.Neighbours)
                    {
                        if (n.IsWater)
                        {
                            canBeWater = true; break;
                        }
                    }
                    cell.IsWater = canBeWater;
                }
                else
                {
                    bool canBeLand = false;
                    foreach (var n in cell.Neighbours)
                    {
                        if (!n.IsWater)
                        {
                            canBeLand = true; break;
                        }
                    }
                    cell.IsWater = !canBeLand;
                }

                // Calculate temperature
                var distanceFromEdge = (radius - TopographyHelper.Distance(new Point(0, 0), cell.Coordinates)) / radius;
                var temperature      = MathHelper.Lerp(scene.Settings.PoleTemperature, scene.Settings.EquatorTemperature,
                                                       (float)Math.Pow(distanceFromEdge, scene.Settings.TemperatureCurvePower));
                var relElevation = (float)cell.Elevation - scene.Settings.SeaLevel;
                if (!cell.IsWater)
                {
                    // Ocean tiles should have temperature at sea level only, otherwise use altitude multiplier
                    temperature += relElevation * scene.Settings.ElevationTemperatureMultiplier;
                }
                cell.Temperature = temperature;

                // Set values
                if (cell.IsWater == true)
                {
                    statSeaTiles++;
                    cell.FoodMod     = random.NextDouble() + random.Next(1, 2);
                    cell.ResourceMod = random.NextDouble() + random.Next(1, 2);
                    cell.MaxHousing  = 0;
                }
                else
                {
                    // Now all data has been set, calculate the modifiers
                    cell.FoodMod     = random.NextDouble() + random.Next(1, 5);
                    cell.ResourceMod = random.NextDouble() + random.Next(1, 5);
                    cell.WealthMod   = random.NextDouble() + random.Next(1, 5);

                    cell.MaxHousing = random.Next(0, 1250) + random.Next(0, 1250) + random.Next(0, 1250) + random.Next(0, 1250);
                }

                // Set biome
                AssignBiome(cell);
            }

            //SimulateWaterflow();

            // Log stats
            Debug.WriteLine("Stats for World Generation");
            Debug.WriteLine("Highest Point: " + statHighestMountain);
            Debug.WriteLine("Lowest Point:  " + statLowestPoint);
            Debug.WriteLine("Ocean Cells:   " + statSeaTiles);
            Debug.WriteLine("Land Cells:    " + (scene.WorldMap.CellCount - statSeaTiles));
            Debug.WriteLine("Water coverage:" + ((float)statSeaTiles / scene.WorldMap.CellCount * 100).ToString("##.##") + "%");
        }
Esempio n. 3
0
        /// <summary>
        /// Generates terrain meshes, cell and biome data from a scene. Can be put in a task.
        /// </summary>
        /// <param name="scene">The scene to generate from</param>
        /// <returns>Results</returns>
        private MeshGenerationResult GenerateMeshesFromScene(Scene scene)
        {
            // Results
            var renderableMeshes = new List <RenderableMesh>();
            var rawMeshes        = new List <Mesh>();
            var cellData         = new Dictionary <Cell, CellMappedData>();
            // Stopwatch used for performance logging
            Stopwatch stopwatch = new Stopwatch();

            stopwatch.Start();
            // Create world meshes
            var hexCombiner = new MeshCombiner();
            int range       = scene.WorldMap.Radius;

            for (int p = -range; p <= range; p++)
            {
                for (int q = -range; q <= range; q++)
                {
                    var cell = scene.WorldMap.GetCell(p, q);
                    // Due to the hexagonal shape we might be out of bounds
                    if (cell == null)
                    {
                        continue;
                    }

                    var  worldPoint = TopographyHelper.HexToWorld(new Point(p, q));
                    var  position   = new Vector3(worldPoint, (float)cell.Elevation);
                    var  color      = GetCellColor(cell);
                    Mesh mesh       = biomeMappedData[cell.Biome.Name].HexMesh;

                    // Ensure sea is actually level
                    if (cell.IsWater && cell.Elevation < scene.Settings.SeaLevel)
                    {
                        position.Z = scene.Settings.SeaLevel;
                    }

                    // Add it to the combiner, using the cell's location as a tag so we can later
                    // get the vertexes of this hex inside the larger mesh
                    int meshIndex = hexCombiner.Add(new MeshInstance
                    {
                        mesh     = mesh,
                        matrix   = Matrix.CreateTranslation(position),
                        tag      = new Point(p, q),
                        color    = color,
                        useColor = true,
                    });

                    // Register partial cell mapped data to be used later
                    var cellMappedData = new CellMappedData
                    {
                        MeshIndex = meshIndex
                    };
                    cellData[cell] = cellMappedData;
                }
            }

            // Combine meshes
            var meshList = hexCombiner.GetCombinedMeshes();

            for (int i = 0; i < meshList.Count; i++)
            {
                var renderable = new RenderableMesh(orbis.GraphicsDevice, meshList[i]);
                renderableMeshes.Add(renderable);
            }
            // Finish cell mapped data
            foreach (var cell in cellData)
            {
                var mesh = meshList[cell.Value.MeshIndex];
                cell.Value.VertexIndexes = mesh.TagIndexMap[cell.Key.Coordinates];
            }

            stopwatch.Stop();
            Debug.WriteLine("Generated " + renderableMeshes.Count + " meshes in " + stopwatch.ElapsedMilliseconds + " ms");

            return(new MeshGenerationResult {
                cellData = cellData,
                rawMeshes = rawMeshes,
                renderableMeshes = renderableMeshes
            });
        }
Esempio n. 4
0
        public override void Update(GameTime gameTime)
        {
            // Not worth updating if we don't have a scene
            if (renderedScene == null)
            {
                return;
            }

            #region Mesh and data updating
            // Check if new meshes have been generated by task
            if (meshTask != null)
            {
                if (meshTask.Status == TaskStatus.Canceled)
                {
                    Debug.WriteLine("Mesh task was cancelled, shouldn't ever happen, call programmer.");
                    meshTask = null;
                }
                else if (meshTask.Status == TaskStatus.Faulted)
                {
                    Debug.WriteLine("Mesh task crashed due to unhandled exception, call programmer.");
                    meshTask = null;
                }
                else if (meshTask.Status == TaskStatus.RanToCompletion)
                {
                    // Just in case this isn't the first map generated we must remove all cell decorations and existing terrain hex meshes
                    // Terrain meshes must be disposed since they won't be used again
                    foreach (var cellMesh in this.cellMeshes)
                    {
                        cellMesh.Dispose();
                    }
                    foreach (var decorationPool in this.decorations)
                    {
                        decorationPool.Value.Clear();
                    }
                    // Cell decorations are in cellMappedData, so they will be overwritten. Their meshes will be reused so MUST NOT be disposed
                    // Update main data sets
                    var meshData = meshTask.Result;
                    this.cellMappedData = meshData.cellData;
                    this.cellMeshes     = meshData.renderableMeshes;
                    // Set cell decorations, this can lock up the application for a while
                    var stopwatch = new Stopwatch();
                    stopwatch.Start();
                    defaultDecorationQueue = new Queue <KeyValuePair <Cell, CellMappedData> >();
                    foreach (var cell in this.cellMappedData)
                    {
                        defaultDecorationQueue.Enqueue(cell);
                    }
                    meshTask = null;

                    stopwatch.Stop();
                    Debug.WriteLine("Took " + stopwatch.Elapsed.TotalMilliseconds + "ms to set decorations");
                }
            }

            // Spread default decoration setting over several frames
            if (defaultDecorationQueue.Count > 0)
            {
                var stopwatch = new Stopwatch();
                stopwatch.Start();
                do
                {
                    var cell      = defaultDecorationQueue.Dequeue();
                    var biomeData = biomeMappedData[cell.Key.Biome.Name];
                    if (biomeData.DefaultDecoration != null && random.NextDouble() < MathHelper.Clamp(biomeData.DefaultDensity, 0, DecorationDensityCap))
                    {
                        SetCellDecoration(cell.Key, cell.Value, biomeData.DefaultDecoration);
                    }
                } while (defaultDecorationQueue.Count > 0 && stopwatch.Elapsed.TotalMilliseconds <= this.MaxUpdateTime);

                // Check which decorations need to be updated this frame
                var set = new HashSet <RenderableMesh>();
                foreach (var dec in this.decorations)
                {
                    dec.Value.Update(set);
                }
                foreach (var mesh in set)
                {
                    meshUpdateQueue.Enqueue(mesh);
                }
                stopwatch.Stop();
            }
            // Update some vertex buffers if we have to without impact framerate too much
            if (meshUpdateQueue.Count > 0)
            {
                var stopwatch = new Stopwatch();
                stopwatch.Start();
                // Use do-while to ensure we update at least one mesh per frame
                do
                {
                    var mesh = meshUpdateQueue.Dequeue();
                    mesh.UpdateVertexBuffer();
                } while(meshUpdateQueue.Count > 0 && stopwatch.Elapsed.TotalMilliseconds <= this.MaxUpdateTime);

                stopwatch.Stop();
            }
            #endregion

            // Camera movement
            var   camMoveDelta  = Vector3.Zero;
            float movementSpeed = 100 * (float)gameTime.ElapsedGameTime.TotalSeconds * (distance / 10);
            float rotationSpeed = 100 * (float)gameTime.ElapsedGameTime.TotalSeconds;
            float zoomSpeed     = 100 * (float)gameTime.ElapsedGameTime.TotalSeconds;
            #region Cam Input
            if (orbis.Input.IsKeyHeld(Keys.LeftShift))
            {
                movementSpeed /= 5;
                rotationSpeed /= 5;
                zoomSpeed     /= 5;
            }
            if (orbis.Input.IsKeyHeld(Keys.LeftControl))
            {
                movementSpeed *= 5;
                rotationSpeed *= 5;
                zoomSpeed     *= 5;
            }
            if (orbis.Input.IsKeyHeld(Keys.Up))
            {
                angle -= rotationSpeed;
            }
            if (orbis.Input.IsKeyHeld(Keys.Down))
            {
                angle += rotationSpeed;
            }
            if (orbis.Input.IsKeyHeld(Keys.Left))
            {
                rotation -= rotationSpeed;
            }
            if (orbis.Input.IsKeyHeld(Keys.Right))
            {
                rotation += rotationSpeed;
            }
            if (orbis.Input.IsKeyHeld(Keys.OemPlus))
            {
                distance -= zoomSpeed;
            }
            if (orbis.Input.IsKeyHeld(Keys.OemMinus))
            {
                distance += zoomSpeed;
            }

            // Mouse Camera movement
            if (orbis.Input.IsMouseHold(Engine.MouseButton.Right))
            {
                //Zooming in/out with the mousewheel
                if (orbis.Input.MouseScroll() != 0)
                {
                    distance -= orbis.Input.MouseScroll() / 10;
                }
                //Rotating the Camera if the mouse moved
                if (orbis.Input.MouseMove() != Point.Zero)
                {
                    rotation += orbis.Input.MouseMove().X / 2;
                    angle    += orbis.Input.MouseMove().Y / 2;
                }
            }

            if (orbis.Input.IsKeyHeld(Keys.W))
            {
                camMoveDelta.Y += movementSpeed * 0.07f;
            }
            if (orbis.Input.IsKeyHeld(Keys.A))
            {
                camMoveDelta.X -= movementSpeed * 0.07f;
            }
            if (orbis.Input.IsKeyHeld(Keys.S))
            {
                camMoveDelta.Y -= movementSpeed * 0.07f;
            }
            if (orbis.Input.IsKeyHeld(Keys.D))
            {
                camMoveDelta.X += movementSpeed * 0.07f;
            }
            #endregion
            // Clamp to > -90, otherwise camera can do a flip
            angle    = MathHelper.Clamp(angle, -89.9f, -5);
            distance = MathHelper.Clamp(distance, 1, 4000);

            camera.LookTarget = camera.LookTarget + Vector3.Transform(camMoveDelta, Matrix.CreateRotationZ(MathHelper.ToRadians(rotation)));
            // Update highlighted cell, snap Camera elevation to it if we can
            var camTile = TopographyHelper.RoundWorldToHex(new Vector2(camera.LookTarget.X, camera.LookTarget.Y));
            HighlightedCell = renderedScene.WorldMap.GetCell(camTile.X, camTile.Y);
            if (HighlightedCell != null)
            {
                var pos = camera.LookTarget;
                pos.Z             = HighlightedCell.IsWater ? renderedScene.Settings.SeaLevel : (float)HighlightedCell.Elevation;
                camera.LookTarget = pos;

                for (int i = 0; i < tileHighlightMesh.VertexData.Length; i++)
                {
                    tileHighlightMesh.VertexData[i].Color = HighlightedCell.Owner != null ?
                                                            HighlightedCell.Owner.Color :
                                                            new Color(230, 232, 237);
                }
                tileHighlightMesh.UpdateVertexBuffer();

                // Draw some cell debug data
                orbis.DrawDebugLine("Current cell: " + HighlightedCell.Coordinates);
                orbis.DrawDebugLine("Owner: " + (HighlightedCell.Owner != null ? HighlightedCell.Owner.Name : "Nobody"));
                orbis.DrawDebugLine("Biome: " + HighlightedCell.Biome.Name);
                orbis.DrawDebugLine("Temperature: " + HighlightedCell.Temperature.ToString("#.#"));
                orbis.DrawDebugLine("Elevation: " + ((HighlightedCell.Elevation - renderedScene.Settings.SeaLevel) * 450).ToString("#.#"));
                orbis.DrawDebugLine("Population: " + HighlightedCell.population);
                orbis.DrawDebugLine("Food: " + HighlightedCell.food.ToString("#.#"));
            }

            // Move camera back from its target
            var camMatrix = Matrix.CreateTranslation(0, -distance, 0) *
                            Matrix.CreateRotationX(MathHelper.ToRadians(angle)) *
                            Matrix.CreateRotationZ(MathHelper.ToRadians(rotation));

            camera.Position = Vector3.Transform(Vector3.Zero, camMatrix) + camera.LookTarget;

            // Recalculate renderinstances
            renderInstances = new List <RenderInstance>();
            foreach (var mesh in this.cellMeshes)
            {
                renderInstances.Add(new RenderInstance
                {
                    mesh   = mesh,
                    matrix = Matrix.Identity
                });
            }
            foreach (var decoration in this.decorations)
            {
                renderInstances.AddRange(decoration.Value.GetActiveInstances());
            }

            base.Update(gameTime);
        }