hedgewars/uTouch.pas
author koda
Fri, 06 Jan 2012 01:51:04 +0100
changeset 6551 a2f39cb9af62
parent 6344 cba81e10235c
child 6580 6155187bf599
permissions -rw-r--r--
fix a couple of loose ends: sdl_mixer is informed of that OGG is provided by Tremor with its own macro, there is no more a segfault on Tremor cleanup, added new event type and timestamp entry for SDL, removed spurious characters from the japanese translation, uSound errors now are output with SDLTry, uSound doesn't need sound preloading any more

(*
 * Hedgewars, a free turn based strategy game
 * Copyright (c) 2011 Richard Deurwaarder <xeli@xelification.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 uTouch;

interface

uses sysutils, math, uConsole, uVariables, SDLh, uTypes, uFloat, uConsts, uIO, uCommands, GLUnit;

// TODO: this type should be Int64
// TODO: this type should be named TSDL_FingerId
type SDL_FingerId = LongInt;

type
    PTouch_Finger = ^Touch_Finger;
    Touch_Finger = record
        id                       : SDL_FingerId;
        x,y                      : LongInt;
        historicalX, historicalY : LongInt;
        timeSinceDown            : Longword;
        end;

procedure initModule;

procedure ProcessTouch;
procedure onTouchDown(x,y: Longword; pointerId: SDL_FingerId);
procedure onTouchMotion(x,y: Longword; dx,dy: LongInt; pointerId: SDL_FingerId);
procedure onTouchUp(x,y: Longword; pointerId: SDL_FingerId);
function convertToCursor(scale: LongInt; xy: LongInt): LongInt;
function addFinger(x,y: Longword; id: SDL_FingerId): PTouch_Finger;
procedure deleteFinger(id: SDL_FingerId);
procedure onTouchClick(finger: Touch_Finger);
procedure onTouchDoubleClick(finger: Touch_Finger);

function findFinger(id: SDL_FingerId): PTouch_Finger;
procedure aim(finger: Touch_Finger);
function isOnCrosshair(finger: Touch_Finger): boolean;
function isOnCurrentHog(finger: Touch_Finger): boolean;
function isOnFireButton(finger: Touch_Finger): boolean;
procedure convertToWorldCoord(var x,y: hwFloat; finger: Touch_Finger);
procedure convertToFingerCoord(var x,y: hwFloat; oldX, oldY: hwFloat);
function fingerHasMoved(finger: Touch_Finger): boolean;
function calculateDelta(finger1, finger2: Touch_Finger): hwFloat;
function getSecondFinger(finger: Touch_Finger): PTouch_Finger;
procedure printFinger(finger: Touch_Finger);
implementation

const
    clicktime = 200;
    nilFingerId = High(SDL_FingerId);

var
    fireButtonLeft, fireButtonRight, fireButtonTop, fireButtonBottom : LongInt;
        


    leftButtonBoundary  : LongInt;
    rightButtonBoundary : LongInt;
    topButtonBoundary   : LongInt;
    
    pointerCount : Longword;
    fingers: array of Touch_Finger;
    moveCursor : boolean;
    invertCursor : boolean;

    xTouchClick,yTouchClick : LongInt;
    timeSinceClick : Longword;

    //Pinch to zoom 
    pinchSize : hwFloat;
    baseZoomValue: GLFloat;


    //aiming
    aiming, movingCrosshair: boolean; 
    crosshairCommand: ShortString;
    targetAngle: LongInt;
    stopFiring: boolean;

    //moving
    stopLeft, stopRight, walkingLeft, walkingRight :  boolean;


procedure onTouchDown(x,y: Longword; pointerId: SDL_FingerId);
var 
    finger: PTouch_Finger;
begin
    finger := addFinger(x,y,pointerId);
    case pointerCount of
        1:
        begin
            moveCursor:= false;
            if bShowAmmoMenu then
            begin
                moveCursor := true;
                exit;
            end;

            if isOnCrosshair(finger^) then
            begin
                aiming:= true;
                exit;
            end;

            if isOnFireButton(finger^) then
            begin
                stopFiring:= false;
                ParseCommand('+attack', true);
                exit;
            end;
            if (finger^.x < leftButtonBoundary) and (finger^.y < 390) then
            begin
                ParseCommand('+left', true);
                walkingLeft := true;
                exit;
            end;
            if finger^.x > rightButtonBoundary then
            begin
                ParseCommand('+right', true);
                walkingRight:= true;
                exit;
            end;
            if finger^.y < topButtonBoundary then
            begin
                ParseCommand('hjump', true);
                exit;
            end;
            moveCursor:= true; 
        end;
        2:
        begin
            aiming:= false;
            stopFiring:= true;
            moveCursor:= false;
            pinchSize := calculateDelta(finger^, getSecondFinger(finger^)^);
            baseZoomValue := ZoomValue
        end;
    end;//end case pointerCount of
end;

procedure onTouchMotion(x,y: Longword;dx,dy: LongInt; pointerId: SDL_FingerId);
var
    finger, secondFinger: PTouch_Finger;
    currentPinchDelta, zoom : hwFloat;
    tmpX, tmpY: LongInt;
begin
    x := x;
    y := y;
    dx := dx;
    dy := dy;
    finger:= findFinger(pointerId);
    tmpX := convertToCursor(cScreenWidth, x);
    tmpY := convertToCursor(cScreenHeight, y);

    if moveCursor then
    begin
        if invertCursor then
        begin
            CursorPoint.X := CursorPoint.X + (finger^.x - tmpX);
            CursorPoint.Y := CursorPoint.Y - (finger^.y - tmpY);
        end
        else
        begin
            CursorPoint.X := CursorPoint.X - (finger^.x - tmpX);
            CursorPoint.Y := CursorPoint.Y + (finger^.y - tmpY);
        end;
        finger^.x := tmpX;
        finger^.y := tmpY;
        exit //todo change into switch rather than ugly ifs
    end;
    
    finger^.x := tmpX;
    finger^.y := tmpY;
    
    if aiming then 
    begin
        aim(finger^);
        exit
    end;
    if pointerCount = 2 then
    begin
       secondFinger := getSecondFinger(finger^);
       currentPinchDelta := calculateDelta(finger^, secondFinger^) - pinchSize;
       zoom := currentPinchDelta/cScreenWidth;
       ZoomValue := baseZoomValue - ((hwFloat2Float(zoom) * cMinMaxZoomLevelDelta));
       if ZoomValue < cMaxZoomLevel then ZoomValue := cMaxZoomLevel;
       if ZoomValue > cMinZoomLevel then ZoomValue := cMinZoomLevel;
    end;
end;

procedure onTouchUp(x,y: Longword; pointerId: SDL_FingerId);
begin
    x := x;
    y := y;
    aiming:= false;
    stopFiring:= true;
    deleteFinger(pointerId);

    if walkingLeft then
    begin
        ParseCommand('-left', true);
        walkingLeft := false;
    end;

    if walkingRight then
    begin
        ParseCommand('-right', true);
        walkingRight := false;
    end;
end;

procedure onTouchDoubleClick(finger: Touch_Finger);
begin
    finger := finger;//avoid compiler hint
    ParseCommand('ljump', true);
end;

procedure onTouchClick(finger: Touch_Finger);
begin
    if (SDL_GetTicks - timeSinceClick < 300) and (DistanceI(finger.X-xTouchClick, finger.Y-yTouchClick) < _30) then
    begin
    onTouchDoubleClick(finger);
    exit; 
    end
    else
    begin
        xTouchClick := finger.x;
        yTouchClick := finger.y;
        timeSinceClick := SDL_GetTicks;
    end;

    if bShowAmmoMenu then 
    begin
        doPut(CursorPoint.X, CursorPoint.Y, false); 
        exit
    end;

    if isOnCurrentHog(finger) then
    begin
        bShowAmmoMenu := true;
        exit;
    end;

    if finger.y < topButtonBoundary then
    begin
        ParseCommand('hjump', true);
        exit;
    end;
end;

function addFinger(x,y: Longword; id: SDL_FingerId): PTouch_Finger;
var 
    xCursor, yCursor, index : LongInt;
begin
    //Check array sizes
    if length(fingers) < Integer(pointerCount) then 
    begin
        setLength(fingers, length(fingers)*2);
        for index := length(fingers) div 2 to length(fingers) do fingers[index].id := nilFingerId;
    end;
    
    
    xCursor := convertToCursor(cScreenWidth, x);
    yCursor := convertToCursor(cScreenHeight, y);
    
    //on removing fingers, all fingers are moved to the left
    //with dynamic arrays being zero based, the new position of the finger is the old pointerCount
    fingers[pointerCount].id := id;
    fingers[pointerCount].historicalX := xCursor;
    fingers[pointerCount].historicalY := yCursor;
    fingers[pointerCount].x := xCursor;
    fingers[pointerCount].y := yCursor;
    fingers[pointerCount].timeSinceDown:= SDL_GetTicks;
 
    addFinger:= @fingers[pointerCount];
    inc(pointerCount);
end;

procedure deleteFinger(id: SDL_FingerId);
var
    index : Longword;
begin
    
    dec(pointerCount);
    for index := 0 to pointerCount do
    begin
         if fingers[index].id = id then
         begin
             //Check for onTouchClick event
             if ((SDL_GetTicks - fingers[index].timeSinceDown) < clickTime) AND  
                 not(fingerHasMoved(fingers[index])) then onTouchClick(fingers[index]);

             //put the last finger into the spot of the finger to be removed, 
             //so that all fingers are packed to the far left
             if  pointerCount <> index then
             begin
                fingers[index].id := fingers[pointerCount].id;    
                fingers[index].x := fingers[pointerCount].x;    
                fingers[index].y := fingers[pointerCount].y;    
                fingers[index].historicalX := fingers[pointerCount].historicalX;    
                fingers[index].historicalY := fingers[pointerCount].historicalY;    
                fingers[index].timeSinceDown := fingers[pointerCount].timeSinceDown;

                fingers[pointerCount].id := nilFingerId;
             end
             else fingers[index].id := nilFingerId;
             break;
         end;
    end;

end;

procedure ProcessTouch;
var
    deltaAngle: LongInt;
begin
    invertCursor := not(bShowAmmoMenu);
    if aiming then
    begin
        if CurrentHedgehog^.Gear <> nil then
        begin
            deltaAngle:= CurrentHedgehog^.Gear^.Angle - targetAngle;
            if (deltaAngle <> 0) and not(movingCrosshair) then 
            begin
                ParseCommand('+' + crosshairCommand, true);
                movingCrosshair := true;
            end
            else 
                if movingCrosshair then 
                begin
                    ParseCommand('-' + crosshairCommand, true);
                    movingCrosshair:= false;
                end;
        end;
    end
    else if movingCrosshair then 
    begin
        ParseCommand('-' + crosshairCommand, true);
        movingCrosshair := false;
    end;

    if stopFiring then 
    begin
        ParseCommand('-attack', true);
        stopFiring:= false;
    end;
    
    if stopRight then
    begin
        stopRight := false;
        ParseCommand('-right', true);
    end;
 
    if stopLeft then
    begin
        stopLeft := false;
        ParseCommand('-left', true);
    end;
    
end;

function findFinger(id: SDL_FingerId): PTouch_Finger;
var
    index: LongWord;
begin
   for index := 0 to High(fingers) do
       if fingers[index].id = id then 
       begin
           findFinger := @fingers[index];
           break;
       end;
end;

procedure aim(finger: Touch_Finger);
var 
    hogX, hogY, touchX, touchY, deltaX, deltaY, tmpAngle: hwFloat;
    tmp: ShortString;
begin
    if CurrentHedgehog^.Gear <> nil then
    begin
        touchX := _0;//avoid compiler hint
        touchY := _0;
        hogX := CurrentHedgehog^.Gear^.X;
        hogY := CurrentHedgehog^.Gear^.Y;

        convertToWorldCoord(touchX, touchY, finger);
        deltaX := hwAbs(TouchX-HogX);
        deltaY := (TouchY-HogY);
        
        tmpAngle:= DeltaY / Distance(deltaX, deltaY) *_2048;
        targetAngle:= (hwRound(tmpAngle) + 2048) div 2;

        tmp := crosshairCommand;
        if CurrentHedgehog^.Gear^.Angle - targetAngle < 0 then crosshairCommand := 'down'
        else crosshairCommand:= 'up';
        if movingCrosshair and (tmp <> crosshairCommand) then 
        begin
            ParseCommand('-' + tmp, true);
            movingCrosshair := false;
        end;

    end; //if CurrentHedgehog^.Gear <> nil
end;

function convertToCursor(scale: LongInt; xy: LongInt): LongInt;
begin
    convertToCursor := round(xy/32768*scale)
end;

function isOnFireButton(finger: Touch_Finger): boolean;
begin
    isOnFireButton:= (finger.x <= fireButtonRight) and (finger.x >= fireButtonLeft) and (finger.y <= fireButtonBottom) and (finger.y >= fireButtonTop);
end;

function isOnCrosshair(finger: Touch_Finger): boolean;
var
    x,y : hwFloat;
begin
    x := _0;//avoid compiler hint
    y := _0;
    convertToFingerCoord(x, y, int2hwFloat(CrosshairX), int2hwFloat(CrosshairY));
    isOnCrosshair:= Distance(int2hwFloat(finger.x)-x, int2hwFloat(finger.y)-y) < _50;
end;

function isOnCurrentHog(finger: Touch_Finger): boolean;
var
    x,y : hwFloat;
begin
    x := _0;
    y := _0;
    convertToFingerCoord(x, y, CurrentHedgehog^.Gear^.X, CurrentHedgehog^.Gear^.Y);
    isOnCurrentHog := Distance(int2hwFloat(finger.X)-x, int2hwFloat(finger.Y)-y) < _50;
end;

procedure convertToFingerCoord(var x,y : hwFloat; oldX, oldY: hwFloat);
begin
    x := oldX + int2hwFloat(WorldDx + (cScreenWidth div 2));
    y := oldY + int2hwFloat(WorldDy);
end;

procedure convertToWorldCoord(var x,y: hwFloat; finger: Touch_Finger);
begin
//if x <> nil then 
    x := int2hwFloat((finger.x-WorldDx) - (cScreenWidth div 2));
//if y <> nil then 
    y := int2hwFloat(finger.y-WorldDy);
end;

//Method to calculate the distance this finger has moved since the downEvent
function fingerHasMoved(finger: Touch_Finger): boolean;
begin
    fingerHasMoved := trunc(sqrt(Power(finger.X-finger.historicalX,2) + Power(finger.y-finger.historicalY, 2))) > 330;
end;

function calculateDelta(finger1, finger2: Touch_Finger): hwFloat; inline;
begin
    calculateDelta := DistanceI(finger2.x-finger1.x, finger2.y-finger1.y);
end;

// Under the premise that all pointer ids in pointerIds:SDL_FingerId are packed to the far left.
// If the pointer to be ignored is not pointerIds[0] the second must be there
function getSecondFinger(finger: Touch_Finger): PTouch_Finger;
begin
    if fingers[0].id = finger.id then getSecondFinger := @fingers[1]
    else getSecondFinger := @fingers[0];
end;

procedure printFinger(finger: Touch_Finger);
begin
    WriteToConsole(Format('id:%d, (%d,%d), (%d,%d), time: %d', [finger.id, finger.x, finger.y, finger.historicalX, finger.historicalY, finger.timeSinceDown]));
end;

procedure initModule;
var
    index: Longword;
    //uRenderCoordScaleX, uRenderCoordScaleY: Longword;
begin
    movingCrosshair := false;
    stopFiring:= false;
    walkingLeft := false;
    walkingRight := false;

    leftButtonBoundary := cScreenWidth div 4;
    rightButtonBoundary := cScreenWidth div 4*3;
    topButtonBoundary := cScreenHeight div 6;
    
    setLength(fingers, 4);
    for index := 0 to High(fingers) do 
        fingers[index].id := nilFingerId;


    //uRenderCoordScaleX := Round(cScreenWidth/0.8 * 2);
    fireButtonLeft := Round(cScreenWidth*0.01);
    fireButtonRight := Round(fireButtonLeft + (spritesData[sprFireButton].Width*0.4));
    fireButtonBottom := Round(cScreenHeight*0.99);
    fireButtonTop := fireButtonBottom - Round(spritesData[sprFireButton].Height*0.4);
end;

begin
end.