【问题标题】:Moving 3D object with mouse and keeping it exactly under the mouse使用鼠标移动 3D 对象并将其保持在鼠标下方
【发布时间】:2014-08-09 16:36:55
【问题描述】:

我正在使用带有 Lazarus 的 GLScene 来渲染 3D 平面(不是飞机),它们将充当 3D 世界中的 2D 位图。

我想实现可以让我移动对象的鼠标事件,这很容易做到。

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

场景:

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

如果我在 64x64 的确切 2D 像素位置单击对象, 我怎样才能确保当我移动鼠标时,对象不仅被移动 但它的 64x64 像素位置也正好在鼠标下方?

另外,不管它离相机有多远,例如它的 Z 位置,如何做到这一点?

【问题讨论】:

  • 如果使用正交投影矩阵,则物体的 Z 值无关紧要(只要它们在相机前面)。
  • @ebbs 由于 GUI 经验的原因,我需要在我的应用程序中使用投影投影矩阵。我正在做的是一种 3D 世界和 2D 世界的混合体。
  • 好的。但是对于您应该使用预计值的职位,所以我想这并不重要。我建议您深入研究投影如何工作以及 3D 位置变化对 2D 平面变化意味着什么的理论。
  • @ebbs 好的。我发现了一个名为“ScreenVectorIntersectWithPlaneXY”的函数,我只是还不知道如何使用它。我还发现了一些其他人的工作,我可能会以此作为参考来解决这个问题。
  • 我玩过一个名为“Camera”的GLScene示例,它位于./samples/lazarus/interface/camera/,它在事件“procedure”中有一行代码TForm1.GLSceneViewer1MouseMove....”(位于第 65 行附近),上面写着“v := GLCamera1.ScreenDeltaToVectorXY(dx, -dy,0.12 * GLCamera1.DistanceToTarget / GLCamera1.FocalLength);” (位于第 94 行)。如果我将值“0.12”更改为“0.128”,我会得到一些看起来像我想要的东西,但是如果将对象移离相机更远,所需的移动结果将不再相同。

标签: opengl 3d mouseevent pascal lazarus


【解决方案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.

【讨论】:

  • 如果这段代码有问题,请告诉我。
猜你喜欢
  • 1970-01-01
  • 2013-11-04
  • 1970-01-01
  • 2020-09-28
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2013-09-14
相关资源
最近更新 更多