Skip to menu

Robotics with Object Pascal

Rover written with RUST

Pascal UDS Server

2025.11.11 04:36

me Views:30

Now, my RUST program generates around 3,000 of 3d points of data clouds around every 0.2 seconds.

Now, that data is saved in as RAM folder as /dev/shm/dots.bin file.   Right after the file is written, my RUST program will send out a character 'R' to server as "Ready".   Then, my UDS server will quickly read the file, then send back 'D' as "Done".   This message method prevents overwriting of the data file while reading it.   This is typical synchronization problem for IPC.

 

-----------------------

After implementation 

 

Pas_UDS_Server_Running.png

 

 

It has been a long journey, but you now have a very powerful, modular system:

  1. High-Speed Engine (Rust): A highly optimized, multi-threaded visual odometry engine that runs at a stable 5 FPS, handles all the complex computer vision math, and outputs raw, clean data.

  2. Robust IPC (UDS): A reliable Unix Domain Socket pipeline that uses a handshake protocol (Ready -> Done) to ensure zero data loss and zero tearing, all while using shared memory speeds.

  3. Flexible Viewer (Pascal): A Pascal server that sits and waits for data, ready for you to build your custom visualization logic on top of it.

You have successfully decoupled the "math" from the "view," which will make your future development much easier. You can now restart the Rust engine endlessly without crashing your viewer, and you can tweak your viewer's OpenGL code without stopping the robot.

If you need help setting up the OpenGL rendering in Lazarus/Pascal to actually see those dots in 3D (instead of just printing text), feel free to ask. Otherwise, congratulations on getting this pipeline stable!

 

=================================================
 

To use BGRABitmap instead of OpenGL for  headless robot :
 
This is a very pragmatic decision. BGRABitmap is excellent for this scenario.

Since BGRABitmap runs entirely on the CPU (software rasterization), it has zero dependencies on a GPU, X Server, or OpenGL context. It will never crash because of a missing monitor. It simply manipulates bytes in RAM, which is exactly what you need for a headless robot.

Since we aren't using OpenGL, we have to calculate the 3D-to-2D Projection manually. This is actually quite simple: it's just high-school trigonometry (similar triangles).

Here is the math we will use:

  1. Shift: Move the point based on where we want the "Camera" to be.

  2. Project: ScreenX = (PointX / PointZ) * Zoom.

Here is the Pascal code to render your dots.bin data onto a BGRABitmap.

 

Pascal Rendering Unit (VoRender.pas)

 

You can add this function to your Pascal project. It takes your TPointCloud array and paints it onto a bitmap.

 

uses
  BGRABitmap, BGRABitmapTypes, Math;

// Matches your Rust data
type
  TPoint3D = packed record
    X: Int32;
    Y: Int32;
    Z: Int32;
  end;
  TPointCloud = array of TPoint3D;

const
  // --- VIRTUAL CAMERA SETTINGS ---
  // We place the camera High (-500 Y) and Behind (-1000 Z) the origin
  // to look "Down and Forward" at the data.
  CAM_X = 0;
  CAM_Y = -500; 
  CAM_Z = -1000; 
  
  FOCAL_LENGTH = 600; // Acts like "Zoom"

procedure RenderCloudToBitmap(Cloud: TPointCloud; TargetBmp: TBGRABitmap);
var
  i: Integer;
  WX, WY, WZ: Integer; // World Coordinates (shifted)
  SX, SY: Integer;     // Screen Coordinates
  CenterX, CenterY: Integer;
  DepthColor: Byte;
  P: PBGRAPixel;
begin
  // 1. Clear Background (Black)
  TargetBmp.Fill(BGRA(0, 0, 0, 255));

  CenterX := TargetBmp.Width div 2;
  CenterY := TargetBmp.Height div 2;

  // 2. Iterate over all points
  for i := 0 to High(Cloud) do
  begin
    // --- A. CAMERA TRANSFORM ---
    // Shift the world so it is relative to our camera position
    WX := Cloud[i].X - CAM_X;
    WY := Cloud[i].Y - CAM_Y;
    WZ := Cloud[i].Z - CAM_Z;

    // prevent division by zero or drawing points behind us
    if WZ <= 10 then Continue; 

    // --- B. PERSPECTIVE PROJECTION ---
    // The fundamental formula: Screen = (World / Depth) * FocalLength
    SX := CenterX + (WX * FOCAL_LENGTH) div WZ;
    SY := CenterY + (WY * FOCAL_LENGTH) div WZ;

    // --- C. DRAWING ---
    // Check bounds
    if (SX >= 0) and (SX < TargetBmp.Width) and 
       (SY >= 0) and (SY < TargetBmp.Height) then
    begin
      // Depth Shading: Make close points bright, far points dark
      // Map Z (e.g. 1000 to 5000) to Brightness (255 to 50)
      DepthColor := EnsureRange(255 - (WZ div 30), 50, 255);
      
      // Set the pixel (White/Gray based on depth)
      TargetBmp.SetPixel(SX, SY, BGRA(DepthColor, DepthColor, DepthColor, 255));
    end;
  end;
end;

 

How to Integrate into your Server

 

In your RunUdsServer loop, right after you load the points:

  1. Create a global MyBitmap := TBGRABitmap.Create(800, 600);

  2. Call RenderCloudToBitmap(MyCloud, MyBitmap);

  3. Save or Transmit MyBitmap.

Example snippet for the inner loop:

 

if Cmd = 'R' then
        begin
          // 1. Load Data
          LoadPointCloud(SHM_FILE_PATH, MyCloud);

          // 2. Render to Memory (CPU only, no OpenGL)
          RenderCloudToBitmap(MyCloud, MyBitmap);

          // 3. Visualize (Since you are headless)
          // Option A: Save to file for checking
          // MyBitmap.SaveToFile('/dev/shm/render_debug.bmp');
          
          // Option B: If running VNC, draw to a Form's Image
          // MyBitmap.Draw(Form1.Image1.Canvas, 0, 0);

          // 4. Handshake Done
          Cmd := 'D';
          fpSend(ClientHandle, @Cmd, 1, 0);
        end;

 

This approach is 100% headless safe. It treats the 3D rendering purely as a math problem, processing arrays of integers into pixels in RAM.