Тестовое задание 3dShapes – 2012 второй вариант

Продолжаем обсуждение тестовых заданий предлагаемых различными организациями для проверки уровня кандидатов. Это задние предлагается формой 3dShapes. На первый взгляд задание довольно простое. На форме необходимо рисовать треугольники, круги и квадраты, по клику на фигуре она начинает вращаться. Ничего сверх естественного , но при выборе конкретной реализации возникает многообразие как сие можно сделать. Данное задание было реализовано в двух видах.

1.            Реализация при помощи стандартных TShape, которые отрисовывались на PaintBox и вращались вокруг свое оси.

2.            При помощи функции канвы которая отрисовывает многоугольник. Каждая фигура при этом реализуется как многоугольник, а при установки ее на форму генерируется множество точек которым она будет представлена. Вращение при этом происходит в плоскости формы.

Рассмотрим подробно первый случай.

В первом случае задача решалась достаточно просто, что само по себе ни есть хорошо. Во втором случае реализация немного усложнялась тем что необходимо было полностью реализовывать отрисовку фигуры на поверхности, и ее вращение., а так же определение, того накакую фигуру была наведена мышка.

Во втором случае Не приятным было то что теперь не было у объекта процедурных свойств OnMouseMove,   OnMouseLeave, OnMouseDown. Пришлось реализовывать его с помощью метода площадей. Этот метод заключается в том, что в выпуклом многоугольнике, если точка лежит внутри него, то сумма площадей треугольников, которые образованный соседними парами его точек и точки которая лежит внутри него должна совпадать с площадью самого многоугольника. В противном случае точка не лежит внутри треугольника. Для того что бы вычислять площадь треугольника была выбрана формула Герона . Ее же использовал для вычисления площади основных размещаемых фигур: треугольника. квадрата, и круга. Правда от вычисления площади круга пришлось отказать, поскольку при его реализацией множеством точек, получались треугольники с очень малой площадью и точности вычислений не хватало. Пришлось вычислять расстояние от точки до центра круга, и сравнивать ее с радиусом круга. В ращение фигуры осуществлялось путем преобразования координат всех точек которые образовывали многоугольник. Матрицу при этом я не строил а сразу прописал результирующую формулу. Возможно, это было не оптимально с точки зрения  масштабирования проекта под 3D, но так уж было сделано. Был описан базовый класс многоугольной фигуры, а для частных случаев создавался класс наследник, в котором доопределялось все что необходимо для каждого конкретного случая.

Исходный код для второго случая. Скачать полный исходный код можно здесь.

unit uMyShapeEngine;
 
interface
uses Classes, Graphics, Windows, SysUtils, Dialogs, Math;
 
type
TMyPointArray = array of TPoint;
 
TMyShapeCommon = class
private
FWidth: Integer;
FTop: Integer;
FHeight: Integer;
FLeft: Integer;
FisRotate: Boolean;
FStepPhi: Extended;
FCurrentPhi: Extended;
procedure SetHeight(const Value: Integer);
procedure SetLeft(const Value: Integer);
procedure SetTop(const Value: Integer);
procedure SetWidth(const Value: Integer);
procedure SetisRotate(const Value: Boolean);
procedure SetCurrentPhi(const Value: Extended);
procedure SetStepPhi(const Value: Extended);
public
FPointList: TList;
FCanvas: TCanvas;
constructor Create;
destructor Destroy;override;
 
procedure Draw;virtual;
procedure AddPoint(X,Y: Integer);
 
function CalculateLengh(P1, P2: TPoint): Extended;
function CalculateSquare: Extended;
function CalculateTriangleSquare(P1, P2, P3 : TPoint): Extended;
procedure GeneratePointList(ATop, ALeft, AWidth, AHeight: Integer);virtual;
abstract;
procedure GeneratePoints;
function isOnShape(X, Y: Integer): Boolean;
function PointListToArray: TMyPointArray; virtual;
procedure Rotate;
property Height: Integer read FHeight write SetHeight;
property Left: Integer read FLeft write SetLeft;
property isRotate: Boolean read FisRotate write SetisRotate;
property CurrentPhi: Extended read FCurrentPhi write SetCurrentPhi;
property StepPhi: Extended read FStepPhi write SetStepPhi;
property Top: Integer read FTop write SetTop;
property Width: Integer read FWidth write SetWidth;
end;
 
TMyTriangle = class(TMyShapeCommon)
public
procedure GeneratePointList(ATop, ALeft, AWidth, AHeight: Integer);override;
end;
 
