Examples Delphi

DELPHI SOURCE FOR A DIY 'PICKING' AKA SELECTION FUNCTION
preamble:
some type definitions
type
TPoint3D = record
x: Real;
y: Real;
z: Real;
end;
TLine3D = record
endPt1: Integer;
endPt2: Integer;
attribute: String;
end;
TPtMatrix3D = array[0..MaxMPoints] of TPoint3D;
TWFrameLines = array[0..MaxMWFLines] of TLine3D;
end;
and some variable declarations
var
wFrameLines: TWFrameLines;
ptMatrix3D: TPtMatrix3D;
displayMatrix: TPtMatrix3D;
and then the gubbins code
function TWireFrame.GetLineNearestToClickPos(clickX, clickY: Integer): TLine3D;
{this is what's known in graphics programming circles as 'PICKING'...}
{code adapted from code by 'Kevin'...}
{This function is generically useful although in the program from which this
code has been lifted we have a three-layer heirarchy of data structures:
1) a 'display wireframe' [called here the displayMatrix] which is a projection
of 3D co-ordinate data from the 'reference wireframe' [see 2 below] onto the 2D
world of the screen 2) a 'reference wireframe' whose co-ordinate sets DON'T
undergo all of the transformations that we might apply to the display wireframe.
This reference wireframe [called ptMatrix in the bigger program from which
the function was taken] in turn will be a stripped-down data set using pretty
much exclusively COORDINATE data taken from the data structure on the bottom
layer, 3) the 'entity data' layer. See notes elsewhere for more detail, but
suffice it to say here that one of the beauties of this arrangement is that
we can ALSO use a 'wireFrameLines' array (where we keep track of INDEXES
into the two point matrices [for line endpoints]) and whatever changes we
make to our lines, in terms of length, angle, etc etc, we can still reference
the same entity on all three levels by virtue of the same index which remains
the same...}
const
MaxStoreSize = 10;
var
slope : Real;
XdoublePrime : Real;
YdoublePrime : Real;
dSquared : Real;
endPt1Idx : Integer;
endPt2Idx : Integer;
X1 : Double;
X2 : Double;
Y1 : Double;
Y2 : Double;
lineIdx : Integer;
distEndPt1Sqr: Real;
distEndPt2Sqr: Real;
lineDistSqr : Real;
begin
for lineIdx := 0 to WireFrame.LinesNum do
begin
endPt1Idx := WFrameLines[lineIdx].endpt1;
endPt2Idx := WFrameLines[lineIdx].endpt2;
X1 := DisplayMatrix[endPt1Idx].x;
Y1 := DisplayMatrix[endPt1Idx].y;
X2 := DisplayMatrix[endPt2Idx].x;
Y2 := DisplayMatrix[endPt2Idx].y;
lineDistSqr := (X2 - X1)*(X2 - X1)+(Y2 - Y1)*(Y2 - Y1);
distEndPt1Sqr := (ClickX - X1)*(ClickX - X1)+(ClickY - Y1)*(ClickY - Y1);
distEndPt2Sqr := (ClickX - X2)*(ClickX - X2)+(ClickY - Y2)*(ClickY - Y2);
if (distEndPt1Sqr > lineDistSqr) or (distEndPt2Sqr > lineDistSqr) then
begin
continue;
end;
if ((X2-X1) = 0) or ((Y2-Y1) = 0) then
begin
if ((X2-X1) = 0) then
begin
dSquared := (ClickX - X1)*(ClickX - X1);
end
else
begin
dSquared := (ClickY - Y1)*(ClickY - Y1);
end;
end
else
begin
slope := (Y2 - Y1) / (X2 - X1);
XdoublePrime := (ClickY - Y1 + slope * X1 + ClickX / slope) /
(slope + 1.0 / slope);
YdoublePrime := Y1 + slope * ((ClickY - Y1 + ClickX / slope - X1 / slope)/
(slope + 1.0 / slope));
dSquared := (ClickX - XdoublePrime) * (ClickX - XdoublePrime) +
(ClickY - YdoublePrime) * (ClickY - YdoublePrime);
end;
if (dSquared <= selectionRadius) then
begin
DisplaySelectionInfo(lineIdx);
end;
end;
end;
procedure TWireFrame.DisplaySelectionInfo(lineIndex: Integer);
var
tempString: String;
begin
if (WFrameLines[lineIndex].attribute = '') then
begin
tempString := 'Line index ' + IntToStr(lineIndex) + ' was clicked.' +
#13 + 'No attribute defined.';
Application.MessageBox(PChar(tempString), ' Selection', mb_OK);
end
else
begin
tempString := 'Line index ' + IntToStr(lineIndex) + ' was clicked.' +
#13 + 'Attribute: ' + WFrameLines[lineIndex].attribute + '.';
Application.MessageBox(PChar(tempString), ' Selection', mb_OK);
end;
end;
*************************************************************************
'PICKING' PSUEDOCODE
for each 'display wireframe' line
{get endPt1 x, y coordinates}
x1 := endPt1x;
y1 := endPt1y;
{get endPt2 x, y coordinates}
x2 := endPt2x;
y2 := endPt2y;
{the next three lines of code enable us to do the test in the subsequent line which
effectively filters out clicks on the EXTENSION of the line (ie past it's endpoints)
as otherwise the maths for 'lines' would deal with lines as though they go on for
ever...}
lineDistanceSquared := ((x2 - x1) * (x2 - x1)) + ((y2 - y1) * (y2 - y1))
distToEndPt1squared := ((xClick - x1) * (xClick - x1) + (yClick - y1) * (yClick - y1))
distToEndPt2squared := ((xClick - x2) * (xClick - x2) + (yClick - y2) * (yClick - y2))
if ((distToEndPt1squared > lineDistanceSquared) or
(distToEndPt2squared > lineDistanceSquared)) then
begin
{ignore the cases of i) lines of length zero ii) lines parallel to the x or y axis,
although actually it's necessary to implement these blocks of code to avoid the
computer having to deal with i) infinite values or ii) divide-by-zero operations}
{now find the slope of the current line, so we can use that in subsequent equations}
slope := ((y2 - y1) / (x2 - x1));
{calculate the coordinates of the point on the current wireframe line that is
perpendicular to the clicked point. The coordinates of this 'perpendicular' point
on the wireframe line will then be (xdoubleprime, ydoubleprime)}
Xdoubleprime := (yClick - y1 + (slope * x1) + (xClick / slope)) / (slope + (1.0 / slope));
Ydoubleprime := y1 + slope * ((yClick - y1 + (xClick / slope) - (x1 / slope)) / (slope + (1.0 / slope)));
dSquared := ((xClick - Xdoubleprime) * (xClick - Xdoubleprime) +
(yClick - Ydoubleprime) * (yClick - Ydoubleprime);
if (current dSquared < previous dSquared) then
begin
if current line is within a given radius from click point then
begin
store current value of dSquared
store current lineNumber as pickedLineIndex
[optionally handle mutiple lines within a given radius from click point]
end if
end if
end if
end for
use pickedLineIndex