Mega Code Archive

 
Categories / Delphi / Examples
 

Pickingfunction

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