TMySquare = class (TMyShapeCommon)
public
procedure GeneratePointList(ATop, ALeft, AWidth, AHeight: Integer);override;
end;
 
TMySircle = class (TMyShapeCommon)
public
procedure GeneratePointList(ATop, ALeft, AWidth, AHeight: Integer);override;
end;
implementation
 
{ TMyShapeCommon }
 
procedure TMyShapeCommon.AddPoint(X, Y: Integer);
var
lPoint: PPoint;
begin
New(lPoint);
lPoint^.X:= X;
lPoint^.Y:= Y;
FPointList.Add(lPoint);
end;
 
function TMyShapeCommon.CalculateLengh(P1, P2: TPoint): Extended;
begin
Result:= RoundTo(Sqrt( (P1.X - P2.X)*(P1.X - P2.X) + (P1.Y - P2.Y)*(P1.Y - P2.Y) ), -6);
end;
 
function TMyShapeCommon.CalculateSquare: Extended;
var
lA, lB, lC: TPoint;
i: Integer;
begin
if FPointList.Count = 3  then begin
lA:= PPoint(FPointList[0])^;
lB:= PPoint(FPointList[1])^;
lC:= PPoint(FPointList[2])^;
Result:= CalculateTriangleSquare(lA, lB, lC);
end
else begin
lC.X:= Left + Width div 2;
lC.Y:= Top + Height div 2;
lA:= PPoint(FPointList[0])^;
lB:= PPoint(FPointList[FPointList.Count - 1])^;
Result:= CalculateTriangleSquare(lA, lB, lC);
for i:= 0 to FPointList.Count - 2 do
begin
lA:= PPoint(FPointList[i])^;
lB:= PPoint(FPointList[i+1])^;
Result:= Result+CalculateTriangleSquare(lA, lC,lB);
end;
end;
end;
 
function TMyShapeCommon.CalculateTriangleSquare(P1, P2, P3: TPoint): Extended;
var
a, b, c: Extended;
begin
a:= CalculateLengh(P1, P2);
b:= CalculateLengh(P2, P3);
c:= CalculateLengh(P3, P1);
//формала Герона
Result:= 0.25*Sqrt((a+b+c)*(a+b-c)*(b+c-a)*(a+c-b));
end;
 
constructor TMyShapeCommon.Create;
begin
FPointList:= TList.Create;
CurrentPhi:= 0;
StepPhi:= 0;
end;
 
destructor TMyShapeCommon.Destroy;
var
i: Integer;
lPoint: PPoint;
begin
for i:= 0 to FPointList.Count - 1 do
begin
lPoint:= FPointList[i];
Dispose(lPoint);
end;
FPointList.Free;
end;
 
procedure TMyShapeCommon.Draw;
var
lMyPointArray: TMyPointArray;
begin
if isRotate then
Rotate;
lMyPointArray:= PointListToArray;
FCanvas.Polygon(lMyPointArray);
end;
 
procedure TMyShapeCommon.GeneratePoints;
begin
GeneratePointList(Top, Left, Width, Height);
end;
 
function TMyShapeCommon.isOnShape(X, Y: Integer): Boolean;
var
lA, lB, lC: TPoint;
i: Integer;
lShape, lTemp: Extended;
lPoint1, lPoint2: TPoint;
begin
lTemp:= 0;
lShape:= 0;
if (X < Left) or (X > Left + Width) then begin
Result:= False;
Exit;
end;
if (Y < Top) or (Y > Top + Height) then begin
Result:= False;
Exit;
end;
 
 
if FPointList.Count = 3  then begin
lShape:= CalculateSquare;
lC.X:= X;
lC.Y:= Y;
 
lA:= PPoint(FPointList[0])^;
lB:= PPoint(FPointList[FPointList.Count - 1])^;
lTemp:= CalculateTriangleSquare(lA, lB, lC);
for i:= 0 to FPointList.Count - 2 do
begin
lA:= PPoint(FPointList[i])^;
lB:= PPoint(FPointList[i+1])^;
lTemp:= lTemp+CalculateTriangleSquare(lA, lB, lC);
end;
end;
if FPointList.Count = 4 then begin
lShape:= CalculateSquare;
lC.X:= X;
lC.Y:= Y;
lA:= PPoint(FPointList[0])^;
lB:= PPoint(FPointList[FPointList.Count - 1])^;
lTemp:= CalculateTriangleSquare(lA, lB, lC);
for i:= 0 to FPointList.Count - 2 do
begin
lA:= PPoint(FPointList[i])^;
lB:= PPoint(FPointList[i+1])^;
lTemp:= lTemp+CalculateTriangleSquare(lA, lB, lC);
end;
end;
if FPointList.Count > 4 then begin
lPoint1.X:= X;
lPoint1.Y:= Y;
lPoint2.X:= Left+Width div 2;
lPoint2.Y:= Top + Height div 2;
lTemp:= CalculateLengh(lPoint1, lPoint2);
lShape:= Height div 2;
end;
Result:= (Round(lTemp) <= Round(lShape));
end;
 
