hedgewars/uRender.pas
author Wolfgang Steffens <WolfgangSteff@gmail.com>
Tue, 10 Jul 2012 11:08:35 +0200
changeset 7304 8b3575750cd2
parent 7188 580cd247511e
child 7377 1aceade403ba
permissions -rw-r--r--
Added auto cropping to atlasing Added splitting of animation sheets to frames and auto crop the frames. Fixed some atlas blitting issues. Vertex coords are still improper tho for auto cropped frames

(*
 * Hedgewars, a free turn based strategy game
 * Copyright (c) 2004-2012 Andrey Korotaev <unC0Rr@gmail.com>
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; version 2 of the License
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA
 *)

{$INCLUDE "options.inc"}

unit uRender;

interface

uses SDLh, uTypes, GLunit, uConsts, uTextures, math;

procedure DrawSprite            (Sprite: TSprite; X, Y, Frame: LongInt);
procedure DrawSpriteFromRect    (Sprite: TSprite; r: TSDL_Rect; X, Y, Height, Position: LongInt);
procedure DrawSpriteClipped     (Sprite: TSprite; X, Y, TopY, RightX, BottomY, LeftX: LongInt);
procedure DrawSpriteRotated     (Sprite: TSprite; X, Y, Dir: LongInt; Angle: real);
procedure DrawSpriteRotatedF    (Sprite: TSprite; X, Y, Frame, Dir: LongInt; Angle: real);

procedure DrawTexture           (X, Y: LongInt; Texture: PTexture); inline;
procedure DrawTexture           (X, Y: LongInt; Texture: PTexture; Scale: GLfloat);
procedure DrawTextureFromRect   (X, Y: LongInt; r: PSDL_Rect; SourceTexture: PTexture);
procedure DrawTextureFromRect   (X, Y, W, H: LongInt; r: PSDL_Rect; SourceTexture: PTexture);
procedure DrawTextureCentered   (X, Top: LongInt; Source: PTexture);
procedure DrawTextureF          (Texture: PTexture; Scale: GLfloat; X, Y, Frame, Dir, w, h: LongInt);
procedure DrawTextureRotated    (Texture: PTexture; hw, hh, X, Y, Dir: LongInt; Angle: real);
procedure DrawTextureRotatedF   (Texture: PTexture; Scale, OffsetX, OffsetY: GLfloat; X, Y, Frame, Dir, w, h: LongInt; Angle: real);

procedure DrawCircle            (X, Y, Radius, Width: LongInt);
procedure DrawCircle            (X, Y, Radius, Width: LongInt; r, g, b, a: Byte);

procedure DrawLine              (X0, Y0, X1, Y1, Width: Single; r, g, b, a: Byte);
procedure DrawFillRect          (r: TSDL_Rect);
procedure DrawHedgehog          (X, Y: LongInt; Dir: LongInt; Pos, Step: LongWord; Angle: real);
procedure DrawScreenWidget      (widget: POnScreenWidget);

// This is just temporary and becomes non public once everything changed to GL2
procedure UpdateModelview;
procedure ResetModelview;
procedure SetOffset(X, Y: Longint);
procedure ResetRotation;


implementation
uses uVariables, uStore;

const DegToRad =  0.01745329252; // 2PI / 360

procedure UpdateModelview;
begin
{$IFDEF GL2}
    UpdateModelviewProjection;
{$ELSE}
    glLoadMatrixf(@mModelview[0,0]);
{$ENDIF}
end;

procedure ResetModelview;
begin
    mModelview[0,0]:= 1.0; mModelview[1,0]:=0.0; mModelview[3,0]:= 0;
    mModelview[0,1]:= 0.0; mModelview[1,1]:=1.0; mModelview[3,1]:= 0;
    UpdateModelview;
end;

procedure SetOffset(X, Y: Longint);
begin
    mModelview[3,0]:= X;
    mModelview[3,1]:= Y;
end;

procedure AddOffset(X, Y: GLfloat); // probably want to refactor this to use integers
begin
    mModelview[3,0]:=mModelview[3,0] + mModelview[0,0]*X + mModelview[1,0]*Y;
    mModelview[3,1]:=mModelview[3,1] + mModelview[0,1]*X + mModelview[1,1]*Y;
end;

procedure SetScale(Scale: GLfloat);
begin
    mModelview[0,0]:= Scale;
    mModelview[1,1]:= Scale;
end;

procedure AddScale(Scale: GLfloat);
begin
    mModelview[0,0]:= mModelview[0,0]*Scale; mModelview[1,0]:= mModelview[1,0]*Scale;
    mModelview[0,1]:= mModelview[0,1]*Scale; mModelview[1,1]:= mModelview[1,1]*Scale;
