hedgewars/uDrawing.pas
changeset 15908 014f4edd0421
equal deleted inserted replaced
15907:a323e1954a6f 15908:014f4edd0421
       
     1 (*
       
     2  * Hedgewars, a free turn based strategy game
       
     3  * Copyright (c) 2004-2015 Andrey Korotaev <unC0Rr@gmail.com>
       
     4  *
       
     5  * This program is free software; you can redistribute it and/or modify
       
     6  * it under the terms of the GNU General Public License as published by
       
     7  * the Free Software Foundation; version 2 of the License
       
     8  *
       
     9  * This program is distributed in the hope that it will be useful,
       
    10  * but WITHOUT ANY WARRANTY; without even the implied warranty of
       
    11  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
       
    12  * GNU General Public License for more details.
       
    13  *
       
    14  * You should have received a copy of the GNU General Public License
       
    15  * along with this program; if not, write to the Free Software
       
    16  * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
       
    17  *)
       
    18 
       
    19 {$INCLUDE "options.inc"}
       
    20 
       
    21 unit uDrawing;
       
    22 (*
       
    23  * This unit defines the Drawing mode, which allows drawing some graphics
       
    24  * private to the members of a clan.
       
    25  *)
       
    26 interface
       
    27 
       
    28 procedure initModule;
       
    29 procedure freeModule;
       
    30 
       
    31 function isDrawingModeActive(): boolean;
       
    32 procedure onModeButtonPressed();
       
    33 procedure onModeButtonReleased();
       
    34 procedure onFocusStateChanged();
       
    35 procedure onCursorMoved();
       
    36 procedure onLeftMouseButtonPressed();
       
    37 procedure onLeftMouseButtonReleased();
       
    38 procedure onRightMouseButtonPressed();
       
    39 procedure onMiddleMouseButtonPressed();
       
    40 procedure handleIPCInput(cmd : shortstring);
       
    41 
       
    42 implementation
       
    43 uses uTypes, uConsts, uVariables, uVisualGearsList, uUtils, uDebug, uIO, SDLh, Math;
       
    44 
       
    45 const
       
    46    cColorsCount = 9;
       
    47    cColors : array [0..cColorsCount - 1] of LongWord = (
       
    48                                 $ff020400,
       
    49                                 $4980c100,
       
    50                                 $1de6ba00,
       
    51                                 $b541ef00,
       
    52                                 $e55bb000,
       
    53                                 $20bf0000,
       
    54                                 $fe8b0e00,
       
    55                                 $8f590200,
       
    56                                 $ffff0100
       
    57                                 );
       
    58    // Reserve one color for the local user
       
    59    cKnownUsersMax = cColorsCount - 1;
       
    60    cPointRadius = 25;
       
    61    cBeaconDuration = 125;
       
    62    cEffectDuration = 500;
       
    63    cMaxDrawingRadius = 150;
       
    64    cEffectGearsCountMax = 4;
       
    65 
       
    66 type
       
    67    TDrawingState = (drwDisabled, drwStart, drwPoint, drwArrow);
       
    68 
       
    69    TVisualEffect = record
       
    70                       vGears     : array [0..cEffectGearsCountMax - 1] of PVisualGear;
       
    71                       gearsCount : integer;
       
    72                    end;
       
    73    TDrawingContext = record
       
    74                         state            : TDrawingState;
       
    75                         currVEffect      : TVisualEffect;
       
    76                         prevAutoCameraOn : boolean;
       
    77                         startCursorX     : LongInt;
       
    78                         startCursorY     : LongInt;
       
    79                         knownUsers       : array [0..cKnownUsersMax - 1] of shortstring;
       
    80                         knownUsersCount  : integer;
       
    81                         lastReplacedUserIdx : integer;
       
    82                      end;
       
    83 
       
    84 var drawingCtx : TDrawingContext;
       
    85 
       
    86 procedure AddKnownUser(user : shortstring);
       
    87 var i : integer;
       
    88 begin
       
    89    with drawingCtx do
       
    90    begin
       
    91       for i:= 0 to knownUsersCount - 1 do
       
    92       begin
       
    93          if knownUsers[i] = user then
       
    94             exit;
       
    95       end;
       
    96       if knownUsersCount < cKnownUsersMax then
       
    97       begin
       
    98          knownUsers[knownUsersCount]:= user;
       
    99          Inc(knownUsersCount);
       
   100       end
       
   101       else
       
   102       begin
       
   103          lastReplacedUserIdx:= (lastReplacedUserIdx + 1) mod cKnownUsersMax;
       
   104          knownUsers[lastReplacedUserIdx]:= user;
       
   105       end;
       
   106    end;
       
   107 end;
       
   108 
       
   109 function GetUserColor(user : shortstring) : LongWord;
       
   110 var i : integer;
       
   111 begin
       
   112    if user = '' then  // local user
       
   113       exit(cColors[0]);
       
   114    with drawingCtx do
       
   115    begin
       
   116       for i:= 0 to knownUsersCount - 1 do
       
   117       begin
       
   118          if knownUsers[i] = user then
       
   119          begin
       
   120             exit(cColors[i + 1]);
       
   121          end;
       
   122       end;
       
   123       exit(cColors[0]);
       
   124    end;
       
   125 end;
       
   126 
       
   127 procedure recalcArrowParams(var arrow: TVisualEffect; X1, Y1, X2, Y2 : real);
       
   128 var tmp, tmpSin, tmpCos : real;
       
   129 begin
       
   130    with arrow.vGears[0]^ do
       
   131    begin
       
   132       X:= X1;
       
   133       Y:= Y1;
       
   134       dX:= X2;
       
   135       dY:= Y2;
       
   136    end;
       
   137    // Compute arrow pointer coordinates
       
   138    if X2 = X1 then
       
   139       if Y2 > Y1 then
       
   140          tmp:= PI / 2
       
   141       else
       
   142          tmp:= -PI / 2
       
   143    else
       
   144       tmp:= arctan2(Y2 - Y1, X2 - X1);
       
   145    tmpSin:= sin(tmp - PI / 4);
       
   146    tmpCos:= cos(tmp - PI / 4);
       
   147    with arrow.vGears[1]^ do
       
   148    begin
       
   149       X:= X2;
       
   150       Y:= Y2;
       
   151       dX:= X2 - 50 * tmpCos;
       
   152       dY:= Y2 - 50 * tmpSin;
       
   153    end;
       
   154    tmpSin:= sin(tmp + PI / 4);
       
   155    tmpCos:= cos(tmp + PI / 4);
       
   156    with arrow.vGears[2]^ do
       
   157    begin
       
   158       X:= X2;
       
   159       Y:= Y2;
       
   160       dX:= X2 - 50 * tmpCos;
       
   161       dY:= Y2 - 50 * tmpSin;
       
   162    end;
       
   163    // Compute circle center
       
   164    with arrow.vGears[3]^ do
       
   165    begin
       
   166       X:= (X1 + X2) / 2;
       
   167       Y:= (Y1 + Y2) / 2;
       
   168    end;
       
   169 end;
       
   170 
       
   171 procedure doStepPoint(Gear: PVisualGear; Steps: Longword);
       
   172 var tmp: LongInt;
       
   173 begin
       
   174 if Gear^.FrameTicks <= Steps then
       
   175    DeleteVisualGear(Gear)
       
   176 else
       
   177 begin
       
   178    dec(Gear^.FrameTicks, Steps);
       
   179    if Gear^.Tag = 0 then
       
   180    begin
       
   181       tmp:= round(Gear^.FrameTicks * $FF / cEffectDuration);
       
   182       if tmp > $FF then
       
   183          tmp:= $FF;
       
   184       if tmp >= 0 then
       
   185          Gear^.Tint:= (Gear^.Tint and $FFFFFF00) or Longword(tmp);
       
   186    end
       
   187    else if Gear^.Tag = 1 then
       
   188    begin
       
   189       Gear^.State:= round(Gear^.FrameTicks * 2048 / cBeaconDuration);
       
   190    end;
       
   191 end;
       
   192 end;
       
   193 
       
   194 procedure doStepArrow(Gear: PVisualGear; Steps: Longword);
       
   195 var tmp: LongInt;
       
   196 begin
       
   197 if Gear^.Tag = 100 then
       
   198    exit;
       
   199 if Gear^.FrameTicks <= Steps then
       
   200    DeleteVisualGear(Gear)
       
   201 else
       
   202 begin
       
   203    dec(Gear^.FrameTicks, Steps);
       
   204    if Gear^.Tag < 3 then
       
   205    begin
       
   206       tmp:= round(Gear^.FrameTicks * $FF / cEffectDuration);
       
   207       if tmp > $FF then
       
   208          tmp:= $FF;
       
   209       if tmp >= 0 then
       
   210          Gear^.Tint:= (Gear^.Tint and $FFFFFF00) or Longword(tmp);
       
   211    end
       
   212    else if Gear^.Tag = 3 then
       
   213    begin
       
   214       Gear^.State:= round(Gear^.FrameTicks * 2048 / cBeaconDuration);
       
   215    end;
       
   216 end;
       
   217 end;
       
   218 
       
   219 function isEffectEmpty(var vEffect : TVisualEffect) : boolean;
       
   220 begin
       
   221    isEffectEmpty:= vEffect.gearsCount = 0;
       
   222 end;
       
   223 
       
   224 function AddVEffectCircle(user : shortstring; X, Y : LongInt) : TVisualEffect;
       
   225 var vGear  : PVisualGear;
       
   226    vEffect : TVisualEffect;
       
   227    color   : LongWord;
       
   228    i       : integer;
       
   229 begin
       
   230    color:= GetUserColor(user);
       
   231    for i:= 0 to 1 do
       
   232    begin
       
   233       vGear := AddVisualGear(X, Y, vgtCircle, cPointRadius, true, 1);
       
   234       if vGear = nil then
       
   235       begin
       
   236          OutError('uDrawing: AddVisualGear returned nil', false);
       
   237          vEffect.gearsCount:= 0;
       
   238          exit(vEffect);
       
   239       end;
       
   240       vGear^.Tint:= color or $FF;
       
   241       vGear^.Angle:= 0;
       
   242       vGear^.Timer:= 10;
       
   243       vGear^.Tag:= i;
       
   244       vGear^.doStep:= @doStepPoint;
       
   245       vEffect.vGears[i]:= vGear;
       
   246    end;
       
   247    vEffect.vGears[0]^.Tint:= color or $FF;
       
   248    vEffect.vGears[0]^.FrameTicks:= cEffectDuration;
       
   249    vEffect.vGears[1]^.Tint:= color or $3F;
       
   250    vEffect.vGears[1]^.FrameTicks:= cBeaconDuration;
       
   251 
       
   252    vEffect.gearsCount:= 2;
       
   253    AddVEffectCircle:= vEffect;
       
   254 end;
       
   255 
       
   256 function AddVEffectArrow(user : shortstring; X1, Y1, X2, Y2 : LongInt) : TVisualEffect;
       
   257 var vGear  : PVisualGear;
       
   258    vEffect : TVisualEffect;
       
   259    color   : LongWord;
       
   260    i       : integer;
       
   261 begin
       
   262    color:= GetUserColor(user);
       
   263    for i:= 0 to 2 do
       
   264    begin
       
   265       vGear := AddVisualGear(0, 0, vgtLine, 10, true, 1);
       
   266       if vGear = nil then
       
   267       begin
       
   268          OutError('uDrawing: AddVisualGear returned nil', false);
       
   269          vEffect.gearsCount:= 0;
       
   270          exit(vEffect);
       
   271       end;
       
   272       vGear^.Tint:= color or $FF;
       
   273       vGear^.FrameTicks:= cEffectDuration;
       
   274       vGear^.Tag:= 100;
       
   275       vGear^.doStep:= @doStepArrow;
       
   276       vEffect.vGears[i]:= vGear;
       
   277    end;
       
   278 
       
   279    vGear := AddVisualGear(0, 0, vgtCircle, 2048, true, 1);
       
   280    if vGear = nil then
       
   281    begin
       
   282       OutError('uDrawing: AddVisualGear returned nil', false);
       
   283       vEffect.gearsCount:= 0;
       
   284       exit(vEffect);
       
   285    end;
       
   286    vGear^.Tint:= color;
       
   287    vGear^.Angle:= 0;
       
   288    vGear^.FrameTicks:= cBeaconDuration;
       
   289    vGear^.Timer:= 10;
       
   290    vGear^.Tag:= 100;
       
   291    vGear^.doStep:= @doStepArrow;
       
   292    vEffect.vGears[3]:= vGear;
       
   293 
       
   294    vEffect.gearsCount:= 4;
       
   295    recalcArrowParams(vEffect, X1, Y1, X2, Y2);
       
   296 
       
   297    AddVEffectArrow:= vEffect;
       
   298 end;
       
   299 
       
   300 procedure VEffectArrowStart(var vEffect : TVisualEffect);
       
   301 var i : integer;
       
   302 begin
       
   303    for i:= 0 to vEffect.gearsCount - 1 do
       
   304       vEffect.vGears[i]^.Tag:= i;
       
   305    vEffect.vGears[3]^.Tint:= (vEffect.vGears[3]^.Tint and $FFFFFF00) or $3F;
       
   306 end;
       
   307 
       
   308 procedure DeleteVEffect(var vEffect : TVisualEffect);
       
   309 var i : integer;
       
   310 begin
       
   311    for i:= 0 to vEffect.gearsCount - 1 do
       
   312       DeleteVisualGear(vEffect.vGears[i]);
       
   313    vEffect.gearsCount:= 0;
       
   314 end;
       
   315 
       
   316 function isDrawingModeActive() : boolean;
       
   317 begin
       
   318    isDrawingModeActive:= drawingCtx.state <> drwDisabled;
       
   319 end;
       
   320 
       
   321 procedure SendIPCArrow(X1, Y1, X2, Y2: LongInt);
       
   322 var s: shortstring;
       
   323 begin
       
   324 s[0]:= #18;
       
   325 s[1]:= 'O';
       
   326 s[2]:= 'a';
       
   327 SDLNet_Write32(X1, @s[3]);
       
   328 SDLNet_Write32(Y1, @s[7]);
       
   329 SDLNet_Write32(X2, @s[11]);
       
   330 SDLNet_Write32(Y2, @s[15]);
       
   331 SendIPC(s)
       
   332 end;
       
   333 
       
   334 procedure SendIPCCircle(X1, Y1: LongInt);
       
   335 var s: shortstring;
       
   336 begin
       
   337 s[0]:= #10;
       
   338 s[1]:= 'O';
       
   339 s[2]:= 'c';
       
   340 SDLNet_Write32(X1, @s[3]);
       
   341 SDLNet_Write32(Y1, @s[7]);
       
   342 SendIPC(s)
       
   343 end;
       
   344 
       
   345 procedure handleIPCInput(cmd: shortstring);
       
   346 var i, drwCmdOffset : integer;
       
   347    userNameLen      : Byte;
       
   348    user             : shortstring;
       
   349    X1, Y1, X2, Y2   : LongInt;
       
   350    VEffect          : TVisualEffect;
       
   351 begin
       
   352 case cmd[1] of
       
   353   'u' : begin
       
   354      userNameLen:= Byte(cmd[2]);
       
   355      for i:= 0 to userNameLen do
       
   356         user[i]:= cmd[2 + i];
       
   357      drwCmdOffset:= 2 + userNameLen + 1;
       
   358      if Length(cmd) < drwCmdOffset then
       
   359         exit;
       
   360      AddKnownUser(user);
       
   361      case cmd[drwCmdOffset] of
       
   362        'a' : begin
       
   363           if Length(cmd) < drwCmdOffset + 4 * 4 then
       
   364              exit;
       
   365           X1:= SDLNet_Read32(@cmd[drwCmdOffset + 1]);
       
   366           Y1:= SDLNet_Read32(@cmd[drwCmdOffset + 1 + 4]);
       
   367           X2:= SDLNet_Read32(@cmd[drwCmdOffset + 1 + 8]);
       
   368           Y2:= SDLNet_Read32(@cmd[drwCmdOffset + 1 + 12]);
       
   369           VEffect:= AddVEffectArrow(user, X1, Y1, X2, Y2);
       
   370           if not isEffectEmpty(VEffect) then
       
   371              VEffectArrowStart(VEffect);
       
   372        end;
       
   373        'c' : begin
       
   374           if Length(cmd) < drwCmdOffset + 4 * 2 then
       
   375              exit;
       
   376           X1:= SDLNet_Read32(@cmd[drwCmdOffset + 1]);
       
   377           Y1:= SDLNet_Read32(@cmd[drwCmdOffset + 1 + 4]);
       
   378           VEffect:= AddVEffectCircle(user, X1, Y1);
       
   379        end;
       
   380      end;
       
   381   end;
       
   382 end;
       
   383 end;
       
   384 
       
   385 procedure onModeButtonPressed();
       
   386 begin
       
   387    drawingCtx.state:= drwStart;
       
   388    drawingCtx.prevAutoCameraOn:= autoCameraOn;
       
   389    autoCameraOn:= false;
       
   390 end;
       
   391 
       
   392 procedure onModeButtonReleased();
       
   393 begin
       
   394    DeleteVEffect(drawingCtx.currVEffect);
       
   395    if drawingCtx.state <> drwDisabled then
       
   396    begin
       
   397       drawingCtx.state:= drwDisabled;
       
   398       autoCameraOn:= drawingCtx.prevAutoCameraOn;
       
   399    end;
       
   400 end;
       
   401 
       
   402 procedure onFocusStateChanged();
       
   403 begin
       
   404    if not cHasFocus then
       
   405       onModeButtonReleased();
       
   406 end;
       
   407 
       
   408 procedure onLeftMouseButtonPressed();
       
   409 begin
       
   410 if not isDrawingModeActive() then
       
   411    exit;
       
   412 case drawingCtx.state of
       
   413     drwStart: begin
       
   414                  drawingCtx.startCursorX:= CursorPoint.X;
       
   415                  drawingCtx.startCursorY:= CursorPoint.Y;
       
   416                  drawingCtx.state:= drwPoint;
       
   417               end;
       
   418 end;
       
   419 end;
       
   420 
       
   421 procedure onLeftMouseButtonReleased();
       
   422 var tmpX, tmpY, tmpX2, tmpY2 : LongInt;
       
   423    vEffect     : TVisualEffect;
       
   424 begin
       
   425 if not isDrawingModeActive() then
       
   426    exit;
       
   427 case drawingCtx.state of
       
   428     drwPoint: begin
       
   429                  tmpX:= drawingCtx.startCursorX - WorldDx;
       
   430                  tmpY:= cScreenHeight - drawingCtx.startCursorY - WorldDy;
       
   431                  vEffect:= AddVEffectCircle('', tmpX, tmpY);
       
   432                  if not isEffectEmpty(vEffect) then
       
   433                     SendIPCCircle(tmpX, tmpY);
       
   434                  drawingCtx.state:= drwStart;
       
   435               end;
       
   436     drwArrow: begin
       
   437                   tmpX2:= CursorPoint.X - WorldDx;
       
   438                   tmpY2:= cScreenHeight - CursorPoint.Y - WorldDy;
       
   439                   with drawingCtx do
       
   440                   begin
       
   441                      tmpX:= startCursorX - WorldDx;
       
   442                      tmpY:= cScreenHeight - startCursorY - WorldDy;
       
   443                      recalcArrowParams(currVEffect, tmpX, tmpY, tmpX2, tmpY2);
       
   444                      VEffectArrowStart(currVEffect);
       
   445                      SendIPCArrow(tmpX, tmpY, tmpX2, tmpY2);
       
   446                      currVEffect.gearsCount:= 0;
       
   447                   end;
       
   448                   drawingCtx.state:= drwStart;
       
   449                end;
       
   450 end;
       
   451 end;
       
   452 
       
   453 procedure onRightMouseButtonPressed();
       
   454 begin
       
   455    if not isDrawingModeActive() then
       
   456       exit;
       
   457    DeleteVEffect(drawingCtx.currVEffect);
       
   458    drawingCtx.state:= drwStart;
       
   459 end;
       
   460 
       
   461 procedure onMiddleMouseButtonPressed();
       
   462 begin
       
   463 end;
       
   464 
       
   465 procedure onCursorMoved();
       
   466 var tmpX, tmpY, tmpX2, tmpY2, dX, dY : LongInt;
       
   467    h                                 : real;
       
   468 begin
       
   469 if not isDrawingModeActive() then
       
   470    exit;
       
   471 autoCameraOn:= false;
       
   472 dX:= CursorPoint.X - drawingCtx.startCursorX;
       
   473 dY:= CursorPoint.Y - drawingCtx.startCursorY;
       
   474 h:= sqrt(dX * dX + dY * dY);
       
   475 if (drawingCtx.state <> drwStart) and (h > cMaxDrawingRadius) then
       
   476 begin
       
   477    CursorPoint.X:= drawingCtx.startCursorX + round(dX * cMaxDrawingRadius / h);
       
   478    CursorPoint.Y:= drawingCtx.startCursorY + round(dY * cMaxDrawingRadius / h);
       
   479 end;
       
   480 case drawingCtx.state of
       
   481     drwPoint : begin
       
   482                  if h > cPointRadius then
       
   483                  begin
       
   484                     tmpX:= drawingCtx.startCursorX - WorldDx;
       
   485                     tmpY:= cScreenHeight - drawingCtx.startCursorY - WorldDy;
       
   486                     tmpX2:= CursorPoint.X - WorldDx;
       
   487                     tmpY2:= cScreenHeight - CursorPoint.Y - WorldDy;
       
   488                     drawingCtx.currVEffect:= AddVEffectArrow('', tmpX, tmpY, tmpX2, tmpY2);
       
   489                     if not isEffectEmpty(drawingCtx.currVEffect) then
       
   490                        drawingCtx.state:= drwArrow
       
   491                     else
       
   492                        drawingCtx.state:= drwStart;
       
   493                  end;
       
   494               end;
       
   495     drwArrow : begin
       
   496                   tmpX:= drawingCtx.startCursorX - WorldDx;
       
   497                   tmpY:= cScreenHeight - drawingCtx.startCursorY - WorldDy;
       
   498                   tmpX2:= CursorPoint.X - WorldDx;
       
   499                   tmpY2:= cScreenHeight - CursorPoint.Y - WorldDy;
       
   500                   with drawingCtx do
       
   501                   begin
       
   502                      recalcArrowParams(currVEffect, tmpX, tmpY, tmpX2, tmpY2);
       
   503                   end;
       
   504                end;
       
   505 end;
       
   506 end;
       
   507 
       
   508 procedure initModule;
       
   509 begin
       
   510    with drawingCtx do
       
   511    begin
       
   512       state:= drwDisabled;
       
   513       currVEffect.gearsCount:= 0;
       
   514       startCursorX:= 0;
       
   515       startCursorY:= 0;
       
   516       knownUsersCount:= 0;
       
   517       lastReplacedUserIdx:= 0;
       
   518    end;
       
   519 end;
       
   520 
       
   521 procedure freeModule;
       
   522 begin
       
   523 end;
       
   524 
       
   525 end.