xaid
xaid

Reputation: 750

Moving 3D object with mouse and keeping it exactly under the mouse

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

Answers (1)

xaid
xaid

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

Related Questions