end;

procedure AddScale(X, Y: GLfloat);
begin
    mModelview[0,0]:= mModelview[0,0]*X; mModelview[1,0]:= mModelview[1,0]*Y;
    mModelview[0,1]:= mModelview[0,1]*X; mModelview[1,1]:= mModelview[1,1]*Y;
end;


procedure SetRotation(Angle, ZAxis: GLfloat);
var s, c: Extended;
begin
    SinCos(Angle*DegToRad, s, c);
    mModelview[0,0]:= c;       mModelview[1,0]:=-s*ZAxis;
    mModelview[0,1]:= s*ZAxis; mModelview[1,1]:= c;
end;

procedure ResetRotation;
begin
    mModelview[0,0]:= 1.0; mModelview[1,0]:=0.0;
    mModelview[0,1]:= 0.0; mModelview[1,1]:=1.0;
end;

procedure DrawSpriteFromRect(Sprite: TSprite; r: TSDL_Rect; X, Y, Height, Position: LongInt);
begin
r.y:= r.y + Height * Position;
r.h:= Height;
DrawTextureFromRect(X, Y, @r, SpritesData[Sprite].Texture)
end;

procedure DrawTextureFromRect(X, Y: LongInt; r: PSDL_Rect; SourceTexture: PTexture);
begin
DrawTextureFromRect(X, Y, r^.w, r^.h, r, SourceTexture)
end;

procedure DrawTextureFromRect(X, Y, W, H: LongInt; r: PSDL_Rect; SourceTexture: PTexture);
var 
    rr: TSDL_Rect;
    VertexBuffer, TextureBuffer: TVertexRect;
    _l, _r, _t, _b: GLfloat;
begin
    if (SourceTexture^.h = 0) or (SourceTexture^.w = 0) then
        exit;

    // do not draw anything outside the visible screen space (first check fixes some sprite drawing, e.g. hedgehogs)
    if (abs(X) > W) and ((abs(X + W / 2) - W / 2) > cScreenWidth / cScaleFactor) then
        exit;
    if (abs(Y) > H) and ((abs(Y + H / 2 - (0.5 * cScreenHeight)) - H / 2) > cScreenHeight / cScaleFactor) then
        exit;

    rr.x:= X;
    rr.y:= Y;
    rr.w:= W;
    rr.h:= H;

    glBindTexture(GL_TEXTURE_2D, SourceTexture^.atlas^.id);

    ComputeTexcoords(SourceTexture, r, @TextureBuffer);

    _l:= X + SourceTexture^.cropInfo.l;
    _r:= X + rr.w - SourceTexture^.cropInfo.l - SourceTexture^.cropInfo.r;
    _t:= Y + SourceTexture^.cropInfo.t;
    _b:= Y + rr.h - SourceTexture^.cropInfo.t - SourceTexture^.cropInfo.b;


    VertexBuffer[0].X:= _l;
    VertexBuffer[0].Y:= _t;
    VertexBuffer[1].X:= _r;
    VertexBuffer[1].Y:= _t;
    VertexBuffer[2].X:= _r;
    VertexBuffer[2].Y:= _b;
    VertexBuffer[3].X:= _l;
    VertexBuffer[3].Y:= _b;

    SetVertexPointer(@VertexBuffer[0]);
    //SetTexCoordPointer(@TextureBuffer[0]);
    SetTexCoordPointer(@SourceTexture^.tb[0]);
    glDrawArrays(GL_TRIANGLE_FAN, 0, Length(VertexBuffer));
end;

procedure DrawTexture(X, Y: LongInt; Texture: PTexture); inline;
begin
    DrawTexture(X, Y, Texture, 1.0);
end;

procedure DrawTexture(X, Y: LongInt; Texture: PTexture; Scale: GLfloat);
begin
SetOffset(X, Y);
ResetRotation;
SetScale(Scale);
UpdateModelview;

glBindTexture(GL_TEXTURE_2D, Texture^.atlas^.id);

SetVertexPointer(@Texture^.vb);
SetTexCoordPointer(@Texture^.tb);
glDrawArrays(GL_TRIANGLE_FAN, 0, Length(Texture^.vb));
ResetModelview;
end;

procedure DrawTextureF(Texture: PTexture; Scale: GLfloat; X, Y, Frame, Dir, w, h: LongInt);
begin
    DrawTextureRotatedF(Texture, Scale, 0, 0, X, Y, Frame, Dir, w, h, 0)
end;

