Maltor
Maltor

Reputation: 583

Basic (fake) raycasting on a 2D heightmap

I'm trying to shade a 2D heightmap using basic raycasting that checks if the ray is intercepted before it should shade it. However it's not working.

Map : Map

And the raycasting is giving me this:

Badx2

The result should be red on back-facing slopes and areas behind large mountains (shadows) and blue on sun-facing slopes (highlights). There should not be any yellow. So this image indicates that either all rays are hitting the wrong place, or are intersected always somewhere else, which is impossible. I suspect the problem is with my trig.

Ray class:

class Ray
    {
        public Vector2 Position;
        public Vector2 Direction; // Think in XZ coordinates for these (they are on a perpendicular plane to the heightmap)
        // Angle is angle from horizon (I think), and height is height above zero (arbitrary)
        public float Angle, Height;
        private TerrainUnit[,] Terrainmap;
        private float U, V;

        public Ray(ref TerrainUnit[,] Terrainmap, float height, float angle)
        {
            this.Terrainmap = Terrainmap;
            this.Angle = angle;
            this.Height = this.V = height;
            
            // Create new straight vector
            this.Direction = new Vector2(0, 1);
            // Rotate it to the values determined by the angle
            this.Direction = Vector2.Transform(Direction, Matrix.CreateRotationX(Angle));
            //this.Direction = new Vector2((float)Math.Sin(angle), -(float)Math.Cos(angle));
            // Find the horizontal distance of the origin-destination triangle
            this.U = V / (float)Math.Tan(Angle);
            // Bleh just initialize the vector to something
            this.Position = new Vector2(U, V);
        }

        public void CastTo(int x, int y)
        {
            // Get the height of the target terrain unit
            float H = (float)Terrainmap[x, y].Height;
            // Find where the ray would have to be to intersect that terrain unit based on its angle and height
            Position = new Vector2(x - U, H + V);

            float Z = 1000 * (float)Terrainmap[0, y].Height;

            // As long as the ray is not below the terrain and not past the destination point
            while (Position.Y > Z && Position.X <= x)
            {
                // If the ray has passed into terrain bounds update Z every step
                if (Position.X > 0) Z = 1000 * (float)Terrainmap[(int)Position.X, y].Height;
                Position.X += Direction.X;
                Position.Y += Direction.Y;
            }

            Terrainmap[x, y].TypeColor = Color.Yellow;
            if ((int)Position.X == x) Terrainmap[x, y].TypeColor = Color.Blue;
            else Terrainmap[x, y].TypeColor = Color.Red;
        }
    }

Function that is casting each ray and how I am calling that:

if (lighting) CastSunRays(1f, MathHelper.PiOver4);

private void CastSunRays(float height, float angle)
{
    Ray ray = new Ray(ref Terrainmap, height, angle);

    for (int x = 0; x < Width; x++)
        for (int y = 0; y < Height; y++)
            ray.CastTo(x, y);
}

Upvotes: 2

Views: 4030

Answers (3)

Christian Petry
Christian Petry

Reputation: 36

I also used Bresenham's line algorithm and decreased calculation time to 1/10! Example can be viewed at my GitHub project TextureGenerator-Online. The terrain tool uses this approach.

Terrain tool

See function setTerrainShadow() at tex_terrain.js.

Upvotes: 0

Maltor
Maltor

Reputation: 583

I ended up using a much simpler approach with Bresenham's Line Algorithm to find the intercept point; I imagine it's much faster and more efficient than the way I was trying to do it would have been.

Upvotes: 2

Markus Jarderot
Markus Jarderot

Reputation: 89241

My guess is that when your Direction vector is applied to Position, it oversteps the lower limit (Position.Y > -1) before it has a chance to hit the surface (Position.Y <= Terrainmap[(int)Position.X, y].Height).

You could try to decrease the lower limit, or re-order your if/while tests.

Another problem might be that the Direction Vector is too large in comparison to your height-range. The distance between two neighboring pixels is 1, while the whole range of height differences is contained in the range (-1,1). This gives a very flat surface from the ray-casters point of view. When the Direction vector is applied to the Position vector is takes a relatively small step over the length, and a relatively large step over the height.

Upvotes: 0

Related Questions