GXS.SceneViewer.pas 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564
  1. //
  2. // The graphics engine GXScene https://github.com/glscene
  3. //
  4. unit GXS.SceneViewer;
  5. (* Windows SceneViewer *)
  6. interface
  7. uses
  8. Winapi.OpenGL,
  9. Winapi.OpenGLext,
  10. GXS.OpenGL, // WGL_EXT_swap_control
  11. Winapi.Windows,
  12. WinApi.Messages,
  13. System.Classes,
  14. System.SysUtils,
  15. System.Types,
  16. FMX.Graphics,
  17. FMX.Forms,
  18. FMX.Controls,
  19. FMX.Dialogs.Win,
  20. FMX.Viewport3D,
  21. GXS.Scene,
  22. GXS.Context,
  23. GXS.WinContext;
  24. type
  25. TCreateParams = record
  26. Caption: PChar;
  27. Style: DWORD;
  28. ExStyle: DWORD;
  29. X, Y: Integer;
  30. Width, Height: Integer;
  31. //WndParent: HWnd; - not fmx
  32. Param: Pointer;
  33. //WindowClass: TWndClass; - not fmx
  34. WinClassName: array[0..63] of Char;
  35. end;
  36. TTouchEvent = procedure(X, Y, TouchWidth, TouchHeight : integer; TouchID : Cardinal; MultiTouch : boolean) of object;
  37. (* Component where the GLScene objects get rendered.
  38. This component delimits the area where OpenGL renders the scene,
  39. it represents the 3D scene viewed from a camera (specified in the
  40. camera property). This component can also render to a file or to a bitmap.
  41. It is primarily a windowed component, but it can handle full-screen
  42. operations : simply make this component fit the whole screen (use a
  43. borderless form).
  44. This viewer also allows to define rendering options such a fog, face culling,
  45. depth testing, etc. and can take care of framerate calculation. *)
  46. TgxSceneViewer = class(TViewPort3D)
  47. private
  48. FBuffer: TgxSceneBuffer;
  49. FVSync: TgxSyncMode;
  50. FOwnDC: HDC;
  51. FOnMouseEnter, FOnMouseLeave: TNotifyEvent;
  52. FMouseInControl: Boolean;
  53. FLastScreenPos: TPoint;
  54. FPenAsTouch: boolean;
  55. FOnTouchMove: TTouchEvent;
  56. FOnTouchUp: TTouchEvent;
  57. FOnTouchDown: TTouchEvent;
  58. procedure WMEraseBkgnd(var Message: TWMEraseBkgnd); message WM_ERASEBKGND;
  59. procedure WMPaint(var Message: TWMPaint); message WM_PAINT;
  60. procedure WMSize(var Message: TWMSize); message WM_SIZE;
  61. procedure WMGetDglCode(var Message: TMessage); message WM_GETDLGCODE;
  62. procedure WMDestroy(var Message: TWMDestroy); message WM_DESTROY;
  63. procedure WMTouch(var Message: TMessage); message WM_TOUCH;
  64. { TODO -oPW -cMessages : Convert message CM_MOUSEENTER to FMX }
  65. procedure CMMouseEnter(var msg: TMessage); ///message CM_MOUSEENTER;
  66. { TODO -oPW -cMessages : Convert message CM_MOUSELEAVE; to FMX }
  67. procedure CMMouseLeave(var msg: TMessage); ///message CM_MOUSELEAVE;
  68. function GetFieldOfView: single;
  69. procedure SetFieldOfView(const Value: single);
  70. function GetIsRenderingContextAvailable: Boolean;
  71. protected
  72. procedure SetBeforeRender(const val: TNotifyEvent);
  73. function GetBeforeRender: TNotifyEvent;
  74. procedure SetPostRender(const val: TNotifyEvent);
  75. function GetPostRender: TNotifyEvent;
  76. procedure SetAfterRender(const val: TNotifyEvent);
  77. function GetAfterRender: TNotifyEvent;
  78. procedure SetCamera(const val: TgxCamera);
  79. function GetCamera: TgxCamera;
  80. procedure SetBuffer(const val: TgxSceneBuffer);
  81. procedure CreateParams(var Params: TCreateParams); /// Vcl - override;
  82. procedure CreateWnd; /// Vcl - override;
  83. procedure DestroyWnd; /// Vcl - override;
  84. procedure Loaded; override;
  85. procedure DoBeforeRender(Sender: TObject); virtual;
  86. procedure DoBufferChange(Sender: TObject); virtual;
  87. procedure DoBufferStructuralChange(Sender: TObject); virtual;
  88. procedure MouseMove(Shift: TShiftState; X, Y: Integer); /// Vcl - override;
  89. public
  90. constructor Create(AOwner: TComponent); override;
  91. destructor Destroy; override;
  92. procedure Notification(AComponent: TComponent; Operation: TOperation); override;
  93. (* Makes TWinControl's RecreateWnd public.
  94. This procedure allows to work around limitations in some OpenGL
  95. drivers (like MS Software OpenGL) that are not able to share lists
  96. between RCs that already have display lists. *)
  97. procedure RecreateWnd;
  98. property IsRenderingContextAvailable: Boolean read GetIsRenderingContextAvailable;
  99. function LastFrameTime: Single;
  100. function FramesPerSecond: Single;
  101. function FramesPerSecondText(decimals: Integer = 1): string;
  102. procedure ResetPerformanceMonitor;
  103. function CreateSnapShotBitmap: TBitmap;
  104. procedure RegisterTouch;
  105. procedure UnregisterTouch;
  106. property RenderDC: HDC read FOwnDC;
  107. property MouseInControl: Boolean read FMouseInControl;
  108. published
  109. // Camera from which the scene is rendered.
  110. property Camera: TgxCamera read GetCamera write SetCamera;
  111. (* Specifies if the refresh should be synchronized with the VSync signal.
  112. If the underlying OpenGL ICD does not support the WGL_EXT_swap_control
  113. extension, this property is ignored. *)
  114. property VSync: TgxSyncMode read FVSync write FVSync default vsmNoSync;
  115. (* Triggered before the scene's objects get rendered.
  116. You may use this event to execute your own OpenGL rendering. *)
  117. property BeforeRender: TNotifyEvent read GetBeforeRender write SetBeforeRender;
  118. { Triggered just after all the scene's objects have been rendered.
  119. The OpenGL context is still active in this event, and you may use it
  120. to execute your own OpenGL rendering. }
  121. property PostRender: TNotifyEvent read GetPostRender write SetPostRender;
  122. (* Called after rendering.
  123. You cannot issue OpenGL calls in this event, if you want to do your own
  124. OpenGL stuff, use the PostRender event. *)
  125. property AfterRender: TNotifyEvent read GetAfterRender write SetAfterRender;
  126. // Access to buffer properties.
  127. property Buffer: TgxSceneBuffer read FBuffer write SetBuffer;
  128. (* Returns or sets the field of view for the viewer, in degrees.
  129. This value depends on the camera and the width and height of the scene.
  130. The value isn't persisted, if the width/height or camera.focallength is
  131. changed, FieldOfView is changed also. *)
  132. property FieldOfView: single read GetFieldOfView write SetFieldOfView;
  133. (* Since Windows 10 Fall Creators Update in 2017, pen also triggers
  134. touch input. You can set PenAsTouch = false to filter out such input. *)
  135. property PenAsTouch: boolean read FPenAsTouch write FPenAsTouch;
  136. property OnMouseLeave: TNotifyEvent read FOnMouseLeave write FOnMouseLeave;
  137. property OnMouseEnter: TNotifyEvent read FOnMouseEnter write FOnMouseEnter;
  138. property OnTouchMove: TTouchEvent read FOnTouchMove write FOnTouchMove;
  139. property OnTouchUp: TTouchEvent read FOnTouchUp write FOnTouchUp;
  140. property OnTouchDown: TTouchEvent read FOnTouchDown write FOnTouchDown;
  141. property Align;
  142. property Anchors;
  143. /// property DragCursor;
  144. property DragMode;
  145. property Enabled;
  146. /// property HelpContext; - Vcl
  147. property Hint;
  148. property PopupMenu;
  149. property Visible;
  150. property OnClick;
  151. property OnDblClick;
  152. property OnDragDrop;
  153. property OnDragOver;
  154. /// property OnStartDrag; - Vcl
  155. /// property OnEndDrag; - Vcl
  156. property OnMouseDown;
  157. property OnMouseMove;
  158. property OnMouseUp;
  159. property OnMouseWheel;
  160. /// property OnMouseWheelDown; - Vcl
  161. /// property OnMouseWheelUp; - Vcl
  162. property OnKeyDown;
  163. property OnKeyUp;
  164. /// property OnContextPopup; - Vcl
  165. property TabStop;
  166. property TabOrder;
  167. property OnEnter;
  168. property OnExit;
  169. property OnGesture;
  170. property Touch;
  171. end;
  172. procedure SetupVSync(const AVSyncMode : TgxSyncMode);
  173. var
  174. Handle: HWND;
  175. // ------------------------------------------------------------------
  176. implementation
  177. // ------------------------------------------------------------------
  178. // ------------------
  179. // ------------------ TgxSceneViewerFMX ------------------
  180. // ------------------
  181. procedure SetupVSync(const AVSyncMode : TgxSyncMode);
  182. var
  183. I: Integer;
  184. begin
  185. if WGL_EXT_swap_control then
  186. begin
  187. {! TODO
  188. I := wglGetSwapIntervalEXT;
  189. case AVSyncMode of
  190. vsmSync : if I <> 1 then wglSwapIntervalEXT(1);
  191. vsmNoSync: if I <> 0 then wglSwapIntervalEXT(0);
  192. else
  193. Assert(False);
  194. end;
  195. }
  196. end;
  197. end;
  198. constructor TgxSceneViewer.Create(AOwner: TComponent);
  199. begin
  200. inherited Create(AOwner);
  201. { TODO -oPW -cStyles : ContrilStyle in FMX }
  202. (* - todo Vcl styles convert to FMX styles
  203. ControlStyle := [csClickEvents, csDoubleClicks, csOpaque, csCaptureMouse];
  204. if csDesigning in ComponentState then
  205. ControlStyle := ControlStyle + [csFramed];
  206. *)
  207. Width := 100;
  208. Height := 100;
  209. FVSync := vsmNoSync;
  210. FBuffer := TgxSceneBuffer.Create(Self);
  211. FBuffer.ViewerBeforeRender := DoBeforeRender;
  212. FBuffer.OnChange := DoBufferChange;
  213. FBuffer.OnStructuralChange := DoBufferStructuralChange;
  214. end;
  215. destructor TgxSceneViewer.Destroy;
  216. begin
  217. FBuffer.Free;
  218. FBuffer := nil;
  219. inherited Destroy;
  220. end;
  221. procedure TgxSceneViewer.Notification(AComponent: TComponent; Operation: TOperation);
  222. begin
  223. if (Operation = opRemove) and (FBuffer <> nil) then
  224. begin
  225. if (AComponent = FBuffer.Camera) then
  226. FBuffer.Camera := nil;
  227. end;
  228. inherited;
  229. end;
  230. procedure TgxSceneViewer.RecreateWnd;
  231. begin
  232. inherited;
  233. end;
  234. procedure TgxSceneViewer.RegisterTouch;
  235. begin
  236. /// RegisterTouchWindow(Handle, 0); - Vcl Handle
  237. end;
  238. procedure TgxSceneViewer.SetBeforeRender(const val: TNotifyEvent);
  239. begin
  240. FBuffer.BeforeRender := val;
  241. end;
  242. function TgxSceneViewer.GetBeforeRender: TNotifyEvent;
  243. begin
  244. Result := FBuffer.BeforeRender;
  245. end;
  246. procedure TgxSceneViewer.SetPostRender(const val: TNotifyEvent);
  247. begin
  248. FBuffer.PostRender := val;
  249. end;
  250. procedure TgxSceneViewer.UnregisterTouch;
  251. begin
  252. /// UnregisterTouchWindow(Handle); - Vcl Handle
  253. end;
  254. function TgxSceneViewer.GetPostRender: TNotifyEvent;
  255. begin
  256. Result := FBuffer.PostRender;
  257. end;
  258. procedure TgxSceneViewer.SetAfterRender(const val: TNotifyEvent);
  259. begin
  260. FBuffer.AfterRender := val;
  261. end;
  262. function TgxSceneViewer.GetAfterRender: TNotifyEvent;
  263. begin
  264. Result := FBuffer.AfterRender;
  265. end;
  266. procedure TgxSceneViewer.SetCamera(const val: TgxCamera);
  267. begin
  268. FBuffer.Camera := val;
  269. end;
  270. function TgxSceneViewer.GetCamera: TgxCamera;
  271. begin
  272. Result := FBuffer.Camera;
  273. end;
  274. procedure TgxSceneViewer.SetBuffer(const val: TgxSceneBuffer);
  275. begin
  276. FBuffer.Assign(val);
  277. end;
  278. procedure TgxSceneViewer.CreateParams(var Params: TCreateParams);
  279. begin
  280. inherited; /// Vcl - inherited CreateParams(Params);
  281. with Params do
  282. begin
  283. Style := Style or WS_CLIPCHILDREN or WS_CLIPSIBLINGS;
  284. { TODO : E2003 Undeclared identifier: 'WindowClass' in FMX.Forms.TCreateParams}
  285. (*WindowClass.Style := WindowClass.Style or CS_OWNDC;*)
  286. end;
  287. end;
  288. procedure TgxSceneViewer.CreateWnd;
  289. begin
  290. inherited; /// Vcl - inherited CreateWnd;
  291. // initialize and activate the OpenGL rendering context
  292. // need to do this only once per window creation as we have a private DC
  293. FBuffer.Resize(0, 0, Round(Self.Width), Round(Self.Height));
  294. FOwnDC := GetDC(Handle);
  295. FBuffer.CreateRC(FOwnDC, False);
  296. end;
  297. procedure TgxSceneViewer.DestroyWnd;
  298. begin
  299. FBuffer.DestroyRC;
  300. if FOwnDC <> 0 then
  301. begin
  302. ReleaseDC(Handle, FOwnDC);
  303. FOwnDC := 0;
  304. end;
  305. inherited;
  306. end;
  307. procedure TgxSceneViewer.WMEraseBkgnd(var Message: TWMEraseBkgnd);
  308. begin
  309. if IsRenderingContextAvailable then
  310. Message.Result := 1
  311. else
  312. inherited;
  313. end;
  314. procedure TgxSceneViewer.WMSize(var Message: TWMSize);
  315. begin
  316. inherited;
  317. FBuffer.Resize(0, 0, Message.Width, Message.Height);
  318. end;
  319. procedure TgxSceneViewer.WMTouch(var Message: TMessage);
  320. function TouchPointToPoint(const TouchPoint: TTouchInput): TPoint;
  321. begin
  322. Result := Point(TOUCH_COORD_TO_PIXEL(TouchPoint.X), TOUCH_COORD_TO_PIXEL(TouchPoint.Y));
  323. PhysicalToLogicalPoint(Handle, Result);
  324. { TODO -oPW -cIncompatibility : Incompatible Handle and Result in FMX }
  325. ///Result:=ScreenToClient(Result);
  326. end;
  327. var
  328. TouchInputs: array of TTouchInput;
  329. TouchInput: TTouchInput;
  330. Handled: Boolean;
  331. Point: TPoint;
  332. Multitouch : boolean;
  333. begin
  334. Handled := False;
  335. SetLength(TouchInputs, Message.WParam);
  336. Multitouch := Message.WParam > 1;
  337. GetTouchInputInfo(Message.LParam, Message.WParam, @TouchInputs[0],
  338. SizeOf(TTouchInput));
  339. try
  340. for TouchInput in TouchInputs do
  341. begin
  342. Point := TouchPointToPoint(TouchInput);
  343. if (TouchInput.dwFlags AND TOUCHEVENTF_MOVE) > 0 then
  344. if Assigned(OnTouchMove) then
  345. begin
  346. OnTouchMove(Point.X, Point.Y, TouchInput.cxContact, TouchInput.cyContact, TouchInput.dwID, Multitouch);
  347. end;
  348. if (TouchInput.dwFlags AND TOUCHEVENTF_DOWN) > 0 then
  349. if Assigned(OnTouchDown) then
  350. begin
  351. OnTouchDown(Point.X, Point.Y, TouchInput.cxContact, TouchInput.cyContact, TouchInput.dwID, Multitouch);
  352. end;
  353. if (TouchInput.dwFlags AND TOUCHEVENTF_UP) > 0 then
  354. if Assigned(OnTouchUp) then
  355. begin
  356. OnTouchUp(Point.X, Point.Y, TouchInput.cxContact, TouchInput.cyContact, TouchInput.dwID, Multitouch);
  357. end;
  358. end;
  359. Handled := True;
  360. finally
  361. if Handled then
  362. CloseTouchInputHandle(Message.LParam)
  363. else
  364. inherited;
  365. end;
  366. end;
  367. procedure TgxSceneViewer.WMPaint(var Message: TWMPaint);
  368. var
  369. PS: TPaintStruct;
  370. p: TPoint;
  371. begin
  372. { TODO -oPW -cUnworkable : FMX.Forms.IFMXWindowService.ClientToScreen in FMX }
  373. /// p := ClientToScreen(Point(0, 0));
  374. if (FLastScreenPos.X <> p.X) or (FLastScreenPos.Y <> p.Y) then
  375. begin
  376. // Workaround for MS OpenGL "black borders" bug
  377. if FBuffer.RCInstantiated then
  378. { TODO -oPW -cUnworkable : Not applicable in FMX }
  379. /// PostMessage(Handle, WM_SIZE, SIZE_RESTORED, Width + (Height shl 16));
  380. FLastScreenPos := p;
  381. end;
  382. BeginPaint(Handle, PS);
  383. try
  384. if IsRenderingContextAvailable and (Width > 0) and (Height > 0) then
  385. FBuffer.Render;
  386. finally
  387. EndPaint(Handle, PS);
  388. Message.Result := 0;
  389. end;
  390. end;
  391. procedure TgxSceneViewer.WMGetDglCode(var Message: TMessage);
  392. begin
  393. Message.Result := Message.Result or DLGC_WANTARROWS;
  394. end;
  395. procedure TgxSceneViewer.WMDestroy(var Message: TWMDestroy);
  396. begin
  397. if Assigned(FBuffer) then
  398. begin
  399. FBuffer.DestroyRC;
  400. if FOwnDC <> 0 then
  401. begin
  402. ReleaseDC(Handle, FOwnDC);
  403. FOwnDC := 0;
  404. end;
  405. end;
  406. inherited;
  407. end;
  408. procedure TgxSceneViewer.CMMouseEnter(var msg: TMessage);
  409. begin
  410. inherited;
  411. FMouseInControl := True;
  412. if Assigned(FOnMouseEnter) then
  413. FOnMouseEnter(Self);
  414. end;
  415. procedure TgxSceneViewer.CMMouseLeave(var msg: TMessage);
  416. begin
  417. inherited;
  418. FMouseInControl := False;
  419. if Assigned(FOnMouseLeave) then
  420. FOnMouseLeave(Self);
  421. end;
  422. procedure TgxSceneViewer.Loaded;
  423. begin
  424. inherited Loaded;
  425. // initiate window creation
  426. { TODO -oPW -cUnworkable : HandleNeeded not found in FMX.Controls }
  427. ///HandleNeeded;
  428. end;
  429. procedure TgxSceneViewer.DoBeforeRender(Sender: TObject);
  430. begin
  431. SetupVSync(VSync);
  432. end;
  433. procedure TgxSceneViewer.DoBufferChange(Sender: TObject);
  434. begin
  435. if (not Buffer.Rendering) and (not Buffer.Freezed) then
  436. { TODO -oPW -cUnworkable : Invalidate not found in FMX.Controls }
  437. /// Invalidate;
  438. end;
  439. procedure TgxSceneViewer.DoBufferStructuralChange(Sender: TObject);
  440. begin
  441. RecreateWnd;
  442. end;
  443. procedure TgxSceneViewer.MouseMove(Shift: TShiftState; X, Y: Integer);
  444. begin
  445. { TODO -oPW -cIncompatibility : not found in FMX.Controls }
  446. /// inherited;
  447. /// if csDesignInteractive in ControlStyle then FBuffer.NotifyMouseMove(Shift, X, Y);
  448. end;
  449. function TgxSceneViewer.LastFrameTime: Single;
  450. begin
  451. Result := FBuffer.LastFrameTime;
  452. end;
  453. function TgxSceneViewer.FramesPerSecond: Single;
  454. begin
  455. Result := FBuffer.FramesPerSecond;
  456. end;
  457. function TgxSceneViewer.FramesPerSecondText(decimals: Integer = 1): string;
  458. begin
  459. Result := Format('%.*f FPS', [decimals, FBuffer.FramesPerSecond]);
  460. end;
  461. procedure TgxSceneViewer.ResetPerformanceMonitor;
  462. begin
  463. FBuffer.ResetPerformanceMonitor;
  464. end;
  465. function TgxSceneViewer.CreateSnapShotBitmap: TBitmap;
  466. begin
  467. Result := TBitmap.Create;
  468. { TODO -oPW -cIncompatibility : Find analog of pf24bit in FMX }
  469. /// Result.PixelFormat := pf24bit;
  470. Result.Width := Round(Width);
  471. Result.Height := Round(Height);
  472. { TODO -oPW -cUnworkable : Handle not found in FMX }
  473. /// BitBlt(Result.Canvas.Handle, 0, 0, Width, Height, RenderDC, 0, 0, SRCCOPY);
  474. end;
  475. function TgxSceneViewer.GetFieldOfView: single;
  476. begin
  477. if not Assigned(Camera) then
  478. result := 0
  479. else if Width < Height then
  480. result := Camera.GetFieldOfView(Width)
  481. else
  482. result := Camera.GetFieldOfView(Height);
  483. end;
  484. function TgxSceneViewer.GetIsRenderingContextAvailable: Boolean;
  485. begin
  486. Result := FBuffer.RCInstantiated and FBuffer.RenderingContext.IsValid;
  487. end;
  488. procedure TgxSceneViewer.SetFieldOfView(const Value: single);
  489. begin
  490. if Assigned(Camera) then
  491. begin
  492. if Width < Height then
  493. Camera.SetFieldOfView(Value, Width)
  494. else
  495. Camera.SetFieldOfView(Value, Height);
  496. end;
  497. end;
  498. // ------------------------------------------------------------------
  499. initialization
  500. // ------------------------------------------------------------------
  501. RegisterClass(TgxSceneViewer);
  502. end.