使用鼠标移动3D对象并将其保持在鼠标下方

时间:2014-08-08 08:00:08

标签: opengl 3d mouseevent pascal lazarus

我正在使用GLScene与Lazarus来渲染3D平面(而不是飞机) 3D世界中的2D位图。

我想实现让我移动对象的鼠标事件,这很容易完成。

但是,我很难弄清楚如何将对象完全保留在鼠标下面 在我点击它的完全相同的位置。

情景:

假设我有一个3D到2D像素尺寸为512x512像素的平面, 意思是即使对象本身是一个3D对象,它的位置和大小 以精确的2D坐标表示屏幕。

如果我在64x64的精确2D像素位置点击对象, 我怎样才能确保当我移动鼠标时,不仅移动了对象 但它的64x64像素位置也完全停留在鼠标下?

此外,无论离相机有多远,例如它的Z位置,怎么办呢?

1 个答案:

答案 0 :(得分:1)

正在编辑!我已更新了我的代码,并且已将完整的来源粘贴到下方

首先,我必须弄清楚如何获得我最初在飞机上点击的位置。

我在这里找到了解决方案:http://glscene.sourceforge.net/wikka/StyleIndepenentRaycast

一旦我能在3D空间中读到这一点,我就不得不映射“鼠标点” 在两架飞机上。

我称之为“MouseEventPlane”的第一架飞机对用户不可见, 并用于在3D世界中绘制鼠标光标位置。

我称之为“myPlane”的第二架飞机是我实际想要移动的飞机。

第一个平面始终与myPlane的Z位置对齐。

以下是我的多层/飞机功能等:

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.