procedure DrawTextureRotatedF(Texture: PTexture; Scale, OffsetX, OffsetY: GLfloat; X, Y, Frame, Dir, w, h: LongInt; Angle: real);
var hw, nx, ny: LongInt;
    r: TSDL_Rect;
    VertexBuffer, TextureBuffer: array [0..3] of TVertex2f;
    _l, _r, _t, _b: GLfloat;
begin

    while (Frame > 0) and (Texture <> nil) do
    begin
        Texture:= Texture^.nextFrame;
        dec(Frame);
    end;

    if Texture = nil then
        exit;

// do not draw anything outside the visible screen space (first check fixes some sprite drawing, e.g. hedgehogs)
if (abs(X) > W) and ((abs(X + dir * OffsetX) - W / 2) * cScaleFactor > cScreenWidth) then
    exit;
if (abs(Y) > H) and ((abs(Y + OffsetY - (0.5 * cScreenHeight)) - W / 2) * cScaleFactor > cScreenHeight) then
    exit;

SetOffset(X, Y);
if Dir = 0 then Dir:= 1;

SetRotation(Angle, Dir);
AddOffset(Dir*OffsetX, OffsetY);
AddScale(Scale);
UpdateModelview;

// Any reason for this call? And why only in t direction, not s?
//glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);

hw:= w div (2 div Dir);

r.y:=0;
r.x:=0;
r.w:=w;
r.h:=h;
ComputeTexcoords(Texture, @r, @TextureBuffer);

glBindTexture(GL_TEXTURE_2D, Texture^.atlas^.id);

_l:= -hw + Texture^.cropInfo.l;
_t:= w/-2 + Texture^.cropInfo.t;
_r:= hw - Texture^.cropInfo.l - Texture^.cropInfo.r;
_b:= w/2 - Texture^.cropInfo.t - Texture^.cropInfo.b;

VertexBuffer[0].X:= _l;
VertexBuffer[0].Y:= _t;
VertexBuffer[1].X:= _r;
VertexBuffer[1].Y:= _t;
VertexBuffer[2].X:= _r;
VertexBuffer[2].Y:= _b;
VertexBuffer[3].X:= _l;
VertexBuffer[3].Y:= _b;

SetVertexPointer(@VertexBuffer[0]);
//SetTexCoordPointer(@TextureBuffer[0]);
SetTexCoordPointer(@Texture^.tb[0]);
glDrawArrays(GL_TRIANGLE_FAN, 0, Length(VertexBuffer));

ResetModelview;
end;

procedure DrawSpriteRotated(Sprite: TSprite; X, Y, Dir: LongInt; Angle: real);
begin
    DrawTextureRotated(SpritesData[Sprite].Texture,
        SpritesData[Sprite].Width,
        SpritesData[Sprite].Height,
        X, Y, Dir, Angle)
end;

procedure DrawSpriteRotatedF(Sprite: TSprite; X, Y, Frame, Dir: LongInt; Angle: real);
begin
SetOffset(X, Y);
if Dir < 0 then
    SetRotation(Angle, -1.0)
else
    SetRotation(Angle, 1.0);
if Dir < 0 then
    AddScale(-1.0, 1.0);
UpdateModelview;

DrawSprite(Sprite, -SpritesData[Sprite].Width div 2, -SpritesData[Sprite].Height div 2, Frame);

ResetModelview;
end;

procedure DrawTextureRotated(Texture: PTexture; hw, hh, X, Y, Dir: LongInt; Angle: real);
var VertexBuffer: array [0..3] of TVertex2f;
    _l, _r, _t, _b: GLfloat;
begin
// do not draw anything outside the visible screen space (first check fixes some sprite drawing, e.g. hedgehogs)
if (abs(X) > 2 * hw) and ((abs(X) - hw) > cScreenWidth / cScaleFactor) then
    exit;
if (abs(Y) > 2 * hh) and ((abs(Y - 0.5 * cScreenHeight) - hh) > cScreenHeight / cScaleFactor) then
    exit;

SetOffset(X, Y);

if Dir < 0 then
    begin
    hw:= - hw;
    SetRotation(Angle, -1.0);
    end
else
    SetRotation(Angle, 1.0);
UpdateModelview;

glBindTexture(GL_TEXTURE_2D, Texture^.atlas^.id);

_l:= -hw + Texture^.cropInfo.l;
_t:= -hh + Texture^.cropInfo.t;
_r:= hw - Texture^.cropInfo.l - Texture^.cropInfo.r;
_b:= hh - Texture^.cropInfo.t - Texture^.cropInfo.b;

