Reputation: 750
I am using GLScene with Lazarus to render 3D planes (not airplanes) that will act as 2D bitmaps in the 3D world.
I want to implement mouse events that will let me move objects, which can easily be done.
However, I'm having a hard time figuring out how to keep the object exactly under the mouse at the exact same position that I clicked it on.
Scenario:
Lets say I have a plane that is 512x512 pixels in size in 3D-to-2D pixel, meaning that even though the object itself is a 3D object, it's position and size is represented at exact 2D coordinates to the screen.
If I click on the object at the exact 2D pixel position of 64x64, how can I ensure that when I move the mouse, the object is not only moved but that it's 64x64 pixel position also stays exactly under the mouse?
Also, how can this be done regardless how far away it is from the camera, e.g it's Z position?
Upvotes: 1
Views: 2093
Reputation: 750
EDIT! I have updated my code, and I have pasted the full source down below
For a start, I had to figure out how to get the point of where I originally clicked on a plane.
I found that solution here: http://glscene.sourceforge.net/wikka/StyleIndepenentRaycast
Once I could read that point in 3D space, I had to map the "point of mouseclick" on two planes.
The first plane which I call "MouseEventPlane" is not visible to the user, and is used to map out the mouse cursor position in the 3D world.
The second plane which I call "myPlane" is the plane that I actually want to move.
The first plane is ALWAYS aligned to myPlane's Z position.
Below is my full sourse with multi-layer/plane capabilities etc:
unit Unit1;
{Important info about component settings:
engine.objectsorting = none
mouseventplane has to be completely transparent (0 alpha = not visible to the user)
and has to aalways aligned with the picked object's X axis}
{$mode objfpc}{$H+}
interface
uses
Classes, SysUtils, FileUtil, Forms, Controls, lclintf, Graphics, Dialogs,
StdCtrls, ExtDlgs, ExtCtrls, GLLCLViewer, GLScene, GLObjects, GLMaterial,
GLTexture, GLGraph, GLSLPostBlurShader, GLOutlineShader, GLSmoothNavigator,
GLWindows, GLGui, GLCrossPlatform, GLColor, GLCoordinates, GLTextureFormat, VectorGeometry;
type
TPoint3D = record
X,Y,Z: single;
end;
{ TForm1 }
TForm1 = class(TForm)
addImage: TButton;
Cam: TGLCamera;
bmp: TGLPlane;
engine: TGLScene;
Memo1: TMemo;
mouseEventPlane: TGLPlane;
GLSphere1: TGLSphere;
Panel1: TPanel;
sc6: TScrollBar;
sceneScale: TScrollBar;
world: TGLSceneViewer;
pod: TOpenPictureDialog;
sc1: TScrollBar;
sc2: TScrollBar;
sc3: TScrollBar;
sc4: TScrollBar;
sc5: TScrollBar;
procedure addImageClick(Sender: TObject);
procedure FormCreate(Sender: TObject);
procedure worldMouseDown(Sender: TObject; Button: TMouseButton;
Shift: TShiftState; X, Y: Integer);
procedure worldMouseMove(Sender: TObject; Shift: TShiftState; X,
Y: Integer);
procedure sc1Change(Sender: TObject);
procedure worldMouseUp(Sender: TObject; Button: TMouseButton;
Shift: TShiftState; X, Y: Integer);
private
procedure AddBitmap(AFilename: string);
public
{ public declarations }
end;
var
Form1: TForm1;
bitmaps: array of TGLPlane;
layerToMove: TGLCustomSceneObject;
leftDown: boolean;
PointOfClick: TPoint3D;
implementation
{$R *.lfm}
{ TForm1 }
function Point3D_to_Vector(Point3D: TPoint3D): TVector;
begin
result.X := Point3D.X;
result.Y := Point3D.Y;
result.Z := Point3D.Z;
end;
function Point3D_to_GLCoordinates(Point3D: TPoint3D): TGLCoordinates;
begin
result.X := Point3D.X;
result.Y := Point3D.Y;
result.Z := Point3D.Z;
end;
function Point3D_To_Str(Point3D: TPoint3D): string;
begin
result := inttostr(round(Point3D.X*10))+':'+inttostr(round(Point3D.Y*10))+':'+inttostr(round(Point3D.Z*10));
end;
function ScreenToPlaneIntersect(World: TGLSceneViewer; Plane: TGLPlane; X,Y: integer): TPoint3D;
var p0, p1, raystart, rayvector, ipoint: TVector;
begin
{This function will return the coordinates of the point of intersection
occurs within a plane boundaries.
This function will automatically fit the results so that no matter
where in the 3D space the plane is located, the results
will be represented as if the plane's center is 0x0x0. }
//get the point near the camera (near plane)
p0:=World.Buffer.ScreenToWorld(vectormake(x, World.height-y, 0));
//get the point on the far plane
p1 := World.Buffer.ScreenToWorld(vectormake(x, World.height-y, 1));
//Use the values for raycasting
raystart := p0;
rayvector := vectornormalize(vectorsubtract(p1,p0));
if not Plane.RayCastIntersect(raystart, rayvector, @ipoint) then exit;
ipoint.X := ipoint.X-Plane.position.X;
ipoint.Y := ipoint.Y-Plane.position.Y;
ipoint.Z := ipoint.Z-Plane.position.Z;
result.X := ipoint.X;
result.Y := ipoint.Y;
result.Z := ipoint.Z;
end;
procedure TForm1.AddBitmap(AFilename: string);
var ms: integer;
begin
setlength(bitmaps, length(bitmaps)+1);
bitmaps[length(bitmaps)-1] := TGLPlane.Create(nil);
with bitmaps[length(bitmaps)-1] do
begin
ms := gettickcount;
Material.Texture.Image.LoadFromFile(pod.FileName);
ms := gettickcount-ms;
addImage.caption := inttostr(ms);
//basically, we assume that scale value 1 is equal to 1000 pixels,
//so we just divide the two values by 1000
Scale.X := Material.Texture.Image.Width/1000;
Scale.Y := Material.Texture.Image.Height/1000;
with Material do
begin
Texture.Enabled := True;
BlendingMode := bmTransparency;
//Texture.TextureMode:=tmModulate;
FrontProperties.Diffuse.Alpha := 1;
Texture.Compression := tcHighSpeed;
end;
end;
engine.Objects.AddChild(bitmaps[length(bitmaps)-1]);
end;
procedure TForm1.sc1Change(Sender: TObject);
begin
Cam.SceneScale := sceneScale.Position / 100;
Cam.Position.Z := sc1.Position / 100;
Cam.Position.X := sc2.Position / 100;
Cam.Position.Y := sc3.Position / 100;
if length(bitmaps) = 0 then exit;
bitmaps[length(bitmaps)-1].PitchAngle := sc4.Position/100;
bitmaps[length(bitmaps)-1].Material.FrontProperties.Diffuse.Alpha := sc5.Position / 100;
end;
procedure TForm1.worldMouseUp(Sender: TObject; Button: TMouseButton; Shift: TShiftState; X, Y: Integer);
var p3: TPoint3D;
begin
leftDown := false;
end;
procedure TForm1.addImageClick(Sender: TObject);
var i: integer;
begin
pod.execute;
if pod.Files.count > 0 then
for i := 0 to pod.files.Count-1 do AddBitmap(pod.files.Strings[i]);
end;
procedure TForm1.FormCreate(Sender: TObject);
begin
mouseEventPlane.Material.FrontProperties.Diffuse.Alpha := 0;
end;
procedure TForm1.worldMouseDown(Sender: TObject; Button: TMouseButton;
Shift: TShiftState; X, Y: Integer);
begin
layerToMove := (world.Buffer.GetPickedObject(x, y) as TGLCustomSceneObject);
if (layerToMove.ToString <> 'TGLPlane') or (layerToMove.name = 'mouseEventPlane') then exit;
engine.Objects.MoveChildLast(engine.Objects.IndexOfChild(layerToMove)); //Will move a myPlane to the top of ther paint order
pointofclick := ScreenToPlaneIntersect(world, (layerToMove as TGLPlane), X, Y);
leftDown := true;
end;
procedure TForm1.worldMouseMove(Sender: TObject; Shift: TShiftState;X, Y: Integer);
var p3: tpoint3d;
begin
if leftDown=true then
begin
p3 := ScreenToPlaneIntersect(world, mouseEventPlane, x, y);
layerToMove.Position.X := p3.X - pointofclick.X;
layerToMove.Position.Y := p3.Y - pointofclick.Y;
end;
end;
end.
Upvotes: 1