public SceneGenerator SelectGenerator(int floor, int seed)
        {
            // for testing
            //return new SceneGeneratorIce(this, floor);

            List <Func <SceneGenerator> > gens = new List <Func <SceneGenerator> >
            {
                () => new SceneGeneratorJungle(this, floor),
                () => new SceneGeneratorLava(this, floor),
                () => new SceneGeneratorIce(this, floor)
            };

            Mathg.Shuffle(new Random(seed), gens);
            gens.Insert(0, () => new SceneGeneratorDefault(this, floor));
            return(gens[((floor - 1) / 4) % gens.Count].Invoke());
        }
        public void Raster(Scene scene)
        {
            Camera camera = scene.Camera;

            // Reset console
            for (int i = 0; i < console.Width; i++)
            {
                for (int j = 0; j < console.Height; j++)
                {
                    console.Data[i, j]  = ' ';
                    console.Color[i, j] = 255;
                    zBuffer[i, j]       = 1;
                    bBuffer[i, j]       = Vector3.Zero;
                    tBuffer[i, j]       = null;
                }
            }

            List <Triangle> triangles = new List <Triangle>();

            ASCII_FPS.triangleCount        = 0;
            ASCII_FPS.triangleCountClipped = 0;
            ASCII_FPS.zonesRendered        = 0;

            // Extract dynamic meshes
            Matrix cameraSpaceMatrix = camera.CameraSpaceMatrix;

            foreach (GameObject gameObject in scene.gameObjects)
            {
                MeshObject mesh = gameObject.MeshObject;
                ASCII_FPS.triangleCount += mesh.triangles.Count;

                Matrix meshToCameraMatrix = mesh.WorldSpaceMatrix * cameraSpaceMatrix;
                foreach (Triangle triangle in mesh.triangles)
                {
                    Vector3 v0 = Vector3.Transform(triangle.V0, meshToCameraMatrix);
                    Vector3 v1 = Vector3.Transform(triangle.V1, meshToCameraMatrix);
                    Vector3 v2 = Vector3.Transform(triangle.V2, meshToCameraMatrix);
                    triangles.Add(new Triangle(v0, v1, v2, triangle.Texture, triangle.UV0, triangle.UV1, triangle.UV2));
                }
            }

            // Clipping
            triangles = ClipTriangles(triangles, camera);
            ASCII_FPS.triangleCountClipped += triangles.Count;

            // Rendering
            RenderTriangles(triangles, camera, 0, console.Width);


            // Find first zone
            Zone firstZone = null;

            foreach (Zone zone in scene.zones)
            {
                if (zone.Bounds.TestPoint(new Vector2(camera.CameraPos.X, camera.CameraPos.Z)))
                {
                    firstZone = zone;
                    break;
                }
            }

            if (firstZone != null)
            {
                ProcessZone(camera, firstZone, 0, console.Width);
            }


            // Shading
            for (int i = 0; i < console.Width; i++)
            {
                for (int j = 0; j < console.Height; j++)
                {
                    Triangle triangle = tBuffer[i, j];

                    if (triangle != null)
                    {
                        float   z   = zBuffer[i, j];
                        Vector3 bar = bBuffer[i, j];


                        // Sample from texture
                        Vector2 uv = bar.X * triangle.UV0 + bar.Y * triangle.UV1 + bar.Z * triangle.UV2;

                        if (EyeEasy)
                        {
                            float d = Math.Clamp(1f - (float)Math.Pow(z, 25), 0f, 1f);
                            console.Data[i, j]  = '@';
                            console.Color[i, j] = Mathg.ColorTo8Bit(triangle.Texture.Sample(uv) * d);
                        }
                        else
                        {
                            int fogId = (z < 0) ? 0 : Math.Min((int)(Math.Pow(z, 10) * fogString.Length + offset[i, j]), fogString.Length - 1);
                            console.Data[i, j]  = fogString[fogId];
                            console.Color[i, j] = Mathg.ColorTo8Bit(triangle.Texture.Sample(uv));
                        }
                    }
                }
            }
        }
        // Render triangles, only for pixels with x in [boundsLeft, boundsRight)
        private void RenderTriangles(List <Triangle> triangles, Camera camera, int boundsLeft, int boundsRight)
        {
            Matrix projectionMatrix = camera.ProjectionMatrix;

            foreach (Triangle triangle in triangles)
            {
                Vector4 v0 = Vector4.Transform(new Vector4(triangle.V0, 1), projectionMatrix);
                Vector4 v1 = Vector4.Transform(new Vector4(triangle.V1, 1), projectionMatrix);
                Vector4 v2 = Vector4.Transform(new Vector4(triangle.V2, 1), projectionMatrix);

                Vector2 p0 = new Vector2(v0.X, -v0.Y) / v0.W;
                Vector2 p1 = new Vector2(v1.X, -v1.Y) / v1.W;
                Vector2 p2 = new Vector2(v2.X, -v2.Y) / v2.W;

                float z0 = v0.Z / v0.W;
                float z1 = v1.Z / v1.W;
                float z2 = v2.Z / v2.W;

                float minX = Math.Min(p0.X, Math.Min(p1.X, p2.X));
                float maxX = Math.Max(p0.X, Math.Max(p1.X, p2.X));
                float minY = Math.Min(p0.Y, Math.Min(p1.Y, p2.Y));
                float maxY = Math.Max(p0.Y, Math.Max(p1.Y, p2.Y));
                int   minI = Math.Max(boundsLeft, (int)((minX + 1f) * 0.5f * console.Width));
                int   maxI = Math.Min(boundsRight, (int)((maxX + 1f) * 0.5f * console.Width) + 1);
                int   minJ = Math.Max(0, (int)((minY + 1f) * 0.5f * console.Height));
                int   maxJ = Math.Min(console.Height, (int)((maxY + 1f) * 0.5f * console.Height) + 1);

                // Four corners of rectangle
                Vector2 topLeft     = new Vector2(2f * minI / console.Width - 1f, 2f * minJ / console.Height - 1f);
                Vector2 bottomLeft  = new Vector2(2f * minI / console.Width - 1f, 2f * maxJ / console.Height - 1f);
                Vector2 topRight    = new Vector2(2f * maxI / console.Width - 1f, 2f * minJ / console.Height - 1f);
                Vector2 bottomRight = new Vector2(2f * maxI / console.Width - 1f, 2f * maxJ / console.Height - 1f);

                // Barycentric coordinates of corners
                Vector3 barTopLeft     = Mathg.Barycentric(topLeft, p0, p1, p2);
                Vector3 barBottomLeft  = Mathg.Barycentric(bottomLeft, p0, p1, p2);
                Vector3 barTopRight    = Mathg.Barycentric(topRight, p0, p1, p2);
                Vector3 barBottomRight = Mathg.Barycentric(bottomRight, p0, p1, p2);

                for (int i = minI; i < maxI; i++)
                {
                    // Barycentric coordinates of points on top and bottom edge, interpolated from corners
                    float   t         = (float)(i - minI) / (maxI - minI);
                    Vector3 barTop    = barTopLeft * (1 - t) + barTopRight * t;
                    Vector3 barBottom = barBottomLeft * (1 - t) + barBottomRight * t;

                    for (int j = minJ; j < maxJ; j++)
                    {
                        // Barycentric coordinates of point (i, j), interpolated from top and bottom
                        float   t2  = (float)(j - minJ) / (maxJ - minJ);
                        Vector3 bar = barTop * (1 - t2) + barBottom * t2;

                        if (bar.X >= -0.01f && bar.Y >= -0.01f && bar.Z >= -0.01f)
                        {
                            float z = z0 * bar.X + z1 * bar.Y + z2 * bar.Z;

                            if (z > -1 && z < 1 && z < zBuffer[i, j])
                            {
                                zBuffer[i, j] = z;
                                tBuffer[i, j] = triangle;
                                bBuffer[i, j] = new Vector3(bar.X / v0.W, bar.Y / v1.W, bar.Z / v2.W)
                                                / (bar.X / v0.W + bar.Y / v1.W + bar.Z / v2.W);
                            }
                        }
                    }
                }
            }
        }