VertexBuffer[0].X:= _l;
VertexBuffer[0].Y:= _t;
VertexBuffer[1].X:= _r;
VertexBuffer[1].Y:= _t;
VertexBuffer[2].X:= _r;
VertexBuffer[2].Y:= _b;
VertexBuffer[3].X:= _l;
VertexBuffer[3].Y:= _b;

SetVertexPointer(@VertexBuffer[0]);
SetTexCoordPointer(@Texture^.tb);
glDrawArrays(GL_TRIANGLE_FAN, 0, Length(VertexBuffer));

ResetModelview;
end;

procedure DrawSprite(Sprite: TSprite; X, Y, Frame: LongInt);
var 
    r: TSDL_Rect;
    tex: PTexture;
begin
    if SpritesData[Sprite].imageHeight = 0 then
        exit;

    tex:= SpritesData[Sprite].Texture;

    while (Frame > 0) and (tex <> nil) do
    begin
        tex:= tex^.nextFrame;
        dec(Frame);
    end;

    if (tex = nil) or (tex^.w = 0) or (tex^.h = 0) then
        exit;

    r.x:= 0;
    r.w:= SpritesData[Sprite].Width;
    r.y:= 0;
    r.h:= SpritesData[Sprite].Height;
    DrawTextureFromRect(X, Y, @r, tex)
end;

procedure DrawSpriteClipped(Sprite: TSprite; X, Y, TopY, RightX, BottomY, LeftX: LongInt);
var r: TSDL_Rect;
begin
r.x:= 0;
r.y:= 0;
r.w:= SpritesData[Sprite].Width;
r.h:= SpritesData[Sprite].Height;

if (X < LeftX) then
    r.x:= LeftX - X;
if (Y < TopY) then
    r.y:= TopY - Y;

if (Y + SpritesData[Sprite].Height > BottomY) then
    r.h:= BottomY - Y + 1;
if (X + SpritesData[Sprite].Width > RightX) then
    r.w:= RightX - X + 1;

dec(r.h, r.y);
dec(r.w, r.x);

DrawTextureFromRect(X + r.x, Y + r.y, @r, SpritesData[Sprite].Texture)
end;

procedure DrawTextureCentered(X, Top: LongInt; Source: PTexture);
var scale: GLfloat;
begin
    if (Source^.w + 20) > cScreenWidth then
        scale:= cScreenWidth / (Source^.w + 20)
    else
        scale:= 1.0;
    DrawTexture(X - round(Source^.w * scale) div 2, Top, Source, scale)
end;

procedure DrawLine(X0, Y0, X1, Y1, Width: Single; r, g, b, a: Byte);
var VertexBuffer: array [0..3] of TVertex2f;
begin
    glDisable(GL_TEXTURE_2D);
    glEnable(GL_LINE_SMOOTH);

    ResetRotation;
    SetOffset(WorldDx, WorldDy);
    UpdateModelview;
    glLineWidth(Width);

    Tint(r, g, b, a);
    VertexBuffer[0].X:= X0;
    VertexBuffer[0].Y:= Y0;
    VertexBuffer[1].X:= X1;
    VertexBuffer[1].Y:= Y1;

    SetVertexPointer(@VertexBuffer[0]);
    glDrawArrays(GL_LINES, 0, Length(VertexBuffer));
    Tint($FF, $FF, $FF, $FF);
    
    ResetModelview;
    
    glEnable(GL_TEXTURE_2D);
    glDisable(GL_LINE_SMOOTH);
end;

procedure DrawFillRect(r: TSDL_Rect);
var VertexBuffer: array [0..3] of TVertex2f;
begin
SetOffset(0, 0);
ResetRotation;
UpdateModelview;

// do not draw anything outside the visible screen space (first check fixes some sprite drawing, e.g. hedgehogs)
if (abs(r.x) > r.w) and ((abs(r.x + r.w / 2) - r.w / 2) * cScaleFactor > cScreenWidth) then
    exit;
if (abs(r.y) > r.h) and ((abs(r.y + r.h / 2 - (0.5 * cScreenHeight)) - r.h / 2) * cScaleFactor > cScreenHeight) then
    exit;

glDisable(GL_TEXTURE_2D);

Tint($00, $00, $00, $80);

VertexBuffer[0].X:= r.x;
VertexBuffer[0].Y:= r.y;
VertexBuffer[1].X:= r.x + r.w;
VertexBuffer[1].Y:= r.y;
VertexBuffer[2].X:= r.x + r.w;
VertexBuffer[2].Y:= r.y + r.h;
VertexBuffer[3].X:= r.x;
VertexBuffer[3].Y:= r.y + r.h;

SetVertexPointer(@VertexBuffer[0]);
glDrawArrays(GL_TRIANGLE_FAN, 0, Length(VertexBuffer));

