我正在使用GLScene与Lazarus来渲染3D平面(而不是飞机) 3D世界中的2D位图。
我想实现让我移动对象的鼠标事件,这很容易完成。
但是,我很难弄清楚如何将对象完全保留在鼠标下面 在我点击它的完全相同的位置。
情景:
假设我有一个3D到2D像素尺寸为512x512像素的平面, 意思是即使对象本身是一个3D对象,它的位置和大小 以精确的2D坐标表示屏幕。
如果我在64x64的精确2D像素位置点击对象, 我怎样才能确保当我移动鼠标时,不仅移动了对象 但它的64x64像素位置也完全停留在鼠标下?
此外,无论离相机有多远,例如它的Z位置,怎么办呢?
答案 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.