Skip to main content
3 of 10
added 24 characters in body

Basic isometric projection in Javascript

I've been working on porting an old closed source game to Javascript with Canvas and I've come to a slight problem.

Right now, to display the land, I just have a pretty basic loop that just draws the 44px by 44px diamond-shaped tiles using ctx.drawImage().

It essentially looks like the left side of the image, while I need them to "stretch" to fill the entire tile (like on the right). The tiles have a Z axis which is just in the +j direction on the screen.

Tile example

Anyway, this leads to my question: Is there a way to correctly do this without using webgl? If not: would it be better to use webgl directly to do this, or use a library to handle it for me?

There is an open source example written in C# for XNA that I was looking at which correctly renders tiles, but I can't seem to do anything like it in Javascript:

    internal virtual bool Draw(SpriteBatch3D sb, Vector3 drawPosition, MouseOverList molist, PickTypes pickType, int maxAlt)
    {
        VertexPositionNormalTextureHue[] vertexBuffer;

        if (Z >= maxAlt)
            return false;

        if (_draw_flip)
        {
            // 2   0    
            // |\  |     
            // |  \|     
            // 3   1
            vertexBuffer = VertexPositionNormalTextureHue.PolyBufferFlipped;
            vertexBuffer[0].Position = drawPosition;
            vertexBuffer[0].Position.X += _draw_X + 44;
            vertexBuffer[0].Position.Y -= _draw_Y;

            vertexBuffer[1].Position = vertexBuffer[0].Position;
            vertexBuffer[1].Position.Y += _draw_height;

            vertexBuffer[2].Position = vertexBuffer[0].Position;
            vertexBuffer[2].Position.X -= _draw_width;

            vertexBuffer[3].Position = vertexBuffer[1].Position;
            vertexBuffer[3].Position.X -= _draw_width;
        }
        else
        {
            // 0---1    
            //    /     
            //  /       
            // 2---3
            vertexBuffer = VertexPositionNormalTextureHue.PolyBuffer;
            vertexBuffer[0].Position = drawPosition;
            vertexBuffer[0].Position.X -= _draw_X;
            vertexBuffer[0].Position.Y -= _draw_Y;

            vertexBuffer[1].Position = vertexBuffer[0].Position;
            vertexBuffer[1].Position.X += _draw_width;

            vertexBuffer[2].Position = vertexBuffer[0].Position;
            vertexBuffer[2].Position.Y += _draw_height;

            vertexBuffer[3].Position = vertexBuffer[1].Position;
            vertexBuffer[3].Position.Y += _draw_height;
        }

        if (vertexBuffer[0].Hue != _draw_hue)
            vertexBuffer[0].Hue = vertexBuffer[1].Hue = vertexBuffer[2].Hue = vertexBuffer[3].Hue = _draw_hue;
        
        if (!sb.Draw(_draw_texture, vertexBuffer))
        {
            return false;
        }
        
        if (_draw_IsometricOverlap)
        {
            drawIsometricOverlap(sb, vertexBuffer, new Vector2(drawPosition.X, drawPosition.Y - (Z * 4)));
        }
        
        if ((pickType & _pickType) == _pickType)
        {
            if (((!_draw_flip) && molist.IsMouseInObject(vertexBuffer[0].Position, vertexBuffer[3].Position)) ||
                ((_draw_flip) && molist.IsMouseInObject(vertexBuffer[2].Position, vertexBuffer[1].Position)))
            {
                MouseOverItem item;
                if (!_draw_flip)
                {
                    item = new MouseOverItem(_draw_texture, vertexBuffer[0].Position, this);
                    item.Vertices = new Vector3[4] { vertexBuffer[0].Position, vertexBuffer[1].Position, vertexBuffer[2].Position, vertexBuffer[3].Position };
                }
                else
                {
                    item = new MouseOverItem(_draw_texture, vertexBuffer[2].Position, this);
                    item.Vertices = new Vector3[4] { vertexBuffer[2].Position, vertexBuffer[0].Position, vertexBuffer[3].Position, vertexBuffer[1].Position };
                }
                molist.Add2DItem(item);
            }
        }

        return true;
    }
    /// <summary>
    /// drawIsometricOverlap will create deferred objects that will be drawn in places where a MapObject
    /// might be overlapped by subsequently drawn objects in the world. NOTE that the vertices of the
    /// deferred objects created by this routine are NOT perfect - they may have UV coords that are greater
    /// than 1.0f or less than 0.0f. This would create graphical artifacts, but we rely on our HLSL shader
    /// to ignore these.
    /// </summary>
    /// <param name="sb">The SpriteBatch3D instance passed to the calling Draw() routine.</param>
    /// <param name="vertices">The Vertices that were drawn by the calling MapObject.</param>
    /// <param name="drawPosition">The upper-left hand corner of the tile where the calling MapObject was drawn.</param>
    private void drawIsometricOverlap(SpriteBatch3D sb, VertexPositionNormalTextureHue[] vertices, Vector2 screenPosition)
    {
        Vector2 overlapCurrent = new Vector2(screenPosition.X + 22, screenPosition.Y + 44);
        Vector2 overlapToHere = _draw_flip ? 
            new Vector2(vertices[1].Position.X, vertices[1].Position.Y) : 
            new Vector2(vertices[3].Position.X, vertices[3].Position.Y);

        int tileX, tileY;
        MapTile tile;
        MapObjectDeferred deferred;
        VertexPositionNormalTextureHue[] verts;
        
        if (overlapToHere.Y > (overlapCurrent.Y - 22))
        {
            tileX = Position.X;
            tileY = Position.Y + (int)Math.Ceiling((overlapToHere.Y - (overlapCurrent.Y - 22)) / 22f);

            // Get the tile associated with this (x, y) position. If the tile is not loaded, don't add a deferred object
            // (it'll be offscreen so it doesn't matter).
            tile = IsometricRenderer.Map.GetMapTile(tileX, tileY, false);
            if (tile != null)
            {
                deferred = new MapObjectDeferred(_draw_texture, this);
                deferred.Position.X = tileX;
                deferred.Position.Y = tileY;
                verts = deferred.Vertices;

                if (_draw_flip)
                {
                    //     0
                    //    / \
                    //   /   1
                    //  /   /
                    // 2---3
                    verts[0].Position = new Vector3(overlapCurrent, 0) + new Vector3(-22, -22, 0);
                    verts[0].TextureCoordinate = new Vector3((overlapToHere.X - verts[0].Position.X) / _draw_texture.Width, 1f - (overlapToHere.Y - verts[0].Position.Y) / _draw_texture.Height, 0);
                    verts[1].Position = new Vector3(overlapCurrent, 0);
                    verts[1].TextureCoordinate = new Vector3((overlapToHere.X - verts[1].Position.X) / _draw_texture.Width, 1f - (overlapToHere.Y - verts[1].Position.Y) / _draw_texture.Height, 0);
                    verts[2].Position = new Vector3(verts[0].Position.X - (overlapToHere.Y - verts[0].Position.Y), overlapToHere.Y, 0);
                    verts[2].TextureCoordinate = new Vector3((overlapToHere.X - verts[2].Position.X) / _draw_texture.Width, 1f - (overlapToHere.Y - verts[2].Position.Y) / _draw_texture.Height, 0);
                    verts[3].Position = new Vector3(verts[1].Position.X - (overlapToHere.Y - verts[1].Position.Y), overlapToHere.Y, 0);
                    verts[3].TextureCoordinate = new Vector3((overlapToHere.X - verts[3].Position.X) / _draw_texture.Width, 1f - (overlapToHere.Y - verts[3].Position.Y) / _draw_texture.Height, 0);
                }
                else
                {
                    //     1
                    //    / \
                    //   /   3
                    //  /   /
                    // 0---2
                    verts[1].Position = new Vector3(overlapCurrent, 0) + new Vector3(-22, -22, 0);
                    verts[1].TextureCoordinate = new Vector3(1f - (overlapToHere.X - verts[1].Position.X) / _draw_texture.Width, 1f - (overlapToHere.Y - verts[1].Position.Y) / _draw_texture.Height, 0);
                    verts[3].Position = new Vector3(overlapCurrent, 0);
                    verts[3].TextureCoordinate = new Vector3(1f - (overlapToHere.X - verts[3].Position.X) / _draw_texture.Width, 1f - (overlapToHere.Y - verts[3].Position.Y) / _draw_texture.Height, 0);
                    verts[0].Position = new Vector3(verts[1].Position.X - (overlapToHere.Y - verts[1].Position.Y), overlapToHere.Y, 0);
                    verts[0].TextureCoordinate = new Vector3(1f - (overlapToHere.X - verts[0].Position.X) / _draw_texture.Width, 1f - (overlapToHere.Y - verts[0].Position.Y) / _draw_texture.Height, 0);
                    verts[2].Position = new Vector3(verts[3].Position.X - (overlapToHere.Y - verts[3].Position.Y), overlapToHere.Y, 0);
                    verts[2].TextureCoordinate = new Vector3(1f - (overlapToHere.X - verts[2].Position.X) / _draw_texture.Width, 1f - (overlapToHere.Y - verts[2].Position.Y) / _draw_texture.Height, 0);
                }

                verts[0].Normal = verts[1].Normal = verts[2].Normal = verts[3].Normal = vertices[0].Normal;
                verts[0].Hue = verts[1].Hue = verts[2].Hue = verts[3].Hue = vertices[0].Hue;

                tile.AddMapObject(deferred);
            }
        }

        // reset the tile position for the upcoming loop.
        tileX = Position.X;
        tileY = Position.Y;

        while (true)
        {
            // We are drawing each subsequent deferred object at (x+1, y+1) relative to the last one.
            tileX += 1;
            tileY += 1;

            // Get the tile associated with this (x, y) position. If the tile is not loaded, don't add a deferred object
            // (it'll be offscreen so it doesn't matter).
            tile = IsometricRenderer.Map.GetMapTile(tileX, tileY, false);
            if (tile == null)
                break;

            // find the vertical and            |
            // horizontal edges that            | <- V
            // we are drawing to.    H -> ------+
            Vector3 verticalEdgeOverlapPt = new Vector3(overlapToHere.X, overlapCurrent.Y - (overlapToHere.X - overlapCurrent.X), 0);
            Vector3 horizEdgeOverlapPt = new Vector3(overlapCurrent.X - (overlapToHere.Y - overlapCurrent.Y), overlapToHere.Y, 0);
            if (horizEdgeOverlapPt.X > overlapToHere.X && verticalEdgeOverlapPt.Y > overlapToHere.Y)
                break;

            float extendX = overlapToHere.X - horizEdgeOverlapPt.X;
            if (extendX > 44)
                extendX = 44;
            float extendY = overlapToHere.Y - verticalEdgeOverlapPt.Y;
            if (extendY > 44)
                extendY = 44;

            deferred = new MapObjectDeferred(_draw_texture, this);
            deferred.Position.X = tileX;
            deferred.Position.Y = tileY;
            verts = deferred.Vertices;

            if (_draw_flip)
            {
                //       0
                //      /|
                //    /  1
                //  /   /
                // 2---3
                verts[0].Position = verticalEdgeOverlapPt;
                verts[0].TextureCoordinate = new Vector3(0f, (verts[0].Position.Y - vertices[0].Position.Y) / _draw_texture.Height, 0);
                verts[1].Position = verticalEdgeOverlapPt + new Vector3(0, extendY, 0);
                verts[1].TextureCoordinate = new Vector3(0f, (verts[1].Position.Y - vertices[0].Position.Y) / _draw_texture.Height, 0);
                verts[2].Position = horizEdgeOverlapPt;
                verts[2].TextureCoordinate = new Vector3(1f - (verts[2].Position.X - vertices[2].Position.X) / _draw_texture.Width, 1f, 0);
                verts[3].Position = horizEdgeOverlapPt + new Vector3(extendX, 0, 0);
                verts[3].TextureCoordinate = new Vector3(1f - (verts[3].Position.X - vertices[2].Position.X) / _draw_texture.Width, 1f, 0);
            }
            else
            {
                //       1
                //      /|
                //    /  3
                //  /   /
                // 0---2
                verts[0].Position = horizEdgeOverlapPt;
                verts[0].TextureCoordinate = new Vector3((verts[0].Position.X - vertices[0].Position.X) / _draw_texture.Width, 1, 0);
                verts[1].Position = verticalEdgeOverlapPt;
                verts[1].TextureCoordinate = new Vector3(1, (verts[1].Position.Y - vertices[1].Position.Y) / _draw_texture.Height, 0);
                verts[2].Position = horizEdgeOverlapPt + new Vector3(extendX, 0, 0);
                verts[2].TextureCoordinate = new Vector3((verts[2].Position.X - vertices[0].Position.X) / _draw_texture.Width, 1, 0);
                verts[3].Position = verticalEdgeOverlapPt + new Vector3(0, extendY, 0);
                verts[3].TextureCoordinate = new Vector3(1, (verts[3].Position.Y - vertices[1].Position.Y) / _draw_texture.Height, 0);
            }

            verts[0].Normal = verts[1].Normal = verts[2].Normal = verts[3].Normal = vertices[0].Normal;
            verts[0].Hue = verts[1].Hue = verts[2].Hue = verts[3].Hue = vertices[0].Hue;

            overlapCurrent.X += 22;
            overlapCurrent.Y += 22;

            tile.AddMapObject(deferred);
        }
    }