Don't enable GL_LINE_SMOOTH for AMD GPUs as a workround for lines and circles not being rendered correctly
(*
* Hedgewars, a free turn based strategy game
* Copyright (c) 2004-2015 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., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
*)
{$INCLUDE "options.inc"}
unit uDrawing;
(*
* This unit defines the Drawing mode, which allows drawing some graphics
* private to the members of a clan.
*)
interface
procedure initModule;
procedure freeModule;
function isDrawingModeActive(): boolean;
procedure onModeButtonPressed();
procedure onModeButtonReleased();
procedure onFocusStateChanged();
procedure onCursorMoved();
procedure onLeftMouseButtonPressed();
procedure onLeftMouseButtonReleased();
procedure onRightMouseButtonPressed();
procedure onMiddleMouseButtonPressed();
procedure handleIPCInput(cmd : shortstring);
implementation
uses uTypes, uConsts, uVariables, uVisualGearsList, uUtils, uDebug, uIO, SDLh, Math;
const
cColorsCount = 9;
cColors : array [0..cColorsCount - 1] of LongWord = (
$ff020400,
$4980c100,
$1de6ba00,
$b541ef00,
$e55bb000,
$20bf0000,
$fe8b0e00,
$8f590200,
$ffff0100
);
// Reserve one color for the local user
cKnownUsersMax = cColorsCount - 1;
cPointRadius = 25;
cBeaconDuration = 125;
cEffectDuration = 500;
cMaxDrawingRadius = 150;
cEffectGearsCountMax = 4;
type
TDrawingState = (drwDisabled, drwStart, drwPoint, drwArrow);
TVisualEffect = record
vGears : array [0..cEffectGearsCountMax - 1] of PVisualGear;
gearsCount : integer;
end;
TDrawingContext = record
state : TDrawingState;
currVEffect : TVisualEffect;
prevAutoCameraOn : boolean;
startCursorX : LongInt;
startCursorY : LongInt;
knownUsers : array [0..cKnownUsersMax - 1] of shortstring;
knownUsersCount : integer;
lastReplacedUserIdx : integer;
end;
var drawingCtx : TDrawingContext;
procedure AddKnownUser(user : shortstring);
var i : integer;
begin
with drawingCtx do
begin
for i:= 0 to knownUsersCount - 1 do
begin
if knownUsers[i] = user then
exit;
end;
if knownUsersCount < cKnownUsersMax then
begin
knownUsers[knownUsersCount]:= user;
Inc(knownUsersCount);
end
else
begin
lastReplacedUserIdx:= (lastReplacedUserIdx + 1) mod cKnownUsersMax;
knownUsers[lastReplacedUserIdx]:= user;
end;
end;
end;
function GetUserColor(user : shortstring) : LongWord;
var i : integer;
begin
if user = '' then // local user
exit(cColors[0]);
with drawingCtx do
begin
for i:= 0 to knownUsersCount - 1 do
begin
if knownUsers[i] = user then
begin
exit(cColors[i + 1]);
end;
end;
exit(cColors[0]);
end;
end;
procedure recalcArrowParams(var arrow: TVisualEffect; X1, Y1, X2, Y2 : real);
var tmp, tmpSin, tmpCos : real;
begin
with arrow.vGears[0]^ do
begin
X:= X1;
Y:= Y1;
dX:= X2;
dY:= Y2;
end;
// Compute arrow pointer coordinates
if X2 = X1 then
if Y2 > Y1 then
tmp:= PI / 2
else
tmp:= -PI / 2
else
tmp:= arctan2(Y2 - Y1, X2 - X1);
tmpSin:= sin(tmp - PI / 4);
tmpCos:= cos(tmp - PI / 4);
with arrow.vGears[1]^ do
begin
X:= X2;
Y:= Y2;
dX:= X2 - 50 * tmpCos;
dY:= Y2 - 50 * tmpSin;
end;
tmpSin:= sin(tmp + PI / 4);
tmpCos:= cos(tmp + PI / 4);
with arrow.vGears[2]^ do
begin
X:= X2;
Y:= Y2;
dX:= X2 - 50 * tmpCos;
dY:= Y2 - 50 * tmpSin;
end;
// Compute circle center
with arrow.vGears[3]^ do
begin
X:= (X1 + X2) / 2;
Y:= (Y1 + Y2) / 2;
end;
end;
procedure doStepPoint(Gear: PVisualGear; Steps: Longword);
var tmp: LongInt;
begin
if Gear^.FrameTicks <= Steps then
DeleteVisualGear(Gear)
else
begin
dec(Gear^.FrameTicks, Steps);
if Gear^.Tag = 0 then
begin
tmp:= round(Gear^.FrameTicks * $FF / cEffectDuration);
if tmp > $FF then
tmp:= $FF;
if tmp >= 0 then
Gear^.Tint:= (Gear^.Tint and $FFFFFF00) or Longword(tmp);
end
else if Gear^.Tag = 1 then
begin
Gear^.State:= round(Gear^.FrameTicks * 2048 / cBeaconDuration);
end;
end;
end;
procedure doStepArrow(Gear: PVisualGear; Steps: Longword);
var tmp: LongInt;
begin
if Gear^.Tag = 100 then
exit;
if Gear^.FrameTicks <= Steps then
DeleteVisualGear(Gear)
else
begin
dec(Gear^.FrameTicks, Steps);
if Gear^.Tag < 3 then
begin
tmp:= round(Gear^.FrameTicks * $FF / cEffectDuration);
if tmp > $FF then
tmp:= $FF;
if tmp >= 0 then
Gear^.Tint:= (Gear^.Tint and $FFFFFF00) or Longword(tmp);
end
else if Gear^.Tag = 3 then
begin
Gear^.State:= round(Gear^.FrameTicks * 2048 / cBeaconDuration);
end;
end;
end;
function isEffectEmpty(var vEffect : TVisualEffect) : boolean;
begin
isEffectEmpty:= vEffect.gearsCount = 0;
end;
function AddVEffectCircle(user : shortstring; X, Y : LongInt) : TVisualEffect;
var vGear : PVisualGear;
vEffect : TVisualEffect;
color : LongWord;
i : integer;
begin
color:= GetUserColor(user);
for i:= 0 to 1 do
begin
vGear := AddVisualGear(X, Y, vgtCircle, cPointRadius, true, 1);
if vGear = nil then
begin
OutError('uDrawing: AddVisualGear returned nil', false);
vEffect.gearsCount:= 0;
exit(vEffect);
end;
vGear^.Tint:= color or $FF;
vGear^.Angle:= 0;
vGear^.Timer:= 10;
vGear^.Tag:= i;
vGear^.doStep:= @doStepPoint;
vEffect.vGears[i]:= vGear;
end;
vEffect.vGears[0]^.Tint:= color or $FF;
vEffect.vGears[0]^.FrameTicks:= cEffectDuration;
vEffect.vGears[1]^.Tint:= color or $3F;
vEffect.vGears[1]^.FrameTicks:= cBeaconDuration;
vEffect.gearsCount:= 2;
AddVEffectCircle:= vEffect;
end;
function AddVEffectArrow(user : shortstring; X1, Y1, X2, Y2 : LongInt) : TVisualEffect;
var vGear : PVisualGear;
vEffect : TVisualEffect;
color : LongWord;
i : integer;
begin
color:= GetUserColor(user);
for i:= 0 to 2 do
begin
vGear := AddVisualGear(0, 0, vgtLine, 10, true, 1);
if vGear = nil then
begin
OutError('uDrawing: AddVisualGear returned nil', false);
vEffect.gearsCount:= 0;
exit(vEffect);
end;
vGear^.Tint:= color or $FF;
vGear^.FrameTicks:= cEffectDuration;
vGear^.Tag:= 100;
vGear^.doStep:= @doStepArrow;
vEffect.vGears[i]:= vGear;
end;
vGear := AddVisualGear(0, 0, vgtCircle, 2048, true, 1);
if vGear = nil then
begin
OutError('uDrawing: AddVisualGear returned nil', false);
vEffect.gearsCount:= 0;
exit(vEffect);
end;
vGear^.Tint:= color;
vGear^.Angle:= 0;
vGear^.FrameTicks:= cBeaconDuration;
vGear^.Timer:= 10;
vGear^.Tag:= 100;
vGear^.doStep:= @doStepArrow;
vEffect.vGears[3]:= vGear;
vEffect.gearsCount:= 4;
recalcArrowParams(vEffect, X1, Y1, X2, Y2);
AddVEffectArrow:= vEffect;
end;
procedure VEffectArrowStart(var vEffect : TVisualEffect);
var i : integer;
begin
for i:= 0 to vEffect.gearsCount - 1 do
vEffect.vGears[i]^.Tag:= i;
vEffect.vGears[3]^.Tint:= (vEffect.vGears[3]^.Tint and $FFFFFF00) or $3F;
end;
procedure DeleteVEffect(var vEffect : TVisualEffect);
var i : integer;
begin
for i:= 0 to vEffect.gearsCount - 1 do
DeleteVisualGear(vEffect.vGears[i]);
vEffect.gearsCount:= 0;
end;
function isDrawingModeActive() : boolean;
begin
isDrawingModeActive:= drawingCtx.state <> drwDisabled;
end;
procedure SendIPCArrow(X1, Y1, X2, Y2: LongInt);
var s: shortstring;
begin
s[0]:= #18;
s[1]:= 'O';
s[2]:= 'a';
SDLNet_Write32(X1, @s[3]);
SDLNet_Write32(Y1, @s[7]);
SDLNet_Write32(X2, @s[11]);
SDLNet_Write32(Y2, @s[15]);
SendIPC(s)
end;
procedure SendIPCCircle(X1, Y1: LongInt);
var s: shortstring;
begin
s[0]:= #10;
s[1]:= 'O';
s[2]:= 'c';
SDLNet_Write32(X1, @s[3]);
SDLNet_Write32(Y1, @s[7]);
SendIPC(s)
end;
procedure handleIPCInput(cmd: shortstring);
var i, drwCmdOffset : integer;
userNameLen : Byte;
user : shortstring;
X1, Y1, X2, Y2 : LongInt;
VEffect : TVisualEffect;
begin
case cmd[1] of
'u' : begin
userNameLen:= Byte(cmd[2]);
for i:= 0 to userNameLen do
user[i]:= cmd[2 + i];
drwCmdOffset:= 2 + userNameLen + 1;
if Length(cmd) < drwCmdOffset then
exit;
AddKnownUser(user);
case cmd[drwCmdOffset] of
'a' : begin
if Length(cmd) < drwCmdOffset + 4 * 4 then
exit;
X1:= SDLNet_Read32(@cmd[drwCmdOffset + 1]);
Y1:= SDLNet_Read32(@cmd[drwCmdOffset + 1 + 4]);
X2:= SDLNet_Read32(@cmd[drwCmdOffset + 1 + 8]);
Y2:= SDLNet_Read32(@cmd[drwCmdOffset + 1 + 12]);
VEffect:= AddVEffectArrow(user, X1, Y1, X2, Y2);
if not isEffectEmpty(VEffect) then
VEffectArrowStart(VEffect);
end;
'c' : begin
if Length(cmd) < drwCmdOffset + 4 * 2 then
exit;
X1:= SDLNet_Read32(@cmd[drwCmdOffset + 1]);
Y1:= SDLNet_Read32(@cmd[drwCmdOffset + 1 + 4]);
VEffect:= AddVEffectCircle(user, X1, Y1);
end;
end;
end;
end;
end;
procedure onModeButtonPressed();
begin
drawingCtx.state:= drwStart;
drawingCtx.prevAutoCameraOn:= autoCameraOn;
autoCameraOn:= false;
end;
procedure onModeButtonReleased();
begin
DeleteVEffect(drawingCtx.currVEffect);
if drawingCtx.state <> drwDisabled then
begin
drawingCtx.state:= drwDisabled;
autoCameraOn:= drawingCtx.prevAutoCameraOn;
end;
end;
procedure onFocusStateChanged();
begin
if not cHasFocus then
onModeButtonReleased();
end;
procedure onLeftMouseButtonPressed();
begin
if not isDrawingModeActive() then
exit;
case drawingCtx.state of
drwStart: begin
drawingCtx.startCursorX:= CursorPoint.X;
drawingCtx.startCursorY:= CursorPoint.Y;
drawingCtx.state:= drwPoint;
end;
end;
end;
procedure onLeftMouseButtonReleased();
var tmpX, tmpY, tmpX2, tmpY2 : LongInt;
vEffect : TVisualEffect;
begin
if not isDrawingModeActive() then
exit;
case drawingCtx.state of
drwPoint: begin
tmpX:= drawingCtx.startCursorX - WorldDx;
tmpY:= cScreenHeight - drawingCtx.startCursorY - WorldDy;
vEffect:= AddVEffectCircle('', tmpX, tmpY);
if not isEffectEmpty(vEffect) then
SendIPCCircle(tmpX, tmpY);
drawingCtx.state:= drwStart;
end;
drwArrow: begin
tmpX2:= CursorPoint.X - WorldDx;
tmpY2:= cScreenHeight - CursorPoint.Y - WorldDy;
with drawingCtx do
begin
tmpX:= startCursorX - WorldDx;
tmpY:= cScreenHeight - startCursorY - WorldDy;
recalcArrowParams(currVEffect, tmpX, tmpY, tmpX2, tmpY2);
VEffectArrowStart(currVEffect);
SendIPCArrow(tmpX, tmpY, tmpX2, tmpY2);
currVEffect.gearsCount:= 0;
end;
drawingCtx.state:= drwStart;
end;
end;
end;
procedure onRightMouseButtonPressed();
begin
if not isDrawingModeActive() then
exit;
DeleteVEffect(drawingCtx.currVEffect);
drawingCtx.state:= drwStart;
end;
procedure onMiddleMouseButtonPressed();
begin
end;
procedure onCursorMoved();
var tmpX, tmpY, tmpX2, tmpY2, dX, dY : LongInt;
h : real;
begin
if not isDrawingModeActive() then
exit;
autoCameraOn:= false;
dX:= CursorPoint.X - drawingCtx.startCursorX;
dY:= CursorPoint.Y - drawingCtx.startCursorY;
h:= sqrt(dX * dX + dY * dY);
if (drawingCtx.state <> drwStart) and (h > cMaxDrawingRadius) then
begin
CursorPoint.X:= drawingCtx.startCursorX + round(dX * cMaxDrawingRadius / h);
CursorPoint.Y:= drawingCtx.startCursorY + round(dY * cMaxDrawingRadius / h);
end;
case drawingCtx.state of
drwPoint : begin
if h > cPointRadius then
begin
tmpX:= drawingCtx.startCursorX - WorldDx;
tmpY:= cScreenHeight - drawingCtx.startCursorY - WorldDy;
tmpX2:= CursorPoint.X - WorldDx;
tmpY2:= cScreenHeight - CursorPoint.Y - WorldDy;
drawingCtx.currVEffect:= AddVEffectArrow('', tmpX, tmpY, tmpX2, tmpY2);
if not isEffectEmpty(drawingCtx.currVEffect) then
drawingCtx.state:= drwArrow
else
drawingCtx.state:= drwStart;
end;
end;
drwArrow : begin
tmpX:= drawingCtx.startCursorX - WorldDx;
tmpY:= cScreenHeight - drawingCtx.startCursorY - WorldDy;
tmpX2:= CursorPoint.X - WorldDx;
tmpY2:= cScreenHeight - CursorPoint.Y - WorldDy;
with drawingCtx do
begin
recalcArrowParams(currVEffect, tmpX, tmpY, tmpX2, tmpY2);
end;
end;
end;
end;
procedure initModule;
begin
with drawingCtx do
begin
state:= drwDisabled;
currVEffect.gearsCount:= 0;
startCursorX:= 0;
startCursorY:= 0;
knownUsersCount:= 0;
lastReplacedUserIdx:= 0;
end;
end;
procedure freeModule;
begin
end;
end.