Recent

Author Topic: Elite/Oolite HUD display: to OpenGL or not to OpenGL?  (Read 517 times)

MarkMLl

  • Hero Member
  • *****
  • Posts: 8365
Elite/Oolite HUD display: to OpenGL or not to OpenGL?
« on: April 14, 2025, 09:19:55 pm »
That is the question. I initially assumed that OpenGL was the best choice for this, but now I'm not so sure.

I've been spinning my wheels over the last couple of weeks trying to work my way up the learning curve, but the more I get into it the more problems I encounter. One of the more significant ones is that while one would expect OpenGL to be the natural choice for 3D vector graphics, it turns out that many of the facilities which superficially appear to make it a good choice are deprecated: one is expected to set up custom shaders at multiple levels, and I feel that as far as Pascal support is concerned I'm heading for the bleeding edge.

Apart from the current unstaunched goriness, I'm worried that an operating system upgrade might completely remove even more "legacy" functionality, which would obviously break anything that I (or younger commercial successors) was trying to maintain.

Adding to that, OpenGL appears to not be good at rendering arbitrary glyphs and (small amounts of) text.

What I'm trying to do is render the red part of the attached image, with the ellipse representing a glide path at a nominal 3 degree angle. Now I could obviously draw the ellipse and distance markers on a plane and then set the viewpoint ("camera position") and possibly also the perspective and clipping ("frustum") appropriately, but I'd then have to work out the geometry of the flags ("poles" must be vertical) as an exception to that and do any annotation of what each represented (coloured glyph plus possibly text) "the hard way".

Taking those considerations into account, and in particular considering the good support of the LCL canvas for arbitrary text etc., I'm beginning to feel that OpenGL might be more trouble than it's worth.

I'd be very interested in anybody else's opinion.