function TMyShapeCommon.PointListToArray: TMyPointArray;
var
lPoint: PPoint;
i: Integer;
begin
SetLength(Result, FPointList.Count);
for i:= 0 to FPointList.Count - 1 do
begin
lPoint:= FPointList[i];
Result[i]:= lPoint^;
end;
end;
 
procedure TMyShapeCommon.Rotate;
var
lPoint: PPoint;
i, lX, lY, X, Y: Integer;
begin
if FPointList.Count > 4 then Exit;
X:= Left+ Width div 2;
Y:= Top + Height div 2;
for i:= 0 to FPointList.Count - 1 do
begin
lPoint:= FPointList[i];
lX:= X + Round((lPoint^.X - X )* Cos(StepPhi) - (lPoint^.Y - Y) * Sin(StepPhi));
lY:= Y + Round((lPoint^.X - X )* Sin(StepPhi) + (lPoint^.Y - Y) * Cos(StepPhi));
lPoint^.X:= lX;
lPoint^.Y:= lY;
end;
end;
 
procedure TMyShapeCommon.SetCurrentPhi(const Value: Extended);
begin
FCurrentPhi := Value;
end;
 
procedure TMyShapeCommon.SetHeight(const Value: Integer);
begin
FHeight := Value;
end;
 
procedure TMyShapeCommon.SetisRotate(const Value: Boolean);
begin
FisRotate := Value;
end;
 
procedure TMyShapeCommon.SetLeft(const Value: Integer);
begin
FLeft := Value;
end;
 
procedure TMyShapeCommon.SetStepPhi(const Value: Extended);
begin
FStepPhi := Value;
end;
 
procedure TMyShapeCommon.SetTop(const Value: Integer);
begin
FTop := Value;
end;
 
procedure TMyShapeCommon.SetWidth(const Value: Integer);
begin
FWidth := Value;
end;
 
{ TMyTriangle }
 
procedure TMyTriangle.GeneratePointList(ATop, ALeft, AWidth, AHeight: Integer);
begin
{ AddPoint(ALeft+AWidth div 2, ATop);
AddPoint(ALeft+AWidth, ATop+ AHeight);
AddPoint(ALeft, ATop+ AHeight);}
AddPoint(ALeft, ATop+ AHeight);
AddPoint(ALeft+AWidth, ATop+ AHeight);
AddPoint(ALeft+AWidth div 2, ATop);
end;
 
{ TmySquare }
 
procedure TmySquare.GeneratePointList(ATop, ALeft, AWidth, AHeight: Integer);
begin
AddPoint(ALeft, ATop);
AddPoint(ALeft, ATop+AHeight);
AddPoint(ALeft+AWidth, ATop+AHeight);
AddPoint(ALeft+AWidth, ATop);
end;
 
{ TMySircle }
 
procedure TMySircle.GeneratePointList(ATop, ALeft, AWidth, AHeight: Integer);
var
i, lX, lY, lR, lX1, lX2: Integer;
begin
lR:= AWidth div 2;
lR:= lR*lR;
lX:= ALeft+AWidth div 2;
lY:= ATop+AHeight div 2;
for i:= AHeight + ATop downto ATop + 1 do
begin
lX1:= Round(-sqrt(lR-(i-lY)*(i-lY))+lX);
lX2:= Round(sqrt(lR-(i-lY)*(i-lY))+lX);
if lX1 > lX2 then
AddPoint(lX2,i)
else
AddPoint(lX1,i);
end;
for i:= ATop to AHeight + ATop - 1 do
begin
lX1:= Round(-sqrt(lR-(i-lY)*(i-lY))+lX);
lX2:= Round(sqrt(lR-(i-lY)*(i-lY))+lX);
if lX1 < lX2 then
AddPoint(lX2,i)
else
AddPoint(lX1,i);
end;
end;
 
end.

Share

Tags: , , ,

Leave a Reply