Opengl 使用鼠标移动三维对象并将其保持在鼠标下方

Opengl 使用鼠标移动三维对象并将其保持在鼠标下方,opengl,3d,mouseevent,pascal,lazarus,Opengl,3d,Mouseevent,Pascal,Lazarus,我正在使用GLScene和Lazarus渲染3D平面(不是飞机),这些平面将作为 3D世界中的2D位图 我想实现鼠标事件,让我移动对象,这很容易做到 然而,我很难弄清楚如何将对象精确地放在鼠标下 在我点击它的同一个位置 情景: 假设我有一个平面,从3D到2D像素大小为512x512像素, 这意味着即使对象本身是3D对象,它的位置和大小 以屏幕的精确二维坐标表示 如果我在64x64的精确2D像素位置单击对象, 如何确保在移动鼠标时,对象不仅被移动 但它的64x64像素位置也正好位于鼠标下方 此外,

我正在使用GLScene和Lazarus渲染3D平面(不是飞机),这些平面将作为 3D世界中的2D位图

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

然而,我很难弄清楚如何将对象精确地放在鼠标下 在我点击它的同一个位置

情景:

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

如果我在64x64的精确2D像素位置单击对象, 如何确保在移动鼠标时,对象不仅被移动 但它的64x64像素位置也正好位于鼠标下方


此外,无论距离相机有多远,例如它的Z位置,如何进行此操作?

编辑我已经更新了我的代码,并将完整的源代码粘贴到下面

首先,我必须弄清楚如何找到我最初在飞机上单击的位置

我在这里找到了解决方案:

一旦我能够在3D空间中读取该点,我就必须绘制“鼠标点击点”的地图 在两架飞机上

我称之为“MouseEventPlane”的第一个平面对用户不可见, 并用于绘制三维世界中鼠标光标的位置

第二个平面,我称之为“我的平面”,是我真正想要移动的平面

第一个平面始终与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.
单元1;
{有关组件设置的重要信息:
engine.objectsorting=none
mouseventplane必须完全透明(0 alpha=用户不可见)
并且必须始终与拾取对象的X轴对齐}
{$mode objfpc}{$H+}
接口
使用
类、SysUtils、FileUtil、窗体、控件、lclintf、图形、对话框、,
StdCtrls、ExtDlgs、ExtCtrls、GLLCLViewer、GLScene、GLObjects、GLMaterial、,
GLTexture、GLGraph、GLSLPostBlurShader、GLOutlineShader、GLSmoothNavigator、,
GLWindows、GLGui、GLCrossPlatform、GLColor、GLCoordinations、GLTextureFormat、VectorGeometry;
类型
TPoint3D=记录
十、 Y,Z:单个;
结束;
{TForm1}
TForm1=类(TForm)
添加图像:t按钮;
凸轮:TGLCamera;
bmp:TGLPlane;
引擎:TGLScene;
备忘录1:TMemo;
mouseEventPlane:TGLPlane;
GLSphere1:TGLSphere;
小组1:TPanel;
sc6:TScrollBar;
场景缩放:滚动条;
世界:TGLSceneViewer;
pod:TOpenPictureDialog;
sc1:TScrollBar;
sc2:TScrollBar;
sc3:TScrollBar;
sc4:TScrollBar;
sc5:TScrollBar;
程序addImageClick(发送方:ToObject);
过程表单创建(发送方:ToObject);
程序worldMouseDown(发送方:ToObject;按钮:TMouseButton;
移位:t移位状态;X,Y:整数);
过程worldMouseMove(发送方:ToObject;Shift:TShiftState;X,
Y:整数);
程序SC1变更(发送方:TObject);
程序worldMouseUp(发送方:ToObject;按钮:TMouseButton;
移位:t移位状态;X,Y:整数);
私有的
过程AddBitmap(文件名:字符串);
公众的
{公开声明}
结束;
变量
表1:TForm1;
位图:TGLPlane的数组;
layerToMove:TGLCustomSceneObject;
leftDown:布尔值;
点击点:TPoint3D;
实施
{$R*.lfm}
{TForm1}
函数点3D_到_向量(点3D:TPoint3D):TVector;
开始
结果.X:=点3d.X;
result.Y:=Point3D.Y;
结果.Z:=Point3D.Z;
结束;
功能点3D_至_坐标(点3D:TPoint3D):TGL坐标;
开始
结果.X:=点3d.X;
result.Y:=Point3D.Y;
结果.Z:=Point3D.Z;
结束;
函数Point3D_To_Str(Point3D:TPoint3D):字符串;
开始
结果:=inttostr(round(Point3D.X*10))+':'+inttostr(round(Point3D.Y*10))+':'+inttostr(round(Point3D.Z*10));
结束;
功能屏幕平面接口(世界:TGLSceneViewer;平面:TGLPlane;X,Y:整数):TPoint3D;
变量p0、p1、光线开始、光线向量、ipoint:TVector;
开始
{此函数将返回交点的坐标
在平面边界内发生。
此功能将自动拟合结果,以便无论
在三维空间中平面所在的位置,将显示结果
将表示为平面的中心为0x0。}
//获取摄影机附近的点(平面附近)
p0:=World.Buffer.ScreenToWorld(vectormake(x,World.height-y,0));
//在远平面上得到点
p1:=World.Buffer.ScreenToWorld(vectormake(x,World.height-y,1));
//使用光线投射的值
射线起始:=p0;
光线向量:=向量规格化(向量减法(p1,p0));
如果不是Plane.RayCastIntersect(光线开始、光线向量、@ipoint),则退出;
ipoint.X:=ipoint.X-平面位置.X;
i点Y:=i点Y平面位置Y;
ipoint.Z:=ipoint.Z平面位置.Z;
结果.X:=ipoint.X;
result.Y:=ipoint.Y;
结果.Z:=ipoint.Z;
结束;
过程TForm1.AddBitmap(文件名:字符串);
var-ms:整数;
开始
设置长度(位图,长度(位图)+1);
位图[长度(位图)-1]:=TGLPlane.Create(nil);
使用位图[长度(位图)-1]执行
开始
ms:=gettickcount;
Material.Texture.Image.LoadFromFile(pod.FileName);
ms:=gettickcount ms;
addImage.caption:=inttostr(毫秒);
//基本上,我们假设比例值1等于1000像素,
//所以我们把这两个值除以1000
Scale.X:=Material.Texture.Image.Width/1000;
Scale.Y:=Material.Texture.Image.Height/1000;
用材料做
开始
Texture.Enabled:=True;
混合模式:=bm透明度;
//Texture.TextureMode:=tmModulate;
FrontProperties.Diffuse.Alpha:=1;
纹理压缩:=tcHighSpeed;
结束;
结束;
AddChild(位图[长度(位图)-1]);
结束;
程序TForm1.SC1变更(发送方:TObject);
开始
Cam.SceneScale:=SceneScale.Position/100;
凸轮位置Z:=sc1位置