MarkMLl
MT+86 & Turbo Pascal v1 on CCP/M-86, multitasking with LAN & graphics in 128Kb.
Logitech, TopSpeed & FTL Modula-2 on bare metal (Z80, '286 protected mode).
Pet hate: people who boast about the size and sophistication of their computer.
GitHub repositories: https://github.com/MarkMLl?tab=repositories

Guva

  • Full Member
  • ***
  • Posts: 162
  • 🌈 ZX-Spectrum !!!
Re: Elite/Oolite HUD display: to OpenGL or not to OpenGL?
« Reply #1 on: April 15, 2025, 04:27:59 am »
Maybe not quite on the topic, but it might come in handy.

Quote
Manually projecting 3d coordinates onto the 2d screen can be useful as seen above to label objects (although you can do a similar thing with billboards), it can also be used to create a kind of 3d radar effect (think Elite)

https://bedroomcoders.co.uk/posts/173

Rewritten pascal code, without shaders for a smaller size
Code: Pascal  [Select][+][-]
  1. program RaylibTest;
  2.  
  3. uses
  4.   SysUtils, Math,
  5.   raylib, raymath;
  6.  
  7. const
  8.   screenWidth = 1280;
  9.   screenHeight = 720;
  10.  
  11. type
  12.   TVector4 = record
  13.     x, y, z, w: Single;
  14.   end;
  15.  
  16.   PModel = ^TModel;
  17.  
  18.   TObj = record
  19.     index: Integer;
  20.     pos: TVector3;
  21.     proj: TVector4;
  22.     m: PModel;
  23.   end;
  24.  
  25. function Project(pos: TVector3; matView, matPerps: TMatrix): TVector4;
  26. var
  27.   temp: TVector4;
  28. begin
  29.   temp.x := matView.m0*pos.x + matView.m4*pos.y + matView.m8*pos.z + matView.m12;
  30.   temp.y := matView.m1*pos.x + matView.m5*pos.y + matView.m9*pos.z + matView.m13;
  31.   temp.z := matView.m2*pos.x + matView.m6*pos.y + matView.m10*pos.z + matView.m14;
  32.   temp.w := matView.m3*pos.x + matView.m7*pos.y + matView.m11*pos.z + matView.m15;
  33.  
  34.   Result.x := matPerps.m0*temp.x + matPerps.m4*temp.y + matPerps.m8*temp.z + matPerps.m12*temp.w;
  35.   Result.y := matPerps.m1*temp.x + matPerps.m5*temp.y + matPerps.m9*temp.z + matPerps.m13*temp.w;
  36.   Result.z := matPerps.m2*temp.x + matPerps.m6*temp.y + matPerps.m10*temp.z + matPerps.m14*temp.w;
  37.   Result.w := -temp.z;
  38.  
  39.   if Result.w <> 0.0 then
  40.   begin
  41.     Result.w := (1.0/Result.w)/0.75;  // TODO fudge of .75 WHY???
  42.     Result.x := Result.x * Result.w;
  43.     Result.y := Result.y * Result.w;
  44.     Result.z := Result.z * Result.w;
  45.   end;
  46. end;
  47.  
  48. var
  49.   camera: TCamera;
  50.   models: array[0..3] of TModel;
  51.   objs: array[0..7] of TObj;
  52.   basePos: array[0..7] of TVector3;
  53.   labels: array[0..7] of string;
  54.   mesh: TMesh;
  55.   //shader: TShader;
  56.   tex: TTexture;
  57.  
  58.   frame: Integer;
  59.   ang: TVector3;
  60.   togglePos, toggleRadar: Boolean;
  61.   view, perps: TMatrix;
  62.   aspect: Single;
  63.   i: Integer;
  64.   xi, mi: Single;
  65.   p: TVector3;
  66.   sorted: Boolean;
  67.   tmp: TObj;
  68.   hsw, hsh: Single;
  69.   l: Integer;
  70.   ft: string;
  71.   GenImage: TImage;
  72. begin
  73.   // Initialization
  74.   InitWindow(screenWidth, screenHeight, 'raylib - test');
  75.  
  76.   // Define the camera
  77.   camera.position := Vector3Create(0.0, 1.0, 0.0);
  78.   camera.target := Vector3Create(0.0, 0.0, 4.0);
  79.   camera.up := Vector3Create(0.0, 1.0, 0.0);
  80.   camera.fovy := 45.0;
  81.   camera.projection := CAMERA_PERSPECTIVE;
  82.  
  83.   // Initialize objects
  84.   for i := 0 to 7 do
  85.   begin
  86.     objs[i].pos := Vector3Create(Cos(0.7853982*i)*4.0, 0, Sin(0.7853982*i)*4.0);
  87.     if (i = 2) or (i = 6) then
  88.       objs[i].pos.y := objs[i].pos.y - 0.5;
  89.     basePos[i] := objs[i].pos;
  90.     objs[i].index := i;
  91.   end;
  92.  
  93.   // Labels
  94.   labels[0] := 'torus 1'; labels[1] := 'cube 1'; labels[2] := 'cylinder 1'; labels[3] := 'sphere 1';
  95.   labels[4] := 'torus 2'; labels[5] := 'cube 2'; labels[6] := 'cylinder 2'; labels[7] := 'sphere 2';
  96.  
  97.   // Create models
  98.   mesh := GenMeshTorus(0.3, 1, 16, 32);
  99.   models[0] := LoadModelFromMesh(mesh);
  100.   mesh := GenMeshCube(1, 1, 1);
  101.   models[1] := LoadModelFromMesh(mesh);
  102.   mesh := GenMeshCylinder(0.5, 1, 32);
  103.   models[2] := LoadModelFromMesh(mesh);
  104.   mesh := GenMeshSphere(0.5, 16, 32);
  105.   models[3] := LoadModelFromMesh(mesh);
  106.  
  107.   // Assign models to objects
  108.   objs[0].m := @models[0]; objs[4].m := @models[0];
  109.   objs[1].m := @models[1]; objs[5].m := @models[1];
  110.   objs[2].m := @models[2]; objs[6].m := @models[2];
  111.   objs[3].m := @models[3]; objs[7].m := @models[3];
  112.  
  113.  
  114.   GenImage := GenImageChecked(2, 2, 1, 1, RED, GREEN);
  115.   tex := LoadTextureFromImage(GenImage);
  116.   UnloadImage(GenImage);
  117.  
  118.   for i := 0 to 3 do
  119.   begin
  120.     models[i].materials[0].maps[MATERIAL_MAP_DIFFUSE].texture := tex;
  121.   end;
  122.  
  123.   // Initialize variables
  124.   frame := 0;
  125.   ang := Vector3Zero();
  126.   togglePos := True;
  127.   toggleRadar := False;
  128.  
  129.   SetTargetFPS(60);
  130.  
  131.   // Main game loop
  132.   while not WindowShouldClose() do
  133.   begin
  134.     // Update
  135.     frame := frame + 1;
  136.     ang.x := ang.x + 0.01;
  137.     ang.y := ang.y + 0.005;
  138.     ang.z := ang.z - 0.0025;
  139.  
  140.     // Rotate model
  141.     models[0].transform := MatrixRotateXYZ(ang);
  142.  
  143.     UpdateCamera(@camera, CAMERA_FIRST_PERSON);
  144.  
  145.     // Get matrices for projection
  146.     view := MatrixLookAt(camera.position, camera.target, camera.up);
  147.     aspect := screenWidth / screenHeight;
  148.     perps := MatrixPerspective(camera.fovy, aspect, 0.01, 1000.0);
  149.  
  150.     if IsKeyPressed(KEY_SPACE) then togglePos := not togglePos;
  151.     if IsKeyPressed(KEY_R) then toggleRadar := not toggleRadar;
  152.  
  153.     // Update object positions and projections
  154.     for i := 0 to 7 do
  155.     begin
  156.       objs[i].pos := basePos[objs[i].index];
  157.       xi := frame / 100.0;
  158.       if (objs[i].index mod 2) = 1 then xi := -xi;
  159.       mi := 0.25;
  160.       if objs[i].index = 0 then mi := 8;
  161.       objs[i].pos.x := objs[i].pos.x + Cos(xi) * mi;
  162.       objs[i].pos.z := objs[i].pos.z + Sin(xi) * mi;
  163.       p := objs[i].pos;
  164.       if togglePos then p.y := 1.0 else p.y := 0.0;
  165.       objs[i].proj := Project(p, view, perps);
  166.     end;
  167.  
  168.     // Sort objects by depth
  169.     sorted := False;
  170.     while not sorted do
  171.     begin
  172.       sorted := True;
  173.       for i := 0 to 6 do
  174.       begin
  175.         if objs[i].proj.w > objs[i+1].proj.w then
  176.         begin
  177.           sorted := False;
  178.           tmp := objs[i];
  179.           objs[i] := objs[i+1];
  180.           objs[i+1] := tmp;
  181.         end;
  182.       end;
  183.     end;
  184.  
  185.     // Draw
  186.     BeginDrawing();
  187.       ClearBackground(BLACK);
  188.  
  189.       BeginMode3D(camera);
  190.         // Draw models
  191.         for i := 0 to 7 do
  192.         begin
  193.           DrawModel(objs[i].m^, objs[i].pos, 1.0, WHITE);
  194.         end;
  195.         DrawGrid(10, 1.0);
  196.       EndMode3D();
  197.  
  198.       DrawFPS(10, 10);
  199.       DrawText('Space : toggle label position,  R : toggle 3d radar', 140, 10, 20, DARKGREEN);
  200.  
  201.       hsw := screenWidth / 2.0;
  202.       hsh := screenHeight / 2.0;
  203.  
  204.       for i := 0 to 7 do
  205.       begin
  206.         if objs[i].proj.w > 0 then
  207.         begin
  208.           // Draw label
  209.           l := MeasureText(Pchar(labels[objs[i].index]), 24);
  210.           DrawRectangle(Round(hsw + objs[i].proj.x * hsw - 4 - l/2),
  211.                         Round(hsh - objs[i].proj.y * hsh - 4),
  212.                         l + 8, 27, BLUE);
  213.           DrawText(PChar(labels[objs[i].index]),
  214.                    Round(hsw + objs[i].proj.x * hsw - l/2),
  215.                    Round(hsh - objs[i].proj.y * hsh), 24, WHITE);
  216.  
  217.           // Draw coordinates
  218.           ft := Format('%2.3f %2.3f', [objs[i].pos.x, objs[i].pos.z]);
  219.           l := MeasureText(PChar(ft), 24);
  220.           DrawRectangle(Round(hsw + objs[i].proj.x * hsw - 4 - l/2),
  221.                         Round(hsh - objs[i].proj.y * hsh - 4 + 27),
  222.                         l + 8, 27, BLUE);
  223.           DrawText(PChar(ft),
  224.                    Round(hsw + objs[i].proj.x * hsw - l/2),
  225.                    Round(hsh - objs[i].proj.y * hsh + 27), 24, WHITE);
  226.         end;
  227.  
  228.         if toggleRadar then
  229.         begin
  230.           // Draw radar
  231.           DrawCircle(Round(hsw + (objs[i].proj.x / objs[i].proj.w) * (hsw/32)),
  232.                      Round(screenHeight - (hsh/3) - (objs[i].proj.y / objs[i].proj.w) * (hsh/32)),
  233.                      3, RED);
  234.         end;
  235.       end;
  236.  
  237.       // Draw frame counter
  238.       ft := Format('Frame %d', [frame]);
  239.       l := MeasureText(PChar(ft), 20);
  240.       DrawRectangle(16, 698, l + 8, 42, BLUE);
  241.       DrawText(PChar(ft), 20, 700, 20, WHITE);
  242.     EndDrawing();
  243.   end;
  244.  
  245.   // Cleanup
  246.   for i := 0 to 3 do
  247.     UnloadModel(models[i]);
  248.   UnloadTexture(tex);
  249.   CloseWindow();
  250. end.
  251.  

MarkMLl

  • Hero Member
  • *****
  • Posts: 8365
Re: Elite/Oolite HUD display: to OpenGL or not to OpenGL?
« Reply #2 on: April 15, 2025, 10:13:21 am »
Thanks, I'll consider that.

From https://github.com/GuvaCode/Ray4Laz I note that it claims no external dependencies, so I presume that your advice is to step away from OpenGL before things get messy.

MarkMLl
MT+86 & Turbo Pascal v1 on CCP/M-86, multitasking with LAN & graphics in 128Kb.
Logitech, TopSpeed & FTL Modula-2 on bare metal (Z80, '286 protected mode).
Pet hate: people who boast about the size and sophistication of their computer.
GitHub repositories: https://github.com/MarkMLl?tab=repositories

MarkMLl

  • Hero Member
  • *****
  • Posts: 8365
Re: Elite/Oolite HUD display: to OpenGL or not to OpenGL?
« Reply #3 on: April 15, 2025, 07:48:25 pm »
I'm inclined to junk OpenGL and simply work on canvases.

If anybody has any strong opinions either way I'd be interested to hear them (come on Thaddy, you can never resist that :-)

MarkMLl
MT+86 & Turbo Pascal v1 on CCP/M-86, multitasking with LAN & graphics in 128Kb.
Logitech, TopSpeed & FTL Modula-2 on bare metal (Z80, '286 protected mode).
Pet hate: people who boast about the size and sophistication of their computer.
GitHub repositories: https://github.com/MarkMLl?tab=repositories

Seenkao

  • Hero Member
  • *****
  • Posts: 676
    • New ZenGL.
Re: Elite/Oolite HUD display: to OpenGL or not to OpenGL?
« Reply #4 on: April 15, 2025, 09:32:56 pm »
Тут всё зависит от того, какого конечного результата вы хотите добиться. Если вас интересует практика и понимание как работает 2D или 3D, то OpenGL очень неплохой выбор в этом случае. Используя его, вы сможете понять как саму 2D/3D графику, так и научиться делать достаточно неплохую графику.

Если же вам нужен результат и вы не хотите разбираться как устроена 2D/3D графика, то мало толку от любого контекста, будь то OpenGL, DX, Vulkan, Metal... В этом случае лучше брать готовые фреймворки/библиотеки которые делают за вас большую часть, а вы только используете функционал.

Так же есть третий вариант. Использовать OpenGL, но при этом просто взять подсмотреть готовые функции в фреймворках/библиотеках/движках.

Ну и напоминаю что, фреймворки/библиотеки/движки, зачастую, содержат в себе функционал больший чем просто работа с графикой.
---------------------------------------------------------------


Google translate:
Here it all depends on what final result you want to achieve. If you are interested in practice and understanding how 2D or 3D works, then OpenGL is a very good choice in this case. Using it, you will be able to understand both 2D/3D graphics itself and learn how to make pretty good graphics.

If you need a result and you do not want to understand how 2D/3D graphics are arranged, then there is little use in any context, be it OpenGL, DX, Vulkan, Metal... In this case, it is better to take ready-made frameworks/libraries that do most of the work for you, and you only use the functionality.

There is also a third option. Use OpenGL, but at the same time just take a look at ready-made functions in frameworks/libraries/engines.

Well, I remind you that frameworks/libraries/engines often contain functionality greater than just working with graphics. ;)
Rus: Стремлюсь к созданию минимальных и достаточно быстрых приложений.

Eng: I strive to create applications that are minimal and reasonably fast.
Working on ZenGL

MarkMLl

  • Hero Member
  • *****
  • Posts: 8365
Re: Elite/Oolite HUD display: to OpenGL or not to OpenGL?
« Reply #5 on: April 15, 2025, 10:01:08 pm »
Here it all depends on what final result you want to achieve. If you are interested in practice and understanding how 2D or 3D works, then OpenGL is a very good choice in this case. Using it, you will be able to understand both 2D/3D graphics itself and learn how to make pretty good graphics.

I'm not. I'm specifically interested in achieving that particular graphical presentation, which I know will resonate deeply with potential customers.

The technology used to achieve it was 8-bit home computers.

MarkMLl
MT+86 & Turbo Pascal v1 on CCP/M-86, multitasking with LAN & graphics in 128Kb.
Logitech, TopSpeed & FTL Modula-2 on bare metal (Z80, '286 protected mode).
Pet hate: people who boast about the size and sophistication of their computer.
GitHub repositories: https://github.com/MarkMLl?tab=repositories

 

TinyPortal © 2005-2018