The math looks of the projection sound and correct, didn't check line by line but one small mistake will not render correct, also it is fine and clean (impressive to be honest).
Now to the point about TPaintBox or not, i always like and prefer TPaintBox, it is the most lightweight control to draw for such use, the portability is ensured as you only
handle its TCanvas, so moving the code to draw may on a TPanel is merely changing one line.
Suggestions :
1) Refactor this rendered into its own class, such class might accept a Canvas as input, or even better it will create own Canvas, of course you can use TBitmap but TBitmap suffer from some un-needed overhead handling, (too many
API to
OS DIB...etc), in general TCanvas is faster, by having its own class you can have something like folder view that generates thumbnails to browse folder of projects.
2) Separating the logic (in this case the rendered) from the form will allow you utilize threads for heavy projects, also will great enhance maintainability of the code.
3) When you refactor it you can have your own ViewPort (and its dimensions) similar to Direct2D or
HTML rendered..., meaning you are free to work on UI to enhance mouse control the view like dragging by mouse right click and zoom by mouse wheel... , reset to center button ...
4) In general handling TFormatSettings is very slow, i mean really slow, because it is big structure that involves many system functions, replacing TFormatSettings.Invariant to be called once does enhance the performance.
Delphi-Quellcode:
..
private
FSegs: TList<TSegment>;
XOffset: Integer;
YOffset: Integer;
FAngX, FAngY, FAngZ: Double;
FScale: Double;
FLastPos: TPoint;
FRotating: Boolean;
FInvariantFS: TFormatSettings; // <---
procedure LoadGCode(const AFile: string);
function Project(const V: TVec3): TPoint;
procedure AutoFit;
..
procedure TfrmGCodeViewer.FormCreate(Sender: TObject);
begin
FInvariantFS := TFormatSettings.Invariant;
DoubleBuffered := True;
..
function ExtractFloat(tag: Char; const s: string; var v: Double): Boolean;
var
p, j: Integer;
num: string;
//FS: TFormatSettings;
begin
..
num := Copy(s, p, j - p);
//FS := TFormatSettings.Invariant;
v := StrToFloatDef(num, 0, FInvariantFS);
end;
5) if you went a head and made it into own renderer class, then you can utilize background thread, use step cache for the last one or two render hence you can removing locking, while the main form can use timer and check for changes and render the last one, in other words remove locking, cache for the last 1 or 2 could be as simple as lock-free ring over 3 items.
6) With its own canvas to render, you can have simulation for the header NC header ( laser it be or drill...) like a red circle, in this case the renderer will utilize its own cache and save the small square to store the portion where is the header then replace the last one, this is very efficient when the ViewPort is not moving or adjusted, we only need to move a red circle and by caching the small square you skip the render in full, while the TPaintBox with timer at 100ms will have 10FPS for moving header, this was just an example.