Tint($FF, $FF, $FF, $FF);
glEnable(GL_TEXTURE_2D);

ResetModelview;
end;

procedure DrawCircle(X, Y, Radius, Width: LongInt; r, g, b, a: Byte); 
begin
    Tint(r, g, b, a);
    DrawCircle(X, Y, Radius, Width); 
    Tint($FF, $FF, $FF, $FF);
end;

procedure DrawCircle(X, Y, Radius, Width: LongInt); 
var
    i: LongInt;
    CircleVertex: array [0..59] of TVertex2f;
begin
    for i := 0 to 59 do begin
        CircleVertex[i].X := X + Radius*cos(i*pi/30);
        CircleVertex[i].Y := Y + Radius*sin(i*pi/30);
    end;
    glDisable(GL_TEXTURE_2D);
    glEnable(GL_LINE_SMOOTH);
    SetOffset(0, 0);
    ResetRotation;
    UpdateModelview;
    glLineWidth(Width);
    SetVertexPointer(@CircleVertex[0]);
    glDrawArrays(GL_LINE_LOOP, 0, 60);
    glEnable(GL_TEXTURE_2D);
    glDisable(GL_LINE_SMOOTH);
    ResetModelview;
end;


procedure DrawHedgehog(X, Y: LongInt; Dir: LongInt; Pos, Step: LongWord; Angle: real);
const VertexBuffers: array[0..1] of TVertexRect = (
        ((x: -16; y: -16),
         (x:  16; y: -16),
         (x:  16; y:  16),
         (x: -16; y:  16)),
        ((x:  16; y: -16),
         (x: -16; y: -16),
         (x: -16; y:  16),
         (x:  16; y:  16)));
var r: TSDL_Rect;
    TextureBuffer: array [0..3] of TVertex2f;
begin
    // do not draw anything outside the visible screen space (first check fixes some sprite drawing, e.g. hedgehogs)
    if (abs(X) > 32) and ((abs(X) - 16) * cScaleFactor > cScreenWidth) then
        exit;
    if (abs(Y) > 32) and ((abs(Y - 0.5 * cScreenHeight) - 16) * cScaleFactor > cScreenHeight) then
        exit;

    r.x:=Step * 32;
    r.y:=Pos * 32;
    r.w:=32;
    r.h:=32;
    ComputeTexcoords(HHTexture, @r, @TextureBuffer);

    SetOffset(X, Y);
    SetRotation(Angle, 1.0);
    UpdateModelview;

    glBindTexture(GL_TEXTURE_2D, HHTexture^.atlas^.id);
    if Dir = -1 then
        SetVertexPointer(@VertexBuffers[1][0])
    else
        SetVertexPointer(@VertexBuffers[0][0]);
    SetTexCoordPointer(@TextureBuffer[0]);
    glDrawArrays(GL_TRIANGLE_FAN, 0, 4);

    ResetModelview;
end;

procedure DrawScreenWidget(widget: POnScreenWidget);
{$IFDEF USE_TOUCH_INTERFACE}
var alpha: byte = $FF;
begin
with widget^ do
    begin
    if (fadeAnimStart <> 0) then
        begin
        if RealTicks > (fadeAnimStart + FADE_ANIM_TIME) then
            fadeAnimStart:= 0
        else
            if show then 
                alpha:= Byte(trunc((RealTicks - fadeAnimStart)/FADE_ANIM_TIME * $FF))
            else 
                alpha:= Byte($FF - trunc((RealTicks - fadeAnimStart)/FADE_ANIM_TIME * $FF));
        end;

    with moveAnim do
        if animate then
            if RealTicks > (startTime + MOVE_ANIM_TIME) then
                begin
                startTime:= 0;
                animate:= false;
                frame.x:= target.x;
                frame.y:= target.y;
                active.x:= active.x + (target.x - source.x);
                active.y:= active.y + (target.y - source.y);
                end
            else
                begin
                frame.x:= source.x + Round((target.x - source.x) * ((RealTicks - startTime) / MOVE_ANIM_TIME));
                frame.y:= source.y + Round((target.y - source.y) * ((RealTicks - startTime) / MOVE_ANIM_TIME));
                end;

    if show or (fadeAnimStart <> 0) then
        begin
        Tint($FF, $FF, $FF, alpha);
        DrawTexture(frame.x, frame.y, spritesData[sprite].Texture, buttonScale);
        Tint($FF, $FF, $FF, $FF);
        end;
    end;
{$ELSE}
begin
widget:= widget; // avoid hint
{$ENDIF}
end;

end.