Documentation
Surfaces
Graphs
const f = function(const u, v: Double): Double begin const r = Sqrt(u*u + v*v); Result := 10 * if IsZero(r) then 1.0 else Sin(r) / r; end; var LSurf := FVisCtl3D.NewObject<TCustomSurface>; LSurf.SurfaceFunction := function(const u, v: Double): rglv begin Result.x := u; Result.y := v; Result.z := f(u, v); end; LSurf.Domain := TRectDom.Create(-10, 10, -10, 10); LSurf.Color := clRed;
Or, with parameter curves:
LSurf.ShowParameterCurves := True;
You can customise the number of parameter curves and their visual attributes, such as their colour:
LSurf.ShowParameterCurves := True; LSurf.ParamCurveCounts := TParamCurveFamilySize.Create(128, 128); LSurf.LineColor := clWebGold;
You can choose to display the parameter curves alone:
LSurf.ShowParameterCurves := True; LSurf.ParamCurveCounts := TParamCurveFamilySize.Create(128, 128); LSurf.LineColor := clNavy; LSurf.ShowSurface := False;
Coloured graphs
const f = function(const u, v: Double): Double begin const r = Sqrt(u*u + v*v); Result := 10 * if IsZero(r) then 1.0 else Sin(r) / r; end; var LSurf := FVisCtl3D.NewObject<TCustomColoredSurface>; LSurf.SurfaceFunction := function(const u, v: Double): rglv begin Result.x := u; Result.y := v; Result.z := f(u, v); end; LSurf.SurfaceColorFunction := function(const u, v: Double): rglv begin const z = f(u, v); Result.r := EnsureRange(Abs(z) / 5, 0.0, 1.0); Result.g := 1.0; Result.b := 0.0; end; LSurf.Domain := TRectDom.Create(-10, 10, -10, 10); LSurf.ShowParameterCurves := True;
Parameterised surfaces
const F = function(const u, v: Double): rglv begin var cu, su, cv, sv: Double; SinCos(u, su, cu); SinCos(v, sv, cv); const cu2 = cu * cu; const cu3 = cu2 * cu; const cu4 = cu2 * cu2; const cu5 = cu4 * cu; const cu6 = cu3 * cu3; const cu7 = cu6 * cu; Result := vec( -2/15 * cu * (3*cv - 30*su + 90*cu4*su - 60*cu6*su + 5*cu*cv*su), -1/15 * su * (3*cv - 3*cu2*cv - 48*cu4*cv + 48*cu6*cv - 60*su + 5*cu*cv*su - 5*cu3*cv*su - 80*cu5*cv*su + 80*cu7*cv*su), 2/15 * (3 + 5*cu*su)*sv ); end; var LSurf := FVisCtl3D.NewObject<TCustomSurface>; LSurf.SurfaceFunction := F; LSurf.Domain := TRectDom.Create(0, Pi, 0, 2*Pi); LSurf.Color := clWebGoldenRod; LSurf.Direction := vec(0, 1, 0); LSurf.Unisided := True;
The Direction property is set to align the bottle with the z axis (which it would not do by default with the given parameterisation). Unisided := True makes the lighting the same on both sides of the surface (otherwise the back side will be much darker). In this case, we don’t really care about the orientation (surface normal direction).
Curves
const F = function(const t: Double): rglv begin Result := vec( Sin(t), Sin(t) * (Exp(Cos(t)) - 2*Cos(4*t) - Power(Sin(t/12), 5)), Cos(t) * (Exp(Cos(t)) - 2*Cos(4*t) - Power(Sin(t/12), 5)) ); end; var LCurve := FVisCtl3D.NewObject<TCurve3D>; LCurve.CurveFunction := F; LCurve.Domain := TInterval.Create(-20, 20); LCurve.Color := clRed;
Coloured curves
const F = function(const t: Double): rglv begin Result := vec( Sin(t), Sin(t) * (Exp(Cos(t)) - 2*Cos(4*t) - Power(Sin(t/12), 5)), Cos(t) * (Exp(Cos(t)) - 2*Cos(4*t) - Power(Sin(t/12), 5)) ); end; const G = function(const t: Double): rglv begin Result := vec( Sin(t), Cos(t), 1 ); end; var LCurve := FVisCtl3D.NewObject<TColoredCurve3D>; LCurve.CurveFunction := F; LCurve.CurveColorFunction := G; LCurve.Domain := TInterval.Create(-20, 20);
Scatter plots
const R = function: Double begin Result := (Random() + Random() + Random() + Random() + Random()) / 5 end; const F = function: rglv begin Result := vec( 20*(R() - 0.5), 20*(R() - 0.5), 5*Random() ); end; const N = 10_000; var LData := TArray<GLfloat3>(nil); SetLength(LData, N); for var i := 0 to N - 1 do LData[i] := F(); var LPlot := FVisCtl3D.NewObject<TSimpleScatterPlot>; LPlot.Data := LData;
const F = function(const t: Double): rglv begin Result := vec( Sin(t), Sin(t) * (Exp(Cos(t)) - 2*Cos(4*t) - Power(Sin(t/12), 5)), Cos(t) * (Exp(Cos(t)) - 2*Cos(4*t) - Power(Sin(t/12), 5)) ); end; const N = 10_000; var LData := TArray<GLfloat3>(nil); SetLength(LData, N); for var i := 0 to N - 1 do LData[i] := F(-5 + 10*Random); var LPlot := FVisCtl3D.NewObject<TSimpleScatterPlot>; LPlot.Data := LData;
const F = function(const t: Double): rglv begin Result := vec( Sin(t) + 0.5*Random, Sin(t) * (Exp(Cos(t)) - 2*Cos(4*t) - Power(Sin(t/12), 5)) + 0.5*Random, Cos(t) * (Exp(Cos(t)) - 2*Cos(4*t) - Power(Sin(t/12), 5)) + 0.5*Random ); end; const N = 10_000; var LData := TArray<GLfloat3>(nil); SetLength(LData, N); for var i := 0 to N - 1 do LData[i] := F(-5 + 10*Random); var LPlot := FVisCtl3D.NewObject<TSimpleScatterPlot>; LPlot.Data := LData;
const F = function: GLr3c3f1v begin Result.r := vec(10*Random, 10*Random, 10*Random); // position Result.c := vec(Result.r.x / 10, Result.r.y / 10, Result.r.z / 10); // RGB colour Result.q := Random(); // size factor end; const N = 10_000; var LData := TArray<GLfloat7>(nil); SetLength(LData, N); for var i := 0 to N - 1 do LData[i] := GLfloat7(F()); var LPlot := FVisCtl3D.NewObject<TAdvScatterPlot>; LPlot.Data := LData;
The data is given to the TAdvScatterPlot object via the Data property, which is an array of GLfloat7 records, each describing a point in the scatter plot.
Each such record consists of seven Single members. To make it easy to use, you can overlay the GLr3c3f1v record, giving names to the individual floats. The GLr3c3f1v
record consists of three floats describing the position (r) of the point, three floats describing the RGB colour of this point (c), and one describing the size factor of the individual point (a single float parameter). Hence
the “r3c3f1” part of the name GLr3c3f1v; the final “v” indicates that the record groups its fields in three separate nested records (position, colour, and size) instead of just being a flat array of seven floats. In code,
GLr3c3f1 = packed record // position, colour, float parameter x, y, z, r, g, b, q: GLfloat end; GLr3c3f1v = packed record r, c: rglv; q: GLfloat; end;
This naming convention is used consistently in this control.
Vector fields
const F = function(const u, v: Double): rglv begin Result := vec( (1 + 0.5*v*Cos(u/2)) * Cos(u), (1 + 0.5*v*Cos(u/2)) * Sin(u), 0.5*v*Sin(u/2) ); end; const N = function(u, v: Double): rglv begin const δ = 0.001; const dFdu = (F(u + δ, v) - F(u - δ, v)) / (2*δ); const dFdv = (F(u, v + δ) - F(u, v - δ)) / (2*δ); Result := (dFdu xor dFdv).Normalized; end; var LSurf := FVisCtl3D.NewObject<TCustomSurface>; LSurf.SurfaceFunction := F; LSurf.Domain := TRectDom.Create(0, 2*Pi, -1, 1); LSurf.Color := clRed; LSurf.Unisided := True; LSurf.ShowParameterCurves := True; LSurf.ParamCurveCounts := TParamCurveFamilySize.Create(64, 8); LSurf.LineColor := clMaroon; var LVectorField := FVisCtl3D.NewObject<TVectorField>; LVectorField.Size := 0.1; LVectorField.AnchorPoint := 1.03; LVectorField.Color := clWebGold; var LVectors := TList<GLr3v3c3v>.Create; try var q := Default(GLr3v3c3v); const A = 64; const B = 8; for var i := 0 to A - 1 do for var j := 0 to B - 1 do begin const u = 2*Pi/A * i; const v = -1 + 2/(B - 1) * j; q.r := F(u, v); q.v := N(u, v); LVectors.Add(q); end; LVectorField.Data := LVectors.ToArray; finally LVectors.Free; end;
This example draws a normal vector field on a Möbius strip.
The data is given to the TVectorField object via the Data property, which is an array of GLr3v3c3v records, each describing a visual vector.
Each such record consists of nine Single members: three describing the position (r) in the field, three describing the vector at that point (v), and three describing the colour (c) of the arrow at that point. Hence
the “r3v3c3” part of the name GLr3v3c3v; the final “v” indicates that the record groups its fields in three separate nested records (each containing three floats) instead of just being a flat array of nine floats. In code,
GLr3v3c3 = packed record // position, vector, colour x, y, z, u, v, w, r, g, b: GLfloat end; GLr3v3c3v = packed record r, v, c: rglv; end;
This naming convention is used consistently in this control.
By default, a visual vector will be drawn with the midpoint of its cylindrical core at the field point having the vector as its value. This doesn’t look good when the field is a normal vector field on a surface.
In this case, we’d prefer to have the end of the arrow at the field point. To achieve this, we set AnchorPoint := 1.03. A value of 1.0 would put the end of the arrow precisely at the field point; by offsetting it slightly from the surface, we reduce the risk of z fighting.
Similarly, a value of 0.0 would put the tip of the arrow precisely at the field point. The default value of AnchorPoint is 0.5, which puts the middle of the arrow’s core cylinder at the field point.
To make each arrow have its own colour (its GLr3v3c3v.c member), set PerVertexColors := True.
Geometric primitives
The control recognises a number of geometric primitives, such as cubes, spheres, and cylinders:
var LIco := FVisCtl3D.NewObject<TSolidIcosahedron>; LIco.Color := clSilver;
The following classes are available:
| Class | Description | Key properties |
|---|---|---|
TEllipsoid |
An ellipsoid. | AxisLengths (rglv) |
TSphere |
A sphere, an ellipsoid with all three axis lengths equal. | Radius |
TCylinder |
A right elliptic cylinder. | AxisLengths (rglv2), Radius (in case of circular cylinder: sets both axis lengths to the same number), Height |
TCone |
A right elliptic cone. | AxisLengths (rglv2), Height |
TPlane |
A rectangular part of a plane. | |
TDisk |
A circular part of a plane. | |
TArrow |
A 3D vector arrow. | Vector (rglv), Aspect (ratio radius ∶ height of top cone), HeadSize (ratio top cone height ∶ core cylinder height) |
TSolidCylinder |
A customisable right solid elliptic cylinder (or cylindrical sector or cylindrical annulus or both). | AxisLengths (rglv2), Radius (in case of circular cylinder) Height, Angle (for sector: default 2π), InnerRadiusFraction (for annulus: default 0.0) |
TSolidCone |
A customisable right solid elliptic cone. | AxisLengths (rglv2), Radius (in case of circular cone) Height |
TSolidCube |
A solid cube. | |
TSolidDodecahedron |
A solid dodecahedron. | |
TSolidIcosahedron |
A solid icosahedron. | |
TSolidOctahedron |
A solid octahedron. | |
TSolidPyramid |
A solid pyramid. | |
TSolidTetrahedron |
A solid tetrahedron. |
In addition to the “key properties”, which are unique to the class, all of the classes also support the Position, Scale, Direction, and Rotation properties
that are common to all geometric objects. Furthermore, the first six classes in the list are subclasses of the base surface class, which means that they inherit the ShowSurface, ShowParameterCurves, ParamCurveCounts, Color, and LineColor properties from this base class.
Please note that in the case of TEllipsoid, TCylinder, TCone, TSolidCylinder, and TSolidCone, the AxisLengths and Height (when applicable) properties are implemented by means of the common Scale property. This means that for a LCyl: TCylinder, for example, LCyl.AxisLengths reads and writes the two first elements of LCyl.Scale, while LCyl.Height reads and write the third element of LCyl.Scale. Hence, LCyl.Height := 5; LCyl.Scale := vec(1, 1, 3) will make LCyl have height 3, and not 15.
OBJ models
You can load any *.obj model:
var LSource := ''; var LDlg := TFileOpenDialog.Create(Self); try var LItem := LDlg.FileTypes.Add; LItem.DisplayName := 'OBJ models'; LItem.FileMask := '*.obj'; if LDlg.Execute then LSource := TFile.ReadAllText(LDlg.FileName) else Abort; finally LDlg.Free; end; var LModel := FVisCtl3D.NewObject<TObjModel>; LModel.Source := LSource; LModel.Color := clWebGoldenRod;
Text and image planes
TTextRect and TImageRect are used to display text and bitmap images, respectively:
var LText := FVisCtl3D.NewObject<TTextRect>; LText.Direction := vec(1, 0, 0); LText.Text := 'Algosim'; LText.Font.Name := 'Palatino Linotype'; var LImage := FVisCtl3D.NewObject<TImageRect>; LImage.Bitmap.LoadFromFile('C:\Users\kvadr\Pictures\sample.bmp'); LImage.TransparentColorMode := tcmDistance; LImage.TransparentColor := clWhite; var LDodeca := FVisCtl3D.NewObject<TSolidDodecahedron>; LDodeca.Color := clRed; LDodeca.Position := vec(-3, -2, 0);
TTextRect.FaceScreen is a boolean property that, when set to True, makes the text plane always face the camera.
TImageRect.TransparentColorMode is one of tcmOff, tcmEqual, tcmDistance, and tcmBipolar. tcmOff makes the image plane opaque. tcmEqual makes the image plane fully transparent at pixels with the precise colour TImageRect.TransparentColor; at all other positions in the image plane, it is fully opaque. tcmDistance makes the opacity of an image plane pixel a linear function of the distance from the pixel’s colour value to the TransparentColor colour. Finally, tcmBipolar makes pixels with the TransparentColor fully transparent, pixels with the OpaqueColor fully opaque, and interpolates the transparency linearly between these two colours in RGB space.
Coordinate axes
The default coordinate axes and grids can be turned on and off both programmatically and using the control’s context menu (right-click menu).
Programmatically, the control’s ShowAxes property is used to toggle the visibility of the default coordinate axes and their associated grids.
This coordinate system (axes + grids) is represented by the Axes: TAxes property, and Axes.Visible is functionally equivalent to ShowAxes;
the control’s ShowAxes property simply reads and sets the Axes.Visible property.
Axes.X, Axes.Y, and Axes.Z represent each individual coordinate axis (TAxis). Each axis contains a cylinder with optional labels. The cylinder extends from −NegativeLength via the origin to +Length in its direction.
The following properties of an axis are used to customise its appearance:
Labels(Boolean): Show or hide labels on the axis.LabelFont(TFont): The font used for the labels on the axis.LabelFormat(string): The format string used to format the numeric axis labels as strings.LabelDelta(Double): The distance between consecutive labels on the axis.Length(Double): The extent in the axis’ positive direction of the axis cylinderNegativeLength(Double): The extent in the axis’ negative direction of the axis cylinder.Radius(Double): The radius of the axis cylinder.Color(TColor): The colour of the axis cylinder. By default, theX,Y, andZaxis cylinders are red, green, and blue, respectively.
By default, each axis has Length = 10 and NegativeLength = 0, meaning that it extends from the origin to 10.0 in its direction. In this example, we change the axes so that the X and Y axes both extend from −10 to +10 while the Z axis only extends from the origin to +5:
FVisCtl3D.Axes.X.NegativeLength := 10; FVisCtl3D.Axes.X.Length := 10; FVisCtl3D.Axes.Y.NegativeLength := 10; FVisCtl3D.Axes.Y.Length := 10; FVisCtl3D.Axes.Z.NegativeLength := 0; FVisCtl3D.Axes.Z.Length := 5; FVisCtl3D.Axes.GridCount := 0;
Notice that we also removed all grids by setting Axes.GridCount := 0. By default, three grids occupy the {0}×[0, 10]×[0, 10], [0, 10]×{0}×[0, 10], and [0, 10]×[0, 10]×{0} regions.
Each grid is a child object of the Axes, so Axes.GridCount simply returns the number of child objects that are of the grid class (TGrid). Setting this property adds or removes grid child objects; you can have any number of grids in a coordinate system (TAxes object). To access the grids of a TAxes object, use the Children and ChildCount properties.
In the following example, we first remove the three default grids and then add a single, large grid in the [−10, +10]²×{0} region:
FVisCtl3D.Axes.X.NegativeLength := 10; FVisCtl3D.Axes.X.Length := 10; FVisCtl3D.Axes.Y.NegativeLength := 10; FVisCtl3D.Axes.Y.Length := 10; FVisCtl3D.Axes.Z.NegativeLength := 0; FVisCtl3D.Axes.Z.Length := 5; FVisCtl3D.Axes.GridCount := 0; var LGrid := FVisCtl3D.Axes.CreateChild<TGrid>; LGrid.Direction := vec(0, 0, 1); LGrid.XMin := -10; LGrid.XMax := +10; LGrid.YMin := -10; LGrid.YMax := +10;
In this final example, we make all axes and grids golden:
FVisCtl3D.Axes.X.NegativeLength := 10; FVisCtl3D.Axes.X.Length := 10; FVisCtl3D.Axes.X.Color := clWebGold; FVisCtl3D.Axes.Y.NegativeLength := 10; FVisCtl3D.Axes.Y.Length := 10; FVisCtl3D.Axes.Y.Color := clWebGold; FVisCtl3D.Axes.Z.NegativeLength := 0; FVisCtl3D.Axes.Z.Length := 5; FVisCtl3D.Axes.Z.Color := clWebGold; FVisCtl3D.Axes.GridCount := 0; var LGrid := FVisCtl3D.Axes.CreateChild<TGrid>; LGrid.Direction := vec(0, 0, 1); LGrid.XMin := -10; LGrid.XMax := +10; LGrid.YMin := -10; LGrid.YMax := +10; LGrid.Color := clWebGold;
Additional coordinate systems
You may create any number of coordinate systems:
var LAxes2 := FVisCtl3D.NewObject<TAxes>; LAxes2.Position := vec(10, 00, 0); var LAxes3 := FVisCtl3D.NewObject<TAxes>; LAxes3.Position := vec(20, 00, 0);
Here we have created two new coordinate systems (axes + grid sets) in addition to the default one (rightmost).
Please note that a “coordinate system” in this sense (a TAxes object) is only a graphical object (like any teapot or graph). The control’s world coordinate system always aligns with the default axes only (and only if you don’t transform that TAxes object).
Common object properties
Almost every object you can display in a scene derives from TGeometricObject3D. This class has four basic properties: Scale (vector), Position (vector), Direction (vector), and Rotation (scalar). These determine the object’s size, position, and orientation in space.
Scale is a three-dimensional vector (an rglv) with scale factors in the three spatial directions. Direction specifies the object’s orientation in space; it directly or indirectly specifies the direction of the object’s vertical axis. Rotation then rotates the object about this axis. Finally, Position translates the object so that its origin is placed at the specified point. While Scale and Position are straightforward, the semantics and behaviour of Direction and Rotation are, unfortunately, somewhat inconsistent between different classes of geometric objects.
You may also bypass these four properties and manually specify the object’s transformation matrix (as a 4×4 matrix, an rglm4) using the ManualMatrix (rglm4) and UseManualMatrix (boolean) properties.
Finally, each geometric object has an AnimationSpeed (double) property, which by default is 0.0. If set to a positive value, the object will be animated and the value is used as the speed of the animation. The type of animation depends on the actual class of geometric object, but typically it involves rotation (spinning) about the object’s vertical axis.
Object groups
A key feature of the control’s scene system is that every object can have child objects, thus making a scene graph. (We have already seen an example of this: The TGrid children of a TAxes object.)
You can create a child node and attach it to any object using the object’s CreateChild<T> function. If you need an object that is only to serve as a parent for child objects, and not have any visual representation itself, you should use TGeometricObject3D.
For example, here’s a water molecule:
var LWater := FVisCtl3D.NewObject<TGeometricObject3D>; var LOxygen := LWater.CreateChild<TSphere>; LOxygen.Color := clRed; LOxygen.Radius := 1.52; const α = DegToRad(104.45) / 2; var LHydrogen1 := LWater.CreateChild<TSphere>; LHydrogen1.Position := 0.9584 * vec(Sin(α), 0, -Cos(α)); LHydrogen1.Color := clWhite; LHydrogen1.Radius := 1.20; var LHydrogen2 := LWater.CreateChild<TSphere>; LHydrogen2.Position := 0.9584 * vec(-Sin(α), 0, -Cos(α)); LHydrogen2.Color := clWhite; LHydrogen2.Radius := 1.20;
In this example, the use of a parent group adds little benefit. However, if we would like to display many water molecules, the benefit of this grouping becomes obvious:
const N = 200; const R = function(): Double begin Result := 30*(Random - 0.5) end; for var i := 1 to N do begin var LWater := FVisCtl3D.NewObject<TGeometricObject3D>; LWater.Position := vec(R(), R(), R()); LWater.Direction := vec(R(), R(), R()).Normalized; LWater.Rotation := 2*Pi*Random; LWater.Scale := 0.2 * rglv_ones; var LOxygen := LWater.CreateChild<TSphere>; LOxygen.Color := clRed; LOxygen.Radius := 1.52; const α = DegToRad(104.45) / 2; var LHydrogen1 := LWater.CreateChild<TSphere>; LHydrogen1.Position := 0.9584 * vec(Sin(α), 0, -Cos(α)); LHydrogen1.Color := clWhite; LHydrogen1.Radius := 1.20; var LHydrogen2 := LWater.CreateChild<TSphere>; LHydrogen2.Position := 0.9584 * vec(-Sin(α), 0, -Cos(α)); LHydrogen2.Color := clWhite; LHydrogen2.Radius := 1.20; end;
Here we can trivially position each water molecule individually, and its constituent atoms are moved along with it. Had we not used the grouping concept, we had been forced to compute the position of each individual oxygen and hydrogen atom manually.
(If we are very, very, unlucky, the preceding code snippet may fail at runtime. Why is that?)
We can also add all of the molecules to a common group, LOcean, and set this group’s AnimationSpeed := 10 to have the entire “ocean” rotate about its axis. Were it not for this group, this effect had not been possible. An AnimationSpeed := 10 on each molecule would similarly make each molecule rotate (or spin) about its own axis; again, this would not have been possible without the molecule group. So without groups, we could only set AnimationSpeed := 10 on each individual oxygen or hydrogen atom, and this effect would be rather boring (why?).
const N = 200; const R = function(): Double begin Result := 30*(Random - 0.5) end; var LOcean := FVisCtl3D.NewObject<TGeometricObject3D>; LOcean.AnimationSpeed := 10; for var i := 1 to N do begin var LWater := LOcean.CreateChild<TGeometricObject3D>; LWater.Position := vec(R(), R(), R()); LWater.Direction := vec(R(), R(), R()).Normalized; LWater.Rotation := 2*Pi*Random; LWater.Scale := 0.2 * rglv_ones; var LOxygen := LWater.CreateChild<TSphere>; LOxygen.Color := clRed; LOxygen.Radius := 1.52; const α = DegToRad(104.45) / 2; var LHydrogen1 := LWater.CreateChild<TSphere>; LHydrogen1.Position := 0.9584 * vec(Sin(α), 0, -Cos(α)); LHydrogen1.Color := clWhite; LHydrogen1.Radius := 1.20; var LHydrogen2 := LWater.CreateChild<TSphere>; LHydrogen2.Position := 0.9584 * vec(-Sin(α), 0, -Cos(α)); LHydrogen2.Color := clWhite; LHydrogen2.Radius := 1.20; end;
Please note that the standard TSphere uses a high-resolution triangulation to approximate a sphere. For that reason it is not a good idea to create a huge number of such objects. 200 is OK on modern hardware, but for thousands or tens of thousands of objects, you need to use simpler objects, such as the markers in a single scatter plot. (If you absolutely have to use individual objects, cubes are a decent choice, since each cube consists of only eight vertices.)
The camera
The camera always orbits a specific point (x, y, z), the “look at” or “(orbit) centre” point. By default, this point is the origin, (0, 0, 0). The camera is positioned a distance r > 0 from this point and directed straight towards it. Its position on this sphere is stored as the two spherical coordinates θ and φ. Hence, the pose (position and orientation) of the camera is specified by six parameters: x, y, z, r, θ, and φ.
The position of the camera is
and it is looking straight at (x, y, z). It is not possible to rotate the camera about this axis.
Keyboard interface
When the control has keyboard focus, it responds to keyboard commands. The following commands are used to move the camera:
Orbit
The Up and Down arrow keys decrease and increase θ, respectively, which is capped to the interval [0, π].
The Left and Right arrow keys decrease and increase φ, respectively, which is understood to be modulo 2π.
The + and − keys decrease and increase r, respectively, which is capped to the interval [0.01, 900].
In all three cases, the Shift modifier can be used to make finer adjustments (smaller ∆θ, ∆φ, and ∆r steps).
In addition, X (or Y or Z) adjusts θ and φ so that the camera is looking in the positive x (or y or z) direction. Shift+X (or Shift+Y or Shift+Z) adjusts θ and φ so that the camera is looking in the negative x (or y or z) direction.
Finally, R moves the camera by picking random values for θ and φ (keeping x, y, z, and r unchanged).
Translation of centre point
The W and S keys move the centre point into and out from the screen, respectively, while keeping the z coordinate fixed. If θ = 0° or 180°, these keys have no effect.
The A and D keys move the centre point to the left and to the right, respectively, while keeping the z coordinate fixed. If θ = 0° or 180°, these keys have no effect.
The Q and E keys decrease and increase the z coordinate, respectively.
In all three cases, the Shift modifier can be used to make smaller steps.
Finally, 0 moves the centre point to the origin (0, 0, 0).
You can press C to reveal the current location of the orbit centre point. This turns on the centre point marker, which is then turned off again automatically after a second or so.
Mouse interface
Moving the mouse while keeping the primary mouse button (typically the left mouse button) down orbits by adjusting θ (vertically) and φ (horizontally).
Moving the mouse while keeping the middle mouse button (typically the scroll wheel) down translates the centre point in the same manner as A and D (horizontally) and Q and E (vertically).
The scroll wheel adjusts the r coordinate; effectively, it “zooms”.
In all three cases, the Shift modifier can be used to move the camera more slowly.
A panning operation (i.e., a camera operation involving moving the mouse with the primary or middle button down) can be aborted and undone by pressing Esc (only in raw input mode); pressing this key will restore the view to the pre-mouse-button-down state (again, only in raw input mode).
Mouse input modes
There are two mouse input modes: classic input mode and raw input mode.
In classic input mode, the mouse cursor stays visible during panning, and when the cursor hits an edge of the control, it jumps to the opposite edge (as if the control had a toroidal topology). Under the hood, the ordinary Windows desktop mouse cursor is used as input.
In raw input mode, raw data from the pointing device is used for input during panning. The mouse cursor is hidden while panning is taking place and when the panning operation ends, the mouse cursor is brought back at the same location as before the operation. If the user presses Esc while panning, the operation is immediately aborted and the view is restored to the pre-panning state.
Raw data from the pointing device can be either relative or absolute. In absolute data mode, panning may be obstructed by the edges of the screen (even though the mouse cursor isn’t visible), so relative data mode is preferable. A typical mouse on a physical Windows machine will produce relative data, but sometimes only absolute data is available, especially in remote-desktop scenarios. If this is the case, classic input mode may be the better choice.
You choose your preferred mode using the RawInput boolean property. If True, raw input mode is used if at all possible; otherwise, classic input mode is used. There’s also a boolean property AllowAbandonRaw. If this is True, the control will automatically set RawInput to False if it detects that the pointing device is sending absolute data or it detects that the application is running in a remote desktop setting (which typically involves absolute data). If AllowAbandonRaw = False, the control will never change the RawInput property on its own.
By default, both RawInput and AllowAbandonRaw are True, meaning that the control will use raw input mode (the best input mode) if that will work properly, and automatically revert to classic input mode otherwise.
Programmatic interface
You can manipulate the view programmatically by means of the View property. This object has properties that can be read and set:
r(Double): The r parameter.Theta(Double): The θ parameter (degrees).ThetaRad(Double): The θ parameter (radians).Phi(Double): The φ parameter (degrees).PhiRad(Double): The φ parameter (radians).TargetCenter(vector, rglv): The orbit centre point.CentrePoint(object): The orbit centre point, accessible from the Object Inspector at design time. Semantically equivalent toTargetCenter.CameraRelativePosition(vector, rglv): The vector from the orbit centre point to the camera.CameraRelativePositionSp(vector, TSphericalCoordinates): The vector from the orbit centre point to the camera expressed in spherical coordinates (r, θ, φ) with the angles in radians.
In addition, there are overloaded procedures FixCamera that set the centre point (x, y, z) and orbit parameters (r, θ, φ) simultaneously.
There are also overloaded procedures AnimateTo that set the centre point and (in most overloads) orbit parameters simultaneously, and animate the transition to the new coordinates. One of the overloads changes the centre point exclusively, keeping the orbit parameters unchanged. The animation is eased at both ends and takes about a second. It is aborted, “fast-forwarded”, if the user tries to use the mouse wheel to zoom. It can also be aborted using the FinishAnimation procedure. This too will “fast-forward” to the final position in the animation.
Projections
The control supports two kinds of projections: orthographic and perspective (default).
Programmatically, you can use the Projection property to change the projection. At runtime, the user can use the context menu (right-click menu) to change the projection. In addition, the O (orthographic) and P (perspective) keys can be used when the control has keyboard focus.
Light position
You can programmatically change the position of the main light source using the control’s LightPos (vector, rglv) property. Using the default shaders, this light source is omnidirectional and the light is not attenuated as you move away from it.
Built-in documentation
At runtime, the “Quick help” context (right click) menu item can be used to display the built-in quick reference dialog. Alternatively, the H key can be used to display this dialog (when the control has keyboard focus).
OpenGL implementation data
If you hold the Shift key down and right click the control, the context menu will have two additional items: “OpenGL info” and “OpenGL extensions”. These will display information about the system’s OpenGL implementation (graphics card and drivers) and list the OpenGL extensions supported by the implementation.
System requirements
Since the control is based on modern OpenGL, the system running the application must support this API for the control to work. While essentially any reasonably modern real computer does support OpenGL, either with a dedicated graphics card or an integrated graphics solution on the CPU, the same does not necessarily hold for virtual machines. A virtual machine may lack the required OpenGL support, and the control is designed to handle this nicely:
You may be able to install a software-based OpenGL driver that will make the control work (but probably with fairly poor performance).
Source code
{ **************************************************************************** } { Rejbrand Algosim 3d visualisation control } { Copyright © 2017–2026 Andreas Rejbrand } { https://english.rejbrand.se/ } { **************************************************************************** } unit trgl; interface uses Windows, SysUtils, OpenGL, OpenGLext, Generics.Defaults, Generics.Collections, Messages, Graphics, Types, UITypes, Classes, Controls, Forms, Dialogs, Menus, ExtCtrls, AppEvnts; const // Note: programs sorted alphabetically; programs for transparent overlays start with "z" P_Default = 'default'; P_UniformColorDefault = 'ucdef'; P_Lighting = 'light'; P_UniformColorLighting = 'uclight'; P_UniformColorLightingUnisided = 'uclightus'; P_Tex = 'tex'; P_Scatter = 'scatter'; P_AdvScatter = 'advsca'; P_VectorField = 'vf'; P_Image = 'zimage'; P_Text = 'ztext'; type TImageFormat = (ifFromExtension, ifBitmap, ifPNG, ifJPG); TAnchorPoint = ( apTopLeft, apTop, apTopRight, apLeft, apCenter, apRight, apBottomLeft, apBottom, apBottomRight ); TLinearAlignment = (laNegative, laMiddle, laPositive); TAnchorPointHelper = record helper for TAnchorPoint strict private const PointNames: array[TAnchorPoint] of string = ( 'top-left', 'top', 'top-right', 'left', 'center', 'right', 'bottom-left', 'bottom', 'bottom-right' ); public function H: TLinearAlignment; inline; function V: TLinearAlignment; inline; function ToString: string; class function FromString(const S: string): TAnchorPoint; static; end; TPPE = (ppIdentity); TPPEHelper = record helper for TPPE function ID: Integer; function Name: string; class function FromString(const S: string): TPPE; static; end; TPPEs = set of TPPE; const AnimatedEffects: TPPEs = []; type TStockSurfaceIndex = ( STOCKSURF_SPHERE = 1, STOCKSURF_CYLINDER = 2, STOCKSURF_PLANE = 3, STOCKSURF_DISK = 4, STOCKSURF_SPHERELET= 5, STOCKSURF_ARROW = 6 ); type GLfloat2 = array[0..1] of GLfloat; GLfloat3 = array[0..2] of GLfloat; GLfloat4 = array[0..3] of GLfloat; GLfloat5 = array[0..4] of GLfloat; GLfloat6 = array[0..5] of GLfloat; GLfloat7 = array[0..6] of GLfloat; GLfloat9 = array[0..8] of GLfloat; GLfloat15 = array[0..14] of GLfloat; ERglError = class(Exception); rglv2 = record class operator Add(const Left, Right: rglv2): rglv2; class operator Subtract(const Left, Right: rglv2): rglv2; class operator Multiply(const Left: Single; const Right: rglv2): rglv2; class operator Multiply(const Left: rglv2; const Right: Single): rglv2; class operator Multiply(const Left, Right: rglv2): Single; class operator Divide(const Left: rglv2; const Right: Single): rglv2; class operator Equal(const Left, Right: rglv2): Boolean; class operator NotEqual(const Left, Right: rglv2): Boolean; class operator Implicit(const arr: GLfloat2): rglv2; class operator Implicit(const v: rglv2): GLfloat2; function Norm: Single; function Normalized: rglv2; function NormSquare: Single; constructor Create(const x, y: Single); function ptr: PGLfloat; class function Zero: rglv2; static; case Byte of 0: (elem: GLfloat2); 1: (x, y: GLfloat); 2: (u, v: GLfloat); 3: (s, t: GLfloat); end; Prglv = ^rglv; rglv = record class operator Add(const Left, Right: rglv): rglv; class operator Subtract(const Left, Right: rglv): rglv; class operator Multiply(const Left: Single; const Right: rglv): rglv; class operator Multiply(const Left: rglv; const Right: Single): rglv; class operator Multiply(const Left, Right: rglv): Single; class operator Divide(const Left: rglv; const Right: Single): rglv; class operator Equal(const Left, Right: rglv): Boolean; class operator NotEqual(const Left, Right: rglv): Boolean; class operator LogicalXor(const Left, Right: rglv): rglv; class operator Implicit(const arr: GLfloat3): rglv; class operator Implicit(const v: rglv): GLfloat3; class operator Implicit(const c: TColor): rglv; function xy: rglv2; function Norm: Single; function Normalized: rglv; function NormSquare: Single; inline; constructor Create(const x, y, z: Single); function ptr: PGLfloat; class function Zero: rglv; static; case Byte of 0: (elem: GLfloat3); 1: (x, y, z: GLfloat); 2: (r, g, b: GLfloat); 3: (s, t, p: GLfloat); end; TCartesianCoordinates = rglv; TSphericalCoordinates = record r, θ, φ: Single; class operator Implicit(const ACoords: TSphericalCoordinates): TCartesianCoordinates; class operator Implicit(const ACoords: TCartesianCoordinates): TSphericalCoordinates; class operator Equal(const Left, Right: TSphericalCoordinates): Boolean; class operator NotEqual(const Left, Right: TSphericalCoordinates): Boolean; class operator Add(const Left, Right: TSphericalCoordinates): TSphericalCoordinates; class operator Multiply(const Left: Single; Right: TSphericalCoordinates): TSphericalCoordinates; constructor Create(const r, θ, φ: Single); end; rθφ = TSphericalCoordinates; TCameraPose = record SceneCenter: rglv; RelativePosition: rglv; class operator Equal(const Left, Right: TCameraPose): Boolean; class operator NotEqual(const Left, Right: TCameraPose): Boolean; end; TCameraPoseSp = record SceneCenter: rglv; RelativePosition: rθφ; class operator Equal(const Left, Right: TCameraPoseSp): Boolean; class operator NotEqual(const Left, Right: TCameraPoseSp): Boolean; end; rglv4 = record class operator Add(const Left, Right: rglv4): rglv4; class operator Subtract(const Left, Right: rglv4): rglv4; class operator Multiply(const Left: Single; const Right: rglv4): rglv4; class operator Multiply(const Left, Right: rglv4): Single; class operator Divide(const Left: rglv4; const Right: Single): rglv4; class operator Equal(const Left, Right: rglv4): Boolean; class operator NotEqual(const Left, Right: rglv4): Boolean; function Norm: Single; function Normalized: rglv4; function NormSquare: Single; constructor Create(const x, y, z, w: Single); function ptr: PGLfloat; class function Zero: rglv4; static; case Byte of 0: (elem: GLfloat4); 1: (x, y, z, w: GLfloat); 2: (r, g, b, a: GLfloat); 3: (s, t, p, q: GLfloat); end; rglm = record private const _dim = 3; _dimh = _dim - 1; _elemc = _dim * _dim; _elemh = _elemc - 1; public class operator Add(const Left, Right: rglm): rglm; class operator Subtract(const Left, Right: rglm): rglm; class operator Multiply(const Left: Single; const Right: rglm): rglm; class operator Multiply(const Left: rglm; const Right: rglv): rglv; class operator Multiply(const Left, Right: rglm): rglm; class operator Divide(const Left: rglm; const Right: Single): rglm; class operator Equal(const Left, Right: rglm): Boolean; class operator NotEqual(const Left, Right: rglm): Boolean; constructor Create(const m11, m12, m13, m21, m22, m23, m31, m32, m33: Single); constructor CreateFromColumns(const u, v, w: rglv); function ptr: PGLfloat; function Transpose: rglm; function Inverse: rglm; function ToString: string; class function Zero: rglm; static; class function Identity: rglm; static; case Boolean of False: (elem: array[0.._elemh] of GLfloat); True: (m: array[0.._dimh] of array [0.._dimh] of GLfloat); end; rglm4 = record private const _dim = 4; _dimh = _dim - 1; _elemc = _dim * _dim; _elemh = _elemc - 1; public class operator Add(const Left, Right: rglm4): rglm4; class operator Subtract(const Left, Right: rglm4): rglm4; class operator Multiply(const Left: Single; const Right: rglm4): rglm4; class operator Multiply(const Left: rglm4; const Right: rglv4): rglv4; class operator Multiply(const Left, Right: rglm4): rglm4; class operator Divide(const Left: rglm4; const Right: Single): rglm4; class operator Equal(const Left, Right: rglm4): Boolean; class operator NotEqual(const Left, Right: rglm4): Boolean; class operator Explicit(const Mat4: rglm4): rglm; class operator Explicit(const Mat: rglm): rglm4; constructor Create(const m11, m12, m13, m14, m21, m22, m23, m24, m31, m32, m33, m34, m41, m42, m43, m44: Single); function ptr: PGLfloat; function Transpose: rglm4; class function Identity: rglm4; static; case Boolean of False: (elem: array[0.._elemh] of GLfloat); True: (m: array[0.._dimh] of array [0.._dimh] of GLfloat); end; GLr3c3 = packed record // position, colour x, y, z, r, g, b: GLfloat end; GLr3c3v = packed record r, c: rglv; end; GLr3u3 = packed record // position, texture coords x, y, z, u, v: GLfloat end; GLr3u3v = packed record r: rglv; u: rglv2; end; GLr3c4 = packed record // position, alpha colour x, y, z, r, g, b, a: GLfloat end; GLr3c4v = packed record r: rglv; c: rglv4 end; GLr3c3f1 = packed record // position, colour, float parameter x, y, z, r, g, b, q: GLfloat end; GLr3c3f1v = packed record r, c: rglv; q: GLfloat; end; GLr3n3 = packed record // position, normal x, y, z, u, v, w: GLfloat end; GLr3n3v = packed record r, n: rglv; end; GLr3v3 = packed record // position, vector x, y, z, u, v, w: GLfloat end; GLr3v3v = packed record r, v: rglv; end; GLr3c3n3 = packed record // position, colour, normal x, y, z, r, g, b, u, v, w: GLfloat end; GLr3c3n3v = packed record r, c, n: rglv; end; GLr3v3c3 = packed record // position, vector, colour x, y, z, u, v, w, r, g, b: GLfloat end; GLr3v3c3v = packed record r, v, c: rglv; end; GLr3m9c3v = packed record // position, 3×3 matrix, colour r: rglv; m: rglm; c: rglv; end; function vec(const x, y, z: Single): rglv; function vec2(const x, y: Single): rglv2; function vec4(const x, y, z, w: Single): rglv4; function mat(const m11, m12, m13, m21, m22, m23, m31, m32, m33: Single): rglm; function mat4(const m11, m12, m13, m14, m21, m22, m23, m24, m31, m32, m33, m34, m41, m42, m43, m44: Single): rglm4; function mat_transpose(const m11, m12, m13, m21, m22, m23, m31, m32, m33: Single): rglm; const XYPlaneProjection: rglm = (elem: (1, 0, 0, 0, 1, 0, 0, 0, 0)); rglv_red: rglv = (r: 1.0; g: 0.0; b: 0.0); rglv_green: rglv = (r: 0.0; g: 1.0; b: 0.0); rglv_blue: rglv = (r: 0.0; g: 0.0; b: 1.0); rglv_black: rglv = (r: 0.0; g: 0.0; b: 0.0); rglv_white: rglv = (r: 1.0; g: 1.0; b: 1.0); rglv_ones: rglv = (x: 1.0; y: 1.0; z: 1.0); function rglLookAt(const eyeX, eyeY, eyeZ: Single; const centerX, centerY, centerZ: Single; const upX, upY, upZ: Single): rglm4; overload; function rglLookAt(const eye, center, up: rglv): rglm4; overload; function rglPerspective(const fovy, aspect, &near, &far: Double): rglm4; function rglOrtho(const left, right, bottom, top, &near, &far: Double): rglm4; function rglOrtho2D(const left, right, bottom, top: Double): rglm4; function rglScale(const x, y, z: Single): rglm4; function rglTranslate(const x, y, z: Single): rglm4; overload; function rglTranslate(const v: rglv): rglm4; overload; function rglRotate(const a, x, y, z: Single): rglm4; overload; function rglRotate(const a: Single; const v: rglv): rglm4; overload; function rglGetString(name: GLenum): string; type TSurfParamFcn = reference to function(const u, v: Double): rglv; TSurfParamColorFcn = reference to function(const u, v: Double): rglv; TSurfParamNormalFcn = reference to function(const u, v: Double): rglv; TCurveParamFcn = reference to function(const t: Double): rglv; TCurveParamColorFcn = reference to function(const t: Double): rglv; TCurveParamNormalFcn = reference to function(const t: Double): rglv; procedure TriangulateSurface(F: TSurfParamFcn; N: TSurfParamNormalFcn; const umin, umax, vmin, vmax: Double; A, B, pccx, pccy: Integer; Normalize: Boolean; out Vertices: TArray<GLfloat6>; out Indices, PCIs: TArray<GLuint>; PCOnly: Boolean; ListData: TArray<rglv>); procedure TriangulateColoredSurface(F: TSurfParamFcn; C: TSurfParamColorFcn; N: TSurfParamNormalFcn; const umin, umax, vmin, vmax: Double; A, B, pccx, pccy: Integer; Normalize: Boolean; out Vertices: TArray<GLfloat9>; out Indices, PCIs: TArray<GLuint>; PCOnly: Boolean; ListData: TArray<GLr3c3v>); type TParamSurfProc<vtype> = procedure(out Vertices: TArray<vtype>; out Indices, PCIs: TArray<GLuint>; A, B, pccx, pccy: Integer; PCOnly: Boolean; Data: Pointer); TSimpleParamSurfProc = TParamSurfProc<GLfloat6>; TColoredParamSurfProc = TParamSurfProc<GLfloat9>; TrglSphereMapType = (rglSphereMapPolar, rglSphereMapConstantArea); procedure rglSpherePolar(out Vertices: TArray<GLfloat6>; out Indices, PCIs: TArray<GLuint>; A, B, pccx, pccy: Integer; PCOnly: Boolean; Data: Pointer); procedure rglSphereConstArea(out Vertices: TArray<GLfloat6>; out Indices, PCIs: TArray<GLuint>; A, B, pccx, pccy: Integer; PCOnly: Boolean; Data: Pointer); procedure rglCylinder(out Vertices: TArray<GLfloat6>; out Indices, PCIs: TArray<GLuint>; A, B, pccx, pccy: Integer; PCOnly: Boolean; Data: Pointer); procedure rglCone(out Vertices: TArray<GLfloat6>; out Indices, PCIs: TArray<GLuint>; A, B, pccx, pccy: Integer; PCOnly: Boolean; Data: Pointer); procedure rglPlane(out Vertices: TArray<GLfloat6>; out Indices, PCIs: TArray<GLuint>; A, B, pccx, pccy: Integer; PCOnly: Boolean; Data: Pointer); procedure rglDisk(out Vertices: TArray<GLfloat6>; out Indices, PCIs: TArray<GLuint>; A, B, pccx, pccy: Integer; PCOnly: Boolean; Data: Pointer); type TRglContext = class; TRglShader = class; TRglShaderClass = class of TRglShader; TShaderKind = (Vertex, Geometry, Fragment); TShaderKindHelper = record helper for TShaderKind function ToString: string; function RglClass: TRglShaderClass; end; TRglShader = class abstract private FContext: TRglContext; FKind: Cardinal; FHandle: Cardinal; FSource: AnsiString; public constructor Create(AContext: TRglContext; const ASource: string); virtual; destructor Destroy; override; procedure Compile; property Handle: Cardinal read FHandle; property Kind: Cardinal read FKind; property Source: AnsiString read FSource; end; TRglVertexShader = class(TRglShader) constructor Create(AContext: TRglContext; const ASource: string); override; end; TRglFragmentShader = class(TRglShader) constructor Create(AContext: TRglContext; const ASource: string); override; end; TRglGeometryShader = class(TRglShader) constructor Create(AContext: TRglContext; const ASource: string); override; end; TRglUniform = class abstract strict private FContext: TRglContext; FName: string; FHandle: Integer; private constructor Create; overload; constructor Create(AContext: TRglContext; AProgram: Cardinal; const AName: string); overload; class function TryCreate(AContext: TRglContext; AProgram: Cardinal; const AName: string): TRglUniform; public destructor Destroy; override; property Context: TRglContext read FContext; property Name: string read FName; property Handle: Integer read FHandle; end; TRglUniformClass = class of TRglUniform; TRglUniformFloat = class(TRglUniform) procedure SetValue(const AValue: GLfloat); end; TRglUniformFloatVec2 = class(TRglUniform) procedure SetValue(const AValue: rglv2); overload; procedure SetValue(const a0, a1: GLfloat); overload; end; TRglUniformFloatVec3 = class(TRglUniform) procedure SetValue(const AValue: rglv); overload; procedure SetValue(const a0, a1, a2: GLfloat); overload; end; TRglUniformFloatVec4 = class(TRglUniform) procedure SetValue(const AValue: rglv4); overload; procedure SetValue(const a0, a1, a2, a3: GLfloat); overload; end; TRglUniformFloatMat3 = class(TRglUniform) procedure SetValue(const AValue: rglm); end; TRglUniformFloatMat4 = class(TRglUniform) procedure SetValue(const AValue: rglm4); end; TRglUniformInt = class(TRglUniform) procedure SetValue(const AValue: GLint); end; TRglUniformUInt = class(TRglUniform) procedure SetValue(const AValue: GLuint); end; TRglUniformDouble = class(TRglUniform) procedure SetValue(const AValue: Double); end; TRglUniformBool = class(TRglUniform) procedure SetValue(const AValue: Boolean); end; TRglProgram = class strict private FContext: TRglContext; FShaders: TDictionary<Integer, AnsiString>; FHandle: Cardinal; FUniforms: TObjectList<TRglUniform>; public constructor Create(AContext: TRglContext); destructor Destroy; override; procedure AttachShader(AShader: TRglShader); procedure Link; function AddAttribute(const AName: string): Integer; function AddUniform<T: TRglUniform>(const AName: string): T; function TryAddUniform<T: TRglUniform>(const AName: string): T; procedure Use; procedure Unuse; end; __version = record Major, Minor: Integer end; __range = packed record Min, Max: Integer end; __linewidths = record Aliased, Smooth: __range end; TGLImplInfo = record Version: __version; VersionString: string; Vendor: string; Renderer: string; GLSL: string; ContextProfileMask: Integer; ContextFlags: Integer; LineWidths: __linewidths; MaxSamples: Integer; MaxTextureSize: Integer; MaxTextureAnisotropy: GLfloat; end; TStockSurfaceData = record VertexData: GLuint; IndexData: GLuint; IndexCount: GLuint; end; TSolidModel = class; TSolidModelClass = class of TSolidModel; TSolidStoreRec = record VertexData: GLuint; Count: Integer; RefCnt: Integer; end; TDrawable3D = class; TDrawable3DClass = class of TDrawable3D; TRglContext = class strict private FWnd: HWND; FDC: HDC; FRC: HGLRC; class var FInstances: TList<TRglContext>; class constructor ClassCreate; class destructor ClassDestroy; class procedure DoMakeCurrent(DC: HDC; RC: HGLRC; const ACaller: string); static; private FStockSurfaces: TDictionary<TStockSurfaceIndex, TStockSurfaceData>; FStoredSolids: TDictionary<TDrawable3DClass, TSolidStoreRec>; FCustomBuffers: TObjectDictionary<TDrawable3DClass, TObject>; class var FCurrentDC: HDC; class var FCurrentRC: HGLRC; public constructor Create(AWnd: HWND); destructor Destroy; override; procedure SwapBuffers; function GetExtensionNames: TArray<string>; function GetImplInfo: TGLImplInfo; procedure MakeCurrent(const ACaller: string); overload; inline; class procedure MakeCurrent(DC: HDC; RC: HGLRC; const ACaller: string); overload; static; inline; function TryMakeCurrent: Boolean; overload; class function TryMakeCurrent(DC: HDC; RC: HGLRC): Boolean; overload; static; inline; class function GlobalCount: Integer; end; TRglControl = class(TCustomControl) strict private FContext: TRglContext; FClearMask: Cardinal; FGlCtlColor: rglv; FPrevTick: Int64; FFPS: Double; function GetAspectRatio: Double; private FPerfFreq: Int64; protected procedure ApplyClearColor; procedure Resize; override; procedure CreateWnd; override; procedure DestroyWnd; override; procedure WMPaint(var Message: TWMPaint); message WM_PAINT; procedure WMEraseBkgnd(var Message: TWMEraseBkgnd); message WM_ERASEBKGND; procedure WMGetDlgCode(var Message: TWMGetDlgCode); message WM_GETDLGCODE; procedure WMDestroy(var Message: TWMDestroy); message WM_DESTROY; procedure CMColorChanged(var Message: TMessage); message CM_COLORCHANGED; procedure GLInit; virtual; procedure CreateParams(var Params: TCreateParams); override; property ClearMask: Cardinal read FClearMask write FClearMask; public constructor Create(AOwner: TComponent); override; destructor Destroy; override; property Context: TRglContext read FContext; property FPS: Double read FFPS; property AspectRatio: Double read GetAspectRatio; published property Color; end; TDrawableOptionsFrm3D = class(TForm) protected FInitialized: Boolean; FDrawable: TDrawable3D; FOnChange: TNotifyEvent; procedure DrawableDestroyed(Sender: TObject); public constructor Create(AOwner: TComponent; ADrawable: TDrawable3D); reintroduce; virtual; procedure Reassign(ADrawable: TDrawable3D); procedure Initialize; virtual; property Initialized: Boolean read FInitialized; procedure UpdateDrawable; virtual; property Drawable: TDrawable3D read FDrawable; property OnChange: TNotifyEvent read FOnChange write FOnChange; end; TDrawableOptionsFrmClass3D = class of TDrawableOptionsFrm3D; TVisCtl3D = class; TDrawable3D = class(TPersistent) strict private class var FModalLevel: Integer; public class property ModalLevel: Integer read FModalLevel; protected FTag: NativeInt; FVisible: Boolean; FCtl: TVisCtl3D; FDefaultProgram: string; FLineWidth: Single; FAnimationSpeed: Double; FOnChange: TNotifyEvent; FOptionsFrmClass: TDrawableOptionsFrmClass3D; FParent: TDrawable3D; FChildren: TList<TDrawable3D>; FParentTag: NativeUInt; FState: NativeInt; function TryContextCurrent: Boolean; private FOnClick: TNotifyEvent; FProtected: Boolean; procedure SetVisible(const Value: Boolean); procedure Draw(const AGlobalTime: Double); virtual; procedure Setup; virtual; procedure Recreate; virtual; procedure ProjectionChanged; virtual; function GetChild(Index: Integer): TDrawable3D; function GetChildCount: Integer; property DefaultProgram: string read FDefaultProgram; procedure SetAnimationSpeed(const Value: Double); function GetDisplayed: Boolean; protected procedure Changed; overload; inline; procedure Changed(Sender: TObject); overload; inline; procedure FreeGLResources; virtual; procedure GLRelease; virtual; function GetLineWidth: Single; virtual; procedure SetLineWidth(const Value: Single); virtual; procedure DoClick; procedure Click; virtual; public constructor Create(ACtl: TVisCtl3D); virtual; destructor Destroy; override; function CreateChild<T: TDrawable3D>: T; procedure DeleteChild(AChild: TDrawable3D); procedure DeleteChildren(ATagMask: NativeUInt); property AnimationSpeed: Double read FAnimationSpeed write SetAnimationSpeed; property ChildCount: Integer read GetChildCount; property Children[Index: Integer]: TDrawable3D read GetChild; property Control: TVisCtl3D read FCtl; property Visible: Boolean read FVisible write SetVisible; property Displayed: Boolean read GetDisplayed; property LineWidth: Single read GetLineWidth write SetLineWidth; property Parent: TDrawable3D read FParent; property Tag: NativeInt read FTag write FTag; property State: NativeInt read FState write FState; property OptionsFormClass: TDrawableOptionsFrmClass3D read FOptionsFrmClass write FOptionsFrmClass; property OnChange: TNotifyEvent read FOnChange write FOnChange; property OnClick: TNotifyEvent read FOnClick write FOnClick; end; TDrawableList3D = class(TDrawable3D) strict private FList: TObjectList<TDrawable3D>; FSortedList: TList<TDrawable3D>; FTimeDependent: Boolean; FAlphaBlending: Boolean; FPrevCameraPose: TCameraPose; FStencilIDs: array[Byte] of Pointer; FSortOrderDirty: Boolean; function GetItem(Index: Integer): TDrawable3D; procedure SetItem(Index: Integer; const Value: TDrawable3D); function GetItemCount: Integer; procedure ObjsNotify(Sender: TObject; const Item: TDrawable3D; Action: TCollectionNotification); procedure Resort; private procedure Draw(const AGlobalTime: Double); override; function GetObjFromStencilID(AStencilID: Byte): TDrawable3D; protected procedure FreeGLResources; override; procedure GLRelease; override; public constructor Create(ACtl: TVisCtl3D); override; destructor Destroy; override; procedure MoveUp(ADrawable: TDrawable3D); procedure MoveDown(ADrawable: TDrawable3D); property List: TObjectList<TDrawable3D> read FList; property Items[Index: Integer]: TDrawable3D read GetItem write SetItem; default; property ItemCount: Integer read GetItemCount; property TimeDependent: Boolean read FTimeDependent; end; TScene = class(TDrawable3D) public constructor Create(ACtl: TVisCtl3D); override; end; TRefAxes = class(TDrawable3D) strict private const N = 64; var FVertexData: GLuint; var FVAO: GLuint; private procedure Draw(const AGlobalTime: Double); override; procedure Setup; override; protected procedure FreeGLResources; override; procedure GLRelease; override; public constructor Create(ACtl: TVisCtl3D); override; destructor Destroy; override; end; TGeometricObject3D = class(TDrawable3D) private FColor: TColor; FPosition: rglv; FDirection: rglv; FScale: rglv; FRotation: Single; FObjectMatrix: rglm4; FNormalMatrix: rglm; FManualMatrix: rglm4; FUseManualMatrix: Boolean; FColorNotApplicable: Boolean; function GetColor: TColor; virtual; procedure SetColor(const Value: TColor); virtual; procedure SetPosition(const Value: rglv); procedure SetDirection(const Value: rglv); procedure SetScale(const Value: rglv); procedure SetRotation(const Value: Single); private procedure ComputeOM; virtual; procedure Draw(const AGlobalTime: Double); override; procedure SetManualMatrix(const Value: rglm4); overload; procedure SetUseManualMatrix(const Value: Boolean); public constructor Create(ACtl: TVisCtl3D); override; procedure Assign(Source: TPersistent); override; procedure SetManualMatrix; overload; property Color: TColor read GetColor write SetColor; property Position: rglv read FPosition write SetPosition; property Direction: rglv read FDirection write SetDirection; property Scale: rglv read FScale write SetScale; property Rotation: Single read FRotation write SetRotation; property ManualMatrix: rglm4 read FManualMatrix write SetManualMatrix; property UseManualMatrix: Boolean read FUseManualMatrix write SetUseManualMatrix; property ColorNotApplicable: Boolean read FColorNotApplicable; end; TParamCurveFamilySize = record nx, ny: Integer; class operator Equal(const Left, Right: TParamCurveFamilySize): Boolean; class operator NotEqual(const Left, Right: TParamCurveFamilySize): Boolean; class operator Implicit(const AValue: Integer): TParamCurveFamilySize; class operator Implicit(const AValue: TSize): TParamCurveFamilySize; class operator Implicit(const AValue: rglv2): TParamCurveFamilySize; constructor Create(X, Y: Integer); end; TAbstractSurface3D = class abstract(TGeometricObject3D) private FShowSurface: Boolean; FShowParameterCurves: Boolean; FLineColor: TColor; FParamCurveCounts: TParamCurveFamilySize; FUnisided: Boolean; private procedure RecreateParamCurves; virtual; procedure SetShowParameterCurves(const Value: Boolean); procedure SetShowSurface(const Value: Boolean); procedure UpdateDefProgram; virtual; procedure SetLineColor(const Value: TColor); procedure SetParamCurveCounts(const Value: TParamCurveFamilySize); procedure SetUnisided(const Value: Boolean); public constructor Create(ACtl: TVisCtl3D); override; procedure Assign(Source: TPersistent); override; property LineColor: TColor read FLineColor write SetLineColor; property ShowSurface: Boolean read FShowSurface write SetShowSurface; property ShowParameterCurves: Boolean read FShowParameterCurves write SetShowParameterCurves; property Unisided: Boolean read FUnisided write SetUnisided; property ParamCurveCounts: TParamCurveFamilySize read FParamCurveCounts write SetParamCurveCounts; end; TSurface3D<vtype> = class abstract(TAbstractSurface3D) private FVertexData: GLuint; FIndexData: GLuint; FPCIData: GLuint; FIndexCount: Integer; FPCICount: Integer; FVAO: GLuint; FSurfProgram: string; FSurfProgramUnisided: string; FCurveProgram: string; var FStockSurface: Boolean; FStockID: TStockSurfaceIndex; private procedure Setup; override; procedure Draw(const AGlobalTime: Double); override; procedure RecreateParamCurves; override; procedure UpdateDefProgram; override; protected FSurfProc: TParamSurfProc<vtype>; protected procedure FreeGLResources; override; procedure GLRelease; override; public destructor Destroy; override; end; TBasicSurface3D = class abstract(TSurface3D<GLfloat6>) constructor Create(ACtl: TVisCtl3D); override; end; TColoredSurface3D = class abstract(TSurface3D<GLfloat9>) constructor Create(ACtl: TVisCtl3D); override; end; TRectDom = record umin, umax, vmin, vmax: Double; class operator Equal(const Left, Right: TRectDom): Boolean; class operator NotEqual(const Left, Right: TRectDom): Boolean; constructor Create(const umin, umax, vmin, vmax: Double); end; TCustomSurface = class(TBasicSurface3D) strict private FSurfaceFunction: TSurfParamFcn; FData: TArray<rglv>; FDomain: TRectDom; procedure SetDomain(const Value: TRectDom); procedure SetSurfaceFunction(const Value: TSurfParamFcn); procedure SetData(const Value: TArray<rglv>); class procedure SurfProc(out Vertices: TArray<GLfloat6>; out Indices, PCIs: TArray<GLuint>; A, B, pccx, pccy: Integer; PCOnly: Boolean; Data: Pointer); static; private procedure Recreate; override; public Nx, Ny: Integer; constructor Create(ACtl: TVisCtl3D); override; property SurfaceFunction: TSurfParamFcn read FSurfaceFunction write SetSurfaceFunction; property Data: TArray<rglv> read FData write SetData; property Domain: TRectDom read FDomain write SetDomain; end; TCustomColoredSurface = class(TColoredSurface3D) strict private FSurfaceFunction: TSurfParamFcn; FSurfaceColorFunction: TSurfParamColorFcn; FData: TArray<glr3c3v>; FDomain: TRectDom; procedure SetDomain(const Value: TRectDom); procedure SetSurfaceFunction(const Value: TSurfParamFcn); procedure SetSurfaceColorFunction(const Value: TSurfParamColorFcn); procedure SetData(const Value: TArray<GLr3c3v>); class procedure SurfProc(out Vertices: TArray<GLfloat9>; out Indices, PCIs: TArray<GLuint>; A, B, pccx, pccy: Integer; PCOnly: Boolean; Data: Pointer); static; private procedure Recreate; override; public Nx, Ny: Integer; constructor Create(ACtl: TVisCtl3D); override; property SurfaceFunction: TSurfParamFcn read FSurfaceFunction write SetSurfaceFunction; property SurfaceColorFunction: TSurfParamColorFcn read FSurfaceColorFunction write SetSurfaceColorFunction; property Data: TArray<GLr3c3v> read FData write SetData; property Domain: TRectDom read FDomain write SetDomain; end; TEllipsoid = class(TBasicSurface3D) private function GetScale: rglv; procedure SetScale(const Value: rglv); public constructor Create(ACtl: TVisCtl3D); override; property AxisLengths: rglv read GetScale write SetScale; end; TSphere = class(TEllipsoid) private function GetRadius: Single; procedure SetRadius(const Value: Single); public constructor Create(ACtl: TVisCtl3D); override; property Radius: Single read GetRadius write SetRadius; end; TCylinder = class(TBasicSurface3D) private function GetAxisLengths: rglv2; function GetHeight: Single; procedure SetAxisLengths(const Value: rglv2); procedure SetHeight(const Value: Single); function GetRadius: Single; procedure SetRadius(const Value: Single); public constructor Create(ACtl: TVisCtl3D); override; property AxisLengths: rglv2 read GetAxisLengths write SetAxisLengths; property Height: Single read GetHeight write SetHeight; property Radius: Single read GetRadius write SetRadius; end; TCone = class(TBasicSurface3D) private function GetAxisLengths: rglv2; function GetHeight: Single; procedure SetAxisLengths(const Value: rglv2); procedure SetHeight(const Value: Single); public constructor Create(ACtl: TVisCtl3D); override; property AxisLengths: rglv2 read GetAxisLengths write SetAxisLengths; property Height: Single read GetHeight write SetHeight; end; TPlane = class(TBasicSurface3D) public constructor Create(ACtl: TVisCtl3D); override; end; TDisk = class(TBasicSurface3D) private function GetRadius: Single; procedure SetRadius(const Value: Single); public constructor Create(ACtl: TVisCtl3D); override; property Radius: Single read GetRadius write SetRadius; end; TInterval = record a, b: Double; class operator Equal(const Left, Right: TInterval): Boolean; class operator NotEqual(const Left, Right: TInterval): Boolean; constructor Create(const a, b: Double); end; TCurve3D = class(TGeometricObject3D) strict private FVertexData: GLuint; FCount: Integer; FCurveFunction: TCurveParamFcn; FData: TArray<rglv>; FDomain: TInterval; FVAO: GLuint; procedure SetDomain(const Value: TInterval); procedure SetCurveFunction(const Value: TCurveParamFcn); procedure SetData(const Value: TArray<rglv>); procedure Sample(out Vertices: TArray<rglv>); private procedure Recreate; override; private procedure Setup; override; procedure Draw(const AGlobalTime: Double); override; protected procedure FreeGLResources; override; procedure GLRelease; override; public constructor Create(ACtl: TVisCtl3D); override; destructor Destroy; override; property CurveFunction: TCurveParamFcn read FCurveFunction write SetCurveFunction; property Data: TArray<rglv> read FData write SetData; property Domain: TInterval read FDomain write SetDomain; end; TColoredCurve3D = class(TGeometricObject3D) strict private FVertexData: GLuint; FCount: Integer; FCurveFunction: TCurveParamFcn; FCurveColorFunction: TCurveParamColorFcn; FData: TArray<GLr3c3v>; FDomain: TInterval; FVAO: GLuint; procedure SetDomain(const Value: TInterval); procedure SetCurveFunction(const Value: TCurveParamFcn); procedure SetCurveColorFunction(const Value: TCurveParamColorFcn); procedure SetData(const Value: TArray<GLr3c3v>); procedure Sample(out Vertices: TArray<GLr3c3v>); private procedure Recreate; override; private procedure Setup; override; procedure Draw(const AGlobalTime: Double); override; protected procedure FreeGLResources; override; procedure GLRelease; override; public constructor Create(ACtl: TVisCtl3D); override; destructor Destroy; override; property CurveFunction: TCurveParamFcn read FCurveFunction write SetCurveFunction; property CurveColorFunction: TCurveParamColorFcn read FCurveColorFunction write SetCurveColorFunction; property Data: TArray<GLr3c3v> read FData write SetData; property Domain: TInterval read FDomain write SetDomain; end; TScatterPlot = class abstract(TGeometricObject3D) private FVertexData: GLuint; FIndexData: GLuint; FIndexCount: Integer; FVAO: GLuint; FInstanceData: GLuint; FSize: Single; procedure MakeBaseMarker; procedure SetSize(const Value: Single); procedure Recreate; override; procedure Draw(const AGlobalTime: Double); override; protected procedure FreeGLResources; override; procedure GLRelease; override; public constructor Create(ACtl: TVisCtl3D); override; destructor Destroy; override; property Size: Single read FSize write SetSize; end; TSimpleScatterPlot = class(TScatterPlot) private FPoints: TArray<GLfloat3>; private procedure Setup; override; procedure Draw(const AGlobalTime: Double); override; procedure SetPoints(const Value: TArray<GLfloat3>); overload; public constructor Create(ACtl: TVisCtl3D); override; property Data: TArray<GLfloat3> write SetPoints; end; TAdvScatterPlot = class(TScatterPlot) private FPoints: TArray<GLfloat7>; private procedure Setup; override; procedure Draw(const AGlobalTime: Double); override; procedure SetPoints(const Value: TArray<GLfloat7>); overload; procedure SetPoints(const Value: TArray<Double>); overload; public constructor Create(ACtl: TVisCtl3D); override; property Data: TArray<GLfloat7> write SetPoints; property DataAsDoubles: TArray<Double> write SetPoints; end; TVectorField = class(TGeometricObject3D) private class var FVertexCount: Integer; var FVertexData: GLuint; FVAO: GLuint; FInstanceData: GLuint; FSize: Single; FVectors: TArray<GLr3v3c3v>; FMaxNorm: Single; FAttribColors: Boolean; FAnchorPoint: Single; procedure MakeBaseArrow; class function MakeMatrix(const AVector: rglv): rglm; static; class function Prepare(const AVectors: TArray<GLr3v3c3v>; out AMaxNorm: Single): TArray<GLfloat15>; static; procedure SetSize(const Value: Single); procedure SetVectors(const Value: TArray<GLr3v3c3v>); procedure SetAttribColors(const Value: Boolean); procedure SetAnchorPoint(const Value: Single); private procedure Setup; override; procedure Draw(const AGlobalTime: Double); override; procedure Recreate; override; protected procedure FreeGLResources; override; procedure GLRelease; override; public constructor Create(ACtl: TVisCtl3D); override; destructor Destroy; override; property Size: Single read FSize write SetSize; property Data: TArray<GLr3v3c3v> write SetVectors; property PerVertexColors: Boolean read FAttribColors write SetAttribColors; property AnchorPoint: Single read FAnchorPoint write SetAnchorPoint; end; TTransparentColorMode = (tcmOff, tcmEqual, tcmDistance, tcmBipolar); TTransparentColorModeHelper = record helper for TTransparentColorMode function ToString: string; class function FromString(const S: string): TTransparentColorMode; static; end; TImageRect = class(TGeometricObject3D) private FTexture: GLuint; FVertexData: GLuint; FVAO: GLuint; FBitmap: TBitmap; FTransparentColor: TColor; FOpaqueColor: TColor; FTransparentColorMode: TTransparentColorMode; private procedure Setup; override; procedure Draw(const AGlobalTime: Double); override; procedure Recreate; override; procedure BitmapChanged(Sender: TObject); procedure SetBitmap(const Value: TBitmap); procedure SetTransparentColor(const Value: TColor); procedure SetOpaqueColor(const Value: TColor); procedure SetTransparentColorMode(const Value: TTransparentColorMode); protected procedure FreeGLResources; override; procedure GLRelease; override; public constructor Create(ACtl: TVisCtl3D); override; destructor Destroy; override; property Bitmap: TBitmap read FBitmap write SetBitmap; property TransparentColor: TColor read FTransparentColor write SetTransparentColor; property OpaqueColor: TColor read FOpaqueColor write SetOpaqueColor; property TransparentColorMode: TTransparentColorMode read FTransparentColorMode write SetTransparentColorMode; end; TTextRect = class(TGeometricObject3D) private FTexture: GLuint; FVertexData: GLuint; FVAO: GLuint; FText: string; FFont: TFont; FAspect: Double; FTextResFactor: Double; FHighQuality: Boolean; FOpacity: Double; FAnchorPoint: TAnchorPoint; FDisplacement: rglv2; FFaceScreen: Boolean; procedure SetAnchorPoint(const Value: TAnchorPoint); procedure SetText(const Value: string); procedure SetFont(const Value: TFont); procedure SetOpacity(const Value: Double); procedure SetDisplacement(const Value: rglv2); procedure SetFaceScreen(const Value: Boolean); procedure FontChanged(Sender: TObject); procedure SetTextResFactor(const Value: Double); procedure SetHighQuality(const Value: Boolean); function MakeBitmap: TBitmap; private procedure Setup; override; procedure Draw(const AGlobalTime: Double); override; procedure Recreate; override; protected procedure FreeGLResources; override; procedure GLRelease; override; public constructor Create(ACtl: TVisCtl3D); override; destructor Destroy; override; property AnchorPoint: TAnchorPoint read FAnchorPoint write SetAnchorPoint; property Font: TFont read FFont write SetFont; property Text: string read FText write SetText; property Aspect: Double read FAspect; property TextResFactor: Double read FTextResFactor write SetTextResFactor; property HighQuality: Boolean read FHighQuality write SetHighQuality; property Opacity: Double read FOpacity write SetOpacity; property FaceScreen: Boolean read FFaceScreen write SetFaceScreen; property Displacement: rglv2 read FDisplacement write SetDisplacement; end; TAxis = class(TGeometricObject3D) private const PTAG_AXISLABEL = 1; var FCylinder: TCylinder; FLabels: Boolean; FLabelFont: TFont; FLabelDelta: Double; FNegativeLength: Double; FLength: Double; FRadius: Double; FLabelFormat: string; procedure FontChange(Sender: TObject); procedure SetLabelDelta(const Value: Double); procedure SetLabelFont(const Value: TFont); procedure SetLabels(const Value: Boolean); procedure SetLength(const Value: Double); procedure SetNegativeLength(const Value: Double); procedure SetRadius(const Value: Double); procedure SetLabelFormat(const Value: string); procedure SetupLabels; function GetColor: TColor; override; procedure SetColor(const Value: TColor); override; private FIndex: Integer; procedure Setup; override; procedure Recreate; override; procedure ProjectionChanged; override; public constructor Create(ACtl: TVisCtl3D); override; destructor Destroy; override; function FormatAxisLabel(const AFormat: string; const AValue: Double): string; property Labels: Boolean read FLabels write SetLabels; property LabelFont: TFont read FLabelFont write SetLabelFont; property LabelFormat: string read FLabelFormat write SetLabelFormat; property LabelDelta: Double read FLabelDelta write SetLabelDelta; property Length: Double read FLength write SetLength; property NegativeLength: Double read FNegativeLength write SetNegativeLength; property Radius: Double read FRadius write SetRadius; end; TGrid = class(TGeometricObject3D) private const PTAG_GRIDLABEL = 2; var FVertexData: GLuint; FCount: Integer; FVAO: GLuint; FXMin, FXMax, FXDelta: Double; FYMin, FYMax, FYDelta: Double; private FIndex: Integer; procedure Setup; override; procedure Draw(const AGlobalTime: Double); override; procedure Recreate; override; procedure SetXDelta(const Value: Double); procedure SetXMax(const Value: Double); procedure SetXMin(const Value: Double); procedure SetYDelta(const Value: Double); procedure SetYMax(const Value: Double); procedure SetYMin(const Value: Double); protected procedure FreeGLResources; override; procedure GLRelease; override; public constructor Create(ACtl: TVisCtl3D); override; destructor Destroy; override; property XMin: Double read FXMin write SetXMin; property XMax: Double read FXMax write SetXMax; property XDelta: Double read FXDelta write SetXDelta; property YMin: Double read FYMin write SetYMin; property YMax: Double read FYMax write SetYMax; property YDelta: Double read FYDelta write SetYDelta; end; TAxes = class(TGeometricObject3D) private FXAxis, FYAxis, FZAxis: TAxis; private procedure AxisChanged(Sender: TObject); procedure GridChanged(Sender: TObject); private procedure SetXAxis(const Value: TAxis); procedure SetYAxis(const Value: TAxis); procedure SetZAxis(const Value: TAxis); function GetGridCount: Integer; procedure SetGridCount(const Value: Integer); public constructor Create(ACtl: TVisCtl3D); override; property X: TAxis read FXAxis write SetXAxis; property Y: TAxis read FYAxis write SetYAxis; property Z: TAxis read FZAxis write SetZAxis; property GridCount: Integer read GetGridCount write SetGridCount; end; TSolidModel = class abstract(TGeometricObject3D) private FVertexData: GLuint; FCount: Integer; FVAO: GLuint; class var FSolidClasses: TDictionary<string, TDrawable3DClass>; class procedure RegisterClass(const AClass: TDrawable3DClass; const AName: string); static; class constructor ClassCreate; class destructor ClassDestroy; private procedure Setup; override; procedure Recreate; override; procedure Draw(const AGlobalTime: Double); override; procedure MakeBaseBuffer; virtual; protected Stored: Boolean; function ModelClass: TSolidModelClass; protected procedure FreeGLResources; override; procedure GLRelease; override; public constructor Create(ACtl: TVisCtl3D); override; destructor Destroy; override; class function FromString(const S: string): TDrawable3DClass; static; class function TryFromString(const S: string; out AClass: TDrawable3DClass): Boolean; static; end; TObjModel = class(TSolidModel) private FSource: string; FBuf: TArray<GLfloat6>; procedure MakeBaseBuffer; override; public constructor Create(ACtl: TVisCtl3D); override; procedure LoadModel(const AData: string); overload; procedure LoadModel(const AData: TArray<string>); overload; procedure LoadModelFromFile(const AFileName: TFileName); overload; procedure LoadModelFromFile(const AFileName: TFileName; const Encoding: TEncoding); overload; property Source: string read FSource write LoadModel; end; TRawModel = class abstract(TSolidModel) private procedure MakeBaseBuffer; override; final; protected function GetRawData: TArray<GLfloat6>; virtual; end; TSolidCube = class(TRawModel) protected function GetRawData: TArray<GLfloat6>; override; public constructor Create(ACtl: TVisCtl3D); override; end; TSolidCylinder = class(TGeometricObject3D) private type TBufferStoreKey = packed record InnerRadius: Double; Angle: Double; end; TBufferStoreItem = record InnerRadius: Double; Angle: Double; VertexData: GLuint; Counts: TArray<Integer>; TwoSides: Boolean; RefCnt: Integer; end; var FBSK: TBufferStoreKey; FVertexData: GLuint; FCounts: TArray<Integer>; FTwoSides: Boolean; FVAO: GLuint; FAngle: Double; FInnerRadius: Double; function GetAxisLengths: rglv2; function GetHeight: Single; procedure SetAxisLengths(const Value: rglv2); procedure SetHeight(const Value: Single); function GetRadius: Single; procedure SetRadius(const Value: Single); procedure SetAngle(const Value: Double); procedure SetInnerRadiusFraction(const Value: Double); procedure ConstructBufferSinHole; procedure ConstructBufferConHole; procedure GetBufferData; procedure ReleaseBuffer; private procedure Setup; override; procedure Recreate; override; procedure Draw(const AGlobalTime: Double); override; protected procedure FreeGLResources; override; procedure GLRelease; override; public constructor Create(ACtl: TVisCtl3D); override; destructor Destroy; override; property Angle: Double read FAngle write SetAngle; property AxisLengths: rglv2 read GetAxisLengths write SetAxisLengths; property Height: Single read GetHeight write SetHeight; property Radius: Single read GetRadius write SetRadius; property InnerRadiusFraction: Double read FInnerRadius write SetInnerRadiusFraction; end; TSolidCone = class(TGeometricObject3D) private FVertexData: GLuint; FCount: Integer; FVAO: GLuint; function GetAxisLengths: rglv2; function GetHeight: Single; procedure SetAxisLengths(const Value: rglv2); procedure SetHeight(const Value: Single); function GetRadius: Single; procedure SetRadius(const Value: Single); procedure MakeBaseBuffer; private procedure Setup; override; procedure Recreate; override; procedure Draw(const AGlobalTime: Double); override; protected procedure FreeGLResources; override; procedure GLRelease; override; public constructor Create(ACtl: TVisCtl3D); override; destructor Destroy; override; property AxisLengths: rglv2 read GetAxisLengths write SetAxisLengths; property Height: Single read GetHeight write SetHeight; property Radius: Single read GetRadius write SetRadius; end; TSolidTetrahedron = class(TRawModel) protected function GetRawData: TArray<GLfloat6>; override; public constructor Create(ACtl: TVisCtl3D); override; end; TSolidOctahedron = class(TRawModel) protected function GetRawData: TArray<GLfloat6>; override; public constructor Create(ACtl: TVisCtl3D); override; end; TSolidDodecahedron = class(TRawModel) protected function GetRawData: TArray<GLfloat6>; override; public constructor Create(ACtl: TVisCtl3D); override; end; TSolidIcosahedron = class(TRawModel) protected function GetRawData: TArray<GLfloat6>; override; public constructor Create(ACtl: TVisCtl3D); override; end; TSolidPyramid = class(TRawModel) protected function GetRawData: TArray<GLfloat6>; override; public constructor Create(ACtl: TVisCtl3D); override; end; TArrow = class(TGeometricObject3D) private v: rglv; FLine: TSolidCylinder; FHead: TSolidCone; Q: Single; FHeadSize: Single; FSetup: Boolean; function GetColor: TColor; override; procedure SetColor(const Value: TColor); override; procedure SetVector(const Value: rglv); procedure SetHeadSize(const Value: Single); procedure SetAspect(const Value: Single); protected function GetLineWidth: Single; override; procedure SetLineWidth(const Value: Single); override; procedure Setup; override; procedure Recreate; override; public constructor Create(ACtl: TVisCtl3D); override; property Aspect: Single read Q write SetAspect; property HeadSize: Single read FHeadSize write SetHeadSize; property Vector: rglv read v write SetVector; end; TProjection = (Orthographic, Perspective); TProjectionHelper = record helper for TProjection function ToString: string; class function FromString(const S: string): TProjection; static; end; TAnimationTimer = class(TTimer) private FControl: TVisCtl3D; class var FAppEvents: TApplicationEvents; class var FInstances: TList<TAnimationTimer>; class procedure AppIdle(Sender: TObject; var Done: Boolean); class constructor ClassCreate; class destructor ClassDestroy; class var FPrioritize: Boolean; class procedure SetPrioritize(const Value: Boolean); static; public constructor Create(AOwner: TComponent); override; destructor Destroy; override; property Control: TVisCtl3D read FControl write FControl; class property Prioritize: Boolean read FPrioritize write SetPrioritize; end; TPublishedCoordinates = class(TPersistent) strict private FVectorPtr: Prglv; FNotifier: TProc; function GetCoordinate(AIndex: Integer): GLfloat; procedure SetCoordinate(AIndex: Integer; const Value: GLfloat); private constructor Create(AVector: Prglv; const ANotifier: TProc); published property X: GLfloat index 0 read GetCoordinate write SetCoordinate; property Y: GLfloat index 1 read GetCoordinate write SetCoordinate; property Z: GLfloat index 2 read GetCoordinate write SetCoordinate; end; TView3D = class(TDrawable3D) strict private const AnimationDurationSec = 1.0; AnimationFramerateFPS = 30; AnimationFrameCount = Round(AnimationDurationSec * AnimationFramerateFPS); AFC = AnimationFrameCount; var FAnimationTimer: TAnimationTimer; FAnimationBegin: TDateTime; FAnimationEnd: TDateTime; FAnimation: array[0..AFC - 1] of TCameraPoseSp; FTargetCenterIntf: TPublishedCoordinates; procedure AnimationTimerTimer(Sender: TObject); private function GetCameraRelativePosition: rglv; inline; function GetCameraRelativePositionSp: TSphericalCoordinates; inline; function GetPhi: Double; inline; function GetPhiRad: Double; inline; function GetR: Double; inline; function GetTheta: Double; inline; function GetThetaRad: Double; inline; function GetTargetCenter: rglv; inline; procedure SetCameraRelativePosition(const Value: rglv); procedure SetCameraRelativePositionSp(const Value: TSphericalCoordinates); procedure SetPhi(const Value: Double); procedure SetPhiRad(const Value: Double); procedure SetR(const Value: Double); procedure SetTheta(const Value: Double); procedure SetThetaRad(const Value: Double); procedure SetTargetCenter(const Value: rglv); public constructor Create(ACtl: TVisCtl3D); override; destructor Destroy; override; property TargetCenter: rglv read GetTargetCenter write SetTargetCenter; property CameraRelativePosition: rglv read GetCameraRelativePosition write SetCameraRelativePosition; property CameraRelativePositionSp: TSphericalCoordinates read GetCameraRelativePositionSp write SetCameraRelativePositionSp; procedure FixCamera(const ACenter: rglv; const ARelativePosition: rglv); overload; procedure FixCamera(const ACameraPose: TCameraPose); overload; inline; procedure FixCamera(const ACameraPose: TCameraPoseSp); overload; inline; procedure AnimateTo(const ATargetCenter: rglv; const ATargetRelativePosition: rglv); overload; procedure AnimateTo(const ATargetCenter: rglv); overload; procedure AnimateTo(const ACameraPose: TCameraPose); overload; procedure AnimateTo(const ACameraPose: TCameraPoseSp); overload; procedure FinishAnimation; published property r: Double read GetR write SetR; property Theta: Double read GetTheta write SetTheta; property Phi: Double read GetPhi write SetPhi; property ThetaRad: Double read GetThetaRad write SetThetaRad; property PhiRad: Double read GetPhiRad write SetPhiRad; property CentrePoint: TPublishedCoordinates read FTargetCenterIntf; end; TManagedProgram = class strict private FName: string; FProgram: TRglProgram; FUMVP: TRglUniformFloatMat4; FUEye: TRglUniformFloatVec3; FULightPos: TRglUniformFloatVec3; FUColor: TRglUniformFloatVec3; FUSize: TRglUniformFloat; FUAnchorPoint: TRglUniformInt; FUFaceScreen: TRglUniformBool; FUAttribColors: TRglUniformBool; FUAspect: TRglUniformFloat; FUDisplacement: TRglUniformFloatVec2; FUObjectMatrix: TRglUniformFloatMat4; FUNormalMatrix: TRglUniformFloatMat3; FUt: TRglUniformFloat; FUTranspColor: TRglUniformFloatVec3; FUOpaqueColor: TRglUniformFloatVec3; FUTranspColorMode: TRglUniformInt; FUOpacity: TRglUniformFloat; public constructor Create(const AName: string; AProgram: TRglProgram); destructor Destroy; override; property Name: string read FName; property &Program: TRglProgram read FProgram; property UMVP: TRglUniformFloatMat4 read FUMVP; property UEye: TRglUniformFloatVec3 read FUEye; property ULightPos: TRglUniformFloatVec3 read FULightPos; property UColor: TRglUniformFloatVec3 read FUColor; property USize: TRglUniformFloat read FUSize; property UAnchorPoint: TRglUniformInt read FUAnchorPoint; property UFaceScreen: TRglUniformBool read FUFaceScreen; property UAttribColors: TRglUniformBool read FUAttribColors; property UAspect: TRglUniformFloat read FUAspect; property UDisplacement: TRglUniformFloatVec2 read FUDisplacement; property UObjectMatrix: TRglUniformFloatMat4 read FUObjectMatrix; property UNormalMatrix: TRglUniformFloatMat3 read FUNormalMatrix; property Ut: TRglUniformFloat read FUt; property UTranspColor: TRglUniformFloatVec3 read FUTranspColor; property UOpaqueColor: TRglUniformFloatVec3 read FUOpaqueColor; property UTranspColorMode: TRglUniformInt read FUTranspColorMode; property UOpacity: TRglUniformFloat read FUOpacity; end; TProgramMgr = class strict private FControl: TVisCtl3D; FPrograms: TObjectList<TManagedProgram>; FCurrentProgram: TManagedProgram; FTimeDependent: Boolean; function LoadProgramResource(const AName: string; const AData: string): TManagedProgram; public constructor Create(AControl: TVisCtl3D); destructor Destroy; override; function GetProgram(const AName: string): TManagedProgram; procedure UseProgram(const AName: string); procedure BeginMonitorTime; function EndMonitorTime: Boolean; property CurrentProgram: TManagedProgram read FCurrentProgram; end; TMSAAValue = (msaa0 = 0, msaa2 = 2, msaa4 = 4, msaa8 = 8, msaa16 = 16, msaa32 = 32, msaa64 = 64, msaa128 = 128); TVisCtlObjectMenuItem = class(TMenuItem) private FObjRef: TGUID; public property ObjRef: TGUID read FObjRef; end; [ComponentPlatforms(pidWin32 or pidWin64)] TVisCtl3D = class(TRglControl) strict private type TRenderOutputData = record Width, Height: Integer; MSAA: TMSAAValue; PostProc: Boolean; Offscreen: Boolean; function Aspect: Double; class operator Equal(const Left, Right: TRenderOutputData): Boolean; static; class operator NotEqual(const Left, Right: TRenderOutputData): Boolean; static; end; const InvalidRawPoint: TPoint = (X: FixedInt.MinValue; Y: FixedInt.MaxValue); var FRenderOutputData: TRenderOutputData; FPrevRenderOutputData: TRenderOutputData; FRenderToBitmap: Boolean; FRenderToClipboard: Boolean; FRenderFileName: string; FFOV: Double; FProjection: TProjection; M, V, P: rglm4; MVP: rglm4; Eye: rglv; FObjs: TDrawableList3D; FCustomMenuItems: TList<TMenuItem>; FOnBeforeContextPopup: TNotifyEvent; FBackgroundPaintLevel: Integer; FInvalidationTimer: TTimer; FAnimationTimer: TAnimationTimer; FProgramMgr: TProgramMgr; FNewObjects: TList<TDrawable3D>; FPrevMousePoint: TPoint; FLightPos: rglv; FMSAAbuf: GLuint; FMSAAbuf_coloratt: GLuint; FMSAAbuf_dsatt: GLuint; Fauxbuf: GLuint; Fauxbuf_coloratt: GLuint; Fauxbuf_dsatt: GLuint; Fosbuf: GLuint; Fosbuf_coloratt: GLuint; FScreenVAO: GLuint; FScreenQuad: GLuint; FMSAAValue: TMSAAValue; FPostProcessing: Boolean; FPopupMenu: TPopupMenu; FToggleAxesMnuItem: TMenuItem; FRemoveMnuItem: TVisCtlObjectMenuItem; FProjSubmenu: TMenuItem; FOrthogonalMnuItem, FPerspectiveMnuItem: TMenuItem; FMSAASubmenu: TMenuItem; FPPSubmenu: TMenuItem; FImplInfoMnuItem: TMenuItem; FExtInfoMnuItem: TMenuItem; FHelpMnuItem: TMenuItem; FEffects: TPPEs; FMaxSamples: Integer; FPrevTick: Int64; FGlobalTime: Double; FImplData: TGLImplInfo; FExts: TArray<string>; FStencil: Boolean; FScene: TScene; FView: TView3D; FAxes: TAxes; FCenterMarker: TGeometricObject3D; FCenterMarkerHiderTimer: TTimer; FMButtonDown: Boolean; FRawInputAvailable: Boolean; FPreferRawInput: Boolean; FAbandonRawInputIfAbsoluteDevice: Boolean; FAbandonRawInputOnPanEnd: Boolean; FPrePanMousePos: TPoint; FPrePanCamPose: TCameraPose; FRawPanning: Boolean; FPrevRawPoint: TPoint; FPanSensitivity, FOrbitSensitivity: Double; FCanClick: Boolean; FOnViewChanged: TNotifyEvent; procedure SetFov(const Value: Double); procedure SetProjection(const Value: TProjection); procedure ViewChanged(Sender: TObject); procedure ComputeM; procedure ComputeV; procedure ComputeP(const ARenderOutputData: TRenderOutputData); procedure ObjChanged(Sender: TObject); procedure LowPriorityInvalidate; procedure InvalidationTimerTimer(Sender: TObject); procedure AnimationTimerTimer(Sender: TObject); function GetObjectCount: Integer; function GetObject(Index: Integer): TDrawable3D; procedure SetupNewObjects; procedure SetLightPos(const Value: rglv); procedure MakeContextMenu; procedure PopupMenuPopup(Sender: TObject); procedure UpdateContextMenuStates(ASelObj: TDrawable3D); procedure MnuToggleAxes(Sender: TObject); procedure MnuSaveSceneToFile(Sender: TObject); procedure MnuCopySceneToClipboard(Sender: TObject); procedure MnuSetProj(Sender: TObject); procedure MnuSetMSAA(Sender: TObject); procedure MnuImplInfo(Sender: TObject); procedure MnuExtInfo(Sender: TObject); procedure MnuQuickHelp(Sender: TObject); procedure SaveSceneToBitmap(const ARenderOutputData: TRenderOutputData; ACleanUp, ARenderToClipboard: Boolean; const AFileName: string); procedure SetMSAAValue(const Value: TMSAAValue); procedure SetEffects(const Value: TPPEs); function GetShowAxes: Boolean; procedure SetShowAxes(const Value: Boolean); procedure CustomizeMenu(AMenu: TMenu); procedure FreeGLResources; function GetCameraPose: TCameraPose; procedure SetCameraPose(const APose: TCameraPose); function GetCameraRelativePos: rglv; procedure SetCameraRelativePos(const ARelativePos: rglv); procedure CenterMarkerHiderTimerTimer(Sender: TObject); procedure CancelPanning(AAbort: Boolean = False); procedure Pan(∆X, ∆Y: Integer); procedure RevealCenterMarker; procedure DoViewChanged; private LookAt: rglv; r, φ, θ: Double; procedure RemoveObjectOrChild(AObject: TDrawable3D); protected procedure GLInit; override; procedure FramebufferSetup(const ARenderOutputData: TRenderOutputData); procedure Paint; override; procedure Resize; override; procedure Click; override; procedure MouseDown(Button: TMouseButton; Shift: TShiftState; X: Integer; Y: Integer); override; procedure MouseMove(Shift: TShiftState; X: Integer; Y: Integer); override; procedure MouseUp(Button: TMouseButton; Shift: TShiftState; X: Integer; Y: Integer); override; function DoMouseWheel(Shift: TShiftState; WheelDelta: Integer; MousePos: TPoint): Boolean; override; procedure KeyDown(var Key: Word; Shift: TShiftState); override; procedure KeyPress(var Key: Char); override; procedure CreateWnd; override; procedure DestroyWnd; override; procedure WMContextMenu(var Message: TWMContextMenu); message WM_CONTEXTMENU; procedure WMDestroy(var Message: TWMDestroy); message WM_DESTROY; procedure WndProc(var Message: TMessage); override; public constructor Create(AOwner: TComponent); override; destructor Destroy; override; property Scene: TScene read FScene; function NewObject<T: TDrawable3D>: T; procedure AddObject(AObject: TDrawable3D); property ObjectCount: Integer read GetObjectCount; property Objects[Index: Integer]: TDrawable3D read GetObject; procedure RemoveObject(AObject: TDrawable3D); function HitTest(X, Y: Integer): TDrawable3D; overload; function HitTest(const P: TPoint): TDrawable3D; overload; inline; procedure ZoomIn(const Delta: Double; Shift: TShiftState); procedure ZoomOut(const Delta: Double; Shift: TShiftState); procedure Zoom(const Delta: Double; Shift: TShiftState); /// <summary>Adds a custom menu item to the control's main context menu. /// The control doesn't take ownership of the item, and the item must not /// be freed by the caller while the control has a reference to it. The /// caller is allowed to change other properties of the item, except its /// parent. After the item has been added to the control, the control takes /// control of its parent property. Hence, if the item previously belonged /// to a menu, it might be removed from that menu.</summary> procedure AddMenuItem(AMenuItem: TMenuItem); procedure AddMenuItems(AMenu: TMenuItem); procedure RemoveMenuItem(AMenuItem: TMenuItem); procedure BeginBackgroundPaint; procedure EndBackgroundPaint; procedure SaveToBitmap(const AFileName: string; AWidth, AHeight: Integer); procedure ClearScene; property ObjectMgr: TDrawableList3D read FObjs; property ProgramMgr: TProgramMgr read FProgramMgr; property CurrentMatrix: rglm4 read MVP; property CameraPos: rglv read Eye; property CameraRelativePos: rglv read GetCameraRelativePos write SetCameraRelativePos; property CameraPose: TCameraPose read GetCameraPose write SetCameraPose; property LightPos: rglv read FLightPos write SetLightPos; property GlobalTime: Double read FGlobalTime write FGlobalTime; property ImplData: TGLImplInfo read FImplData; property Stencil: Boolean read FStencil; property Mat_M: rglm4 read M; property Mat_V: rglm4 read V; property Mat_P: rglm4 read P; property Mat_MVP: rglm4 read MVP; property Axes: TAxes read FAxes; property _ContextMenu: TPopupMenu read FPopupMenu; published property Anchors; property Align; property AlignWithMargins; property Color; property Cursor; property Effects: TPPEs read FEffects write SetEffects; property Enabled; property Font; property FOV: Double read FFOV write SetFov; property Projection: TProjection read FProjection write SetProjection default Perspective; property PanSensitivity: Double read FPanSensitivity write FPanSensitivity; property OrbitSensitivity: Double read FOrbitSensitivity write FOrbitSensitivity; property MSAA: TMSAAValue read FMSAAValue write SetMSAAValue; property ShowAxes: Boolean read GetShowAxes write SetShowAxes default True; property TabStop default True; property Visible; property View: TView3D read FView; property RawInput: Boolean read FPreferRawInput write FPreferRawInput default True; property AllowAbandonRaw: Boolean read FAbandonRawInputIfAbsoluteDevice write FAbandonRawInputIfAbsoluteDevice default True; property OnBeforeContextPopup: TNotifyEvent read FOnBeforeContextPopup write FOnBeforeContextPopup; property OnClick; property OnDblClick; property OnMouseDown; property OnMouseMove; property OnMouseUp; property OnMouseEnter; property OnMouseLeave; property OnKeyDown; property OnKeyPress; property OnKeyUp; property OnViewChanged: TNotifyEvent read FOnViewChanged write FOnViewChanged; end; procedure rglEnableDebugLog; procedure rglLog(const S: string); overload; {noexcept} procedure rglLog(const S: string; const AArgs: array of const); overload; {noexcept} procedure SaveGraphicToFile(AGraphics: TGraphic; const AFileName: TFileName; AFormat: TImageFormat); overload; procedure SaveGraphicToFile(AGraphics: TGraphic; ADlgOwner: TComponent; const ADefFilename: TFileName = ''); overload; type ERglObjFileError = class(Exception); function ObjFileParseObjData(const AData: TArray<string>): TArray<GLfloat6>; function ObjFilePrintRawBuffer(ABuffer: Pointer; ALength: Integer; const ASep: string = ''): string; overload; function ObjFilePrintRawBuffer(ABuffer: TArray<GLfloat6>; const ASep: string = ''): string; overload; procedure Register; implementation uses StrUtils, Math, DateUtils, IOUtils, Clipbrd, Jpeg, PngImage, TrglImageSizeForm, Character, TrglTableDialog; { Win32 Raw Input } {$REGION Win32 Raw Input} type PRawInputDevice = ^TRawInputDevice; TRawInputDevice = record usUsagePage: USHORT; usUsage: USHORT; dwFlags: DWORD; hwndTarget: HWND; end; PRawInputHeader = ^TRawInputHeader; TRawInputHeader = record dwType: DWORD; dwSize: DWORD; hDevice: THandle; wParam: WPARAM; end; PRawMouse = ^TRawMouse; TRawMouse = record public usFlags: USHORT; strict private Buttons: record case Boolean of False: ( ulButtons: ULONG; ); True: ( usButtonFlags: USHORT; usButtonData: USHORT; ); end; public ulRawButtons: ULONG; lLastX: LONG; lLastY: LONG; ulExtraInformation: ULONG; property ulButtons: ULONG read Buttons.ulButtons write Buttons.ulButtons; property usButtonFlags: USHORT read Buttons.usButtonFlags write Buttons.usButtonFlags; property usButtonData: USHORT read Buttons.usButtonData write Buttons.usButtonData; end; PRawKeyboard = ^TRawKeyboard; TRawKeyboard = record MakeCode: USHORT; Flags: USHORT; Reserved: USHORT; VKey: USHORT; &Message: UInt; ExtraInformation: ULONG; end; PRawHid = ^TRawHid; TRawHid = record dwSizeHid: DWORD; dwCount: DWORD; bRawData: array[0..0] of Byte; end; PRawInput = ^TRawInput; TRawInput = record header: TRawInputHeader; data: record case Byte of 0: (mouse: TRawMouse); 1: (keyboard: TRawKeyboard); 2: (hid: TRawHid); end; end; const RIM_TYPEMOUSE = 0; RIM_TYPEKEYBOARD = 1; RIM_TYPEHID = 2; RID_HEADER = $10000005; RID_INPUT = $10000003; MOUSE_MOVE_RELATIVE = $00; MOUSE_MOVE_ABSOLUTE = $01; MOUSE_VIRTUAL_DESKTOP = $02; MOUSE_ATTRIBUTES_CHANGED = $04; MOUSE_MOVE_NOCOALESCE = $08; function RegisterRawInputDevices(pRawInputDevices: PRAWINPUTDEVICE; uiNumDevices: UINT; cbSize: UINT): BOOL; stdcall; external user32 name 'RegisterRawInputDevices'; function GetRawInputData(hRawInput: LPARAM; uiCommand: UINT; pData: Pointer; var pcbSize: UINT; cbSizeHeader: UINT): UINT; stdcall; external user32 name 'GetRawInputData'; {$ENDREGION} var InvFS: TFormatSettings; RglDebugLog: Boolean; procedure glLineWidth(width: GLfloat); inline; begin end; function Floor64(const X: Double): Int64; begin Result := Int64(Trunc(X)); if Frac(X) < 0 then Dec(Result); end; function rmod(const x, y: Double): Double; inline; begin Result := x - Floor64(x / y) * y; end; { TAnchorPointHelper } class function TAnchorPointHelper.FromString(const S: string): TAnchorPoint; begin for var ap := Low(TAnchorPoint) to High(TAnchorPoint) do if ap.ToString = S then Exit(ap); raise Exception.Create('Invalid anchor point.'); end; function TAnchorPointHelper.H: TLinearAlignment; begin case Self of apTopLeft, apLeft, apBottomLeft: Result := laNegative; apTop, apCenter, apBottom: Result := laMiddle; apTopRight, apRight, apBottomRight: Result := laPositive; else raise Exception.Create('Invalid anchor point.'); end; end; function TAnchorPointHelper.ToString: string; begin if InRange(Ord(Self), Ord(Low(TAnchorPoint)), Ord(High(TAnchorPoint))) then Result := PointNames[Self] else Result := ''; end; function TAnchorPointHelper.V: TLinearAlignment; begin case Self of apTopLeft, apTop, apTopRight: Result := laNegative; apLeft, apCenter, apRight: Result := laMiddle; apBottomLeft, apBottom, apBottomRight: Result := laPositive; else raise Exception.Create('Invalid anchor point.'); end; end; { rglv } procedure rglEnableDebugLog; begin RglDebugLog := RglDebugLog or AllocConsole; end; procedure rglLog(const S: string); {noexcept} begin {$IFDEF DEBUG} if IsDebuggerPresent then OutputDebugString(PChar(S)); {$ENDIF} if RglDebugLog then try Writeln(S); except RglDebugLog := False; end; end; procedure rglLog(const S: string; const AArgs: array of const); {noexcept} begin try rglLog(Format(S, Aargs)); except var ArgsStr := ''; try for var LArg in AArgs do case LArg.VType of vtInteger: ArgsStr := ArgsStr + IfThen(not ArgsStr.IsEmpty, ', ') + LArg.VInteger.ToString; vtInt64: ArgsStr := ArgsStr + IfThen(not ArgsStr.IsEmpty, ', ') + LArg.VInt64^.ToString; vtBoolean: ArgsStr := ArgsStr + IfThen(not ArgsStr.IsEmpty, ', ') + LArg.VBoolean.ToString(TUseBoolStrs.True); vtExtended: ArgsStr := ArgsStr + IfThen(not ArgsStr.IsEmpty, ', ') + LArg.VExtended^.ToString; vtString: ArgsStr := ArgsStr + IfThen(not ArgsStr.IsEmpty, ', ') + QuotedStr(string(LArg.VString^)); vtAnsiString: ArgsStr := ArgsStr + IfThen(not ArgsStr.IsEmpty, ', ') + QuotedStr(string(AnsiString(LArg.VAnsiString))); vtUnicodeString: ArgsStr := ArgsStr + IfThen(not ArgsStr.IsEmpty, ', ') + QuotedStr(string(UnicodeString(LArg.VUnicodeString))); vtWideString: ArgsStr := ArgsStr + IfThen(not ArgsStr.IsEmpty, ', ') + QuotedStr(string(WideString(LArg.VWideString))); vtChar: ArgsStr := ArgsStr + IfThen(not ArgsStr.IsEmpty, ', ') + QuotedStr(string(LArg.VChar)); vtWideChar: ArgsStr := ArgsStr + IfThen(not ArgsStr.IsEmpty, ', ') + QuotedStr(string(LArg.VWideChar)); vtPointer: ArgsStr := ArgsStr + IfThen(not ArgsStr.IsEmpty, ', ') + NativeUInt(LArg.VPointer).ToHexString; vtObject: ArgsStr := ArgsStr + IfThen(not ArgsStr.IsEmpty, ', ') + NativeUInt(LArg.VObject).ToHexString; vtClass: ArgsStr := ArgsStr + IfThen(not ArgsStr.IsEmpty, ', ') + NativeUInt(LArg.VClass).ToHexString; else ArgsStr := ArgsStr + IfThen(not ArgsStr.IsEmpty, ', ') + '?'; end; except ArgsStr := '<argument error>'; end; rglLog('rglLog: Format failed on ' + QuotedStr(S) + ' with arguments ' + ArgsStr + '.'); end; end; class operator rglv.Add(const Left, Right: rglv): rglv; begin Result.x := Left.x + Right.x; Result.y := Left.y + Right.y; Result.z := Left.z + Right.z; end; constructor rglv.Create(const x, y, z: Single); begin Self.x := x; Self.y := y; Self.z := z; end; class operator rglv.Divide(const Left: rglv; const Right: Single): rglv; begin Result.x := Left.x / Right; Result.y := Left.y / Right; Result.z := Left.z / Right; end; class operator rglv.Equal(const Left, Right: rglv): Boolean; begin Result := (Left.x = Right.x) and (Left.y = Right.y) and (Left.z = Right.z); end; class operator rglv.Implicit(const c: TColor): rglv; begin const rgb = ColorToRGB(c); Result.r := GetRValue(rgb) / 255; Result.g := GetGValue(rgb) / 255; Result.b := GetBValue(rgb) / 255; end; class operator rglv.Implicit(const v: rglv): GLfloat3; begin Result := v.elem; end; class operator rglv.Implicit(const arr: GLfloat3): rglv; begin Result.elem := arr; end; class operator rglv.LogicalXor(const Left, Right: rglv): rglv; begin Result.x := Left.y * Right.z - Left.z * Right.y; Result.y := Left.z * Right.x - Left.x * Right.z; Result.z := Left.x * Right.y - Left.y * Right.x; end; class operator rglv.Multiply(const Left: Single; const Right: rglv): rglv; begin Result.x := Left * Right.x; Result.y := Left * Right.y; Result.z := Left * Right.z; end; class operator rglv.Multiply(const Left: rglv; const Right: Single): rglv; begin Result.x := Left.x * Right; Result.y := Left.y * Right; Result.z := Left.z * Right; end; class operator rglv.Multiply(const Left, Right: rglv): Single; begin Result := Left.x * Right.x + Left.y * Right.y + Left.z * Right.z; end; function rglv.Norm: Single; begin Result := Sqrt(Self * Self); end; function rglv.Normalized: rglv; begin Result := (1 / Norm) * Self; end; function rglv.NormSquare: Single; begin Result := Self * Self; end; class operator rglv.NotEqual(const Left, Right: rglv): Boolean; begin Result := not (Left = Right); end; function rglv.ptr: PGLfloat; begin Result := PGLfloat(@Self); end; class operator rglv.Subtract(const Left, Right: rglv): rglv; begin Result.x := Left.x - Right.x; Result.y := Left.y - Right.y; Result.z := Left.z - Right.z; end; function rglv.xy: rglv2; begin Result := vec2(x, y); end; class function rglv.Zero: rglv; const Z: rglv = ( elem: ( 0, 0, 0 ) ); begin Result := Z; end; function vec(const x, y, z: Single): rglv; begin Result.x := x; Result.y := y; Result.z := z; end; { rglm } class operator rglm.Add(const Left, Right: rglm): rglm; begin for var i := 0 to _elemh do Result.elem[i] := Left.elem[i] + Right.elem[i]; end; constructor rglm.Create(const m11, m12, m13, m21, m22, m23, m31, m32, m33: Single); begin elem[0] := m11; elem[1] := m12; elem[2] := m13; elem[3] := m21; elem[4] := m22; elem[5] := m23; elem[6] := m31; elem[7] := m32; elem[8] := m33; end; constructor rglm.CreateFromColumns(const u, v, w: rglv); begin elem[0] := u.x; elem[1] := v.x; elem[2] := w.x; elem[3] := u.y; elem[4] := v.y; elem[5] := w.y; elem[6] := u.z; elem[7] := v.z; elem[8] := w.z; end; class operator rglm.Divide(const Left: rglm; const Right: Single): rglm; begin for var i := 0 to _elemh do Result.elem[i] := Left.elem[i] / Right; end; class operator rglm.Equal(const Left, Right: rglm): Boolean; begin for var i := 0 to _elemh do if Left.elem[i] <> Right.elem[i] then Exit(False); Result := True; end; class function rglm.Identity: rglm; const I: rglm = ( elem: ( 1, 0, 0, 0, 1, 0, 0, 0, 1 ) ); begin Result := I; end; function rglm.Inverse: rglm; begin Result := mat( elem[4] * elem[8] - elem[5] * elem[7], elem[2] * elem[7] - elem[1] * elem[8], elem[1] * elem[5] - elem[2] * elem[4], elem[5] * elem[6] - elem[3] * elem[8], elem[0] * elem[8] - elem[2] * elem[6], elem[2] * elem[3] - elem[0] * elem[5], elem[3] * elem[7] - elem[4] * elem[6], elem[1] * elem[6] - elem[0] * elem[7], elem[0] * elem[4] - elem[1] * elem[3] ) / ( elem[0] * (elem[4] * elem[8] - elem[5] * elem[7]) + elem[1] * (elem[5] * elem[6] - elem[3] * elem[8]) + elem[2] * (elem[3] * elem[7] - elem[4] * elem[6]) ) end; class operator rglm.Multiply(const Left: rglm; const Right: rglv): rglv; begin Result := Default(rglv); for var i := 0 to _dimh do for var j := 0 to _dimh do Result.elem[i] := Result.elem[i] + Left.m[i, j] * Right.elem[j]; end; class operator rglm.Multiply(const Left: Single; const Right: rglm): rglm; begin for var i := 0 to _elemh do Result.elem[i] := Left * Right.elem[i]; end; class operator rglm.Multiply(const Left, Right: rglm): rglm; begin Result := Default(rglm); for var i := 0 to _dimh do for var j := 0 to _dimh do for var k := 0 to _dimh do Result.m[i, j] := Result.m[i, j] + Left.m[i, k] * Right.m[k, j]; end; class operator rglm.NotEqual(const Left, Right: rglm): Boolean; begin Result := not (Left = Right); end; function rglm.ptr: PGLfloat; begin Result := PGLFloat(@Self); end; class operator rglm.Subtract(const Left, Right: rglm): rglm; begin for var i := 0 to _elemh do Result.elem[i] := Left.elem[i] - Right.elem[i]; end; function rglm.ToString: string; begin Result := ''; for var y := 0 to _dimh do begin for var x := 0 to _dimh do begin Result := Result + FloatToStrF(m[y, x], ffFixed, 10, 4, InvFS); if x < _dimh then Result := Result + #32; end; if y < _dimh then Result := Result + #13#10 end; end; function rglm.Transpose: rglm; begin for var i := 0 to _dimh do for var j := 0 to _dimh do Result.m[j, i] := Self.m[i, j]; end; class function rglm.Zero: rglm; const Z: rglm = ( elem: ( 0, 0, 0, 0, 0, 0, 0, 0, 0 ) ); begin Result := Z; end; function mat(const m11, m12, m13, m21, m22, m23, m31, m32, m33: Single): rglm; begin Result.elem[0] := m11; Result.elem[1] := m12; Result.elem[2] := m13; Result.elem[3] := m21; Result.elem[4] := m22; Result.elem[5] := m23; Result.elem[6] := m31; Result.elem[7] := m32; Result.elem[8] := m33; end; function mat_transpose(const m11, m12, m13, m21, m22, m23, m31, m32, m33: Single): rglm; begin Result.elem[0] := m11; Result.elem[3] := m12; Result.elem[6] := m13; Result.elem[1] := m21; Result.elem[4] := m22; Result.elem[7] := m23; Result.elem[2] := m31; Result.elem[5] := m32; Result.elem[8] := m33; end; { rglv2 } class operator rglv2.Add(const Left, Right: rglv2): rglv2; begin Result.x := Left.x + Right.x; Result.y := Left.y + Right.y; end; constructor rglv2.Create(const x, y: Single); begin Self.x := x; Self.y := y; end; class operator rglv2.Divide(const Left: rglv2; const Right: Single): rglv2; begin Result.x := Left.x / Right; Result.y := Left.y / Right; end; class operator rglv2.Equal(const Left, Right: rglv2): Boolean; begin Result := (Left.x = Right.x) and (Left.y = Right.y); end; class operator rglv2.Implicit(const v: rglv2): GLfloat2; begin Result := v.elem; end; class operator rglv2.Implicit(const arr: GLfloat2): rglv2; begin Result.elem := arr; end; class operator rglv2.Multiply(const Left: Single; const Right: rglv2): rglv2; begin Result.x := Left * Right.x; Result.y := Left * Right.y; end; class operator rglv2.Multiply(const Left: rglv2; const Right: Single): rglv2; begin Result.x := Left.x * Right; Result.y := Left.y * Right; end; class operator rglv2.Multiply(const Left, Right: rglv2): Single; begin Result := Left.x * Right.x + Left.y * Right.y; end; function rglv2.Norm: Single; begin Result := Sqrt(Self * Self); end; function rglv2.Normalized: rglv2; begin Result := (1 / Norm) * Self; end; function rglv2.NormSquare: Single; begin Result := Self * Self; end; class operator rglv2.NotEqual(const Left, Right: rglv2): Boolean; begin Result := not (Left = Right); end; function rglv2.ptr: PGLfloat; begin Result := PGLfloat(@Self); end; class operator rglv2.Subtract(const Left, Right: rglv2): rglv2; begin Result.x := Left.x - Right.x; Result.y := Left.y - Right.y; end; class function rglv2.Zero: rglv2; const Z: rglv2 = ( elem: ( 0, 0 ) ); begin Result := Z; end; function vec2(const x, y: Single): rglv2; begin Result.x := x; Result.y := y; end; { rglv4 } class operator rglv4.Add(const Left, Right: rglv4): rglv4; begin Result.x := Left.x + Right.x; Result.y := Left.y + Right.y; Result.z := Left.z + Right.z; Result.w := Left.w + Right.w; end; constructor rglv4.Create(const x, y, z, w: Single); begin Self.x := x; Self.y := y; Self.z := z; Self.w := w; end; class operator rglv4.Divide(const Left: rglv4; const Right: Single): rglv4; begin Result.x := Left.x / Right; Result.y := Left.y / Right; Result.z := Left.z / Right; Result.w := Left.w / Right; end; class operator rglv4.Equal(const Left, Right: rglv4): Boolean; begin Result := (Left.x = Right.x) and (Left.y = Right.y) and (Left.z = Right.z) and (Left.w = Right.w); end; class operator rglv4.Multiply(const Left: Single; const Right: rglv4): rglv4; begin Result.x := Left * Right.x; Result.y := Left * Right.y; Result.z := Left * Right.z; Result.w := Left * Right.w; end; class operator rglv4.Multiply(const Left, Right: rglv4): Single; begin Result := Left.x * Right.x + Left.y * Right.y + Left.z * Right.z + Left.w * Right.w; end; function rglv4.Norm: Single; begin Result := Sqrt(Self * Self); end; function rglv4.Normalized: rglv4; begin Result := (1 / Norm) * Self; end; function rglv4.NormSquare: Single; begin Result := Self * Self; end; class operator rglv4.NotEqual(const Left, Right: rglv4): Boolean; begin Result := not (Left = Right); end; function rglv4.ptr: PGLfloat; begin Result := PGLfloat(@Self); end; class operator rglv4.Subtract(const Left, Right: rglv4): rglv4; begin Result.x := Left.x - Right.x; Result.y := Left.y - Right.y; Result.z := Left.z - Right.z; Result.w := Left.w - Right.w; end; class function rglv4.Zero: rglv4; const Z: rglv4 = ( elem: ( 0, 0, 0, 0 ) ); begin Result := Z; end; function vec4(const x, y, z, w: Single): rglv4; begin Result.x := x; Result.y := y; Result.z := z; Result.w := w; end; { rglm4 } class operator rglm4.Add(const Left, Right: rglm4): rglm4; begin for var i := 0 to _elemh do Result.elem[i] := Left.elem[i] + Right.elem[i]; end; constructor rglm4.Create(const m11, m12, m13, m14, m21, m22, m23, m24, m31, m32, m33, m34, m41, m42, m43, m44: Single); begin elem[0] := m11; elem[1] := m12; elem[2] := m13; elem[3] := m14; elem[4] := m21; elem[5] := m22; elem[6] := m23; elem[7] := m24; elem[8] := m31; elem[9] := m32; elem[10] := m33; elem[11] := m34; elem[12] := m41; elem[13] := m42; elem[14] := m43; elem[15] := m44; end; class operator rglm4.Divide(const Left: rglm4; const Right: Single): rglm4; begin for var i := 0 to _elemh do Result.elem[i] := Left.elem[i] / Right; end; class operator rglm4.Equal(const Left, Right: rglm4): Boolean; begin for var i := 0 to _elemh do if Left.elem[i] <> Right.elem[i] then Exit(False); Result := True; end; class operator rglm4.Explicit(const Mat: rglm): rglm4; begin Result := rglm4.Identity; for var i := 0 to Mat._dimh do for var j := 0 to Mat._dimh do Result.m[i, j] := Mat.m[i, j]; end; class operator rglm4.Explicit(const Mat4: rglm4): rglm; begin for var i := 0 to Result._dimh do for var j := 0 to Result._dimh do Result.m[i, j] := Mat4.m[i, j]; end; class function rglm4.Identity: rglm4; const I: rglm4 = ( elem: ( 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1 ) ); begin Result := I; end; class operator rglm4.Multiply(const Left: Single; const Right: rglm4): rglm4; begin for var i := 0 to _elemh do Result.elem[i] := Left * Right.elem[i]; end; class operator rglm4.Multiply(const Left: rglm4; const Right: rglv4): rglv4; begin Result := Default(rglv4); for var i := 0 to _dimh do for var j := 0 to _dimh do Result.elem[i] := Result.elem[i] + Left.m[i, j] * Right.elem[j]; end; class operator rglm4.Multiply(const Left, Right: rglm4): rglm4; begin Result := Default(rglm4); for var i := 0 to _dimh do for var j := 0 to _dimh do for var k := 0 to _dimh do Result.m[i, j] := Result.m[i, j] + Left.m[i, k] * Right.m[k, j]; end; class operator rglm4.NotEqual(const Left, Right: rglm4): Boolean; begin Result := not (Left = Right); end; function rglm4.ptr: PGLfloat; begin Result := PGLFloat(@Self); end; class operator rglm4.Subtract(const Left, Right: rglm4): rglm4; begin for var i := 0 to _elemh do Result.elem[i] := Left.elem[i] - Right.elem[i]; end; function rglm4.Transpose: rglm4; begin for var i := 0 to _dimh do for var j := 0 to _dimh do Result.m[j, i] := Self.m[i, j]; end; function mat4(const m11, m12, m13, m14, m21, m22, m23, m24, m31, m32, m33, m34, m41, m42, m43, m44: Single): rglm4; begin Result.elem[0] := m11; Result.elem[1] := m12; Result.elem[2] := m13; Result.elem[3] := m14; Result.elem[4] := m21; Result.elem[5] := m22; Result.elem[6] := m23; Result.elem[7] := m24; Result.elem[8] := m31; Result.elem[9] := m32; Result.elem[10] := m33; Result.elem[11] := m34; Result.elem[12] := m41; Result.elem[13] := m42; Result.elem[14] := m43; Result.elem[15] := m44; end; function rglLookAt(const eyeX, eyeY, eyeZ: Single; const centerX, centerY, centerZ: Single; const upX, upY, upZ: Single): rglm4; begin Result := rglLookAt(vec(eyeX, eyeY, eyeZ), vec(centerX, centerY, centerZ), vec(upX, upY, upZ)); end; function rglLookAt(const eye, center, up: rglv): rglm4; begin var f := (center - eye).Normalized; var u := up.Normalized; var s := (f xor u).Normalized; var v := S xor f; Result := mat4( s.x, s.y, s.z, 0, v.x, v.y, v.z, 0, -f.x, -f.y, -f.z, 0, 0, 0, 0, 1 ) * rglTranslate(-eye.x, -eye.y, -eye.z); end; function rglPerspective(const fovy, aspect, &near, &far: Double): rglm4; begin var f := Cot(DegToRad(fovy / 2)); Result := mat4( f / aspect, 0, 0, 0, 0, f, 0, 0, 0, 0, (&far + &near) / (&near - &far), 2 * &far * &near / (&near - &far), 0, 0, -1, 0 ); end; function rglOrtho(const left, right, bottom, top, &near, &far: Double): rglm4; begin var tx := (left + right) / (left - right); var ty := (bottom + top) / (bottom - top); var tz := (&near + &far) / (&near - &far); Result := mat4( 2 / (right - left), 0, 0, tx, 0, 2 / (top - bottom), 0, ty, 0, 0, 2 / (&near - &far), tz, 0, 0, 0, 1 ); end; function rglOrtho2D(const left, right, bottom, top: Double): rglm4; begin Result := rglOrtho(left, right, bottom, top, -1.0, 1.0); end; function rglScale(const x, y, z: Single): rglm4; begin Result := mat4( x, 0, 0, 0, 0, y, 0, 0, 0, 0, z, 0, 0, 0, 0, 1 ); end; function rglTranslate(const x, y, z: Single): rglm4; begin Result := mat4( 1, 0, 0, x, 0, 1, 0, y, 0, 0, 1, z, 0, 0, 0, 1 ); end; function rglTranslate(const v: rglv): rglm4; begin Result := rglTranslate(v.x, v.y, v.z); end; function rglRotate(const a, x, y, z: Single): rglm4; begin var s, c: Single; SinCos(DegToRad(a), s, c); var cc := 1 - c; Result := mat4( x*x*cc+c, x*y*cc-z*s, x*z*cc+y*s, 0, y*x*cc+z*s, y*y*cc+c, y*z*cc-x*s, 0, x*z*cc-y*s, y*z*cc+x*s, z*z*cc+c, 0, 0, 0, 0, 1 ); end; function rglRotate(const a: Single; const v: rglv): rglm4; begin if IsZero(a) then Exit(rglm4.Identity); with v.Normalized do Result := rglRotate(a, x, y, z); end; function rglGetString(name: GLenum): string; begin Result := string(glGetString(name)); end; procedure TriangulateSurface(F: TSurfParamFcn; N: TSurfParamNormalFcn; const umin, umax, vmin, vmax: Double; A, B, pccx, pccy: Integer; Normalize: Boolean; out Vertices: TArray<GLfloat6>; out Indices, PCIs: TArray<GLuint>; PCOnly: Boolean; ListData: TArray<rglv>); const RevDir: array[Boolean] of Integer = (+1, -1); begin if not ((@F = nil) xor (ListData = nil)) or (umin >= umax) or (vmin >= vmax) or (A < 2) or (B < 2) or Assigned(ListData) and (Length(ListData) <> A*B) then raise Exception.Create('TriangulateSurface: Invalid parameters.'); pccx := EnsureRange(pccx, 1, A); pccy := EnsureRange(pccy, 1, B); if not PCOnly then begin SetLength(Vertices, A*B); SetLength(Indices, 3 * 2 * (A - 1) * (B - 1)); var ∆u := (umax - umin) / (A - 1); var ∆v := (vmax - vmin) / (B - 1); var idx := 0; for var j := 0 to B - 1 do begin var v := vmin + j*∆v; for var i := 0 to A - 1 do begin var u := umin + i*∆u; if Assigned(F) then GLr3n3v(Vertices[idx]).r := F(u, v) else GLr3n3v(Vertices[idx]).r := ListData[idx]; if Assigned(N) then begin if Normalize then GLr3n3v(Vertices[idx]).n := N(u, v).Normalized else GLr3n3v(Vertices[idx]).n := N(u, v); end; Inc(idx); end; end; if not Assigned(N) then begin idx := 0; for var j := 0 to B - 1 do begin for var i := 0 to A - 1 do begin var k, l: Integer; if i > 0 then k := i - 1 else k := i + 1; if j > 0 then l := j - 1 else l := j + 1; GLr3n3v(Vertices[idx]).n := ( (GLr3n3v(Vertices[j * A + i]).r - GLr3n3v(Vertices[j * A + k]).r) * RevDir[i = 0] xor (GLr3n3v(Vertices[j * A + i]).r - GLr3n3v(Vertices[l * A + i]).r) * RevDir[j = 0] ).Normalized; Inc(idx); end; end; end; idx := 0; for var j := 0 to B - 2 do for var i := 0 to A - 2 do begin var BaseIndex := j * A + i; Indices[idx] := BaseIndex; Inc(idx); Indices[idx] := BaseIndex + A; Inc(idx); Indices[idx] := BaseIndex + 1; Inc(idx); Indices[idx] := BaseIndex + 1; Inc(idx); Indices[idx] := BaseIndex + A; Inc(idx); Indices[idx] := BaseIndex + A + 1; Inc(idx); end; end; SetLength(PCIs, 2 * A * (B - 1) + 2 * B * (A - 1)); var pccsx := Round(A / pccx); var pccsy := Round(B / pccy); var idx := 0; for var i := 0 to A - 1 do begin if i mod pccsx <> 0 then Continue; for var j := 0 to B - 2 do begin PCIs[idx] := i + j * A; Inc(idx); PCIs[idx] := i + (j + 1) * A; Inc(idx); end; end; for var j := 0 to B - 1 do begin if j mod pccsy <> 0 then Continue; for var i := 0 to A - 2 do begin PCIs[idx] := j * A + i; Inc(idx); PCIs[idx] := j * A + (i + 1); Inc(idx); end; end; end; procedure TriangulateColoredSurface(F: TSurfParamFcn; C: TSurfParamColorFcn; N: TSurfParamNormalFcn; const umin, umax, vmin, vmax: Double; A, B, pccx, pccy: Integer; Normalize: Boolean; out Vertices: TArray<GLfloat9>; out Indices, PCIs: TArray<GLuint>; PCOnly: Boolean; ListData: TArray<GLr3c3v>); const RevDir: array[Boolean] of Integer = (+1, -1); begin if not ((@F = nil) xor (ListData = nil)) or (umin >= umax) or (vmin >= vmax) or (A < 2) or (B < 2) or Assigned(ListData) and (Length(ListData) <> A*B) then raise Exception.Create('TriangulateColoredSurface: Invalid parameters.'); pccx := EnsureRange(pccx, 1, A); pccy := EnsureRange(pccy, 1, B); if not PCOnly then begin SetLength(Vertices, A*B); SetLength(Indices, 3 * 2 * (A - 1) * (B - 1)); var ∆u := (umax - umin) / (A - 1); var ∆v := (vmax - vmin) / (B - 1); var idx := 0; for var j := 0 to B - 1 do begin var v := vmin + j*∆v; for var i := 0 to A - 1 do begin var u := umin + i*∆u; if Assigned(F) then begin GLr3c3n3v(Vertices[idx]).r := F(u, v); if Assigned(C) then GLr3c3n3v(Vertices[idx]).c := C(u, v); end else begin GLr3c3n3v(Vertices[idx]).r := ListData[idx].r; GLr3c3n3v(Vertices[idx]).c := ListData[idx].c; end; if Assigned(N) then begin if Normalize then GLr3c3n3v(Vertices[idx]).n := N(u, v).Normalized else GLr3c3n3v(Vertices[idx]).n := N(u, v); end; Inc(idx); end; end; if not Assigned(N) then begin idx := 0; for var j := 0 to B - 1 do begin for var i := 0 to A - 1 do begin var k, l: Integer; if i > 0 then k := i - 1 else k := i + 1; if j > 0 then l := j - 1 else l := j + 1; GLr3c3n3v(Vertices[idx]).n := ( (GLr3c3n3v(Vertices[j * A + i]).r - GLr3c3n3v(Vertices[j * A + k]).r) * RevDir[i = 0] xor (GLr3c3n3v(Vertices[j * A + i]).r - GLr3c3n3v(Vertices[l * A + i]).r) * RevDir[j = 0] ).Normalized; Inc(idx); end; end; end; idx := 0; for var j := 0 to B - 2 do for var i := 0 to A - 2 do begin var BaseIndex := j * A + i; Indices[idx] := BaseIndex; Inc(idx); Indices[idx] := BaseIndex + A; Inc(idx); Indices[idx] := BaseIndex + 1; Inc(idx); Indices[idx] := BaseIndex + 1; Inc(idx); Indices[idx] := BaseIndex + A; Inc(idx); Indices[idx] := BaseIndex + A + 1; Inc(idx); end; end; SetLength(PCIs, 2 * A * (B - 1) + 2 * B * (A - 1)); var pccsx := Round(A / pccx); var pccsy := Round(B / pccy); var idx := 0; for var i := 0 to A - 1 do begin if i mod pccsx <> 0 then Continue; for var j := 0 to B - 2 do begin PCIs[idx] := i + j * A; Inc(idx); PCIs[idx] := i + (j + 1) * A; Inc(idx); end; end; for var j := 0 to B - 1 do begin if j mod pccsy <> 0 then Continue; for var i := 0 to A - 2 do begin PCIs[idx] := j * A + i; Inc(idx); PCIs[idx] := j * A + (i + 1); Inc(idx); end; end; end; procedure zdef(const vars: array of PInteger; const vals: array of Integer); begin if Length(vars) <> Length(vals) then raise Exception.Create('zdef: Invalid parameters.'); for var i := Low(vars) to High(vars) do if vars[i]^ = 0 then vars[i]^ := vals[i]; end; function __spherefcn(const u, v: Double): rglv; begin var s, c: Single; SinCos(Single(u), s, c); var rt: Single := Sqrt(1 - v*v); Result.x := Cos(u) * rt; Result.y := Sin(u) * rt; Result.z := v; end; function __spherenormalfcn(const u, v: Double): rglv; begin var s, c: Single; SinCos(Single(u), s, c); var rt: Single := Sqrt(1 - v*v); Result.x := c * rt; Result.y := s * rt; Result.z := v; end; function __polarspherefcn(const θ, φ: Double): rglv; begin Result.x := Sin(θ) * Cos(φ); Result.y := Sin(θ) * Sin(φ); Result.z := Cos(θ); end; function __polarspherenormalfcn(const θ, φ: Double): rglv; begin Result.x := Sin(θ) * Cos(φ); Result.y := Sin(θ) * Sin(φ); Result.z := Cos(θ); end; procedure rglSpherePolar(out Vertices: TArray<GLfloat6>; out Indices, PCIs: TArray<GLuint>; A, B, pccx, pccy: Integer; PCOnly: Boolean; Data: Pointer); begin zdef([@A, @B, @pccx, @pccy], [129, 129, 64, 64]); TriangulateSurface(__polarspherefcn, __polarspherenormalfcn, 0, Pi, 0, 2*Pi, A, B, pccx, pccy, False, Vertices, Indices, PCIs, PCOnly, nil); end; procedure rglSphereConstArea(out Vertices: TArray<GLfloat6>; out Indices, PCIs: TArray<GLuint>; A, B, pccx, pccy: Integer; PCOnly: Boolean; Data: Pointer); begin zdef([@A, @B, @pccx, @pccy], [129, 129, 64, 64]); TriangulateSurface(__spherefcn, __spherenormalfcn, 0, 2*Pi, -1, 1, A, B, pccx, pccy, False, Vertices, Indices, PCIs, PCOnly, nil) end; function __cylinderfcn(const φ, z: Double): rglv; begin var s, c: Single; SinCos(Single(φ), s, c); Result.x := c; Result.y := s; Result.z := z; end; function __cylindernormalfcn(const φ, z: Double): rglv; begin var s, c: Single; SinCos(Single(φ), s, c); Result.x := c; Result.y := s; Result.z := 0; end; procedure rglCylinder(out Vertices: TArray<GLfloat6>; out Indices, PCIs: TArray<GLuint>; A, B, pccx, pccy: Integer; PCOnly: Boolean; Data: Pointer); begin zdef([@A, @B, @pccx, @pccy], [129, 129, 64, 64]); TriangulateSurface(__cylinderfcn, __cylindernormalfcn, 0, 2*Pi, 0, 1, A, B, pccx, pccy, False, Vertices, Indices, PCIs, PCOnly, nil); end; function __conefcn(const φ, z: Double): rglv; begin var s, c: Single; SinCos(Single(φ), s, c); Result.x := z*c; Result.y := z*s; Result.z := z; end; function __conenormalfcn(const φ, z: Double): rglv; begin var s, c: Single; SinCos(Single(φ), s, c); Result.x := c / Sqrt(2); Result.y := s / Sqrt(2); Result.z := -1 / Sqrt(2); end; procedure rglCone(out Vertices: TArray<GLfloat6>; out Indices, PCIs: TArray<GLuint>; A, B, pccx, pccy: Integer; PCOnly: Boolean; Data: Pointer); begin zdef([@A, @B, @pccx, @pccy], [129, 129, 64, 64]); TriangulateSurface(__conefcn, __conenormalfcn, 0, 2*Pi, 0, 1, A, B, pccx, pccy, False, Vertices, Indices, PCIs, PCOnly, nil); end; function __planefcn(const x, y: Double): rglv; begin Result.x := x; Result.y := y; Result.z := 0; end; function __planenormalfcn(const x, y: Double): rglv; begin Result.x := 0; Result.y := 0; Result.z := 1; end; procedure rglPlane(out Vertices: TArray<GLfloat6>; out Indices, PCIs: TArray<GLuint>; A, B, pccx, pccy: Integer; PCOnly: Boolean; Data: Pointer); begin zdef([@A, @B, @pccx, @pccy], [129, 129, 64, 64]); TriangulateSurface(__planefcn, __planenormalfcn, -1, +1, -1, +1, A, B, pccx, pccy, False, Vertices, Indices, PCIs, PCOnly, nil); end; function __diskfcn(const ρ, φ: Double): rglv; begin var s, c: Single; SinCos(Single(φ), s, c); Result.x := ρ*s; Result.y := ρ*c; Result.z := 0; end; function __disknormalfcn(const ρ, φ: Double): rglv; begin Result.x := 0; Result.y := 0; Result.z := 1; end; procedure rglDisk(out Vertices: TArray<GLfloat6>; out Indices, PCIs: TArray<GLuint>; A, B, pccx, pccy: Integer; PCOnly: Boolean; Data: Pointer); begin zdef([@A, @B, @pccx, @pccy], [129, 129, 64, 64]); TriangulateSurface(__diskfcn, __disknormalfcn, 0, +1, 0, 2*Pi, A, B, pccx, pccy, False, Vertices, Indices, PCIs, PCOnly, nil); end; { TPPEHelper } class function TPPEHelper.FromString(const S: string): TPPE; begin for var e := Low(TPPE) to High(TPPE) do if SameText(e.Name, S) then Exit(e); raise ERglError.CreateFmt('Unknown effect: "%s".', [S]); end; function TPPEHelper.ID: Integer; begin Result := 1 shl Ord(Self); end; function TPPEHelper.Name: string; begin case Self of ppIdentity: Result := 'Identity'; else Result := ''; end; end; { TRglShader } procedure TRglShader.Compile; begin if FHandle = 0 then raise ERglError.Create('Shader handle empty.'); FContext.MakeCurrent('TRglShader.Compile'); glCompileShader(FHandle); var i: GLInt := 0; glGetShaderiv(FHandle, GL_COMPILE_STATUS, @i); if i <> GL_TRUE then begin var maxlen: GLint; glGetShaderiv(FHandle, GL_INFO_LOG_LENGTH, @maxlen); var S: AnsiString; SetLength(S, maxlen); glGetShaderInfoLog(FHandle, maxlen, @maxlen, PAnsiChar(S)); SetLength(S, maxlen); raise ERglError.Create('Shader not successfully compiled: ' + string(S)); end; end; constructor TRglShader.Create(AContext: TRglContext; const ASource: string); begin FContext := AContext; FContext.MakeCurrent('TRglShader.Create'); FHandle := glCreateShader(FKind); if FHandle = 0 then raise ERglError.Create('Couldn''t create shader.'); FSource := AnsiString(ASource); glShaderSource(FHandle, 1, @FSource, nil); end; destructor TRglShader.Destroy; // TRglShader objects are destroyed immediately begin if Assigned(FContext) and FContext.TryMakeCurrent then begin glDeleteShader(FHandle); FHandle := 0; end else rglLog('TRglShader.Destroy error'); inherited; end; { TRglVertexShader } constructor TRglVertexShader.Create(AContext: TRglContext; const ASource: string); begin FKind := GL_VERTEX_SHADER; inherited; end; { TRglFragmentShader } constructor TRglFragmentShader.Create(AContext: TRglContext; const ASource: string); begin FKind := GL_FRAGMENT_SHADER; inherited; end; { TRglGeometryShader } constructor TRglGeometryShader.Create(AContext: TRglContext; const ASource: string); begin FKind := GL_GEOMETRY_SHADER; inherited; end; { TRglProgram } function TRglProgram.AddAttribute(const AName: string): Integer; begin FContext.MakeCurrent('TRglProgram.AddAttribute'); Result := glGetAttribLocation(FHandle, PAnsiChar(AnsiString(AName))); if Result = -1 then raise ERglError.Create('Couldn''t bind attribute.'); end; function TRglProgram.AddUniform<T>(const AName: string): T; begin Result := T(TRglUniformClass(T).Create(FContext, FHandle, AName)); FUniforms.Add(Result); end; procedure TRglProgram.AttachShader(AShader: TRglShader); begin FContext.MakeCurrent('TRglProgram.AttachShader'); if not FShaders.TryAdd(AShader.Handle, AShader.Source) then raise ERglError.Create('A shader of this kind has already been attached to the program.'); glAttachShader(FHandle, AShader.Handle); end; constructor TRglProgram.Create(AContext: TRglContext); begin FContext := AContext; FContext.MakeCurrent('TRglProgram.Create'); FShaders := TDictionary<Integer, AnsiString>.Create; FUniforms := TObjectList<TRglUniform>.Create; FHandle := glCreateProgram; end; destructor TRglProgram.Destroy; begin if Assigned(FContext) and FContext.TryMakeCurrent then begin glDeleteProgram(FHandle); FHandle := 0; end else rglLog('TRglProgram.Destroy error'); FreeAndNil(FUniforms); FreeAndNil(FShaders); inherited; end; procedure TRglProgram.Link; begin if FHandle = 0 then raise ERglError.Create('Program handle empty.'); FContext.MakeCurrent('TRglProgram.Link'); glLinkProgram(FHandle); var i: GLint := 0; glGetProgramiv(FHandle, GL_LINK_STATUS, @i); if i <> GL_TRUE then begin var maxlen: GLint; glGetProgramiv(FHandle, GL_INFO_LOG_LENGTH, @maxlen); var S: AnsiString; SetLength(S, maxlen); glGetProgramInfoLog(FHandle, maxlen, @maxlen, PAnsiChar(S)); SetLength(S, maxlen); raise ERglError.Create('Program not successfully linked: ' + string(S)); end; for var S in FShaders do glDetachShader(FHandle, S.Key); FShaders.Clear; end; function TRglProgram.TryAddUniform<T>(const AName: string): T; begin Result := T(TRglUniformClass(T).TryCreate(FContext, FHandle, AName)); if Assigned(Result) then FUniforms.Add(Result); end; procedure TRglProgram.Unuse; begin FContext.MakeCurrent('TRglProgram.Unuse'); glUseProgram(0); end; procedure TRglProgram.Use; begin FContext.MakeCurrent('TRglProgram.Use'); glUseProgram(FHandle); end; { TRglUniformFloat } procedure TRglUniformFloat.SetValue(const AValue: GLfloat); begin if Self <> nil then begin Context.MakeCurrent('TRglUniformFloat.SetValue'); glUniform1f(Handle, AValue); end; end; { TRglUniformFloatVec2 } procedure TRglUniformFloatVec2.SetValue(const AValue: rglv2); begin if Self <> nil then begin Context.MakeCurrent('TRglUniformFloatVec2.SetValue'); glUniform2fv(Handle, 1, AValue.ptr); end; end; procedure TRglUniformFloatVec2.SetValue(const a0, a1: GLfloat); begin if Self <> nil then begin Context.MakeCurrent('TRglUniformFloatVec2.SetValue'); glUniform2f(Handle, a0, a1); end; end; { TRglUniformFloatVec3 } procedure TRglUniformFloatVec3.SetValue(const AValue: rglv); begin if Self <> nil then begin Context.MakeCurrent('TRglUniformFloatVec3.SetValue'); glUniform3fv(Handle, 1, AValue.ptr); end; end; procedure TRglUniformFloatVec3.SetValue(const a0, a1, a2: GLfloat); begin if Self <> nil then begin Context.MakeCurrent('TRglUniformFloatVec3.SetValue'); glUniform3f(Handle, a0, a1, a2); end; end; { TRglUniformFloatVec4 } procedure TRglUniformFloatVec4.SetValue(const AValue: rglv4); begin if Self <> nil then begin Context.MakeCurrent('TRglUniformFloatVec4.SetValue'); glUniform4fv(Handle, 1, AValue.ptr); end; end; procedure TRglUniformFloatVec4.SetValue(const a0, a1, a2, a3: GLfloat); begin if Self <> nil then begin Context.MakeCurrent('TRglUniformFloatVec4.SetValue'); glUniform4f(Handle, a0, a1, a2, a3); end; end; { TRglUniformFloatMat3 } procedure TRglUniformFloatMat3.SetValue(const AValue: rglm); begin if Self <> nil then begin Context.MakeCurrent('TRglUniformFloatMat3.SetValue'); glUniformMatrix3fv(Handle, 1, GL_TRUE, AValue.ptr); end; end; { TRglUniformFloatMat4 } procedure TRglUniformFloatMat4.SetValue(const AValue: rglm4); begin if Self <> nil then begin Context.MakeCurrent('TRglUniformFloatMat4.SetValue'); glUniformMatrix4fv(Handle, 1, GL_TRUE, AValue.ptr); end; end; { TRglUniformInt } procedure TRglUniformInt.SetValue(const AValue: GLint); begin if Self <> nil then begin Context.MakeCurrent('TRglUniformInt.SetValue'); glUniform1i(Handle, AValue); end; end; { TRglUniformUInt } procedure TRglUniformUInt.SetValue(const AValue: GLuint); begin if Self <> nil then begin Context.MakeCurrent('TRglUniformUInt.SetValue'); glUniform1ui(Handle, AValue); end; end; { TRglUniformDouble } procedure TRglUniformDouble.SetValue(const AValue: Double); begin if Self <> nil then begin Context.MakeCurrent('TRglUniformDouble.SetValue'); glUniform1d(Handle, AValue); end; end; { TRglUniformBool } procedure TRglUniformBool.SetValue(const AValue: Boolean); begin if Self <> nil then begin Context.MakeCurrent('TRglUniformBool.SetValue'); glUniform1i(Handle, Ord(AValue <> False)); end; end; { TRglUniform } constructor TRglUniform.Create; begin end; constructor TRglUniform.Create(AContext: TRglContext; AProgram: Cardinal; const AName: string); begin FContext := AContext; FContext.MakeCurrent('TRglUniform.Create'); FName := AName; FHandle := glGetUniformLocation(AProgram, PAnsiChar(AnsiString(AName))); if FHandle = -1 then raise ERglError.Create('Couldn''t bind uniform.'); end; destructor TRglUniform.Destroy; begin FHandle := 0; inherited; end; class function TRglUniform.TryCreate(AContext: TRglContext; AProgram: Cardinal; const AName: string): TRglUniform; begin AContext.MakeCurrent('TRglUniform.TryCreate'); var LName := AName; var LHandle := glGetUniformLocation(AProgram, PAnsiChar(AnsiString(AName))); if LHandle <> -1 then begin Result := TRglUniform.Create; Result.FContext := AContext; Result.FName := LName; Result.FHandle := LHandle; end else Result := nil; end; { TRglContext } type wglCreateContextAttribsARB = function(DC: HDC; hShareContext: HGLRC; attribList: PInteger): HGLRC; stdcall; class constructor TRglContext.ClassCreate; begin FInstances := TList<TRglContext>.Create; end; class destructor TRglContext.ClassDestroy; begin FreeAndNil(FInstances); end; constructor TRglContext.Create(AWnd: HWND); const pfd: TPixelFormatDescriptor = ( nSize: SizeOf(TPixelFormatDescriptor); nVersion: 1; dwFlags: PFD_SUPPORT_OPENGL or PFD_DRAW_TO_WINDOW or PFD_DOUBLEBUFFER; iPixelType: PFD_TYPE_RGBA; cColorBits: 32; cRedBits: 0; cRedShift: 0; cGreenBits: 0; cGreenShift: 0; cBlueBits: 0; cBlueShift: 0; cAlphaBits: 0; cAlphaShift: 0; cAccumBits: 0; cAccumRedBits: 0; cAccumGreenBits: 0; cAccumBlueBits: 0; cAccumAlphaBits: 0; cDepthBits: 24; cStencilBits: 0; cAuxBuffers: 0; iLayerType: PFD_MAIN_PLANE; bReserved: 0; dwLayerMask: 0; dwVisibleMask: 0; dwDamageMask: 0; ); WGL_CONTEXT_MAJOR_VERSION_ARB = $2091; WGL_CONTEXT_MINOR_VERSION_ARB = $2092; WGL_CONTEXT_LAYER_PLANE_ARB = $2093; WGL_CONTEXT_FLAGS_ARB = $2094; WGL_CONTEXT_PROFILE_MASK_ARB = $9126; WGL_CONTEXT_DEBUG_BIT_ARB = $0001; WGL_CONTEXT_FORWARD_COMPATIBLE_BIT_ARB = $0002; WGL_CONTEXT_CORE_PROFILE_BIT_ARB = $00000001; WGL_CONTEXT_COMPATIBILITY_PROFILE_BIT_ARB = $00000002; attribs: array[0..6] of Integer = ( WGL_CONTEXT_MAJOR_VERSION_ARB, 3, WGL_CONTEXT_MINOR_VERSION_ARB, 3, WGL_CONTEXT_FLAGS_ARB, {$IFDEF DEBUG} WGL_CONTEXT_DEBUG_BIT_ARB {$ELSE} 0 {$ENDIF}, 0 ); begin rglLog('TRglContext.Create'); if Assigned(FInstances) then begin FInstances.Add(Self); rglLog('RGL context count: ' + FInstances.Count.ToString); end; FStockSurfaces := TDictionary<TStockSurfaceIndex, TStockSurfaceData>.Create; FStoredSolids := TDictionary<TDrawable3DClass, TSolidStoreRec>.Create; FCustomBuffers := TObjectDictionary<TDrawable3DClass, TObject>.Create([doOwnsValues]); FWnd := AWnd; FDC := GetDC(AWnd); if FDC = 0 then raise ERglError.Create('Couldn''t obtain device context during GL context creation.'); var PixelFormat := ChoosePixelFormat(FDC, @pfd); if PixelFormat = 0 then RaiseLastOSError; if not SetPixelFormat(FDC, PixelFormat, @pfd) then RaiseLastOSError; FRC := wglCreateContext(FDC); if FRC = 0 then RaiseLastOSError; TRglContext.MakeCurrent(FDC, FRC, 'TRglContext.Create[1]'); InitOpenGLext; if not Assigned(glGenFramebuffers) then raise Exception.Create('OpenGL not available.'); var LwglCreateContextAttribsARB: wglCreateContextAttribsARB := wglGetProcAddress('wglCreateContextAttribsARB'); if Assigned(LwglCreateContextAttribsARB) then begin TRglContext.MakeCurrent(0, 0, 'TRglContext.Create[2]'); wglDeleteContext(FRC); FRC := LwglCreateContextAttribsARB(FDC, 0, @attribs); if FRC = 0 then RaiseLastOSError; TRglContext.MakeCurrent(FDC, FRC, 'TRglContext.Create[3]'); end; end; destructor TRglContext.Destroy; begin rglLog('TRglContext.Destroy'); if wglGetCurrentContext = FRC then begin if not TRglContext.TryMakeCurrent(0, 0) then rglLog('TRglContext.Destroy: TryMakeCurrent failed'); end; if FDC <> 0 then begin ReleaseDC(FWnd, FDC); FDC := 0; end; if FRC <> 0 then begin if not wglDeleteContext(FRC) then rglLog('TRglContext.Destroy: wglDeleteContext failed'); FRC := 0; // yes, in any case end; FWnd := 0; FreeAndNil(FCustomBuffers); FreeAndNil(FStoredSolids); FreeAndNil(FStockSurfaces); if Assigned(FInstances) then begin FInstances.Remove(Self); rglLog('RGL context count: ' + FInstances.Count.ToString); end; inherited; end; function TRglContext.GetExtensionNames: TArray<string>; begin MakeCurrent('TRglContext.GetExtensionNames'); var c: GLInt := 0; glGetIntegerv(GL_NUM_EXTENSIONS, @c); SetLength(Result, c); for var i := 0 to c - 1 do Result[i] := string(PAnsiChar(glGetStringi(GL_EXTENSIONS, i))); end; function TRglContext.GetImplInfo: TGLImplInfo; const MAX_TEXTURE_MAX_ANISOTROPY_EXT: GLenum = 34047; begin MakeCurrent('TRglContext.GetImplInfo'); Result := Default(TGLImplInfo); glGetIntegerv(GL_MAJOR_VERSION, @Result.Version.Major); glGetIntegerv(GL_MINOR_VERSION, @Result.Version.Minor); Result.VersionString := rglGetString(GL_VERSION); Result.Vendor := rglGetString(GL_VENDOR); Result.Renderer := rglGetString(GL_RENDERER); Result.GLSL := rglGetString(GL_SHADING_LANGUAGE_VERSION); glGetIntegerv(GL_CONTEXT_PROFILE_MASK, @Result.ContextProfileMask); glGetIntegerv(GL_CONTEXT_FLAGS, @Result.ContextFlags); glGetIntegerv(GL_ALIASED_LINE_WIDTH_RANGE, @Result.LineWidths.Aliased); glGetIntegerv(GL_SMOOTH_LINE_WIDTH_RANGE, @Result.LineWidths.Smooth); glGetIntegerv(GL_MAX_SAMPLES, @Result.MaxSamples); glGetIntegerv(GL_MAX_TEXTURE_SIZE, @Result.MaxTextureSize); glGetFloatv(MAX_TEXTURE_MAX_ANISOTROPY_EXT, @Result.MaxTextureAnisotropy) end; class function TRglContext.GlobalCount: Integer; begin if Assigned(FInstances) then Result := FInstances.Count else Result := -1; end; class procedure TRglContext.MakeCurrent(DC: HDC; RC: HGLRC; const ACaller: string); begin if (FCurrentDC <> DC) or (FCurrentRC <> RC) then DoMakeCurrent(DC, RC, ACaller); end; procedure TRglContext.MakeCurrent(const ACaller: string); begin if (FCurrentDC <> FDC) or (FCurrentRC <> FRC) then DoMakeCurrent(FDC, FRC, ACaller); end; procedure TRglContext.SwapBuffers; begin Windows.SwapBuffers(FDC); end; class function TRglContext.TryMakeCurrent(DC: HDC; RC: HGLRC): Boolean; begin Result := (FCurrentDC = DC) and (FCurrentRC = RC) or wglMakeCurrent(DC, RC); if Result then begin FCurrentDC := DC; FCurrentRC := RC; end; end; function TRglContext.TryMakeCurrent: Boolean; begin Result := (FCurrentDC = FDC) and (FCurrentRC = FRC) or wglMakeCurrent(FDC, FRC); if Result then begin FCurrentDC := FDC; FCurrentRC := FRC; end; end; class procedure TRglContext.DoMakeCurrent(DC: HDC; RC: HGLRC; const ACaller: string); begin if wglMakeCurrent(DC, RC) then begin FCurrentDC := DC; FCurrentRC := RC end else begin rglLog( 'TRglContext.DoMakeCurrent failed when invoked by %s for DC %x and RC %x.', [ACaller, NativeUInt(DC), NativeUInt(RC)] ); RaiseLastOSError; end; end; { TRglControl } procedure TRglControl.ApplyClearColor; begin if Assigned(FContext) then begin FContext.MakeCurrent('TRglControl.ApplyClearColor'); glClearColor(FGlCtlColor.r, FGlCtlColor.g, FGlCtlColor.b, 1.0); end; end; procedure TRglControl.CMColorChanged(var Message: TMessage); begin var C := ColorToRGB(Color); FGlCtlColor := vec(GetRValue(C) / $FF, GetGValue(C) / $FF, GetBValue(C) / $FF); ApplyClearColor; Invalidate; end; constructor TRglControl.Create(AOwner: TComponent); begin inherited; QueryPerformanceFrequency(FPerfFreq); Color := clWhite; FGlCtlColor := vec(1.0, 1.0, 1.0); FClearMask := GL_COLOR_BUFFER_BIT or GL_DEPTH_BUFFER_BIT; end; procedure TRglControl.CreateParams(var Params: TCreateParams); begin inherited; Params.WindowClass.Style := Params.WindowClass.Style or CS_OWNDC; end; procedure TRglControl.CreateWnd; begin inherited; FreeAndNil(FContext); try FContext := TRglContext.Create(Handle); except on E: Exception do OutputDebugString(PChar(E.Message)); end; ApplyClearColor; GlInit; end; destructor TRglControl.Destroy; begin inherited; end; procedure TRglControl.DestroyWnd; begin FreeAndNil(FContext); inherited; end; function TRglControl.GetAspectRatio: Double; begin var W := ClientWidth; var H := ClientHeight; if (W <> 0) and (H <> 0) then Result := W/H else Result := 1.0; end; procedure TRglControl.GLInit; begin end; procedure TRglControl.Resize; begin if Assigned(FContext) then begin FContext.MakeCurrent('TRglControl.Resize'); glViewport(0, 0, ClientWidth, ClientHeight); end; Invalidate; inherited; end; procedure TRglControl.WMDestroy(var Message: TWMDestroy); begin FreeAndNil(FContext); inherited; end; procedure TRglControl.WMEraseBkgnd(var Message: TWMEraseBkgnd); begin Message.Result := 1; end; procedure TRglControl.WMGetDlgCode(var Message: TWMGetDlgCode); begin inherited; Message.Result := Message.Result or DLGC_WANTARROWS; end; procedure TRglControl.WMPaint(var Message: TWMPaint); var PaintStruct: TPaintStruct; begin var LThisTick: Int64; QueryPerformanceCounter(LThisTick); if (FPrevTick <> 0) and (LThisTick > FPrevTick) then FFPS := FPerfFreq / (LThisTick - FPrevTick); FPrevTick := LThisTick; BeginPaint(Handle, PaintStruct); try if Assigned(FContext) then begin FContext.MakeCurrent('TRglControl.WMPaint'); if FClearMask <> 0 then glClear(FClearMask); Paint; if not ((Self is TVisCtl3D) and TVisCtl3D(Self).Stencil) then FContext.SwapBuffers; end else begin Canvas.Brush.Color := clWhite; Canvas.Brush.Style := bsSolid; Canvas.FillRect(ClientRect); Canvas.Font.Assign(Font); Canvas.Font.Color := clBlack; var R := ClientRect; var S := 'OpenGL context not set up.'; Canvas.TextRect(R, S, [tfSingleLine, tfCenter, tfVerticalCenter, tfEndEllipsis]); end; finally EndPaint(Handle, PaintStruct); end; end; { TCenterMarker } type TCenterMarker = class(TGeometricObject3D) private FXAxis, FYAxis, FZAxis: TCylinder; private procedure AxisChanged(Sender: TObject); public constructor Create(ACtl: TVisCtl3D); override; property X: TCylinder read FXAxis; property Y: TCylinder read FYAxis; property Z: TCylinder read FZAxis; end; constructor TCenterMarker.Create(ACtl: TVisCtl3D); begin inherited; const L: Double = 100.0; FXAxis := CreateChild<TCylinder>; FXAxis.Direction := vec(1, 0, 0); FXAxis.Position := vec(-L / 2, 0, 0); FXAxis.Radius := 0.01; FXAxis.Height := L; FXAxis.Color := clSilver; FXAxis.OnChange := AxisChanged; FYAxis := CreateChild<TCylinder>; FYAxis.Direction := vec(0, 1, 0); FYAxis.Position := vec(0, -L / 2, 0); FYAxis.Radius := 0.01; FYAxis.Height := L; FYAxis.Color := clSilver; FYAxis.OnChange := AxisChanged; FZAxis := CreateChild<TCylinder>; FZAxis.Direction := vec(0, 0, 1); FZAxis.Position := vec(0, 0, -L / 2); FZAxis.Radius := 0.01; FZAxis.Height := L; FZAxis.Color := clSilver; FZAxis.OnChange := AxisChanged; var LLabel := CreateChild<TTextRect>; LLabel.FaceScreen := True; LLabel.Text := 'Camera orbit point'; LLabel.AnchorPoint := apBottomLeft; LLabel.Font.Size := 16; LLabel.Displacement := vec2(0.25, 0.25); LLabel.Opacity := 0.5; end; procedure TCenterMarker.AxisChanged(Sender: TObject); begin Changed; end; { TVisCtl3D.TRenderOutputData } function TVisCtl3D.TRenderOutputData.Aspect: Double; begin if Height <> 0 then Result := Width / Height else Result := 1.0; end; class operator TVisCtl3D.TRenderOutputData.Equal(const Left, Right: TRenderOutputData): Boolean; begin Result := (Left.Width = Right.Width) and (Left.Height = Right.Height) and (Left.Offscreen = Right.Offscreen) and (Left.MSAA = Right.MSAA) and (Left.PostProc = Right.PostProc); end; class operator TVisCtl3D.TRenderOutputData.NotEqual(const Left, Right: TRenderOutputData): Boolean; begin Result := not (Left = Right); end; { TVisCtl3D } procedure SaveGraphicToFile(AGraphics: TGraphic; const AFileName: TFileName; AFormat: TImageFormat); overload; const exts: array[TImageFormat] of string = ( '', 'bmp', 'png', 'jpg|jpeg' ); clss: array[TImageFormat] of TGraphicClass = ( nil, TBitmap, TPngImage, TJPEGImage ); var ext: string; &if: TImageFormat; TargetGraphic: TGraphic; begin if AFormat = ifFromExtension then begin ext := ExtractFileExt(AFileName).TrimLeft(['.']); for &if := Low(TImageFormat) to High(TImageFormat) do if IndexText(ext, exts[&if].Split(['|'])) <> -1 then begin AFormat := &if; Break; end; end; if AFormat = ifFromExtension then raise Exception.CreateFmt('Unsupported file extension: "%s".', [ext]); if AGraphics.InheritsFrom(clss[AFormat]) then AGraphics.SaveToFile(AFileName) else begin TargetGraphic := clss[AFormat].Create; try TargetGraphic.Assign(AGraphics); TargetGraphic.SaveToFile(AFileName); finally TargetGraphic.Free; end; end; end; procedure SaveGraphicToFile(AGraphics: TGraphic; ADlgOwner: TComponent; const ADefFilename: TFileName); overload; const ImageFormats: array[0..2] of TImageFormat = (ifBitmap, ifPng, ifJpg); var dlg: TFileSaveDialog; begin dlg := TFileSaveDialog.Create(ADlgOwner); try dlg.ClientGuid := '{DB7FB35D-C336-4A82-B108-29738F9E77CB}'; with dlg.FileTypes.Add do begin FileMask := '*.bmp'; DisplayName := 'Bitmap images'; end; with dlg.FileTypes.Add do begin FileMask := '*.png'; DisplayName := 'PNG images'; end; with dlg.FileTypes.Add do begin FileMask := '*.jpg;*.jpeg'; DisplayName := 'JPEG images'; end; dlg.DefaultExtension := '.bmp'; dlg.FileName := ADefFilename; dlg.Options := dlg.Options + [fdoOverwritePrompt]; if dlg.Execute and InRange(Pred(dlg.FileTypeIndex), Ord(Low(ImageFormats)), Ord(High(ImageFormats))) then SaveGraphicToFile(AGraphics, dlg.FileName, ImageFormats[Pred(dlg.FileTypeIndex)]); finally dlg.Free; end; end; var LVisCtl3DSSTB_Level: Cardinal = 0; procedure TVisCtl3D.SaveSceneToBitmap(const ARenderOutputData: TRenderOutputData; ACleanUp, ARenderToClipboard: Boolean; const AFileName: string); begin Inc(LVisCtl3DSSTB_Level); try try var bm := TBitmap.Create(ARenderOutputData.Width, ARenderOutputData.Height); try bm.PixelFormat := pf24bit; var p := bm.ScanLine[bm.Height - 1]; // bm is bottom-up glReadPixels(0, 0, ARenderOutputData.Width, ARenderOutputData.Height, GL_BGR, GL_UNSIGNED_BYTE, p); if ARenderToClipboard then Clipboard.Assign(bm) else if not AFileName.IsEmpty then SaveGraphicToFile(bm, AFileName, ifFromExtension) else SaveGraphicToFile(bm, Parent); finally bm.Free; end; finally if ACleanUp and ARenderOutputData.Offscreen then begin if Fosbuf_coloratt <> 0 then begin glDeleteTextures(1, @Fosbuf_coloratt); Fosbuf_coloratt := 0; end; if Fosbuf <> 0 then begin glDeleteRenderbuffers(1, @Fosbuf); Fosbuf := 0; end; end; end; finally Dec(LVisCtl3DSSTB_Level); end; end; procedure TVisCtl3D.SaveToBitmap(const AFileName: string; AWidth, AHeight: Integer); begin FRenderToBitmap := True; FRenderToClipboard := False; FRenderFileName := AFileName; FRenderOutputData.Offscreen := (AWidth <> ClientWidth) or (AHeight <> ClientHeight); FRenderOutputData.Width := AWidth; FRenderOutputData.Height := AHeight; Invalidate; end; procedure TVisCtl3D.SetEffects(const Value: TPPEs); begin if FEffects <> Value then begin FEffects := Value; FPostProcessing := FEffects <> []; Invalidate; end; end; procedure TVisCtl3D.SetFov(const Value: Double); begin if FFov <> Value then begin FFov := Value; if FProjection = Perspective then begin ComputeP(FRenderOutputData); Invalidate; end; end; end; procedure TVisCtl3D.SetLightPos(const Value: rglv); begin if FLightPos <> Value then begin FLightPos := Value; Invalidate; end; end; procedure TVisCtl3D.SetMSAAValue(const Value: TMSAAValue); begin if FMSAAValue <> Value then begin FMSAAValue := Value; Invalidate; end; end; procedure TVisCtl3D.SetProjection(const Value: TProjection); begin if FProjection <> Value then begin FProjection := Value; ComputeP(FRenderOutputData); for var obj in FObjs.List do obj.ProjectionChanged; Invalidate; end; end; procedure TVisCtl3D.SetShowAxes(const Value: Boolean); begin if Assigned(FAxes) then FAxes.Visible := Value; end; procedure TVisCtl3D.SetupNewObjects; begin if Assigned(FNewObjects) and (FNewObjects.Count > 0) then begin Screen.Cursor := crHourGlass; try for var NewObject in FNewObjects do try NewObject.Setup; except NewObject.FVisible := False; end; FNewObjects.Clear; finally Screen.Cursor := crDefault; end; end; end; function TVisCtl3D.GetCameraPose: TCameraPose; begin Result.SceneCenter := LookAt; Result.RelativePosition := CameraPos - LookAt; end; procedure TVisCtl3D.SetCameraPose(const APose: TCameraPose); begin View.FixCamera(APose); end; function TVisCtl3D.GetCameraRelativePos: rglv; begin Result := CameraPos - LookAt; end; procedure TVisCtl3D.SetCameraRelativePos(const ARelativePos: rglv); begin View.FixCamera(LookAt, ARelativePos); end; procedure TVisCtl3D.CenterMarkerHiderTimerTimer(Sender: TObject); begin if Assigned(FCenterMarkerHiderTimer) then FCenterMarkerHiderTimer.Enabled := False; if Assigned(FCenterMarker) then FCenterMarker.Visible := False; end; procedure TVisCtl3D.CancelPanning(AAbort: Boolean); begin if FRawPanning then begin FRawPanning := False; Screen.Cursor := crDefault; Mouse.CursorPos := FPrePanMousePos; if AAbort then View.AnimateTo(FPrePanCamPose); if FAbandonRawInputOnPanEnd then FPreferRawInput := False; end; end; procedure TVisCtl3D.Pan(∆X: Integer; ∆Y: Integer); function rmod(const x, y: Double): Double; begin Result := x - Floor64(x / y) * y; end; const P: rglm = (elem: (1, 0, 0, 0, 1, 0, 0, 0, 0)); begin if (∆X <> 0) or (∆Y <> 0) then FCanClick := False; const LShift = IfThen(GetKeyState(VK_SHIFT) < 0, 0.1, 1.0); if csLButtonDown in ControlState then begin const LSensitivity = FPanSensitivity * LShift; θ := EnsureRange(θ - ∆Y * LSensitivity / 250, 0, Pi); φ := rmod(φ - ∆X * LSensitivity / 500, 2*Pi); ComputeV; Invalidate; DoViewChanged; end else if FMButtonDown then begin const LSensitivity = FOrbitSensitivity * LShift; View.TargetCenter := View.TargetCenter + vec(0, 0, ∆Y * LSensitivity / 25) - P * (vec(0, 0, 1) xor View.CameraRelativePosition.Normalized) * (∆X * LSensitivity / 25); end; end; procedure TVisCtl3D.RevealCenterMarker; begin if Assigned(FCenterMarker) then begin FCenterMarker.Position := LookAt; FCenterMarker.Visible := True; if Assigned(FCenterMarkerHiderTimer) then begin FCenterMarkerHiderTimer.Enabled := False; FCenterMarkerHiderTimer.Enabled := True; end; end; end; procedure TVisCtl3D.DoViewChanged; begin if Assigned(FOnViewChanged) then FOnViewChanged(Self); end; procedure TVisCtl3D.UpdateContextMenuStates(ASelObj: TDrawable3D); begin if Assigned(FPopupMenu) then begin if Assigned(FToggleAxesMnuItem) then FToggleAxesMnuItem.Checked := ShowAxes; if Assigned(FMSAASubmenu) then for var i := 0 to FMSAASubmenu.Count - 1 do begin FMSAASubmenu.Items[i].Checked := FMSAASubmenu.Items[i].Tag = Ord(FMSAAValue); FMSAASubmenu.Items[i].Visible := FMSAASubmenu.Items[i].Tag <= FMaxSamples; end; if Assigned(FPPSubmenu) then for var i := 0 to FPPSubmenu.Count - 1 do FPPSubmenu.Items[i].Checked := TPPE(FPPSubmenu.Items[i].Tag) in FEffects; if Assigned(FOrthogonalMnuItem) then FOrthogonalMnuItem.Checked := Projection = Orthographic; if Assigned(FPerspectiveMnuItem) then FPerspectiveMnuItem.Checked := Projection = Perspective; for var i := FPopupMenu.Items.Count - 1 downto 0 do if (FPopupMenu.Items[i] is TVisCtlObjectMenuItem) and (FPopupMenu.Items[i] <> FRemoveMnuItem) then FPopupMenu.Items.Delete(i); var ShiftKeyDown := GetKeyState(VK_SHIFT) < 0; if Assigned(FImplInfoMnuItem) then FImplInfoMnuItem.Visible := ShiftKeyDown; if Assigned(FExtInfoMnuItem) then FExtInfoMnuItem.Visible := ShiftKeyDown; end; end; procedure TVisCtl3D.ViewChanged(Sender: TObject); begin ComputeV; if FProjection = Orthographic then ComputeP(FRenderOutputData); if Assigned(FCenterMarker) and ((FCenterMarker.Position <> LookAt) or (LookAt <> LookAt.Zero)) then begin FCenterMarker.Position := LookAt; FCenterMarker.Visible := True; FCenterMarkerHiderTimer.Enabled := False; FCenterMarkerHiderTimer.Enabled := True; end; Invalidate; DoViewChanged; end; procedure TVisCtl3D.WMContextMenu(var Message: TWMContextMenu); var P: TPoint; begin if Assigned(FPopupMenu) and Assigned(Context) then begin var LObj := TDrawable3D(nil); if Message.Pos = Point(-1, -1) then P := ClientToScreen(BoundsRect.CenterPoint) else begin P := Message.Pos; with ScreenToClient(P) do LObj := HitTest(X, Y); end; UpdateContextMenuStates(LObj); CustomizeMenu(FPopupMenu); if Assigned(FOnBeforeContextPopup) then FOnBeforeContextPopup(Self); FPopupMenu.OnPopup := nil; try FPopupMenu.Popup(P.X, P.Y); finally if Assigned(Self) and Assigned(FPopupMenu) then FPopupMenu.OnPopup := PopupMenuPopup; end; end else inherited; end; procedure TVisCtl3D.WMDestroy(var Message: TWMDestroy); begin FreeGLResources; inherited; end; procedure TVisCtl3D.WndProc(var Message: TMessage); begin inherited; case Message.Msg of WM_MBUTTONDOWN: begin FMButtonDown := True; SetCaptureControl(Self); end; WM_MBUTTONUP: begin FMButtonDown := False; if GetCapture = Handle then SetCaptureControl(nil) end; WM_CANCELMODE: begin FMButtonDown := False; CancelPanning; end; WM_CAPTURECHANGED: if GetCapture <> Handle then begin FMButtonDown := False; CancelPanning; end; WM_INPUT: begin var RI := Default(TRawInput); var LSize: Cardinal := SizeOf(RI); if GetRawInputData(Message.LParam, RID_INPUT, @RI, LSize, SizeOf(TRawInputHeader)) > 0 then begin if RI.header.dwType <> RIM_TYPEMOUSE then Exit; if FRawPanning then begin if RI.data.mouse.usFlags and MOUSE_MOVE_ABSOLUTE <> 0 then begin if FAbandonRawInputIfAbsoluteDevice then FAbandonRawInputOnPanEnd := True; var LThisRawPoint := Point(RI.data.mouse.lLastX, RI.data.mouse.lLastY); begin // Since MOUSE_MOVE_ABSOLUTE is set, LThisRawPoint is an absolute point -- in a relative coordinate system. // We need to convert it to the absolute point int the standard absolute desktop coordinate system. const LIsVirtual = RI.Data.mouse.usFlags and MOUSE_VIRTUAL_DESKTOP <> 0; const LWidth = GetSystemMetrics(IfThen(LIsVirtual, SM_CXVIRTUALSCREEN, SM_CXSCREEN)); const LHeight = GetSystemMetrics(IfThen(LIsVirtual, SM_CYVIRTUALSCREEN, SM_CYSCREEN)); LThisRawPoint.X := Round(LThisRawPoint.X / Word.MaxValue * LWidth); LThisRawPoint.Y := Round(LThisRawPoint.Y / Word.MaxValue * LHeight); end; if FPrevRawPoint <> InvalidRawPoint then with LThisRawPoint - FPrevRawPoint do Pan(X, Y); FPrevRawPoint := LThisRawPoint; end else Pan(RI.data.mouse.lLastX, RI.data.mouse.lLastY); end; end; end; end; end; procedure TVisCtl3D.Zoom(const Delta: Double; Shift: TShiftState); begin var F := 1.0; if ssCtrl in Shift then F := 10 else if ssShift in Shift then F := 0.1; var q: Double := Power(1.1, -F * Delta); r := q * r; r := EnsureRange(r, 0.01, 900); ComputeV; if FProjection = Orthographic then ComputeP(FRenderOutputData); Invalidate; DoViewChanged; end; procedure TVisCtl3D.ZoomIn(const Delta: Double; Shift: TShiftState); begin Zoom(Abs(Delta), Shift); end; procedure TVisCtl3D.ZoomOut(const Delta: Double; Shift: TShiftState); begin Zoom(-Abs(Delta), Shift); end; procedure TVisCtl3D.ClearScene; begin var LObjects := TList<TDrawable3D>.Create; try for var i := 0 to FObjs.ItemCount - 1 do begin var LObj := FObjs.Items[i]; if Assigned(LObj) and (LObj.Parent = nil) and not LObj.FProtected and (LObj <> FAxes) and (LObj <> FCenterMarker) then LObjects.Add(LObj); end; for var LObj in LObjects do RemoveObject(LObj); finally LObjects.Free; end; end; procedure TVisCtl3D.ComputeM; begin M := rglRotate(-90, 0, 0, 1) * rglRotate(-90, 0, 1, 0); end; procedure TVisCtl3D.ComputeP(const ARenderOutputData: TRenderOutputData); begin var AR: Single; if ((ARenderOutputData.Width = 0) or (ARenderOutputData.Height = 0)) and not ARenderOutputData.Offscreen then begin if (ClientWidth <> 0) and (ClientHeight <> 0) then AR := ClientWidth / ClientHeight else AR := 1.0; end else AR := ARenderOutputData.Aspect; case FProjection of Orthographic: P := rglOrtho(-r, r, -r / AR, r / AR, 0.1, 1000); Perspective: P := rglPerspective(FFOV, AR, 0.1, 1000) else P := rglm4.Identity; end; end; procedure TVisCtl3D.ComputeV; begin eye.x := LookAt.x + r*Sin(θ)*Cos(φ); eye.y := LookAt.y + r*Sin(θ)*Sin(φ); eye.z := LookAt.z + r*Cos(θ); const z = eye.x; // cycl. const x = eye.y; const y = eye.z; const LAz = LookAt.x; // cycl. const LAx = LookAt.y; const LAy = LookAt.z; var u: rglv; if IsZero(θ, 1E-7) then u := vec(-Sin(φ), 0, -Cos(φ)) else if IsZero(θ - Pi, 1E-7) then u := vec(Sin(φ), 0, Cos(φ)) else u := vec(0, 1, 0); V := rglLookAt(x, y, z, LAx, LAy, LAz, u.x, u.y, u.z); end; constructor TVisCtl3D.Create(AOwner: TComponent); begin inherited; Width := 600; Height := 400; FMSAAValue := msaa8; ClearMask := 0; r := 25; θ := Pi/4; φ := Pi/4; ComputeM; ComputeV; FPrevRawPoint := InvalidRawPoint; FPanSensitivity := 1.0; FOrbitSensitivity := 1.0; FPreferRawInput := True; FAbandonRawInputIfAbsoluteDevice := True; FFOV := 45; FLightPos := vec(10.0, 12.0, 8.0); FProjection := Perspective; FNewObjects := TList<TDrawable3D>.Create; FScene := TScene.Create(Self); FView := TView3D.Create(Self); FView.OnChange := ViewChanged; FObjs := TDrawableList3D.Create(Self); FObjs.OnChange := ObjChanged; FAxes := NewObject<TAxes>; FAxes.FProtected := True; FCenterMarker := NewObject<TCenterMarker>; FCenterMarker.FProtected := True; FCenterMarker.Visible := False; FCenterMarkerHiderTimer := TTimer.Create(Self); FCenterMarkerHiderTimer.Interval := 1000; FCenterMarkerHiderTimer.Enabled := False; FCenterMarkerHiderTimer.OnTimer := CenterMarkerHiderTimerTimer; FInvalidationTimer := TTimer.Create(Self); FInvalidationTimer.Interval := 50; FInvalidationTimer.OnTimer := InvalidationTimerTimer; FInvalidationTimer.Enabled := False; FAnimationTimer := TAnimationTimer.Create(Self); FAnimationTimer.Control := Self; FAnimationTimer.OnTimer := AnimationTimerTimer; FAnimationTimer.Enabled := False; TabStop := True; FCustomMenuItems := TList<TMenuItem>.Create; MakeContextMenu; end; procedure TVisCtl3D.CreateWnd; begin inherited; var RID := Default(TRawInputDevice); RID.usUsagePage := 1; RID.usUsage := 2; RID.hwndTarget := Handle; FRawInputAvailable := RegisterRawInputDevices(@RID, 1, SizeOf(RID)); end; procedure TVisCtl3D.CustomizeMenu(AMenu: TMenu); begin if FCustomMenuItems = nil then Exit; if AMenu = nil then Exit; for var i := 0 to FCustomMenuItems.Count - 1 do begin if Assigned(FCustomMenuItems[i].Parent) then FCustomMenuItems[i].Parent.Remove(FCustomMenuItems[i]); AMenu.Items.Add(FCustomMenuItems[i]); end; end; procedure TVisCtl3D.ObjChanged(Sender: TObject); begin LowPriorityInvalidate; end; procedure TVisCtl3D.AnimationTimerTimer(Sender: TObject); begin Invalidate; end; procedure TVisCtl3D.BeginBackgroundPaint; begin Inc(FBackgroundPaintLevel); end; procedure TVisCtl3D.EndBackgroundPaint; begin Dec(FBackgroundPaintLevel); end; procedure TVisCtl3D.FramebufferSetup(const ARenderOutputData: TRenderOutputData); const ScreenQuad: array[0..2*2*2*3-1] of GLfloat = ( -1.0, +1.0, 0.0, 1.0, -1.0, -1.0, 0.0, 0.0, +1.0, -1.0, 1.0, 0.0, -1.0, +1.0, 0.0, 1.0, +1.0, -1.0, 1.0, 0.0, +1.0, +1.0, 1.0, 1.0 ); begin if Context = nil then Exit; Context.MakeCurrent('TVisCtl3D.FramebufferSetup'); var LMaxSamples: GLint; glGetIntegerv(GL_MAX_SAMPLES, @LMaxSamples); if Ord(FMSAAValue) > LMaxSamples then FMSAAValue := TMSAAValue(LMaxSamples); FMaxSamples := LMaxSamples; // 1: Multi-sample buffer for MSAA if FMSAAValue <> msaa0 then begin if FMSAAbuf = 0 then glGenFramebuffers(1, @FMSAAbuf); glBindFramebuffer(GL_FRAMEBUFFER, FMSAAbuf); if FMSAAbuf_coloratt = 0 then glGenTextures(1, @FMSAAbuf_coloratt); glBindTexture(GL_TEXTURE_2D_MULTISAMPLE, FMSAAbuf_coloratt); glTexImage2DMultisample(GL_TEXTURE_2D_MULTISAMPLE, Ord(FMSAAValue), GL_RGB, ARenderOutputData.Width, ARenderOutputData.Height, GL_TRUE); glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D_MULTISAMPLE, FMSAAbuf_coloratt, 0); if FMSAAbuf_dsatt = 0 then glGenRenderbuffers(1, @FMSAAbuf_dsatt); glBindRenderbuffer(GL_RENDERBUFFER, FMSAAbuf_dsatt); glRenderbufferStorageMultisample(GL_RENDERBUFFER, Ord(FMSAAValue), GL_DEPTH24_STENCIL8, ARenderOutputData.Width, ARenderOutputData.Height); glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_DEPTH_STENCIL_ATTACHMENT, GL_RENDERBUFFER, FMSAAbuf_dsatt); if glCheckFramebufferStatus(GL_FRAMEBUFFER) <> GL_FRAMEBUFFER_COMPLETE then DebugBreak; end else begin if FMSAAbuf_dsatt <> 0 then begin glDeleteRenderbuffers(1, @FMSAAbuf_dsatt); FMSAAbuf_dsatt := 0; end; if FMSAAbuf_coloratt <> 0 then begin glDeleteTextures(1, @FMSAAbuf_coloratt); FMSAAbuf_coloratt := 0; end; if FMSAAbuf <> 0 then begin glDeleteFramebuffers(1, @FMSAAbuf); FMSAAbuf := 0; end; end; // 2: Off-screen buffer for post-processing if FPostProcessing then begin if Fauxbuf = 0 then glGenFramebuffers(1, @Fauxbuf); glBindFramebuffer(GL_FRAMEBUFFER, Fauxbuf); if Fauxbuf_coloratt = 0 then glGenTextures(1, @Fauxbuf_coloratt); glBindTexture(GL_TEXTURE_2D, Fauxbuf_coloratt); glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, ARenderOutputData.Width, ARenderOutputData.Height, 0, GL_RGB, GL_UNSIGNED_BYTE, nil); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, Fauxbuf_coloratt, 0); if FMSAAValue = msaa0 then begin if Fauxbuf_dsatt = 0 then glGenRenderbuffers(1, @Fauxbuf_dsatt); glBindRenderbuffer(GL_RENDERBUFFER, Fauxbuf_dsatt); glRenderbufferStorage(GL_RENDERBUFFER, GL_DEPTH24_STENCIL8, ARenderOutputData.Width, ARenderOutputData.Height); glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_DEPTH_STENCIL_ATTACHMENT, GL_RENDERBUFFER, Fauxbuf_dsatt); end; if glCheckFramebufferStatus(GL_FRAMEBUFFER) <> GL_FRAMEBUFFER_COMPLETE then DebugBreak; end else begin if Fauxbuf_dsatt <> 0 then begin glDeleteRenderbuffers(1, @Fauxbuf_dsatt); Fauxbuf_dsatt := 0; end; if Fauxbuf_coloratt <> 0 then begin glDeleteTextures(1, @Fauxbuf_coloratt); Fauxbuf_coloratt := 0; end; if Fauxbuf <> 0 then begin glDeleteFramebuffers(1, @Fauxbuf); Fauxbuf := 0; end; end; // 3: Screen quad if FScreenVAO = 0 then begin glGenVertexArrays(1, @FScreenVAO); glBindVertexArray(FScreenVAO); glGenBuffers(1, @FScreenQuad); glBindBuffer(GL_ARRAY_BUFFER, FScreenVAO); glBufferData(GL_ARRAY_BUFFER, SizeOf(ScreenQuad), @ScreenQuad, GL_STATIC_DRAW); glEnableVertexAttribArray(0); glEnableVertexAttribArray(1); glVertexAttribPointer(0, 2, GL_FLOAT, GL_FALSE, 4*SizeOf(GLfloat), nil); glVertexAttribPointer(1, 2, GL_FLOAT, GL_FALSE, 4*SizeOf(GLfloat), Pointer(2*SizeOf(GLfloat))); glBindVertexArray(0); end; // 4: Off-screen output buffer if ARenderOutputData.Offscreen then begin if Fosbuf = 0 then glGenFramebuffers(1, @Fosbuf); glBindFramebuffer(GL_FRAMEBUFFER, Fosbuf); if Fosbuf_coloratt = 0 then glGenTextures(1, @Fosbuf_coloratt); glBindTexture(GL_TEXTURE_2D, Fosbuf_coloratt); glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, ARenderOutputData.Width, ARenderOutputData.Height, 0, GL_RGB, GL_UNSIGNED_BYTE, nil); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, Fosbuf_coloratt, 0); if glCheckFramebufferStatus(GL_FRAMEBUFFER) <> GL_FRAMEBUFFER_COMPLETE then DebugBreak; end else begin if Fosbuf_coloratt <> 0 then begin glDeleteTextures(1, @Fosbuf_coloratt); Fosbuf_coloratt := 0; end; if Fosbuf <> 0 then begin glDeleteFramebuffers(1, @Fosbuf); Fosbuf := 0; end; end; end; procedure TVisCtl3D.FreeGLResources; begin if HandleAllocated and Assigned(Context) then begin if Context.TryMakeCurrent then begin if Assigned(FObjs) then FObjs.FreeGLResources; glDeleteBuffers (1, @FScreenQuad); FScreenQuad := 0; glDeleteTextures (1, @Fosbuf_coloratt); Fosbuf_coloratt := 0; glDeleteFramebuffers (1, @Fosbuf); Fosbuf := 0; glDeleteRenderbuffers (1, @Fauxbuf_dsatt); Fauxbuf_dsatt := 0; glDeleteTextures (1, @Fauxbuf_coloratt); Fauxbuf_coloratt := 0; glDeleteFramebuffers (1, @Fauxbuf); Fauxbuf := 0; glDeleteVertexArrays (1, @FScreenVAO); FScreenVAO := 0; glDeleteRenderbuffers (1, @FMSAAbuf_dsatt); FMSAAbuf_dsatt := 0; glDeleteTextures (1, @FMSAAbuf_coloratt); FMSAAbuf_coloratt := 0; glDeleteFramebuffers (1, @FMSAAbuf); FMSAAbuf := 0; end else rglLog('TVisCtl3D.FreeGLResources: TryMakeCurrent failed'); FreeAndNil(FProgramMgr); end; end; procedure TVisCtl3D.InvalidationTimerTimer(Sender: TObject); begin FInvalidationTimer.Enabled := False; Invalidate; end; procedure TVisCtl3D.KeyDown(var Key: Word; Shift: TShiftState); const P: rglm = (elem: (1, 0, 0, 0, 1, 0, 0, 0, 0)); begin inherited; if [ssCtrl, ssAlt] * Shift <> [] then Exit; const Tenth = IfThen(ssShift in Shift, 0.1, 1.0); case Key of VK_ESCAPE: CancelPanning(True); VK_LEFT: View.PhiRad := φ - Pi/50 * Tenth; VK_RIGHT: View.PhiRad := φ + Pi/50 * Tenth; VK_UP: View.ThetaRad := θ - Pi/50 * Tenth; VK_DOWN: View.ThetaRad := θ + Pi/50 * Tenth; VK_OEM_MINUS, VK_SUBTRACT: ZoomOut(1, Shift); VK_OEM_PLUS, VK_ADD: ZoomIn(1, Shift); Ord('0'), VK_NUMPAD0: View.AnimateTo(rglv.Zero); Ord('E'): View.TargetCenter := View.TargetCenter + vec(0, 0, 1 * Tenth); Ord('Q'): View.TargetCenter := View.TargetCenter - vec(0, 0, 1 * Tenth); Ord('W'): View.TargetCenter := View.TargetCenter - Tenth * P * View.CameraRelativePosition.Normalized; Ord('S'): View.TargetCenter := View.TargetCenter + Tenth * P * View.CameraRelativePosition.Normalized; Ord('A'): View.TargetCenter := View.TargetCenter - Tenth * P * (vec(0, 0, 1) xor View.CameraRelativePosition.Normalized); Ord('D'): View.TargetCenter := View.TargetCenter + Tenth * P * (vec(0, 0, 1) xor View.CameraRelativePosition.Normalized); end; end; procedure TVisCtl3D.KeyPress(var Key: Char); begin inherited; case Key of 'X': View.AnimateTo(View.TargetCenter, View.r * vec(1, 0, 0)); 'Y': View.AnimateTo(View.TargetCenter, View.r * vec(0, 1, 0)); 'Z': View.AnimateTo(View.TargetCenter, View.r * vec(0, 0, 1)); 'x': View.AnimateTo(View.TargetCenter, View.r * vec(-1, 0, 0)); 'y': View.AnimateTo(View.TargetCenter, View.r * vec(0, -1, 0)); 'z': View.AnimateTo(View.TargetCenter, View.r * vec(0, 0, -1)); 'o': Projection := Orthographic; 'p': Projection := Perspective; 'r': View.AnimateTo(View.TargetCenter, rθφ.Create(View.r, 2*Pi*Random, ArcCos(2*Random - 1))); 'h': MnuQuickHelp(Self); 'c': RevealCenterMarker; ^A: MnuToggleAxes(Self); ^C: MnuCopySceneToClipboard(Self); ^S: MnuSaveSceneToFile(Self); end; end; procedure TVisCtl3D.LowPriorityInvalidate; begin if FBackgroundPaintLevel <= 0 then Invalidate else FInvalidationTimer.Enabled := True; end; procedure TVisCtl3D.MakeContextMenu; begin if Assigned(FPopupMenu) then Exit; FPopupMenu := TPopupMenu.Create(Self); FPopupMenu.OnPopup := PopupMenuPopup; var mi := TMenuItem.Create(FPopupMenu); mi.Caption := 'Show axes'#9'Ctrl+A'; mi.Hint := 'Shows or hides the coordinate axes.'; mi.OnClick := MnuToggleAxes; FPopupMenu.Items.Add(mi); FToggleAxesMnuItem := mi; FPopupMenu.Items.NewBottomLine; mi := TMenuItem.Create(FPopupMenu); mi.Caption := 'Save image to file...'#9'Ctrl+S'; mi.Hint := 'Saves the current scene to a bitmap picture file.'; mi.OnClick := MnuSaveSceneToFile; FPopupMenu.Items.Add(mi); mi := TMenuItem.Create(FPopupMenu); mi.Caption := 'Copy image to clipboard...'#9'Ctrl+C'; mi.Hint := 'Copies the current scene to the clipboard.'; mi.OnClick := MnuCopySceneToClipboard; FPopupMenu.Items.Add(mi); FPopupMenu.Items.NewBottomLine; mi := TMenuItem.Create(FPopupMenu); mi.Caption := 'Projection'; mi.Hint := 'Contains the available projections.'; FPopupMenu.Items.Add(mi); FProjSubmenu := mi; var smi := TMenuItem.Create(mi); smi.Caption := 'Orthographic'#9'O'; smi.Hint := 'Displays the scene using orthographic projection.'; smi.Tag := 0; smi.OnClick := MnuSetProj; smi.RadioItem := True; mi.Add(smi); FOrthogonalMnuItem := smi; smi := TMenuItem.Create(mi); smi.Caption := 'Perspective'#9'P' ; smi.Hint := 'Displays the scene using perspective projection.'; smi.Tag := 1; smi.OnClick := MnuSetProj; smi.RadioItem := True; mi.Add(smi); FPerspectiveMnuItem := smi; mi := TMenuItem.Create(FPopupMenu); mi.Caption := 'Anti-aliasing'; mi.Hint := 'Contains the anti-aliasing options.'; FPopupMenu.Items.Add(mi); FMSAASubmenu := mi; smi := TMenuItem.Create(mi); smi.Caption := 'No anti-aliasing'; smi.Hint := 'Disables all anti-aliasing.'; smi.Tag := Ord(msaa0); smi.OnClick := MnuSetMSAA; smi.RadioItem := True; mi.Add(smi); var LMSAAValue := 2; while LMSAAValue <= Ord(High(TMSAAValue)) do begin smi := TMenuItem.Create(mi); smi.Caption := Format('%d× MSAA', [LMSAAValue]); smi.Hint := Format('Enables %d× MSAA.', [LMSAAValue]); smi.Tag := LMSAAValue; smi.OnClick := MnuSetMSAA; smi.RadioItem := True; mi.Add(smi); LMSAAValue := 2 * LMSAAValue; end; FPopupMenu.Items.NewBottomLine; mi := TMenuItem.Create(FPopupMenu); mi.Caption := 'Quick &help'#9'H'; mi.Hint := 'Displays information about the 3D visualisation control’s keyboard and mouse interface.'; mi.OnClick := MnuQuickHelp; FPopupMenu.Items.Add(mi); FHelpMnuItem := mi; FPopupMenu.Items.NewBottomLine; mi := TMenuItem.Create(FPopupMenu); mi.Caption := 'OpenGL info'; mi.Hint := 'Displays information about your system’s OpenGL implementation.'; mi.OnClick := MnuImplInfo; FPopupMenu.Items.Add(mi); FImplInfoMnuItem := mi; mi := TMenuItem.Create(FPopupMenu); mi.Caption := 'OpenGL extensions'; mi.Hint := 'Displays information about your system’s OpenGL extensions.'; mi.OnClick := MnuExtInfo; FPopupMenu.Items.Add(mi); FExtInfoMnuItem := mi; mi := TMenuItem.Create(FPopupMenu); mi.Caption := '-'; FPopupMenu.Items.Add(mi); end; procedure TVisCtl3D.MnuCopySceneToClipboard(Sender: TObject); begin var W: Integer := Max(ClientWidth, 1); var H: Integer := Max(ClientHeight, 1); var Aspect: Double := 0.0; if ImageSizeDialog(GetParentForm(Self), W, H, Aspect) then begin FRenderToBitmap := True; FRenderToClipboard := True; FRenderOutputData.Offscreen := (W <> ClientWidth) or (H <> ClientHeight); FRenderOutputData.Width := W; FRenderOutputData.Height := H; Invalidate; end; end; procedure TVisCtl3D.MnuImplInfo(Sender: TObject); begin TTableDialog.ShowTable( GetParentForm(Self), '3D Visualisation Control', 'OpenGL Implementation Data', [ 'Version', 'Vendor', 'Renderer', 'GLSL version', 'Context profile mask', 'Context flags', 'Line widths (aliased)', 'Line widths (smooth)', 'Max MSAA samples', 'Max texture size', 'Max texture anisotropy' ], [ FImplData.VersionString + #32'(' + FImplData.Version.Major.ToString + '.' + FImplData.Version.Minor.ToString + ')', FImplData.Vendor, FImplData.Renderer, FImplData.GLSL, FImplData.ContextProfileMask.ToHexString, FImplData.ContextFlags.ToHexString, FImplData.LineWidths.Aliased.Min.ToString + '..' + FImplData.LineWidths.Aliased.Max.ToString, FImplData.LineWidths.Smooth.Min.ToString + '..' + FImplData.LineWidths.Smooth.Max.ToString, FImplData.MaxSamples.ToString, FImplData.MaxTextureSize.ToString, FImplData.MaxTextureAnisotropy.ToString ], TMsgDlgType.mtCustom ); end; procedure TVisCtl3D.MnuExtInfo(Sender: TObject); begin var LExts := TList<TPair<string, string>>.Create; try LExts.Capacity := Length(FExts) div 2 + Ord(Odd(Length(FExts))); for var i := 0 to Length(FExts) div 2 - 1 do LExts.Add(TPair<string, string>.Create(FExts[2*i], FExts[2*i + 1])); if Odd(Length(FExts)) then LExts.Add(TPair<string, string>.Create(FExts[High(FExts)], '')); TTableDialog.ShowTable( GetParentForm(Self), '3D Visualisation Control', 'OpenGL Extensions', LExts.ToArray, TMsgDlgType.mtCustom ); finally LExts.Free; end; end; procedure TVisCtl3D.MnuQuickHelp(Sender: TObject); function p(const A: string = ''; const B: string = ''; const Cond: Boolean = True): TPair<string, string>; begin if not Cond then Exit(Default(TPair<string, string>)); Result.Key := A; Result.Value := B; end; begin while True do if ( TTableDialog.ShowTable( GetParentForm(Self), '3D Visualisation Control', 'Quick Reference', TArray<TPair<string, string>>.Create( p('W', 'Moves the orbit centre forward keeping z fixed.'), p('S', 'Moves the orbit centre backward keeping z fixed.'), p('A', 'Moves the orbit centre to the left keeping z fixed.'), p('D', 'Moves the orbit centre to the right keeping z fixed.'), p('Q', 'Moves the orbit centre down (decreasing z).'), p('E', 'Moves the orbit centre up (increasing z).'), p('MMB-drag', 'Horizontally: left/right. Vertically: z.'), p, p('0', 'Moves the orbit centre to (0, 0, 0).'), p, p('Left', 'Orbits around the centre: Decrease φ.'), p('Right', 'Orbits around the centre: Increase φ.'), p('Up', 'Orbits around the centre: Decrease θ.'), p('Down', 'Orbits around the centre: Increase θ.'), p('R', 'Orbits around the centre: Move to random (θ, φ).'), p('LMB-drag', 'Horizontally: φ. Vertically: θ.'), p, p('+', 'Decreases distance from orbit centre.'), p('−', 'Increases distance from orbit centre.'), p, p('Shift', 'Moves at 1/10 the speed (modifier).'), p('Escape', 'Aborts and undoes the current mouse panning operation.', FRawInputAvailable and FPreferRawInput) ), [mrRetry, mrClose], ['&More', 'Close'], mrClose, mrClose, TMsgDlgType.mtCustom ) <> mrRetry ) or ( TTableDialog.ShowTable( GetParentForm(Self), '3D Visualisation Control', 'Quick Reference', TArray<TPair<string, string>>.Create( p('x', 'Orbits the view to look in the +x̂ direction.'), p('X', 'Orbits the view to look in the −x̂ direction.'), p('y', 'Orbits the view to look in the +ŷ direction.'), p('Y', 'Orbits the view to look in the −ŷ direction.'), p('z', 'Orbits the view to look in the +ẑ direction.'), p('Z', 'Orbits the view to look in the −ẑ direction.'), p, p('p', 'Sets projection mode to perspective.'), p('o', 'Sets projection mode to orthographic.'), p, p('c', 'Reveals the camera orbit centre point.'), p('h', 'Displays these quick reference tables.') ), [mrRetry, mrClose], ['&Back', 'Close'], mrClose, mrClose, TMsgDlgType.mtCustom ) <> mrRetry ) then Break; end; procedure TVisCtl3D.MnuSaveSceneToFile(Sender: TObject); begin var W: Integer := Max(ClientWidth, 1); var H: Integer := Max(ClientHeight, 1); var Aspect: Double := 0.0; if ImageSizeDialog(GetParentForm(Self), W, H, Aspect) then SaveToBitmap('', W, H); end; procedure TVisCtl3D.MnuSetMSAA(Sender: TObject); begin if Sender is TMenuItem then MSAA := TMSAAValue(TMenuItem(Sender).Tag); end; procedure TVisCtl3D.MnuSetProj(Sender: TObject); begin if Sender is TMenuItem then case TMenuItem(Sender).Tag of 0: Projection := Orthographic; 1: Projection := Perspective; end; end; procedure TVisCtl3D.MnuToggleAxes(Sender: TObject); begin ShowAxes := not ShowAxes; end; procedure TVisCtl3D.Click; begin inherited; if FCanClick then begin const LObj = HitTest(ScreenToClient(Mouse.CursorPos)); if Assigned(LObj) then LObj.Click; end; end; procedure TVisCtl3D.MouseDown(Button: TMouseButton; Shift: TShiftState; X, Y: Integer); begin inherited; FPrevMousePoint := Point(X, Y); FCanClick := True; if CanFocus then SetFocus; if Context = nil then Exit; if FRawInputAvailable and FPreferRawInput and (Button in [TMouseButton.mbLeft, TMouseButton.mbMiddle]) and not FRawPanning then begin if FAbandonRawInputIfAbsoluteDevice then begin if GetSystemMetrics(SM_REMOTESESSION) <> 0 then begin FPreferRawInput := False; Exit; end; end; FPrevRawPoint := InvalidRawPoint; FRawPanning := True; FAbandonRawInputOnPanEnd := False; Screen.Cursor := crNone; FPrePanMousePos := Mouse.CursorPos; FPrePanCamPose := CameraPose; end; end; procedure TVisCtl3D.MouseUp(Button: TMouseButton; Shift: TShiftState; X, Y: Integer); begin inherited; if FRawPanning and not (csLButtonDown in ControlState) and not FMButtonDown then CancelPanning; end; procedure TVisCtl3D.MouseMove(Shift: TShiftState; X, Y: Integer); begin inherited; if not FRawInputAvailable or not FPreferRawInput then begin if (csLButtonDown in ControlState) or FMButtonDown then begin const ∆X = X - FPrevMousePoint.X; const ∆Y = Y - FPrevMousePoint.Y; if (Abs(∆x) < ClientWidth) and (Abs(∆y) < ClientHeight) then Pan(∆x, ∆y); FPrevMousePoint := Point(X, Y); if not ClientRect.Contains(Point(X, Y)) then begin FPrevMousePoint.X := (X + ClientWidth) mod ClientWidth; FPrevMousePoint.Y := (Y + ClientHeight) mod ClientHeight; Mouse.CursorPos := ClientToScreen(FPrevMousePoint); end; end; end; end; function TVisCtl3D.NewObject<T>: T; begin Result := T.Create(Self); AddObject(Result); end; destructor TVisCtl3D.Destroy; begin FreeGLResources; FreeAndNil(FObjs); FreeAndNil(FProgramMgr); FreeAndNil(FView); FreeAndNil(FScene); FreeAndNil(FNewObjects); if Assigned(FPopupMenu) then FPopupMenu.CloseMenu; FreeAndNil(FPopupMenu); FreeAndNil(FCustomMenuItems); inherited; end; procedure TVisCtl3D.DestroyWnd; begin FreeGLResources; inherited; end; function TVisCtl3D.DoMouseWheel(Shift: TShiftState; WheelDelta: Integer; MousePos: TPoint): Boolean; begin if Assigned(FView) then FView.FinishAnimation; Zoom(WheelDelta / 120, Shift); Result := True; end; function TVisCtl3D.GetObject(Index: Integer): TDrawable3D; begin if FObjs = nil then Result := nil else Result := FObjs[Index]; end; function TVisCtl3D.GetObjectCount: Integer; begin if FObjs = nil then Result := 0 else Result := FObjs.ItemCount; end; function TVisCtl3D.GetShowAxes: Boolean; begin Result := Assigned(FAxes) and FAxes.Visible; end; procedure debugproc(source: GLenum; _type: GLenum; id: GLuint; severity: GLenum; length: GLsizei; const _message: PGLchar; userParam: Pointer); stdcall; begin OutputDebugStringA(_message); if severity = GL_DEBUG_SEVERITY_HIGH then DebugBreak; end; procedure TVisCtl3D.GLInit; begin inherited; if Context = nil then Exit; Context.MakeCurrent('TVisCtl3D.GLInit'); {$IFDEF DEBUG} if Assigned(glDebugMessageCallback) then begin glEnable(GL_DEBUG_OUTPUT); glEnable(GL_DEBUG_OUTPUT_SYNCHRONOUS_ARB); glDebugMessageCallback(debugproc, nil); end; {$ENDIF} glEnable(GL_DEPTH_TEST); glEnable(GL_BLEND); glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); glPolygonOffset(1, 1); glEnable(GL_POLYGON_OFFSET_FILL); FImplData := Context.GetImplInfo; FExts := Context.GetExtensionNames; FreeAndNil(FProgramMgr); FProgramMgr := TProgramMgr.Create(Self); if Assigned(FObjs) then begin FObjs.GLRelease; if Assigned(FNewObjects) then for var obj in FObjs.List do if Assigned(obj) then FNewObjects.Add(obj); end; end; procedure TVisCtl3D.Paint; begin if (Context = nil) or (FProgramMgr = nil) then Exit; Context.MakeCurrent('TVisCtl3D.Paint'); var LRenderToBitmap := FRenderToBitmap; FRenderToBitmap := False; var LRenderToClipboard := FRenderToClipboard; FRenderToClipboard := False; var LRenderFileName := FRenderFileName; FRenderFileName := ''; var LRenderOutputData := FRenderOutputData; FRenderOutputData := Default(TRenderOutputData); LRenderOutputData.MSAA := FMSAAValue; LRenderOutputData.PostProc := FPostProcessing; if not LRenderOutputData.Offscreen then begin LRenderOutputData.Width := ClientWidth; LRenderOutputData.Height := ClientHeight; end; if (FScreenVAO = 0) or (LRenderOutputData <> FPrevRenderOutputData) then begin FramebufferSetup(LRenderOutputData); ComputeP(LRenderOutputData); end; FPrevRenderOutputData := LRenderOutputData; // glViewport(0, 0, LRenderOutputData.Width, LRenderOutputData.Height); if FMSAAValue <> msaa0 then glBindFramebuffer(GL_FRAMEBUFFER, FMSAAbuf) else if FPostProcessing then glBindFramebuffer(GL_FRAMEBUFFER, Fauxbuf) else glBindFramebuffer(GL_FRAMEBUFFER, 0); ApplyClearColor; if FStencil then glClear(GL_COLOR_BUFFER_BIT or GL_DEPTH_BUFFER_BIT or GL_STENCIL_BUFFER_BIT) else glClear(GL_COLOR_BUFFER_BIT or GL_DEPTH_BUFFER_BIT); glEnable(GL_DEPTH_TEST); if FStencil then begin glEnable(GL_STENCIL_TEST); glStencilFunc(GL_ALWAYS, 0, 0); glStencilOp(GL_KEEP, GL_KEEP, GL_REPLACE); end else glDisable(GL_STENCIL_TEST); SetupNewObjects; MVP := P*V*M; ProgramMgr.BeginMonitorTime; FObjs.Draw(FGlobalTime); var LTimeDependent := ProgramMgr.EndMonitorTime or FObjs.TimeDependent; var LOutputFramebuffer: GLuint; if LRenderOutputData.Offscreen then LOutputFramebuffer := Fosbuf else LOutputFramebuffer := 0; if FMSAAValue <> msaa0 then begin glBindFramebuffer(GL_READ_FRAMEBUFFER, FMSAAbuf); if FPostProcessing then glBindFramebuffer(GL_DRAW_FRAMEBUFFER, Fauxbuf) else glBindFramebuffer(GL_DRAW_FRAMEBUFFER, LOutputFramebuffer); glBlitFramebuffer(0, 0, LRenderOutputData.Width, LRenderOutputData.Height, 0, 0, LRenderOutputData.Width, LRenderOutputData.Height, GL_COLOR_BUFFER_BIT, GL_NEAREST); end; if FPostProcessing then begin glBindFramebuffer(GL_FRAMEBUFFER, LOutputFramebuffer); ProgramMgr.UseProgram(P_Tex); glClear(GL_COLOR_BUFFER_BIT); glDisable(GL_DEPTH_TEST); glBindTexture(GL_TEXTURE_2D, Fauxbuf_coloratt); glBindVertexArray(FScreenVAO); glDrawArrays(GL_TRIANGLES, 0, 6); glBindVertexArray(0); LTimeDependent := LTimeDependent or (FEffects * AnimatedEffects <> []); end; if LRenderToBitmap then begin glBindFramebuffer(GL_FRAMEBUFFER, LOutputFramebuffer); SaveSceneToBitmap(LRenderOutputData, True, LRenderToClipboard, LRenderFileName); end; if not LRenderToBitmap and LTimeDependent then begin var LTick: Int64; if QueryPerformanceCounter(LTick) and (FPerfFreq <> 0) then begin if FPrevTick <> 0 then FGlobalTime := FGlobalTime + (LTick - FPrevTick) / FPerfFreq; FPrevTick := LTick; FAnimationTimer.Enabled := True; end; end else begin FPrevTick := 0; FAnimationTimer.Enabled := False; end; end; procedure TVisCtl3D.PopupMenuPopup(Sender: TObject); begin UpdateContextMenuStates(nil); end; procedure TVisCtl3D.Resize; begin if Assigned(OnResize) then OnResize(Self); end; procedure TVisCtl3D.AddMenuItem(AMenuItem: TMenuItem); begin if FCustomMenuItems = nil then Exit; if AMenuItem = nil then Exit; FCustomMenuItems.Add(AMenuItem); end; procedure TVisCtl3D.AddMenuItems(AMenu: TMenuItem); begin if FCustomMenuItems = nil then Exit; if AMenu = nil then Exit; for var i := 0 to AMenu.Count - 1 do AddMenuItem(AMenu[i]); end; procedure TVisCtl3D.AddObject(AObject: TDrawable3D); begin if Assigned(FObjs) and Assigned(FObjs.List) and Assigned(AObject) then begin FObjs.List.Add(AObject); AObject.OnChange := ObjChanged; FNewObjects.Add(AObject); end; end; procedure TVisCtl3D.RemoveMenuItem(AMenuItem: TMenuItem); begin if FCustomMenuItems = nil then Exit; if AMenuItem = nil then Exit; FCustomMenuItems.Remove(AMenuItem); end; procedure TVisCtl3D.RemoveObject(AObject: TDrawable3D); begin if (Self = nil) or (FObjs = nil) or (FObjs.List = nil) then Exit; if AObject = nil then Exit; if AObject.FProtected then raise ERglError.Create('Cannot remove protected object.'); if Assigned(AObject.FParent) then // We are only allowed to remove a child raise ERglError.Create('Cannot remove child object.'); // if we are also removing its parent. for var Child in AObject.FChildren do // Otherwise the parent may find itself having Self.RemoveObjectOrChild(Child); // a dangling ptr. FObjs.List.Remove(AObject); // Also, the TVisCtl3D user shoud only request if Assigned(FNewObjects) then // to remove top-level objects, so RemoveObject FNewObjects.Remove(AObject); // is public, while RemoveObjectOrChild isn't. end; procedure TVisCtl3D.RemoveObjectOrChild(AObject: TDrawable3D); begin if (FObjs = nil) or (FObjs.List = nil) then Exit; if AObject = nil then Exit; if AObject.FProtected then raise ERglError.Create('Cannot remove protected object.'); for var Child in AObject.FChildren do Self.RemoveObjectOrChild(Child); FObjs.List.Remove(AObject); if Assigned(FNewObjects) then FNewObjects.Remove(AObject); end; function TVisCtl3D.HitTest(const P: TPoint): TDrawable3D; begin Result := HitTest(P.X, P.Y); end; function TVisCtl3D.HitTest(X: Integer; Y: Integer): TDrawable3D; begin if Context = nil then Exit(nil); Context.MakeCurrent('TVisCtl3D.HitTest'); var LOldMSAA := Self.MSAA; var LOldEffects := Self.Effects; try FStencil := True; Self.MSAA := msaa0; Self.Effects := [ppIdentity]; Repaint; glBindFramebuffer(GL_FRAMEBUFFER, Fauxbuf); var b: Byte := 0; glReadPixels(X, ClientHeight - Y, 1, 1, GL_STENCIL_INDEX, GL_UNSIGNED_BYTE, @b); glBindFramebuffer(GL_FRAMEBUFFER, 0); Result := FObjs.GetObjFromStencilID(b); finally FStencil := False; Self.MSAA := LOldMSAA; Self.Effects := LOldEffects; end end; { TDrawableOptionsFrm3D } constructor TDrawableOptionsFrm3D.Create(AOwner: TComponent; ADrawable: TDrawable3D); begin FDrawable := ADrawable; inherited Create(AOwner); Initialize; FInitialized := True; end; procedure TDrawableOptionsFrm3D.DrawableDestroyed(Sender: TObject); begin end; procedure TDrawableOptionsFrm3D.Initialize; begin end; procedure TDrawableOptionsFrm3D.Reassign(ADrawable: TDrawable3D); begin if Assigned(FDrawable) and Initialized then UpdateDrawable; FDrawable := ADrawable; FInitialized := False; Initialize; FInitialized := True; end; procedure TDrawableOptionsFrm3D.UpdateDrawable; begin if Assigned(FOnChange) then FOnChange(Self); end; { TDrawable3D } procedure TDrawable3D.Changed(Sender: TObject); begin if Assigned(FOnChange) then FOnChange(Self); end; procedure TDrawable3D.Changed; begin if Assigned(FOnChange) then FOnChange(Self); end; constructor TDrawable3D.Create(ACtl: TVisCtl3D); begin FVisible := True; FCtl := ACtl; FLineWidth := 1.5; FDefaultProgram := P_Default; FChildren := TList<TDrawable3D>.Create; end; function TDrawable3D.CreateChild<T>: T; begin if FCtl = nil then Exit(nil); if FChildren = nil then Exit(nil); Result := T.Create(FCtl); FCtl.AddObject(Result); // transfer of ownership // FCtl now owns Result Result.FParent := Self; FChildren.Add(Result); end; procedure TDrawable3D.DeleteChild(AChild: TDrawable3D); begin if Assigned(FCtl) and Assigned(FChildren) and FChildren.Contains(AChild) then begin FChildren.Remove(AChild); FCtl.RemoveObjectOrChild(AChild); end; end; procedure TDrawable3D.DeleteChildren(ATagMask: NativeUInt); begin if Assigned(FCtl) and Assigned(FChildren) then begin var LList := TList<TDrawable3D>.Create; try for var Child in FChildren do if Child.FParentTag and ATagMask <> 0 then LList.Add(Child); for var Child in LList do begin FChildren.Remove(Child); FCtl.RemoveObjectOrChild(Child); end; finally LList.Free; end; end; end; destructor TDrawable3D.Destroy; begin FreeAndNil(FChildren); // FCtl (not Self) owns each child if Assigned(Control) and Assigned(Control.Context) then FreeGLResources; inherited; end; procedure TDrawable3D.Draw(const AGlobalTime: Double); begin end; procedure TDrawable3D.FreeGLResources; begin end; function TDrawable3D.GetChild(Index: Integer): TDrawable3D; begin Result := FChildren[Index]; end; function TDrawable3D.GetChildCount: Integer; begin if Assigned(Self) and Assigned(FChildren) then Result := FChildren.Count else Result := 0; end; function TDrawable3D.GetDisplayed: Boolean; begin Result := Visible and ((FParent = nil) or FParent.Displayed); end; function TDrawable3D.GetLineWidth: Single; begin Result := FLineWidth; end; procedure TDrawable3D.GLRelease; begin end; procedure TDrawable3D.ProjectionChanged; begin end; procedure TDrawable3D.Recreate; begin end; procedure TDrawable3D.SetAnimationSpeed(const Value: Double); begin if FAnimationSpeed <> Value then begin FAnimationSpeed := Value; Changed; end; end; procedure TDrawable3D.SetLineWidth(const Value: Single); begin if FLineWidth <> Value then begin FLineWidth := Value; Changed; end; end; procedure TDrawable3D.DoClick; begin if Assigned(FOnClick) then FOnClick(Self); end; procedure TDrawable3D.Click; begin DoClick; end; procedure TDrawable3D.Setup; begin end; procedure TDrawable3D.SetVisible(const Value: Boolean); begin if FVisible <> Value then begin FVisible := Value; Changed; end; end; function TDrawable3D.TryContextCurrent: Boolean; begin Result := Assigned(Control) and Assigned(Control.Context) and Control.Context.TryMakeCurrent; end; { TDrawableList3D } constructor TDrawableList3D.Create(ACtl: TVisCtl3D); begin inherited; FList := TObjectList<TDrawable3D>.Create; FList.OnNotify := ObjsNotify; FSortedList := TList<TDrawable3D>.Create; end; destructor TDrawableList3D.Destroy; begin FreeAndNil(FSortedList); FreeAndNil(FList); inherited; end; procedure TDrawableList3D.Draw(const AGlobalTime: Double); begin if FSortOrderDirty or FAlphaBlending and (FPrevCameraPose <> Control.CameraPose) then Resort; FTimeDependent := False; glDepthMask(GL_TRUE); var DepthWriteDisabled := False; try var FStencilID: Byte := 0; if Assigned(FSortedList) then for var obj in FSortedList do if Assigned(obj) and obj.Displayed then begin if Control.Stencil and (FStencilID < $FF) then begin Inc(FStencilID); FStencilIDs[FStencilID] := Pointer(obj); end; if not obj.DefaultProgram.IsEmpty then begin Control.ProgramMgr.UseProgram(obj.DefaultProgram); if not DepthWriteDisabled and (obj.DefaultProgram[1] = 'z') then begin glDepthMask(GL_FALSE); DepthWriteDisabled := True; end; end; if Control.Stencil then glStencilFunc(GL_ALWAYS, FStencilID, 0); obj.Draw(AGlobalTime); FTimeDependent := FTimeDependent or (obj.AnimationSpeed <> 0); end; FPrevCameraPose := Control.CameraPose; finally if DepthWriteDisabled then glDepthMask(GL_TRUE); end; end; procedure TDrawableList3D.FreeGLResources; begin if Assigned(FList) then for var obj in FList do if Assigned(obj) then obj.FreeGLResources; inherited; end; function TDrawableList3D.GetItem(Index: Integer): TDrawable3D; begin Result := FList[Index]; end; function TDrawableList3D.GetItemCount: Integer; begin if Assigned(FList) then Result := FList.Count else Result := 0; end; function TDrawableList3D.GetObjFromStencilID(AStencilID: Byte): TDrawable3D; begin for var obj in FList do if Pointer(obj) = FStencilIDs[AStencilID] then Exit(obj); Result := nil; end; procedure TDrawableList3D.GLRelease; begin if Assigned(FList) then for var obj in FList do if Assigned(obj) then obj.GLRelease; inherited; end; procedure TDrawableList3D.MoveDown(ADrawable: TDrawable3D); begin var Idx := FList.IndexOf(ADrawable); if Idx <> -1 then begin FList.Move(Idx, Succ(Idx)); Changed; end; end; procedure TDrawableList3D.MoveUp(ADrawable: TDrawable3D); begin var Idx := FList.IndexOf(ADrawable); if Idx <> -1 then begin FList.Move(Idx, Pred(Idx)); Changed; end; end; procedure TDrawableList3D.ObjsNotify(Sender: TObject; const Item: TDrawable3D; Action: TCollectionNotification); begin FAlphaBlending := False; if Assigned(FList) then begin for var Obj in FList do if Obj.DefaultProgram.StartsWith('z') then begin FAlphaBlending := True; Break; end; end; Changed; FSortOrderDirty := True; end; procedure TDrawableList3D.Resort; begin if Assigned(FList) and Assigned(FSortedList) then begin FSortOrderDirty := False; FSortedList.Clear; FSortedList.AddRange(FList); FSortedList.Sort( TComparer<TDrawable3D>.Construct( function(const Left, Right: TDrawable3D): Integer begin if (Left = nil) or (Right = nil) then Exit(CompareValue(NativeUInt(Left), NativeUInt(Right))); var LeftAlpha := (Left.DefaultProgram.Length > 0) and (Left.DefaultProgram[1] = 'z'); var RightAlpha := (Right.DefaultProgram.Length > 0) and (Right.DefaultProgram[1] = 'z'); if not LeftAlpha and not RightAlpha then Result := CompareStr(Left.DefaultProgram, Right.DefaultProgram) else if LeftAlpha and not RightAlpha then Result := 1 else if RightAlpha and not LeftAlpha then Result := -1 else begin if (Left is TGeometricObject3D) and (Right is TGeometricObject3D) then begin Result := -CompareValue( (TGeometricObject3D(Left).Position - Control.CameraPos).NormSquare, (TGeometricObject3D(Right).Position - Control.CameraPos).NormSquare ) end else Result := 0; end; if Result = 0 then Result := CompareValue(NativeUInt(Left), NativeUInt(Right)); end ) ); end; end; procedure TDrawableList3D.SetItem(Index: Integer; const Value: TDrawable3D); begin FList[Index] := Value; end; { TManagedProgram } constructor TManagedProgram.Create(const AName: string; AProgram: TRglProgram); begin FName := AName; FProgram := AProgram; FUMVP := FProgram.TryAddUniform<TRglUniformFloatMat4>('MVP'); FUEye := FProgram.TryAddUniform<TRglUniformFloatVec3>('eye'); FULightPos := FProgram.TryAddUniform<TRglUniformFloatVec3>('lightpos'); FUColor := FProgram.TryAddUniform<TRglUniformFloatVec3>('color'); FUSize := FProgram.TryAddUniform<TRglUniformFloat>('size'); FUAnchorPoint := FProgram.TryAddUniform<TRglUniformInt>('AnchorPoint'); FUFaceScreen := FProgram.TryAddUniform<TRglUniformBool>('FaceScreen'); FUAttribColors := FProgram.TryAddUniform<TRglUniformBool>('AttribColors'); FUAspect := FProgram.TryAddUniform<TRglUniformFloat>('aspect'); FUDisplacement := FProgram.TryAddUniform<TRglUniformFloatVec2>('displacement'); FUObjectMatrix := FProgram.TryAddUniform<TRglUniformFloatMat4>('OM'); FUNormalMatrix := FProgram.TryAddUniform<TRglUniformFloatMat3>('NormalMatrix'); FUt := FProgram.TryAddUniform<TRglUniformFloat>('t'); FUTranspColor := FProgram.TryAddUniform<TRglUniformFloatVec3>('transpcolor'); FUOpaqueColor := FProgram.TryAddUniform<TRglUniformFloatVec3>('opaquecolor'); FUTranspColorMode := FProgram.TryAddUniform<TRglUniformInt>('transpmode'); FUOpacity := FProgram.TryAddUniform<TRglUniformFloat>('opacity'); end; destructor TManagedProgram.Destroy; begin FreeAndNil(FProgram); inherited; end; { TProgramMgr } procedure TProgramMgr.BeginMonitorTime; begin FTimeDependent := False; end; constructor TProgramMgr.Create(AControl: TVisCtl3D); begin FControl := AControl; FPrograms := TObjectList<TManagedProgram>.Create; end; destructor TProgramMgr.Destroy; begin FCurrentProgram := nil; FreeAndNil(FPrograms); inherited; end; function TProgramMgr.EndMonitorTime: Boolean; begin Result := FTimeDependent; end; const Shaders: array[0..10] of record Name, Code: string; end = ( (Name: P_Default; Code: ''' #vertex shader #version 330 core layout (location = 0) in vec3 coords; layout (location = 1) in vec3 color; varying vec3 v_color; uniform mat4 MVP; uniform mat4 OM; void main() { gl_Position = MVP * OM * vec4(coords, 1.0); v_color = color; }; #fragment shader #version 330 core out vec4 FragColor; varying vec3 v_color; void main() { FragColor = vec4(v_color, 1.0); }; ''' ), (Name: P_Lighting; Code: ''' #vertex shader #version 330 core layout (location = 0) in vec3 coords; layout (location = 1) in vec3 color; layout (location = 2) in vec3 normal; out vec3 v_coords; out vec3 v_normal; out vec3 v_color; uniform mat4 MVP; uniform mat4 OM; uniform mat3 NormalMatrix; void main() { gl_Position = MVP * OM * vec4(coords, 1.0); v_normal = NormalMatrix * normal; v_color = color; v_coords = vec3(OM * vec4(coords, 1.0)); }; #fragment shader #version 330 core out vec4 FragColor; in vec3 v_coords; in vec3 v_normal; in vec3 v_color; uniform vec3 eye; uniform vec3 lightpos; void main() { vec3 whitened = mix(v_color, vec3(1.0, 1.0, 1.0), 0.2); vec3 lightdir = normalize(v_coords - lightpos); vec3 N = normalize(v_normal); vec3 refdir = reflect(lightdir, N); vec3 viewdir = normalize(eye - v_coords); vec3 ambient = v_color * 0.30; vec3 diffuse = v_color * max(0.0, dot(N, -lightdir)) * 0.50; vec3 specular = whitened * pow(max(0.0, dot(viewdir, refdir)), 32) * 0.20; vec3 contrast1 = v_color * max(0.0, dot(N, -normalize(vec3(1, -0.5, -0.2)))) * 0.05; vec3 contrast2 = v_color * max(0.0, dot(N, -normalize(vec3(0.5, 0.4, 0.9)))) * 0.04; vec3 contrast3 = v_color * max(0.0, dot(N, -normalize(vec3(0.4, 0.3, -0.5)))) * 0.07; FragColor = vec4(clamp(ambient + diffuse + specular + contrast1 + contrast2 + contrast3, 0.0, 1.0), 1.0); }; ''' ), ( Name: P_UniformColorDefault; Code: ''' #vertex shader #version 330 core layout (location = 0) in vec3 coords; uniform mat4 MVP; uniform mat4 OM; void main() { gl_Position = MVP * OM * vec4(coords, 1.0); }; #fragment shader #version 330 core out vec4 FragColor; uniform vec3 color; void main() { FragColor = vec4(color, 1.0); }; ''' ), ( Name: P_UniformColorLighting; Code: ''' #vertex shader #version 330 core layout (location = 0) in vec3 coords; layout (location = 2) in vec3 normal; out vec3 v_coords; out vec3 v_normal; uniform mat4 MVP; uniform mat4 OM; uniform mat3 NormalMatrix; void main() { gl_Position = MVP * OM * vec4(coords, 1.0); v_normal = NormalMatrix * normal; v_coords = vec3(OM * vec4(coords, 1.0)); }; #fragment shader #version 330 core out vec4 FragColor; in vec3 v_coords; in vec3 v_normal; uniform vec3 eye; uniform vec3 color; uniform vec3 lightpos; void main() { vec3 whitened = mix(color, vec3(1.0, 1.0, 1.0), 0.2); vec3 lightdir = normalize(v_coords - lightpos); vec3 N = normalize(v_normal); vec3 refdir = reflect(lightdir, N); vec3 viewdir = normalize(eye - v_coords); vec3 ambient = color * 0.30; vec3 diffuse = color * max(0.0, dot(N, -lightdir)) * 0.50; vec3 specular = whitened * pow(max(0.0, dot(viewdir, refdir)), 32) * 0.20; vec3 contrast1 = color * max(0.0, dot(N, -normalize(vec3(1, -0.5, -0.2)))) * 0.05; vec3 contrast2 = color * max(0.0, dot(N, -normalize(vec3(0.5, 0.4, 0.9)))) * 0.04; vec3 contrast3 = color * max(0.0, dot(N, -normalize(vec3(0.4, 0.3, -0.5)))) * 0.07; FragColor = vec4(clamp(ambient + diffuse + specular + contrast1 + contrast2 + contrast3, 0.0, 1.0), 1.0); }; ''' ), ( Name: P_Text; Code: ''' #vertex shader #version 330 core layout (location = 0) in vec3 coords; layout (location = 1) in vec2 texcoords; out vec2 v_texcoords; uniform mat4 MVP; uniform mat4 OM; uniform int AnchorPoint; uniform bool FaceScreen; uniform float aspect; uniform vec2 displacement; uniform float size; const vec3[9] AnchorDeltas3 = vec3[] ( vec3( 0.0, 0.0, 0.0), vec3( 0.0, -0.5, 0.0), vec3( 0.0, -1.0, 0.0), vec3(-0.5, 0.0, 0.0), vec3(-0.5, -0.5, 0.0), vec3(-0.5, -1.0, 0.0), vec3(-1.0, 0.0, 0.0), vec3(-1.0, -0.5, 0.0), vec3(-1.0, -1.0, 0.0) ); const vec2[9] AnchorDeltas2 = vec2[] ( vec2( 0.0, 0.0), vec2(-0.5, 0.0), vec2(-1.0, 0.0), vec2( 0.0, 0.5), vec2(-0.5, 0.5), vec2(-1.0, 0.5), vec2( 0.0, 1.0), vec2(-0.5, 1.0), vec2(-1.0, 1.0) ); void main() { if (FaceScreen) { gl_Position = MVP * OM * vec4(0.0, 0.0, 0.0, 1.0); gl_Position.xy = gl_Position.xy + (vec2(0.0, -1.0) + texcoords + AnchorDeltas2[AnchorPoint]) * vec2(aspect, 1.0) * pow(determinant(OM) / pow(aspect, 2), 1.0/3.0) * size + displacement; v_texcoords = texcoords; } else { gl_Position = MVP * OM * vec4(size * (coords + AnchorDeltas3[AnchorPoint]), 1.0); v_texcoords = texcoords; } }; #fragment shader #version 330 core out vec4 FragColor; in vec2 v_texcoords; uniform sampler2D screentex; uniform vec3 color; uniform float opacity; void main() { FragColor = texture(screentex, v_texcoords) * opacity + vec4(color, 0.0); if (FragColor.a == 0.0) discard; }; ''' ), ( Name: P_Tex; Code: ''' #vertex shader #version 330 core layout (location = 0) in vec2 coords; layout (location = 1) in vec2 texcoords; out vec2 v_texcoords; void main() { gl_Position = vec4(coords, 0.0, 1.0); v_texcoords = texcoords; }; #fragment shader #version 330 core out vec4 FragColor; in vec2 v_texcoords; uniform sampler2D screentex; uniform float t; void main() { vec2 texcoords = v_texcoords; FragColor = texture(screentex, texcoords); }; ''' ), ( Name: P_Image; Code: ''' #vertex shader #version 330 core layout (location = 0) in vec3 coords; layout (location = 1) in vec2 texcoords; out vec2 v_texcoords; uniform mat4 MVP; uniform mat4 OM; void main() { gl_Position = MVP * OM * vec4(coords, 1.0); v_texcoords = texcoords; }; #fragment shader #version 330 core out vec4 FragColor; in vec2 v_texcoords; uniform sampler2D screentex; uniform int transpmode; uniform vec3 transpcolor; uniform vec3 opaquecolor; const int tcmOff = 0; const int tcmEqual = 1; const int tcmDistance = 2; const int tcmBipolar = 3; const vec3 epsilon = vec3(1.0 / 255.0); void main() { FragColor = texture(screentex, v_texcoords); switch (transpmode) { case tcmOff: break; case tcmEqual: if (all(lessThanEqual(abs(FragColor.rgb - transpcolor), epsilon))) discard; break; case tcmDistance: FragColor.a = distance(FragColor.rgb, transpcolor) / sqrt(3); break; case tcmBipolar: vec3 delta = opaquecolor - transpcolor; float t = dot(FragColor.rgb - transpcolor, delta) / dot(delta, delta); FragColor.a = t; break; } }; ''' ), ( Name: P_UniformColorLightingUnisided; Code: ''' #vertex shader #version 330 core layout (location = 0) in vec3 coords; layout (location = 2) in vec3 normal; out vec3 v_coords; out vec3 v_normal; uniform mat4 MVP; uniform mat4 OM; uniform mat3 NormalMatrix; void main() { gl_Position = MVP * OM * vec4(coords, 1.0); v_normal = NormalMatrix * normal; v_coords = vec3(OM * vec4(coords, 1.0)); }; #fragment shader #version 330 core out vec4 FragColor; in vec3 v_coords; in vec3 v_normal; uniform vec3 eye; uniform vec3 color; uniform vec3 lightpos; void main() { vec3 whitened = mix(color, vec3(1.0, 1.0, 1.0), 0.2); vec3 lightdir = normalize(v_coords - lightpos); vec3 N = normalize(v_normal); vec3 refdir = reflect(lightdir, N); vec3 viewdir = normalize(eye - v_coords); vec3 ambient = color * 0.30; vec3 diffuse = color * abs(dot(N, -lightdir)) * 0.50; vec3 specular = whitened * pow(abs(dot(viewdir, refdir)), 32) * 0.20; vec3 contrast1 = color * abs(dot(N, -normalize(vec3(1, -0.5, -0.2)))) * 0.05; vec3 contrast2 = color * abs(dot(N, -normalize(vec3(0.5, 0.4, 0.9)))) * 0.04; vec3 contrast3 = color * abs(dot(N, -normalize(vec3(0.4, 0.3, -0.5)))) * 0.07; FragColor = vec4(clamp(ambient + diffuse + specular + contrast1 + contrast2 + contrast3, 0.0, 1.0), 1.0); }; ''' ), ( Name: P_Scatter; Code: ''' #vertex shader #version 330 core layout (location = 0) in vec3 coords; layout (location = 2) in vec3 normal; layout (location = 3) in vec3 origin; out vec3 v_normal; uniform mat4 MVP; uniform mat4 OM; uniform mat3 NormalMatrix; uniform float size; void main() { gl_Position = MVP * OM * vec4(origin + size*coords, 1.0); v_normal = NormalMatrix * normal; }; #fragment shader #version 330 core out vec4 FragColor; in vec3 v_normal; uniform vec3 color; const vec3 lightdir = vec3(1.0, 1.0, 1.0) / sqrt(3); void main() { vec3 N = normalize(v_normal); vec3 ambient = color * 0.5; vec3 diffuse = color * max(0.0, dot(N, lightdir)) * 0.5; FragColor = vec4(clamp(ambient + diffuse, 0.0, 1.0), 1.0); }; ''' ), ( Name: P_AdvScatter; Code: ''' #vertex shader #version 330 core layout (location = 0) in vec3 coords; layout (location = 2) in vec3 normal; layout (location = 3) in vec3 origin; layout (location = 4) in vec3 color; layout (location = 5) in float relsize; out vec3 v_normal; out vec3 v_color; uniform mat4 MVP; uniform mat4 OM; uniform mat3 NormalMatrix; uniform float size; void main() { gl_Position = MVP * OM * vec4(origin + relsize*size*coords, 1.0); v_normal = NormalMatrix * normal; v_color = color; }; #fragment shader #version 330 core out vec4 FragColor; in vec3 v_normal; in vec3 v_color; const vec3 lightdir = vec3(1.0, 1.0, 1.0) / sqrt(3); void main() { vec3 N = normalize(v_normal); vec3 ambient = v_color * 0.5; vec3 diffuse = v_color * max(0.0, dot(N, lightdir)) * 0.5; FragColor = vec4(clamp(ambient + diffuse, 0.0, 1.0), 1.0); }; ''' ), ( Name: P_VectorField; Code: ''' #vertex shader #version 330 core layout (location = 0) in vec3 coords; layout (location = 2) in vec3 normal; layout (location = 3) in vec3 origin; layout (location = 4) in vec3 trc1; layout (location = 5) in vec3 trc2; layout (location = 6) in vec3 trc3; layout (location = 7) in vec3 color; out vec3 v_normal; out vec3 v_color; uniform mat4 MVP; uniform mat4 OM; uniform mat3 NormalMatrix; uniform float size; uniform vec2 displacement; void main() { mat3 A = mat3(trc1, trc2, trc3); gl_Position = MVP * OM * vec4(origin + size*A*(coords + vec3(0, 0, displacement.x)), 1.0); v_normal = NormalMatrix * normal; v_color = color; }; #fragment shader #version 330 core out vec4 FragColor; in vec3 v_normal; in vec3 v_color; uniform vec3 color; uniform bool AttribColors; const vec3 lightdir = vec3(1.0, 1.0, 1.0) / sqrt(3); const vec3 shadowdir = vec3(0.0, 0.0, -1.0); void main() { vec3 actcolor = mix(color, v_color, float(AttribColors)); vec3 N = normalize(v_normal); vec3 ambient = actcolor * 0.5; vec3 diffuse = actcolor * max(0.0, dot(N, lightdir)) * 0.5; vec3 shadow = actcolor * max(0.0, pow(dot(N, shadowdir), 64)) * 0.05; FragColor = vec4(clamp(ambient + diffuse - shadow, 0.0, 1.0), 1.0); }; ''' ) ); function TProgramMgr.GetProgram(const AName: string): TManagedProgram; begin for var NP in FPrograms do if NP.Name = AName then Exit(NP); var SL := TStringList.Create; try for var sh in Shaders do if SameText(sh.Name, AName) then Exit(LoadProgramResource(AName, sh.Code)); raise ERglError.CreateFmt('Shader not found: "%s"', [AName]); finally SL.Free; end; end; function TProgramMgr.LoadProgramResource(const AName: string; const AData: string): TManagedProgram; var Sources: array[TShaderKind] of string; Shaders: array[TShaderKind] of TRglShader; begin for var NP in FPrograms do if NP.Name = AName then raise Exception.Create('A program named "%s" is already loaded.'); var ShaderKind := Vertex; for var S in AData.Split([#13#10]) do begin var GLSL := True; if (Length(S) >= 1) and (S[1] = '#') then for var sk := Low(TShaderKind) to High(TShaderKind) do if S.StartsWith('#' + sk.ToString) then begin ShaderKind := sk; GLSL := False; Break; end; if GLSL then Sources[ShaderKind] := Sources[ShaderKind] + #13#10 + S; end; var &Program := TRglProgram.Create(FControl.Context); try FillChar(Shaders, SizeOf(Shaders), 0); try for var sk := Low(TShaderKind) to High(TShaderKind) do if not Sources[sk].IsEmpty then begin Shaders[sk] := sk.RglClass.Create(FControl.Context, Sources[sk]); Shaders[sk].Compile; &Program.AttachShader(Shaders[sk]); end; &Program.Link; finally for var sk := Low(TShaderKind) to High(TShaderKind) do Shaders[sk].Free; end; except &Program.Free; raise; end; Result := TManagedProgram.Create(AName, &Program); // transfer of ownership FPrograms.Add(Result); // transfer of ownership end; procedure TProgramMgr.UseProgram(const AName: string); begin var FProgram := GetProgram(AName); if FCurrentProgram <> FProgram then FProgram.&Program.Use; FCurrentProgram := FProgram; FProgram.UMVP.SetValue(FControl.CurrentMatrix); FProgram.UEye.SetValue(FControl.CameraPos); FProgram.ULightPos.SetValue(FControl.LightPos); FProgram.UObjectMatrix.SetValue(rglm4.Identity); FProgram.UNormalMatrix.SetValue(rglm.Identity); FTimeDependent := FTimeDependent or Assigned(FProgram.Ut); end; { TShaderKindHelper } function TShaderKindHelper.RglClass: TRglShaderClass; begin case Self of Vertex: Result := TRglVertexShader; Geometry: Result := TRglGeometryShader; Fragment: Result := TRglFragmentShader; else Result := nil; end; end; function TShaderKindHelper.ToString: string; begin case Self of Vertex: Result := 'vertex'; Geometry: Result := 'geometry'; Fragment: Result := 'fragment'; else Result := ''; end; end; { TScene } constructor TScene.Create(ACtl: TVisCtl3D); begin inherited; end; { TRefAxes } constructor TRefAxes.Create(ACtl: TVisCtl3D); begin inherited; FDefaultProgram := P_Lighting; end; destructor TRefAxes.Destroy; begin inherited; end; procedure TRefAxes.Draw(const AGlobalTime: Double); begin glBindVertexArray(FVAO); glDrawArrays(GL_TRIANGLE_STRIP, 0, 2*N); glDrawArrays(GL_TRIANGLE_STRIP, 2*N, 2*N); glDrawArrays(GL_TRIANGLE_STRIP, 4*N, 2*N); glBindVertexArray(0); end; procedure TRefAxes.FreeGLResources; begin if TryContextCurrent then begin glDeleteBuffers(1, @FVertexData); FVertexData := 0; glDeleteVertexArrays(1, @FVAO); FVAO := 0; end else rglLog('TRefAxes.FreeGLResources: TryContextCurrent returned false'); inherited; end; procedure TRefAxes.GLRelease; begin FVertexData := 0; FVAO := 0; inherited; end; procedure TRefAxes.Setup; var VertexData: array[0..3*2*N - 1] of GLfloat9; S, C: Single; Sʹ, Cʹ: Single; const R: array[0..2] of Double = (1.0, 0.0, 0.0); G: array[0..2] of Double = (0.0, 1.0, 0.0); B: array[0..2] of Double = (0.0, 0.0, 1.0); begin if (Control = nil) or (Control.Context = nil) then Exit; Control.Context.MakeCurrent('TRefAxes.Setup'); for var i := 0 to N - 1 do begin SinCos(i * 2*Pi / (N - 1), S, C); SinCos((i + 0.5) * 2*Pi / (N - 1), Sʹ, Cʹ); for var j := 0 to 2 do begin var k := Succ(j) mod 3; var l := Succ(k) mod 3; VertexData[2*N*j + 2*i ][j] := 0.0; VertexData[2*N*j + 2*i ][k] := 0.1 * C; VertexData[2*N*j + 2*i ][l] := 0.1 * S; VertexData[2*N*j + 2*i ][3] := R[j]; VertexData[2*N*j + 2*i ][4] := G[j]; VertexData[2*N*j + 2*i ][5] := B[j]; VertexData[2*N*j + 2*i ][j+6] := 0; VertexData[2*N*j + 2*i ][k+6] := C; VertexData[2*N*j + 2*i ][l+6] := S; VertexData[2*N*j + 2*i + 1][j] := 10.0; VertexData[2*N*j + 2*i + 1][k] := 0.1 * Cʹ; VertexData[2*N*j + 2*i + 1][l] := 0.1 * Sʹ; VertexData[2*N*j + 2*i + 1][3] := R[j]; VertexData[2*N*j + 2*i + 1][4] := G[j]; VertexData[2*N*j + 2*i + 1][5] := B[j]; VertexData[2*N*j + 2*i + 1][j+6] := 0; VertexData[2*N*j + 2*i + 1][k+6] := C; VertexData[2*N*j + 2*i + 1][l+6] := S; end; end; if FVAO = 0 then glGenVertexArrays(1, @FVAO); glBindVertexArray(FVAO); try if FVertexData = 0 then glGenBuffers(1, @FVertexData); glBindBuffer(GL_ARRAY_BUFFER, FVertexData); glBufferData(GL_ARRAY_BUFFER, SizeOf(VertexData), @VertexData[0], GL_STATIC_DRAW); glEnableVertexAttribArray(0); glEnableVertexAttribArray(1); glEnableVertexAttribArray(2); glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 9*SizeOf(GLfloat), nil); glVertexAttribPointer(1, 3, GL_FLOAT, GL_FALSE, 9*SizeOf(GLfloat), Pointer(3*SizeOf(GLfloat))); glVertexAttribPointer(2, 3, GL_FLOAT, GL_FALSE, 9*SizeOf(GLfloat), Pointer(6*SizeOf(GLfloat))); finally glBindVertexArray(0); end; end; { TGeometricObject3D } procedure TGeometricObject3D.Assign(Source: TPersistent); begin if Source is TGeometricObject3D then begin FColor := TGeometricObject3D(Source).FColor; FPosition := TGeometricObject3D(Source).FPosition; FDirection := TGeometricObject3D(Source).FDirection; FScale := TGeometricObject3D(Source).FScale; FRotation := TGeometricObject3D(Source).FRotation; FLineWidth := TGeometricObject3D(Source).FLineWidth; FAnimationSpeed := TGeometricObject3D(Source).FAnimationSpeed; ComputeOM; Changed; end else inherited; end; procedure TGeometricObject3D.ComputeOM; begin if FUseManualMatrix then FObjectMatrix := FManualMatrix else begin var R: rglm4; if IsZero(FDirection.x) and IsZero(FDirection.y) then begin R := rglm4.Identity; if FDirection.z < 0 then R.m[2, 2] := -1.0; end else R := rglRotate(180*ArcCos(FDirection.Normalized.z)/Pi, vec(0, 0, 1) xor FDirection); FObjectMatrix := rglTranslate(FPosition) * R * rglRotate(FRotation, vec(0, 0, 1)) * rglScale(FScale.x, FScale.y, FScale.z); end; if FParent is TGeometricObject3D then FObjectMatrix := TGeometricObject3D(FParent).FObjectMatrix * FObjectMatrix; FNormalMatrix := rglm(FObjectMatrix).Transpose.Inverse; if Assigned(FChildren) then for var Child in FChildren do if Child is TGeometricObject3D then TGeometricObject3D(Child).ComputeOM; end; constructor TGeometricObject3D.Create(ACtl: TVisCtl3D); begin inherited; FDefaultProgram := P_UniformColorLighting; FDirection := vec(0, 0, 1); FScale := vec(1, 1, 1); FManualMatrix := rglm4.Identity; ComputeOM; end; procedure TGeometricObject3D.Draw(const AGlobalTime: Double); begin inherited; if FAnimationSpeed <> 0.0 then begin FRotation := AGlobalTime * FAnimationSpeed; ComputeOM; end; Control.ProgramMgr.CurrentProgram.UColor.SetValue(FColor); Control.ProgramMgr.CurrentProgram.UObjectMatrix.SetValue(FObjectMatrix); Control.ProgramMgr.CurrentProgram.UNormalMatrix.SetValue(FNormalMatrix); end; function TGeometricObject3D.GetColor: TColor; begin Result := FColor; end; procedure TGeometricObject3D.SetColor(const Value: TColor); begin if FColor <> Value then begin FColor := Value; Changed; end; end; procedure TGeometricObject3D.SetDirection(const Value: rglv); begin if FDirection <> Value then begin FDirection := Value; ComputeOM; Changed; end; end; procedure TGeometricObject3D.SetManualMatrix; begin var R: rglm4; if IsZero(FDirection.x) and IsZero(FDirection.y) then begin R := rglm4.Identity; if FDirection.z < 0 then R.m[2, 2] := -1.0; end else R := rglRotate(180*ArcCos(FDirection.Normalized.z)/Pi, vec(0, 0, 1) xor FDirection); FManualMatrix := rglTranslate(FPosition) * R * rglRotate(FRotation, vec(0, 0, 1)) * rglScale(FScale.x, FScale.y, FScale.z); end; procedure TGeometricObject3D.SetManualMatrix(const Value: rglm4); begin if FManualMatrix <> Value then begin FManualMatrix := Value; if FUseManualMatrix then begin ComputeOM; Changed; end; end; end; procedure TGeometricObject3D.SetPosition(const Value: rglv); begin if FPosition <> Value then begin FPosition := Value; ComputeOM; Changed; end; end; procedure TGeometricObject3D.SetRotation(const Value: Single); begin if FRotation <> Value then begin FRotation := Value; ComputeOM; Changed; end; end; procedure TGeometricObject3D.SetScale(const Value: rglv); begin if FScale <> Value then begin FScale := Value; ComputeOM; Changed; end; end; procedure TGeometricObject3D.SetUseManualMatrix(const Value: Boolean); begin if FUseManualMatrix <> Value then begin FUseManualMatrix := Value; ComputeOM; Changed; end; end; { TAbstractSurface3D } procedure TAbstractSurface3D.Assign(Source: TPersistent); begin if Source is TAbstractSurface3D then begin inherited; ShowSurface := TAbstractSurface3D(Source).FShowSurface; {setter} ShowParameterCurves := TAbstractSurface3D(Source).FShowParameterCurves; {setter} ParamCurveCounts := TAbstractSurface3D(Source).FParamCurveCounts; {setter} FLineColor := TAbstractSurface3D(Source).FLineColor; Changed; end else inherited; end; constructor TAbstractSurface3D.Create(ACtl: TVisCtl3D); begin inherited; FShowSurface := True; FParamCurveCounts.nx := 64; FParamCurveCounts.ny := 64; end; procedure TAbstractSurface3D.RecreateParamCurves; begin end; procedure TAbstractSurface3D.SetLineColor(const Value: TColor); begin if FLineColor <> Value then begin FLineColor := Value; Changed; end; end; procedure TAbstractSurface3D.SetParamCurveCounts( const Value: TParamCurveFamilySize); begin if FParamCurveCounts <> Value then begin FParamCurveCounts := Value; RecreateParamCurves; Changed; end; end; procedure TAbstractSurface3D.SetShowParameterCurves(const Value: Boolean); begin if FShowParameterCurves <> Value then begin FShowParameterCurves := Value; UpdateDefProgram; Changed; end; end; procedure TAbstractSurface3D.SetShowSurface(const Value: Boolean); begin if FShowSurface <> Value then begin FShowSurface := Value; UpdateDefProgram; Changed; end; end; procedure TAbstractSurface3D.SetUnisided(const Value: Boolean); begin if FUnisided <> Value then begin FUnisided := Value; UpdateDefProgram; Changed; end; end; procedure TAbstractSurface3D.UpdateDefProgram; begin end; { TSurface3D } destructor TSurface3D<vtype>.Destroy; begin inherited; end; procedure TSurface3D<vtype>.Draw(const AGlobalTime: Double); begin if not FShowSurface and not FShowParameterCurves then Exit; inherited; glBindVertexArray(FVAO); if FShowSurface then begin if FUnisided then Control.ProgramMgr.UseProgram(FSurfProgramUnisided) else Control.ProgramMgr.UseProgram(FSurfProgram); Control.ProgramMgr.CurrentProgram.UColor.SetValue(FColor); Control.ProgramMgr.CurrentProgram.UObjectMatrix.SetValue(FObjectMatrix); Control.ProgramMgr.CurrentProgram.UNormalMatrix.SetValue(FNormalMatrix); glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, FIndexData); glDrawElements(GL_TRIANGLES, FIndexCount, GL_UNSIGNED_INT, nil); end; if FShowParameterCurves then begin Control.ProgramMgr.UseProgram(FCurveProgram); Control.ProgramMgr.CurrentProgram.UColor.SetValue(FLineColor); Control.ProgramMgr.CurrentProgram.UObjectMatrix.SetValue(FObjectMatrix); glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, FPCIData); if FLineWidth <> 1.0 then glLineWidth(FLineWidth); glDrawElements(GL_LINES, FPCICount, GL_UNSIGNED_INT, nil); if FLineWidth <> 1.0 then glLineWidth(1); end; glBindVertexArray(0); end; procedure TSurface3D<vtype>.FreeGLResources; begin if TryContextCurrent then begin if not FStockSurface then begin glDeleteBuffers(1, @FIndexData); FIndexData := 0; glDeleteBuffers(1, @FVertexData); FVertexData := 0; end; glDeleteBuffers(1, @FPCIData); FPCIData := 0; glDeleteVertexArrays(1, @FVAO); FVAO := 0; end else rglLog('TSurface3D<vtype>.FreeGLResources: TryContextCurrent returned false'); inherited; end; procedure TSurface3D<vtype>.GLRelease; begin FVertexData := 0; FIndexData := 0; FPCIData := 0; FIndexCount := 0; FPCICount := 0; FVAO := 0; inherited; end; procedure TSurface3D<vtype>.RecreateParamCurves; begin if FVertexData = 0 then Exit; // The object hasn't been initialized yet. When it is, the new values of FParamCurveCounts will be used. Control.Context.MakeCurrent('TSurface3D<vtype>'); glBindVertexArray(FVAO); try var LVertices: TArray<vtype>; var LIndices: TArray<GLuint>; var LPCIs: TArray<GLuint>; FSurfProc(LVertices, LIndices, LPCIs, 257, 257, FParamCurveCounts.nx, FParamCurveCounts.ny, True, Pointer(Self)); FPCICount := Length(LPCIs); if FPCIData = 0 then glGenBuffers(1, @FPCIData); glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, FPCIData); glBufferData(GL_ELEMENT_ARRAY_BUFFER, Length(LPCIs) * SizeOf(GLuint), Pointer(LPCIs), GL_STATIC_DRAW); finally glBindVertexArray(0); end; end; procedure TSurface3D<vtype>.Setup; begin if (Control = nil) or (Control.Context = nil) then Exit; Control.Context.MakeCurrent('TSurface3D<vtype>'); if FVAO = 0 then glGenVertexArrays(1, @FVAO); glBindVertexArray(FVAO); try // Stock surfaces must be solid colour FStockSurface := FStockSurface and (SizeOf(vtype) = SizeOf(GLfloat6)); if FStockSurface then begin var SSD: TStockSurfaceData; if Control.Context.FStockSurfaces.TryGetValue(FStockID, SSD) then begin FVertexData := SSD.VertexData; FIndexData := SSD.IndexData; FIndexCount := SSD.IndexCount; glEnableVertexAttribArray(0); glEnableVertexAttribArray(2); glBindBuffer(GL_ARRAY_BUFFER, FVertexData); glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, SizeOf(vtype), nil); glVertexAttribPointer(2, 3, GL_FLOAT, GL_FALSE, SizeOf(vtype), Pointer(3*SizeOf(GLfloat))); RecreateParamCurves; Exit; end end; var LVertices: TArray<vtype>; var LIndices: TArray<GLuint>; var LPCIs: TArray<GLuint>; FSurfProc(LVertices, LIndices, LPCIs, 257, 257, FParamCurveCounts.nx, FParamCurveCounts.ny, False, Pointer(Self)); FIndexCount := Length(LIndices); FPCICount := Length(LPCIs); if (FVertexData = 0) or FStockSurface then glGenBuffers(1, @FVertexData); glBindBuffer(GL_ARRAY_BUFFER, FVertexData); glBufferData(GL_ARRAY_BUFFER, Length(LVertices) * SizeOf(vtype), Pointer(LVertices), GL_STATIC_DRAW); if (FIndexData = 0) or FStockSurface then glGenBuffers(1, @FIndexData); glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, FIndexData); glBufferData(GL_ELEMENT_ARRAY_BUFFER, Length(LIndices) * SizeOf(GLuint), Pointer(LIndices), GL_STATIC_DRAW); if FPCIData = 0 then glGenBuffers(1, @FPCIData); glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, FPCIData); glBufferData(GL_ELEMENT_ARRAY_BUFFER, Length(LPCIs) * SizeOf(GLuint), Pointer(LPCIs), GL_STATIC_DRAW); glEnableVertexAttribArray(0); if SizeOf(vtype) = SizeOf(GLfloat9) then glEnableVertexAttribArray(1); glEnableVertexAttribArray(2); glBindBuffer(GL_ARRAY_BUFFER, FVertexData); glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, SizeOf(vtype), nil); if SizeOf(vtype) = SizeOf(GLfloat9) then begin glVertexAttribPointer(1, 3, GL_FLOAT, GL_FALSE, SizeOf(vtype), Pointer(3*SizeOf(GLfloat))); glVertexAttribPointer(2, 3, GL_FLOAT, GL_FALSE, SizeOf(vtype), Pointer(6*SizeOf(GLfloat))); end else glVertexAttribPointer(2, 3, GL_FLOAT, GL_FALSE, SizeOf(vtype), Pointer(3*SizeOf(GLfloat))); if FStockSurface then begin var SSD := Default(TStockSurfaceData); SSD.VertexData := FVertexData; SSD.IndexData := FIndexData; SSD.IndexCount := FIndexCount; Control.Context.FStockSurfaces.Add(FStockID, SSD); end; finally glBindVertexArray(0); end; end; procedure TSurface3D<vtype>.UpdateDefProgram; begin if FShowSurface then begin if FUnisided then FDefaultProgram := FSurfProgramUnisided else FDefaultProgram := FSurfProgram end else if FShowParameterCurves then FDefaultProgram := FCurveProgram; end; { TEllipsoid } constructor TEllipsoid.Create(ACtl: TVisCtl3D); begin inherited; FSurfProc := rglSpherePolar; FStockSurface := True; FStockID := STOCKSURF_SPHERE; end; function TEllipsoid.GetScale: rglv; begin Result := Scale; end; procedure TEllipsoid.SetScale(const Value: rglv); begin Scale := Value; end; { TSphere } constructor TSphere.Create(ACtl: TVisCtl3D); begin inherited; end; function TSphere.GetRadius: Single; begin Result := Scale.x; end; procedure TSphere.SetRadius(const Value: Single); begin Scale := vec(Value, Value, Value); end; { TCylinder } constructor TCylinder.Create(ACtl: TVisCtl3D); begin inherited; FSurfProc := rglCylinder; FStockSurface := True; FStockID := STOCKSURF_CYLINDER; end; function TCylinder.GetAxisLengths: rglv2; begin Result.x := Scale.x; Result.y := Scale.y; end; function TCylinder.GetHeight: Single; begin Result := Scale.z; end; function TCylinder.GetRadius: Single; begin Result := FScale.x; end; procedure TCylinder.SetAxisLengths(const Value: rglv2); begin Scale := vec(Value.x, Value.y, FScale.z); end; procedure TCylinder.SetHeight(const Value: Single); begin Scale := vec(FScale.x, FScale.y, Value); end; procedure TCylinder.SetRadius(const Value: Single); begin Scale := vec(Value, Value, FScale.z); end; { TCone } constructor TCone.Create(ACtl: TVisCtl3D); begin inherited; FSurfProc := rglCone; end; function TCone.GetAxisLengths: rglv2; begin Result.x := Scale.x; Result.y := Scale.y; end; function TCone.GetHeight: Single; begin Result := Scale.z; end; procedure TCone.SetAxisLengths(const Value: rglv2); begin Scale := vec(Value.x, Value.y, FScale.z); end; procedure TCone.SetHeight(const Value: Single); begin Scale := vec(FScale.x, FScale.y, Value); end; { TPlane } constructor TPlane.Create(ACtl: TVisCtl3D); begin inherited; FSurfProc := rglPlane; FStockSurface := True; FStockID := STOCKSURF_PLANE; end; { TParamCurveFamilySize } constructor TParamCurveFamilySize.Create(X, Y: Integer); begin nx := X; ny := Y; end; class operator TParamCurveFamilySize.Equal(const Left, Right: TParamCurveFamilySize): Boolean; begin Result := (Left.nx = Right.nx) and (Left.ny = Right.ny); end; class operator TParamCurveFamilySize.Implicit( const AValue: rglv2): TParamCurveFamilySize; begin Result.nx := Round(AValue.x); Result.ny := Round(AValue.y); end; class operator TParamCurveFamilySize.Implicit( const AValue: Integer): TParamCurveFamilySize; begin Result.nx := AValue; Result.ny := AValue; end; class operator TParamCurveFamilySize.Implicit( const AValue: TSize): TParamCurveFamilySize; begin Result.nx := AValue.cx; Result.ny := AValue.cy; end; class operator TParamCurveFamilySize.NotEqual(const Left, Right: TParamCurveFamilySize): Boolean; begin Result := not (Left = Right); end; { TCustomSurface } constructor TCustomSurface.Create(ACtl: TVisCtl3D); begin inherited; FSurfProc := TCustomSurface.SurfProc; end; procedure TCustomSurface.Recreate; begin if FVertexData <> 0 then Setup; end; procedure TCustomSurface.SetData(const Value: TArray<rglv>); begin if FData <> Value then begin FData := Value; if Length(Value) > 0 then FSurfaceFunction := nil; Recreate; Changed; end; end; procedure TCustomSurface.SetDomain(const Value: TRectDom); begin if FDomain <> Value then begin FDomain := Value; Recreate; Changed; end; end; procedure TCustomSurface.SetSurfaceFunction(const Value: TSurfParamFcn); begin if @FSurfaceFunction <> @Value then begin FSurfaceFunction := Value; if Assigned(Value) then FData := nil; Recreate; Changed; end; end; class procedure TCustomSurface.SurfProc(out Vertices: TArray<GLfloat6>; out Indices, PCIs: TArray<GLuint>; A, B, pccx, pccy: Integer; PCOnly: Boolean; Data: Pointer); begin if TObject(Data) is TCustomSurface then begin var s := TCustomSurface(Data); var d := s.Domain; zdef([@A, @B, @pccx, @pccy], [129, 129, 64, 64]); if Assigned(s.Data) then begin A := s.Nx; B := s.Ny; end; TriangulateSurface(s.SurfaceFunction, nil, d.umin, d.umax, d.vmin, d.vmax, A, B, pccx, pccy, False, Vertices, Indices, PCIs, PCOnly, s.FData); end; end; { TRectDom } constructor TRectDom.Create(const umin, umax, vmin, vmax: Double); begin Self.umin := umin; Self.umax := umax; Self.vmin := vmin; Self.vmax := vmax; end; class operator TRectDom.Equal(const Left, Right: TRectDom): Boolean; begin Result := (Left.umin = Right.umin) and (Left.umax = Right.umax) and (Left.vmin = Right.vmin) and (Left.vmax = Right.vmax); end; class operator TRectDom.NotEqual(const Left, Right: TRectDom): Boolean; begin Result := not (Left = Right); end; { TBasicSurface3D } constructor TBasicSurface3D.Create(ACtl: TVisCtl3D); begin inherited; FSurfProgram := P_UniformColorLighting; FSurfProgramUnisided := P_UniformColorLightingUnisided; FCurveProgram := P_UniformColorDefault; FDefaultProgram := FSurfProgram; end; { TColoredSurface3D } constructor TColoredSurface3D.Create(ACtl: TVisCtl3D); begin inherited; FSurfProgram := P_Lighting; FSurfProgramUnisided := P_Lighting; FCurveProgram := P_Default; FDefaultProgram := FSurfProgram; FColorNotApplicable := True; end; { TCustomColoredSurface } constructor TCustomColoredSurface.Create(ACtl: TVisCtl3D); begin inherited; FSurfProc := TCustomColoredSurface.SurfProc; end; procedure TCustomColoredSurface.Recreate; begin if FVertexData <> 0 then Setup; end; procedure TCustomColoredSurface.SetData(const Value: TArray<GLr3c3v>); begin if FData <> Value then begin FData := Value; if Length(Value) > 0 then begin FSurfaceFunction := nil; FSurfaceColorFunction := nil; end; Recreate; Changed; end; end; procedure TCustomColoredSurface.SetDomain(const Value: TRectDom); begin if FDomain <> Value then begin FDomain := Value; Recreate; Changed; end; end; procedure TCustomColoredSurface.SetSurfaceColorFunction( const Value: TSurfParamColorFcn); begin if @FSurfaceColorFunction <> @Value then begin FSurfaceColorFunction := Value; if Assigned(Value) then FData := nil; Recreate; Changed; end; end; procedure TCustomColoredSurface.SetSurfaceFunction(const Value: TSurfParamFcn); begin if @FSurfaceFunction <> @Value then begin FSurfaceFunction := Value; if Assigned(Value) then FData := nil; Recreate; Changed; end; end; class procedure TCustomColoredSurface.SurfProc(out Vertices: TArray<GLfloat9>; out Indices, PCIs: TArray<GLuint>; A, B, pccx, pccy: Integer; PCOnly: Boolean; Data: Pointer); begin if TObject(Data) is TCustomColoredSurface then begin var s := TCustomColoredSurface(Data); var d := s.Domain; zdef([@A, @B, @pccx, @pccy], [129, 129, 64, 64]); if Assigned(s.Data) then begin A := s.Nx; B := s.Ny; end; TriangulateColoredSurface(s.SurfaceFunction, s.SurfaceColorFunction, nil, d.umin, d.umax, d.vmin, d.vmax, A, B, pccx, pccy, False, Vertices, Indices, PCIs, PCOnly, s.FData); end; end; { TDisk } constructor TDisk.Create(ACtl: TVisCtl3D); begin inherited; FSurfProc := rglDisk; FStockSurface := True; FStockID := STOCKSURF_DISK; end; function TDisk.GetRadius: Single; begin Result := Scale.x; end; procedure TDisk.SetRadius(const Value: Single); begin Scale := vec(Value, Value, Value); end; { TCurve3D } constructor TCurve3D.Create(ACtl: TVisCtl3D); begin inherited; FDefaultProgram := P_UniformColorDefault; end; destructor TCurve3D.Destroy; begin inherited; end; procedure TCurve3D.Draw(const AGlobalTime: Double); begin inherited; glBindVertexArray(FVAO); if FLineWidth <> 1.0 then glLineWidth(FLineWidth); glDrawArrays(GL_LINE_STRIP, 0, FCount); if FLineWidth <> 1.0 then glLineWidth(1); glBindVertexArray(0); end; procedure TCurve3D.FreeGLResources; begin if TryContextCurrent then begin glDeleteBuffers(1, @FVertexData); FVertexData := 0; glDeleteVertexArrays(1, @FVAO); FVAO := 0; end else rglLog('TCurve3D.FreeGLResources: TryContextCurrent returned false'); inherited; end; procedure TCurve3D.GLRelease; begin FVertexData := 0; FCount := 0; FVAO := 0; inherited; end; procedure TCurve3D.Recreate; begin if FVertexData <> 0 then Setup; end; procedure TCurve3D.Sample(out Vertices: TArray<rglv>); begin Vertices := nil; if @FCurveFunction = nil then Exit; if FDomain.b = FDomain.a then Exit; var dt := (FDomain.b - FDomain.a) / 1000; SetLength(Vertices, 1000); for var i := 0 to High(Vertices) do begin var t := FDomain.a + dt * i; Vertices[i] := FCurveFunction(t); end; end; procedure TCurve3D.SetCurveFunction(const Value: TCurveParamFcn); begin if @FCurveFunction <> @Value then begin FCurveFunction := Value; if Assigned(Value) then FData := nil; Recreate; Changed; end; end; procedure TCurve3D.SetData(const Value: TArray<rglv>); begin if FData <> Value then begin FData := Value; if Length(Value) > 0 then FCurveFunction := nil; Recreate; Changed; end; end; procedure TCurve3D.SetDomain(const Value: TInterval); begin if FDomain <> Value then begin FDomain := Value; Recreate; Changed; end; end; procedure TCurve3D.Setup; begin if (Control = nil) or (Control.Context = nil) then Exit; Control.Context.MakeCurrent('TCurve3D.Setup'); if FVAO = 0 then glGenVertexArrays(1, @FVAO); glBindVertexArray(FVAO); try var LVertices: TArray<rglv>; if Assigned(FData) then LVertices := FData else Sample(LVertices); FCount := Length(LVertices); if FVertexData = 0 then glGenBuffers(1, @FVertexData); glBindBuffer(GL_ARRAY_BUFFER, FVertexData); glBufferData(GL_ARRAY_BUFFER, Length(LVertices) * SizeOf(GLfloat3), Pointer(LVertices), GL_STATIC_DRAW); glEnableVertexAttribArray(0); glBindBuffer(GL_ARRAY_BUFFER, FVertexData); glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 3*SizeOf(GLfloat), nil); finally glBindVertexArray(0); end; end; { TInterval } constructor TInterval.Create(const a, b: Double); begin Self.a := a; Self.b := b; end; class operator TInterval.Equal(const Left, Right: TInterval): Boolean; begin Result := (Left.a = Right.a) and (Left.b = Right.b); end; class operator TInterval.NotEqual(const Left, Right: TInterval): Boolean; begin Result := not (Left = Right); end; { TColoredCurve3D } constructor TColoredCurve3D.Create(ACtl: TVisCtl3D); begin inherited; FDefaultProgram := P_Default; FColorNotApplicable := True; end; destructor TColoredCurve3D.Destroy; begin inherited; end; procedure TColoredCurve3D.Draw(const AGlobalTime: Double); begin inherited; glBindVertexArray(FVAO); if FLineWidth <> 1.0 then glLineWidth(FLineWidth); glDrawArrays(GL_LINE_STRIP, 0, FCount); if FLineWidth <> 1.0 then glLineWidth(1); glBindVertexArray(0); end; procedure TColoredCurve3D.FreeGLResources; begin if TryContextCurrent then begin glDeleteBuffers(1, @FVertexData); FVertexData := 0; glDeleteVertexArrays(1, @FVAO); FVAO := 0; end else rglLog('TColoredCurve3D.FreeGLResources: TryContextCurrent returned false'); inherited; end; procedure TColoredCurve3D.GLRelease; begin FVertexData := 0; FCount := 0; FVAO := 0; inherited; end; procedure TColoredCurve3D.Recreate; begin if FVertexData <> 0 then Setup; end; procedure TColoredCurve3D.Sample(out Vertices: TArray<GLr3c3v>); begin Vertices := nil; if @FCurveFunction = nil then Exit; if FDomain.b = FDomain.a then Exit; var dt := (FDomain.b - FDomain.a) / 1000; SetLength(Vertices, 1000); for var i := 0 to High(Vertices) do begin var t := FDomain.a + dt * i; Vertices[i].r := FCurveFunction(t); if Assigned(FCurveColorFunction) then Vertices[i].c := FCurveColorFunction(t); end; end; procedure TColoredCurve3D.SetCurveColorFunction( const Value: TCurveParamColorFcn); begin if @FCurveColorFunction <> @Value then begin FCurveColorFunction := Value; if Assigned(Value) then FData := nil; Recreate; Changed; end; end; procedure TColoredCurve3D.SetCurveFunction(const Value: TCurveParamFcn); begin if @FCurveFunction <> @Value then begin FCurveFunction := Value; if Assigned(Value) then FData := nil; Recreate; Changed; end; end; procedure TColoredCurve3D.SetData(const Value: TArray<GLr3c3v>); begin if FData <> Value then begin FData := Value; if Length(Value) > 0 then begin FCurveFunction := nil; FCurveColorFunction := nil; end; Recreate; Changed; end; end; procedure TColoredCurve3D.SetDomain(const Value: TInterval); begin if FDomain <> Value then begin FDomain := Value; Recreate; Changed; end; end; procedure TColoredCurve3D.Setup; begin if (Control = nil) or (Control.Context = nil) then Exit; Control.Context.MakeCurrent('TColoredCurve3D.Setup'); if FVAO = 0 then glGenVertexArrays(1, @FVAO); glBindVertexArray(FVAO); try var LVertices: TArray<GLr3c3v>; if Assigned(FData) then LVertices := FData else Sample(LVertices); FCount := Length(LVertices); if FVertexData = 0 then glGenBuffers(1, @FVertexData); glBindBuffer(GL_ARRAY_BUFFER, FVertexData); glBufferData(GL_ARRAY_BUFFER, Length(LVertices) * SizeOf(GLfloat6), Pointer(LVertices), GL_STATIC_DRAW); glEnableVertexAttribArray(0); glEnableVertexAttribArray(1); glBindBuffer(GL_ARRAY_BUFFER, FVertexData); glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 6*SizeOf(GLfloat), nil); glVertexAttribPointer(1, 3, GL_FLOAT, GL_FALSE, 6*SizeOf(GLfloat), Pointer(3*SizeOf(GLfloat))); finally glBindVertexArray(FVAO); end; end; { TScatterPlot } constructor TScatterPlot.Create(ACtl: TVisCtl3D); begin inherited; FSize := 0.1; end; destructor TScatterPlot.Destroy; begin inherited; end; procedure TScatterPlot.Draw(const AGlobalTime: Double); begin inherited; Control.ProgramMgr.CurrentProgram.USize.SetValue(FSize); end; procedure TScatterPlot.FreeGLResources; begin if TryContextCurrent then begin glDeleteBuffers(1, @FInstanceData); FInstanceData := 0; glDeleteVertexArrays(1, @FVAO); FVAO := 0; end else rglLog('TScatterPlot.FreeGLResources: TryContextCurrent returned false'); inherited; end; procedure TScatterPlot.GLRelease; begin FVertexData := 0; FIndexData := 0; FIndexCount := 0; FVAO := 0; FInstanceData := 0; inherited; end; procedure TScatterPlot.MakeBaseMarker; begin var LVertices: TArray<GLfloat6>; var LIndices: TArray<GLuint>; var LPCIs: TArray<GLuint>; rglSpherePolar(LVertices, LIndices, LPCIs, 33, 33, 0, 0, False, nil); FIndexCount := Length(LIndices); if FVertexData = 0 then glGenBuffers(1, @FVertexData); glBindBuffer(GL_ARRAY_BUFFER, FVertexData); glBufferData(GL_ARRAY_BUFFER, Length(LVertices) * SizeOf(GLfloat6), Pointer(LVertices), GL_STATIC_DRAW); if FIndexData = 0 then glGenBuffers(1, @FIndexData); glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, FIndexData); glBufferData(GL_ELEMENT_ARRAY_BUFFER, Length(LIndices) * SizeOf(GLuint), Pointer(LIndices), GL_STATIC_DRAW); end; procedure TScatterPlot.Recreate; begin if FVAO <> 0 then Setup; end; procedure TScatterPlot.SetSize(const Value: Single); begin if FSize <> Value then begin FSize := Value; Changed; end; end; { TSimpleScatterPlot } constructor TSimpleScatterPlot.Create(ACtl: TVisCtl3D); begin inherited; FDefaultProgram := P_Scatter; FColor := clRed; end; procedure TSimpleScatterPlot.Draw(const AGlobalTime: Double); begin inherited; glBindVertexArray(FVAO); glDrawElementsInstanced(GL_TRIANGLES, FIndexCount, GL_UNSIGNED_INT, nil, Length(FPoints)); glBindVertexArray(0); end; procedure TSimpleScatterPlot.SetPoints(const Value: TArray<GLfloat3>); begin if FPoints <> Value then begin FPoints := Value; Recreate; Changed; end; end; procedure TSimpleScatterPlot.Setup; begin if (Control = nil) or (Control.Context = nil) then Exit; Control.Context.MakeCurrent('TSimpleScatterPlot.Setup'); if FVAO = 0 then glGenVertexArrays(1, @FVAO); glBindVertexArray(FVAO); try var SSD := Default(TStockSurfaceData); if Control.Context.FStockSurfaces.TryGetValue(STOCKSURF_SPHERELET, SSD) then begin FVertexData := SSD.VertexData; FIndexData := SSD.IndexData; FIndexCount := SSD.IndexCount; end else begin MakeBaseMarker; SSD.VertexData := FVertexData; SSD.IndexData := FIndexData; SSD.IndexCount := FIndexCount; Control.Context.FStockSurfaces.Add(STOCKSURF_SPHERELET, SSD); end; glBindBuffer(GL_ARRAY_BUFFER, FVertexData); glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, FIndexData); if FInstanceData = 0 then glGenBuffers(1, @FInstanceData); glBindBuffer(GL_ARRAY_BUFFER, FInstanceData); glBufferData(GL_ARRAY_BUFFER, Length(FPoints) * SizeOf(GLfloat3), Pointer(FPoints), GL_STATIC_DRAW); glEnableVertexAttribArray(0); glEnableVertexAttribArray(2); glEnableVertexAttribArray(3); glBindBuffer(GL_ARRAY_BUFFER, FVertexData); glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 6*SizeOf(GLfloat), nil); glVertexAttribPointer(2, 3, GL_FLOAT, GL_FALSE, 6*SizeOf(GLfloat), Pointer(3*SizeOf(GLfloat))); glBindBuffer(GL_ARRAY_BUFFER, FInstanceData); glVertexAttribPointer(3, 3, GL_FLOAT, GL_FALSE, 3*SizeOf(GLfloat), nil); glVertexAttribDivisor(3, 1); finally glBindVertexArray(0); end; end; { TAdvScatterPlot } constructor TAdvScatterPlot.Create(ACtl: TVisCtl3D); begin inherited; FDefaultProgram := P_AdvScatter; FColorNotApplicable := True; end; procedure TAdvScatterPlot.Draw(const AGlobalTime: Double); begin inherited; glBindVertexArray(FVAO); glDrawElementsInstanced(GL_TRIANGLES, FIndexCount, GL_UNSIGNED_INT, nil, Length(FPoints)); glBindVertexArray(0); end; procedure TAdvScatterPlot.SetPoints(const Value: TArray<GLfloat7>); begin if FPoints <> Value then begin FPoints := Value; Recreate; Changed; end; end; function RBSwap(AColor: Integer): Integer; begin Result := AColor and $FF00FF00 or Byte(AColor shr 16) or Byte(AColor) shl 16; end; procedure TAdvScatterPlot.SetPoints(const Value: TArray<Double>); begin SetLength(FPoints, Length(Value) div 5); for var i := 0 to High(FPoints) do begin FPoints[i][0] := Value[5*i + 0]; // x FPoints[i][1] := Value[5*i + 1]; // y FPoints[i][2] := Value[5*i + 2]; // z var Color := TColor(RBSwap(Round(Value[5*i + 3]))); FPoints[i][3] := GetRValue(Color) / 255; FPoints[i][4] := GetGValue(Color) / 255; FPoints[i][5] := GetBValue(Color) / 255; FPoints[i][6] := Value[5*i + 4]; // s end; Recreate; Changed; end; procedure TAdvScatterPlot.Setup; begin if (Control = nil) or (Control.Context = nil) then Exit; Control.Context.MakeCurrent('TAdvScatterPlot.Setup'); if FVAO = 0 then glGenVertexArrays(1, @FVAO); glBindVertexArray(FVAO); try if (FVertexData = 0) or (FIndexData = 0) then begin MakeBaseMarker; end else begin glBindBuffer(GL_ARRAY_BUFFER, FVertexData); glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, FIndexData); end; if FInstanceData = 0 then glGenBuffers(1, @FInstanceData); glBindBuffer(GL_ARRAY_BUFFER, FInstanceData); glBufferData(GL_ARRAY_BUFFER, Length(FPoints) * SizeOf(GLfloat7), Pointer(FPoints), GL_STATIC_DRAW); glEnableVertexAttribArray(0); glEnableVertexAttribArray(2); glEnableVertexAttribArray(3); glEnableVertexAttribArray(4); glEnableVertexAttribArray(5); glBindBuffer(GL_ARRAY_BUFFER, FVertexData); glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 6*SizeOf(GLfloat), nil); glVertexAttribPointer(2, 3, GL_FLOAT, GL_FALSE, 6*SizeOf(GLfloat), Pointer(3*SizeOf(GLfloat))); glBindBuffer(GL_ARRAY_BUFFER, FInstanceData); glVertexAttribPointer(3, 3, GL_FLOAT, GL_FALSE, 7*SizeOf(GLfloat), nil); glVertexAttribPointer(4, 3, GL_FLOAT, GL_FALSE, 7*SizeOf(GLfloat), Pointer(3*SizeOf(GLfloat))); glVertexAttribPointer(5, 1, GL_FLOAT, GL_FALSE, 7*SizeOf(GLfloat), Pointer(6*SizeOf(GLfloat))); glVertexAttribDivisor(3, 1); glVertexAttribDivisor(4, 1); glVertexAttribDivisor(5, 1); finally glBindVertexArray(0); end; end; { TVectorField } constructor TVectorField.Create(ACtl: TVisCtl3D); begin inherited; FDefaultProgram := P_VectorField; FSize := 1.0; FColor := clRed; FAnchorPoint := 0.5; end; destructor TVectorField.Destroy; begin inherited; end; procedure TVectorField.Draw(const AGlobalTime: Double); begin inherited; if IsZero(FMaxNorm) then Control.ProgramMgr.CurrentProgram.USize.SetValue(FSize) else Control.ProgramMgr.CurrentProgram.USize.SetValue(FSize / FMaxNorm); Control.ProgramMgr.CurrentProgram.UAttribColors.SetValue(FAttribColors); Control.ProgramMgr.CurrentProgram.UDisplacement.SetValue(FAnchorPoint - 0.5, 0.0); glBindVertexArray(FVAO); glDrawArraysInstanced(GL_TRIANGLES, 0, FVertexCount, Length(FVectors)); glBindVertexArray(0); end; procedure TVectorField.FreeGLResources; begin if TryContextCurrent then begin glDeleteBuffers(1, @FInstanceData); FInstanceData := 0; glDeleteVertexArrays(1, @FVAO); FVAO := 0; end else rglLog('TVectorField.FreeGLResources: TryContextCurrent returned false'); inherited; end; procedure TVectorField.GLRelease; begin FVertexData := 0; FVAO := 0; FInstanceData := 0; inherited; end; procedure TVectorField.SetAnchorPoint(const Value: Single); begin if FAnchorPoint <> Value then begin FAnchorPoint := Value; Changed; end; end; procedure TVectorField.SetAttribColors(const Value: Boolean); begin if FAttribColors <> Value then begin FAttribColors := Value; FColorNotApplicable := FAttribColors; Changed; end; end; procedure TVectorField.SetSize(const Value: Single); begin if FSize <> Value then begin FSize := Value; Changed; end; end; procedure TVectorField.SetVectors(const Value: TArray<GLr3v3c3v>); begin if FVectors <> Value then begin FVectors := Value; Recreate; Changed; end; end; procedure TVectorField.MakeBaseArrow; const N = 32; R = 0.03; L = 1.00; Q = 0.10; f = 0.35; Geometry: record Disk, Cylinder, Septum, Cone: Integer; end = ( Disk: 3*N; Cylinder: 6*N; Septum: 3*N; Cone: 3*N; ); var Sines, Cosines: array[0..N] of Single; begin var LVertices: TArray<GLfloat6>; FVertexCount := Geometry.Disk + Geometry.Cylinder + Geometry.Septum + Geometry.Cone; SetLength(LVertices, FVertexCount); var Idx := 0; for var i := 0 to N do SinCos(Single(i * 2*Pi/N), Sines[i], Cosines[i]); // Disk for var i := 0 to N - 1 do begin GLr3n3v(LVertices[Idx]).r := vec(0, 0, -L/2); GLr3n3v(LVertices[Idx]).n := vec(0, 0, -1); Inc(Idx); GLr3n3v(LVertices[Idx]).r := vec(R*Cosines[Succ(i)], R*Sines[Succ(i)], -L/2); GLr3n3v(LVertices[Idx]).n := vec(0, 0, -1); Inc(Idx); GLr3n3v(LVertices[Idx]).r := vec(R*Cosines[i], R*Sines[i], -L/2); GLr3n3v(LVertices[Idx]).n := vec(0, 0, -1); Inc(Idx); end; // Cylinder for var i := 0 to N - 1 do begin GLr3n3v(LVertices[Idx]).r := vec(R*Cosines[i], R*Sines[i], -L/2); GLr3n3v(LVertices[Idx]).n := vec(Cosines[i], Sines[i], 0); Inc(Idx); GLr3n3v(LVertices[Idx]).r := vec(R*Cosines[Succ(i)], R*Sines[Succ(i)], -L/2); GLr3n3v(LVertices[Idx]).n := vec(Cosines[Succ(i)], Sines[Succ(i)], 0); Inc(Idx); GLr3n3v(LVertices[Idx]).r := vec(R*Cosines[i], R*Sines[i], +L/2 - f*L); GLr3n3v(LVertices[Idx]).n := vec(Cosines[i], Sines[i], 0); Inc(Idx); GLr3n3v(LVertices[Idx]).r := vec(R*Cosines[Succ(i)], R*Sines[Succ(i)], -L/2); GLr3n3v(LVertices[Idx]).n := vec(Cosines[Succ(i)], Sines[Succ(i)], 0); Inc(Idx); GLr3n3v(LVertices[Idx]).r := vec(R*Cosines[Succ(i)], R*Sines[Succ(i)], +L/2 - f*L); GLr3n3v(LVertices[Idx]).n := vec(Cosines[Succ(i)], Sines[Succ(i)], 0); Inc(Idx); GLr3n3v(LVertices[Idx]).r := vec(R*Cosines[i], R*Sines[i], +L/2 - f*L); GLr3n3v(LVertices[Idx]).n := vec(Cosines[i], Sines[i], 0); Inc(Idx); end; // Septum for var i := 0 to N - 1 do begin GLr3n3v(LVertices[Idx]).r := vec(0, 0, +L/2 - f*L); GLr3n3v(LVertices[Idx]).n := vec(0, 0, -1); Inc(Idx); GLr3n3v(LVertices[Idx]).r := vec(Q*Cosines[Succ(i)], Q*Sines[Succ(i)], +L/2 - f*L); GLr3n3v(LVertices[Idx]).n := vec(0, 0, -1); Inc(Idx); GLr3n3v(LVertices[Idx]).r := vec(Q*Cosines[i], Q*Sines[i], +L/2 - f*L); GLr3n3v(LVertices[Idx]).n := vec(0, 0, -1); Inc(Idx); end; // Cone for var i := 0 to N - 1 do begin GLr3n3v(LVertices[Idx]).r := vec(0, 0, +L/2); GLr3n3v(LVertices[Idx]).n := vec(Cosines[i], Sines[i], f*L/Q).Normalized; Inc(Idx); GLr3n3v(LVertices[Idx]).r := vec(Q*Cosines[i], Q*Sines[i], +L/2 - f*L); GLr3n3v(LVertices[Idx]).n := vec(Cosines[i], Sines[i], f*L/Q).Normalized; Inc(Idx); GLr3n3v(LVertices[Idx]).r := vec(Q*Cosines[Succ(i)], Q*Sines[Succ(i)], +L/2 - f*L); GLr3n3v(LVertices[Idx]).n := vec(Cosines[Succ(i)], Sines[Succ(i)], f*L/Q).Normalized; Inc(Idx); end; Assert(Idx = Length(LVertices)); if FVertexData = 0 then glGenBuffers(1, @FVertexData); glBindBuffer(GL_ARRAY_BUFFER, FVertexData); glBufferData(GL_ARRAY_BUFFER, Length(LVertices) * SizeOf(GLfloat6), Pointer(LVertices), GL_STATIC_DRAW); end; class function TVectorField.MakeMatrix(const AVector: rglv): rglm; begin var LNorm := AVector.Norm; if IsZero(LNorm) then Exit(rglm.Zero); if IsZero(AVector.x) and IsZero(AVector.y) then Exit(AVector.z * rglm.Identity); var LDirection := AVector / LNorm; var a := LDirection.x; var b := LDirection.y; var c := LDirection.z; var a2 := a*a; var ab := a*b; var b2 := b*b; var q := -1/(1+c); Result := LNorm * mat_transpose( 1 + a2*q, ab*q, a, ab*q, 1 + b2*q, b, -a, -b, 1 + (a2 + b2)*q ); end; class function TVectorField.Prepare( const AVectors: TArray<GLr3v3c3v>; out AMaxNorm: Single): TArray<GLfloat15>; begin SetLength(Result, Length(AVectors)); AMaxNorm := 0.0; for var i := 0 to High(Result) do begin GLr3m9c3v(Result[i]).r := AVectors[i].r; GLr3m9c3v(Result[i]).m := MakeMatrix(AVectors[i].v); GLr3m9c3v(Result[i]).c := AVectors[i].c; var LThisNorm := AVectors[i].v.Norm; if LThisNorm > AMaxNorm then AMaxNorm := LThisNorm; end; end; procedure TVectorField.Recreate; begin if FVAO <> 0 then Setup; end; procedure TVectorField.Setup; begin if (Control = nil) or (Control.Context = nil) then Exit; Control.Context.MakeCurrent('TVectorField.Setup'); if FVAO = 0 then glGenVertexArrays(1, @FVAO); glBindVertexArray(FVAO); try var SSD := Default(TStockSurfaceData); if Control.Context.FStockSurfaces.TryGetValue(STOCKSURF_ARROW, SSD) then FVertexData := SSD.VertexData else begin MakeBaseArrow; SSD.VertexData := FVertexData; Control.Context.FStockSurfaces.Add(STOCKSURF_ARROW, SSD); end; glBindBuffer(GL_ARRAY_BUFFER, FVertexData); if FInstanceData = 0 then glGenBuffers(1, @FInstanceData); glBindBuffer(GL_ARRAY_BUFFER, FInstanceData); begin var LBufferData := Prepare(FVectors, FMaxNorm); glBufferData(GL_ARRAY_BUFFER, Length(LBufferData) * SizeOf(GLfloat15), Pointer(LBufferData), GL_STATIC_DRAW); end; glEnableVertexAttribArray(0); glEnableVertexAttribArray(2); glEnableVertexAttribArray(3); glEnableVertexAttribArray(4); glEnableVertexAttribArray(5); glEnableVertexAttribArray(6); glEnableVertexAttribArray(7); glBindBuffer(GL_ARRAY_BUFFER, FVertexData); glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 6*SizeOf(GLfloat), nil); glVertexAttribPointer(2, 3, GL_FLOAT, GL_FALSE, 6*SizeOf(GLfloat), Pointer(3*SizeOf(GLfloat))); glBindBuffer(GL_ARRAY_BUFFER, FInstanceData); glVertexAttribPointer(3, 3, GL_FLOAT, GL_FALSE, 15*SizeOf(GLfloat), nil); glVertexAttribPointer(4, 3, GL_FLOAT, GL_FALSE, 15*SizeOf(GLfloat), Pointer(3*SizeOf(GLfloat))); glVertexAttribPointer(5, 3, GL_FLOAT, GL_FALSE, 15*SizeOf(GLfloat), Pointer(6*SizeOf(GLfloat))); glVertexAttribPointer(6, 3, GL_FLOAT, GL_FALSE, 15*SizeOf(GLfloat), Pointer(9*SizeOf(GLfloat))); glVertexAttribPointer(7, 3, GL_FLOAT, GL_FALSE, 15*SizeOf(GLfloat), Pointer(12*SizeOf(GLfloat))); glVertexAttribDivisor(3, 1); glVertexAttribDivisor(4, 1); glVertexAttribDivisor(5, 1); glVertexAttribDivisor(6, 1); glVertexAttribDivisor(7, 1); finally glBindVertexArray(0); end; end; { TImageRect } procedure TImageRect.BitmapChanged(Sender: TObject); begin Recreate; Changed; end; constructor TImageRect.Create(ACtl: TVisCtl3D); begin inherited; FDefaultProgram := P_Image; FBitmap := TBitmap.Create; FBitmap.OnChange := BitmapChanged; end; destructor TImageRect.Destroy; begin FreeAndNil(FBitmap); inherited; end; procedure TImageRect.Draw(const AGlobalTime: Double); begin inherited; Control.ProgramMgr.CurrentProgram.UTranspColor.SetValue(FTransparentColor); Control.ProgramMgr.CurrentProgram.UOpaqueColor.SetValue(FOpaqueColor); Control.ProgramMgr.CurrentProgram.UTranspColorMode.SetValue(Ord(FTransparentColorMode)); glBindVertexArray(FVAO); glBindTexture(GL_TEXTURE_2D, FTexture); glDrawArrays(GL_TRIANGLES, 0, 6); glBindVertexArray(0); end; procedure TImageRect.FreeGLResources; begin if TryContextCurrent then begin glDeleteTextures(1, @FTexture); FTexture := 0; glDeleteBuffers(1, @FVertexData); FVertexData := 0; glDeleteVertexArrays(1, @FVAO); FVAO := 0; end else rglLog('TImageRect.FreeGLResources: TryContextCurrent returned false'); inherited; end; procedure TImageRect.GLRelease; begin FTexture := 0; FVertexData := 0; FVAO := 0; inherited; end; procedure TImageRect.Recreate; begin if FVAO <> 0 then Setup; end; procedure TImageRect.SetBitmap(const Value: TBitmap); begin if FBitmap <> Value then begin FBitmap.Assign(Value); Recreate; end; end; procedure TImageRect.SetTransparentColor(const Value: TColor); begin if FTransparentColor <> Value then begin FTransparentColor := Value; Changed; end; end; procedure TImageRect.SetOpaqueColor(const Value: TColor); begin if FOpaqueColor <> Value then begin FOpaqueColor := Value; Changed; end; end; procedure TImageRect.SetTransparentColorMode( const Value: TTransparentColorMode); begin if FTransparentColorMode <> Value then begin FTransparentColorMode := Value; Changed; end; end; procedure TImageRect.Setup; const FrameQuad: array[0..29] of GLfloat = ( 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0, 1.0, 0.0, 0.0, 1.0, 1.0, 1.0, 1.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 1.0, 1.0, 1.0, 1.0, 0.0, 0.0, 1.0, 0.0, 1.0 ); begin if (Control = nil) or (Control.Context = nil) then Exit; Control.Context.MakeCurrent('TImageRect.Setup'); if FVAO = 0 then glGenVertexArrays(1, @FVAO); glBindVertexArray(FVAO); try if FVertexData = 0 then glGenBuffers(1, @FVertexData); glBindBuffer(GL_ARRAY_BUFFER, FVertexData); glBufferData(GL_ARRAY_BUFFER, Length(FrameQuad) * SizeOf(GLfloat5), @FrameQuad, GL_STATIC_DRAW); glEnableVertexAttribArray(0); glEnableVertexAttribArray(1); glBindBuffer(GL_ARRAY_BUFFER, FVertexData); glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 5*SizeOf(GLfloat), nil); glVertexAttribPointer(1, 2, GL_FLOAT, GL_FALSE, 5*SizeOf(GLfloat), Pointer(3*SizeOf(GLfloat))); if FTexture = 0 then glGenTextures(1, @FTexture); glBindTexture(GL_TEXTURE_2D, FTexture); if Assigned(FBitmap) and (FBitmap.Height >= 1) then begin FBitmap.PixelFormat := pf24bit; glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, FBitmap.Width, FBitmap.Height, 0, GL_BGR, GL_UNSIGNED_BYTE, FBitmap.ScanLine[FBitmap.Height - 1]); end; glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); finally glBindVertexArray(0); end; end; { TTextRect } constructor TTextRect.Create(ACtl: TVisCtl3D); begin inherited; FDefaultProgram := P_Text; FTextResFactor := 1.0; FHighQuality := True; FAnchorPoint := apTopLeft; FOpacity := 1.0; FFont := TFont.Create; FFont.OnChange := FontChanged; end; destructor TTextRect.Destroy; begin FreeAndNil(FFont); inherited; end; procedure TTextRect.Draw(const AGlobalTime: Double); begin inherited; Control.ProgramMgr.CurrentProgram.UAnchorPoint.SetValue(Ord(FAnchorPoint)); Control.ProgramMgr.CurrentProgram.UFaceScreen.SetValue(FFaceScreen); Control.ProgramMgr.CurrentProgram.UAspect.SetValue(FAspect / Control.AspectRatio); Control.ProgramMgr.CurrentProgram.UDisplacement.SetValue(FDisplacement); Control.ProgramMgr.CurrentProgram.UOpacity.SetValue(FOpacity); Control.ProgramMgr.CurrentProgram.USize.SetValue(FFont.Size / 24.0); glBindVertexArray(FVAO); glBindTexture(GL_TEXTURE_2D, FTexture); glDrawArrays(GL_TRIANGLES, 0, 6); glBindVertexArray(0); end; procedure TTextRect.FontChanged(Sender: TObject); begin Recreate; Changed; end; procedure TTextRect.FreeGLResources; begin if TryContextCurrent then begin glDeleteTextures(1, @FTexture); FTexture := 0; glDeleteBuffers(1, @FVertexData); FVertexData := 0; glDeleteVertexArrays(1, @FVAO); FVAO := 0; end else rglLog('TTextRect.FreeGLResources: TryContextCurrent returned false'); inherited; end; procedure TTextRect.GLRelease; begin FTexture := 0; FVertexData := 0; FVAO := 0; inherited; end; procedure TTextRect.SetAnchorPoint(const Value: TAnchorPoint); begin if FAnchorPoint <> Value then begin FAnchorPoint := Value; Changed; end; end; procedure TTextRect.SetDisplacement(const Value: rglv2); begin if FDisplacement <> Value then begin FDisplacement := Value; Changed; end; end; procedure TTextRect.SetFaceScreen(const Value: Boolean); begin if FFaceScreen <> Value then begin FFaceScreen := Value; Changed; end; end; procedure TTextRect.SetFont(const Value: TFont); begin FFont.Assign(Value); end; procedure TTextRect.SetHighQuality(const Value: Boolean); begin if FHighQuality <> Value then begin FHighQuality := Value; Recreate; Changed; end; end; procedure TTextRect.SetOpacity(const Value: Double); begin var LOpacity := EnsureRange(Value, 0.0, 1.0); if FOpacity <> LOpacity then begin FOpacity := LOpacity; Changed; end; end; function TTextRect.MakeBitmap: TBitmap; const eps = 2; begin Result := TBitmap.Create; try Result.Canvas.Font := FFont; var OptimalHeight: Integer; if FText.Length <= 4 then OptimalHeight := Round(1.5 * Screen.Height) else if FText.Length <= 12 then OptimalHeight := Screen.Height else OptimalHeight := Screen.Height div 2; OptimalHeight := Round(FTextResFactor * OptimalHeight); Result.Canvas.Font.Height := EnsureRange(OptimalHeight, 100, 2500); var LSize := Result.Canvas.TextExtent(FText) + TSize.Create(2*eps, 2*eps); var LArea := UInt64(LSize.Width) * UInt64(LSize.Height); const MaxArea = 7372800; const MaxLength = IfThen(Control.ImplData.MaxTextureSize > 0, Control.ImplData.MaxTextureSize, 4096); var Factor := Max(Max(LSize.cx / MaxLength, LSize.cy / MaxLength), Sqrt(LArea / MaxArea)); if Factor > 1.0 then begin Result.Canvas.Font.Height := EnsureRange(Round(Result.Canvas.Font.Height / Factor), 32, 2500); LSize := Result.Canvas.TextExtent(FText) + TSize.Create(2*eps, 2*eps); // LArea := UInt64(LSize.Width) * UInt64(LSize.Height); end; if LSize.Width > MaxLength then LSize.Width := MaxLength; if LSize.Height > MaxLength then LSize.Height := MaxLength; Result.PixelFormat := pf24bit; Result.SetSize(LSize.Width, LSize.Height); Result.Canvas.Brush.Color := clBlack; Result.Canvas.FillRect(Rect(0, 0, LSize.Width, LSize.Height)); Result.Canvas.Font.Color := clWhite; Result.Canvas.TextOut(eps, eps, FText); if LSize.cy <> 0 then FAspect := LSize.cx / LSize.cy; except Result.Free; raise; end; end; procedure TTextRect.Recreate; begin if FVAO <> 0 then Setup; end; procedure TTextRect.SetText(const Value: string); begin if FText <> Value then begin FText := Value; Recreate; Changed; end; end; procedure TTextRect.SetTextResFactor(const Value: Double); begin if FTextResFactor <> Value then begin FTextResFactor := Value; Recreate; Changed; end; end; procedure TTextRect.Setup; const FrameQuad: array[0..29] of GLfloat = ( 0.0, 0.0, 0.0, 0.0, 1.0, 1.0, 0.0, 0.0, 0.0, 0.0, 1.0, 1.0, 0.0, 1.0, 0.0, 0.0, 0.0, 0.0, 0.0, 1.0, 1.0, 1.0, 0.0, 1.0, 0.0, 0.0, 1.0, 0.0, 1.0, 1.0 ); swiz: array[0..3] of GLint = (GL_ZERO, GL_ZERO, GL_ZERO, GL_RED); begin if (Control = nil) or (Control.Context = nil) then Exit; Control.Context.MakeCurrent('TTextRect.Setup'); if FVAO = 0 then glGenVertexArrays(1, @FVAO); glBindVertexArray(FVAO); try if FVertexData = 0 then glGenBuffers(1, @FVertexData); glBindBuffer(GL_ARRAY_BUFFER, FVertexData); glBufferData(GL_ARRAY_BUFFER, Length(FrameQuad) * SizeOf(GLfloat5), @FrameQuad, GL_STATIC_DRAW); glEnableVertexAttribArray(0); glEnableVertexAttribArray(1); glBindBuffer(GL_ARRAY_BUFFER, FVertexData); glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 5*SizeOf(GLfloat), nil); glVertexAttribPointer(1, 2, GL_FLOAT, GL_FALSE, 5*SizeOf(GLfloat), Pointer(3*SizeOf(GLfloat))); if FTexture = 0 then glGenTextures(1, @FTexture); glBindTexture(GL_TEXTURE_2D, FTexture); begin var LBitmap := MakeBitmap; try glTexImage2D(GL_TEXTURE_2D, 0, GL_R8, LBitmap.Width, LBitmap.Height, 0, GL_BGR, GL_UNSIGNED_BYTE, LBitmap.ScanLine[LBitmap.Height - 1]); finally LBitmap.Free; end; end; glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); glTexParameteriv(GL_TEXTURE_2D, GL_TEXTURE_SWIZZLE_RGBA, @swiz); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_BORDER); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_BORDER); const black: rglv4 = rglv4.Zero; glTexParameterfv(GL_TEXTURE_2D, GL_TEXTURE_BORDER_COLOR, @black); if FHighQuality then begin glGenerateMipmap(GL_TEXTURE_2D); glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAX_ANISOTROPY_EXT, Min(16.0, Control.ImplData.MaxTextureAnisotropy)); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR); glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_LOD_BIAS, -0.75); end; FScale.y := FScale.x * FAspect; ComputeOM; finally glBindVertexArray(0); end; end; { TAxes } procedure TAxes.AxisChanged(Sender: TObject); begin Changed; end; function TAxes.GetGridCount: Integer; begin Result := 0; for var Child in FChildren do if Child is TGrid then Inc(Result); end; procedure TAxes.GridChanged(Sender: TObject); begin Changed; end; constructor TAxes.Create(ACtl: TVisCtl3D); begin inherited; FXAxis := CreateChild<TAxis>; FXAxis.FIndex := 0; FXAxis.FCylinder.Direction := vec(1, 0, 0); FXAxis.FCylinder.Color := clRed; FXAxis.OnChange := AxisChanged; FYAxis := CreateChild<TAxis>; FYAxis.FIndex := 1; FYAxis.FCylinder.Direction := vec(0, 1, 0); FYAxis.FCylinder.Color := clGreen; FYAxis.OnChange := AxisChanged; FZAxis := CreateChild<TAxis>; FZAxis.FIndex := 2; FZAxis.FCylinder.Direction := vec(0, 0, 1); FZAxis.FCylinder.Color := clBlue; FZAxis.OnChange := AxisChanged; var LXYGrid := CreateChild<TGrid>; LXYGrid.FIndex := 0; LXYGrid.Direction := vec(0, 0, 1); LXYGrid.OnChange := GridChanged; var LYZGrid := CreateChild<TGrid>; LYZGrid.FIndex := 0; LYZGrid.Direction := vec(1, 0, 0); LYZGrid.Rotation := 90; LYZGrid.OnChange := GridChanged; var LXZGrid := CreateChild<TGrid>; LXZGrid.FIndex := 0; LXZGrid.Direction := vec(0, 1, 0); LXZGrid.Rotation := -90; LXZGrid.OnChange := GridChanged; end; procedure TAxes.SetGridCount(const Value: Integer); begin if not InRange(Value, 0, 1024) then raise ERglError.Create('Number of grids must be between 0 and 1024.'); var CurrentCount := GetGridCount; if CurrentCount > Value then begin var GridsToRemove := CurrentCount - Value; for var i := FChildren.Count - 1 downto 0 do if FChildren[i] is TGrid then begin DeleteChild(FChildren[i]); Dec(GridsToRemove); if GridsToRemove = 0 then Break; end; end; for var i := CurrentCount + 1 to Value do CreateChild<TGrid> end; procedure TAxes.SetXAxis(const Value: TAxis); begin FXAxis.Assign(Value); end; procedure TAxes.SetYAxis(const Value: TAxis); begin FYAxis.Assign(Value); end; procedure TAxes.SetZAxis(const Value: TAxis); begin FZAxis.Assign(Value); end; { TAxis } constructor TAxis.Create(ACtl: TVisCtl3D); begin inherited; FCylinder := CreateChild<TCylinder>; FLabelFont := TFont.Create; FLabelFont.Size := 8; FLabelFont.OnChange := FontChange; FLabels := True; FLabelDelta := 1.0; FLabelFormat := '0'; FLength := 10.0; FRadius := 0.1; end; destructor TAxis.Destroy; begin FreeAndNil(FLabelFont); inherited; end; procedure TAxis.FontChange(Sender: TObject); begin SetupLabels; Changed; end; function TAxis.FormatAxisLabel(const AFormat: string; const AValue: Double): string; begin Result := FormatFloat(AFormat, AValue, InvFS) .Replace('-', '−') end; function TAxis.GetColor: TColor; begin if Assigned(FCylinder) then Result := FCylinder.Color else Result := clBlack; end; procedure TAxis.ProjectionChanged; begin if (FIndex = 2) and FLabels then for var lbl in FChildren do if (lbl is TTextRect) and (lbl.FParentTag and PTAG_AXISLABEL <> 0) then case Control.Projection of Orthographic: begin TTextRect(lbl).FaceScreen := False; TTextRect(lbl).Direction := vec(1.0, 0.0, 0.0); TTextRect(lbl).Rotation := 0; TTextRect(lbl).Position := vec(0.0, -1.5 * FCylinder.Radius, TTextRect(lbl).Position.z); TTextRect(lbl).AnchorPoint := apRight; end; Perspective: begin TTextRect(lbl).FaceScreen := True; TTextRect(lbl).Position := vec(0, 0, TTextRect(lbl).Position.z); TTextRect(lbl).Direction := vec(1.0, 0.0, 0.0); TTextRect(lbl).Displacement := vec2(-0.25, 0); TTextRect(lbl).Rotation := 90; TTextRect(lbl).AnchorPoint := apRight; end; end; end; procedure TAxis.Recreate; begin Setup; end; procedure TAxis.SetColor(const Value: TColor); begin if Assigned(FCylinder) then FCylinder.Color := Value; end; procedure TAxis.SetLabelDelta(const Value: Double); begin if (FLabelDelta <> Value) and (Value > Single.Epsilon) then begin FLabelDelta := Value; SetupLabels; Changed; end; end; procedure TAxis.SetLabelFont(const Value: TFont); begin if FLabelFont <> Value then FLabelFont.Assign(Value); end; procedure TAxis.SetLabelFormat(const Value: string); begin if FLabelFormat <> Value then begin FLabelFormat := Value; SetupLabels; Changed; end; end; procedure TAxis.SetLabels(const Value: Boolean); begin if FLabels <> Value then begin FLabels := Value; SetupLabels; Changed; end; end; procedure TAxis.SetLength(const Value: Double); begin if (FLength <> Value) and (Value >= 0.0) then begin FLength := Value; Recreate; Changed; end; end; procedure TAxis.SetNegativeLength(const Value: Double); begin if (FNegativeLength <> Value) and (Value >= 0.0) then begin FNegativeLength := Value; Recreate; Changed; end; end; procedure TAxis.SetRadius(const Value: Double); begin if (FRadius <> Value) and (Value > 0.0) then begin FRadius := Value; Recreate; Changed; end; end; procedure TAxis.Setup; begin if (Control = nil) or (Control.Context = nil) then Exit; Control.Context.MakeCurrent('TAxis.Setup'); if FCylinder = nil then FCylinder := CreateChild<TCylinder>; FCylinder.Radius := FRadius; FCylinder.Height := FNegativeLength + FLength; FCylinder.Position := -FNegativeLength * vec(Ord(FIndex = 0), Ord(FIndex = 1), Ord(FIndex = 2)); SetupLabels; end; procedure TAxis.SetupLabels; begin Control.Context.MakeCurrent('TAxis.SetupLabels'); DeleteChildren(PTAG_AXISLABEL); if not FLabels then Exit; for var LNegative := False to True do begin var x := 0.0; var LLimit: Double; if LNegative then LLimit := FNegativeLength else LLimit := FLength; while x <= LLimit do begin var L := CreateChild<TTextRect>; var LSign := 1 - 2*Ord(LNegative); L.FParentTag := PTAG_AXISLABEL; L.Text := FormatAxisLabel(FLabelFormat, LSign * x); case FIndex of 0: begin L.Position := vec(LSign * x, 0, -FCylinder.Radius); L.Direction := vec(0, 1, 0); L.AnchorPoint := apTop; L.Rotation := 90; end; 1: begin L.Position := vec(0, LSign * x, -FCylinder.Radius); L.Direction := vec(1, 0, 0); L.AnchorPoint := apTop; L.Rotation := 0; end; 2: begin if Control.Projection = Perspective then begin L.Position := vec(0, 0, LSign * x); L.AnchorPoint := apRight; L.FaceScreen := True; L.Displacement := vec2(-0.25, 0); L.Direction := vec(1, 1, 0); L.Rotation := 45; end else begin L.Position := vec(0, -1.5*FCylinder.Radius, LSign * x); L.Direction := vec(1.0, 0.0, 0.0); L.Rotation := 0; L.AnchorPoint := apRight; end; end; end; L.TextResFactor := 0.5; L.Font := FLabelFont; L.Color := FLabelFont.Color; x := x + FLabelDelta; end; end; end; { TGrid } constructor TGrid.Create(ACtl: TVisCtl3D); begin inherited; FXMin := 0.0; FXMax := 10.0; FXDelta := 1.0; FYMin := 0.0; FYMax := 10.0; FYDelta := 1.0; FColor := clBlack; FDefaultProgram := P_UniformColorDefault; end; destructor TGrid.Destroy; begin inherited; end; procedure TGrid.Draw(const AGlobalTime: Double); begin inherited; glBindVertexArray(FVAO); glBindBuffer(GL_ARRAY_BUFFER, FVertexData); if FLineWidth <> 1.0 then glLineWidth(FLineWidth); glDrawArrays(GL_LINES, 0, FCount); if FLineWidth <> 1.0 then glLineWidth(1); glBindVertexArray(0); end; procedure TGrid.FreeGLResources; begin if TryContextCurrent then begin glDeleteBuffers(1, @FVertexData); FVertexData := 0; glDeleteVertexArrays(1, @FVAO); FVAO := 0; end else rglLog('TGrid.FreeGLResources: TryContextCurrent returned false'); inherited; end; procedure TGrid.GLRelease; begin FVertexData := 0; FCount := 0; FVAO := 0; inherited; end; procedure TGrid.Recreate; begin if FVertexData <> 0 then Setup; end; procedure TGrid.Setup; begin if (Control = nil) or (Control.Context = nil) then Exit; Control.Context.MakeCurrent('TGrid.Setup'); if FVAO = 0 then glGenVertexArrays(1, @FVAO); glBindVertexArray(FVAO); try var LVertices: TArray<GLfloat3>; var LLineCountX := Floor((FXMax - FXMin) / FXDelta + FXDelta / 1000) + 1; var LLineCountY := Floor((FYMax - FYMin) / FYDelta + FYDelta / 1000) + 1; var LLineCount := LLineCountX + LLineCountY; FCount := 2 * LLineCount; SetLength(LVertices, FCount); var Idx := 0; for var i := 0 to LLineCountX - 1 do begin var x := FXMin + FXDelta * i; LVertices[Idx] := vec(x, FYMin, 0); Inc(Idx); LVertices[Idx] := vec(x, FYMax, 0); Inc(Idx); end; for var i := 0 to LLineCountY - 1 do begin var y := FYMin + FYDelta * i; LVertices[Idx] := vec(FXMin, y, 0); Inc(Idx); LVertices[Idx] := vec(FXMax, y, 0); Inc(Idx); end; if FVertexData = 0 then glGenBuffers(1, @FVertexData); glBindBuffer(GL_ARRAY_BUFFER, FVertexData); glBufferData(GL_ARRAY_BUFFER, Length(LVertices) * SizeOf(GLfloat3), Pointer(LVertices), GL_STATIC_DRAW); glEnableVertexAttribArray(0); glBindBuffer(GL_ARRAY_BUFFER, FVertexData); glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 3*SizeOf(GLfloat), nil); finally glBindVertexArray(0); end; end; procedure TGrid.SetXDelta(const Value: Double); begin if (FXDelta <> Value) and (Value > Single.Epsilon) then begin FXDelta := Value; Recreate; Changed; end; end; procedure TGrid.SetXMax(const Value: Double); begin if FXMax <> Value then begin FXMax := Value; Recreate; Changed; end; end; procedure TGrid.SetXMin(const Value: Double); begin if FXMin <> Value then begin FXMin := Value; Recreate; Changed; end; end; procedure TGrid.SetYDelta(const Value: Double); begin if (FYDelta <> Value) and (Value > Single.Epsilon) then begin FYDelta := Value; Recreate; Changed; end; end; procedure TGrid.SetYMax(const Value: Double); begin if FYMax <> Value then begin FYMax := Value; Recreate; Changed; end; end; procedure TGrid.SetYMin(const Value: Double); begin if FYMin <> Value then begin FYMin := Value; Recreate; Changed; end; end; { TSolidModel } class constructor TSolidModel.ClassCreate; begin RegisterClass(TSolidCube, 'cube'); RegisterClass(TSolidCylinder, 'cylinder'); RegisterClass(TSolidCone, 'cone'); RegisterClass(TSolidTetrahedron, 'tetrahedron'); RegisterClass(TSolidOctahedron, 'octahedron'); RegisterClass(TSolidDodecahedron, 'dodecahedron'); RegisterClass(TSolidIcosahedron, 'icosahedron'); RegisterClass(TSolidPyramid, 'pyramid'); end; class destructor TSolidModel.ClassDestroy; begin FreeAndNil(FSolidClasses); end; constructor TSolidModel.Create(ACtl: TVisCtl3D); begin inherited; end; destructor TSolidModel.Destroy; begin inherited; end; procedure TSolidModel.Draw(const AGlobalTime: Double); begin inherited; if FVertexData = 0 then Exit; glBindVertexArray(FVAO); glDrawArrays(GL_TRIANGLES, 0, FCount); glBindVertexArray(0); end; procedure TSolidModel.FreeGLResources; begin if TryContextCurrent then begin glDeleteVertexArrays(1, @FVAO); FVAO := 0; if FVertexData <> 0 then begin if Stored then begin var Rec := Default(TSolidStoreRec); if Assigned(Control.Context.FStoredSolids) and Control.Context.FStoredSolids.TryGetValue(ModelClass, Rec) then begin Assert(Rec.VertexData = FVertexData); Assert(Rec.Count = FCount); Dec(Rec.RefCnt); if Rec.RefCnt = 0 then begin glDeleteBuffers(1, @FVertexData); FVertexData := 0; Control.Context.FStoredSolids.Remove(ModelClass); end else begin Control.Context.FStoredSolids.AddOrSetValue(ModelClass, Rec); FVertexData := 0; end; end else begin glDeleteBuffers(1, @FVertexData); FVertexData := 0; end; end else begin glDeleteBuffers(1, @FVertexData); FVertexData := 0; end; end; end else rglLog('TSolidModel.FreeGLResources: TryContextCurrent returned false'); inherited; end; class function TSolidModel.FromString(const S: string): TDrawable3DClass; begin if (FSolidClasses = nil) or not FSolidClasses.TryGetValue(S, Result) then raise Exception.CreateFmt('Unknown solid: "%s".', [S]); end; procedure TSolidModel.GLRelease; begin FVertexData := 0; FCount := 0; FVAO := 0; inherited; end; procedure TSolidModel.MakeBaseBuffer; begin end; function TSolidModel.ModelClass: TSolidModelClass; begin Result := TSolidModelClass(ClassType); end; procedure TSolidModel.Recreate; begin if FVertexData <> 0 then Setup; end; class procedure TSolidModel.RegisterClass(const AClass: TDrawable3DClass; const AName: string); begin if FSolidClasses = nil then FSolidClasses := TDictionary<string, TDrawable3DClass>.Create; FSolidClasses.Add(AName, AClass); end; procedure TSolidModel.Setup; begin if (Control = nil) or (Control.Context = nil) then Exit; Control.Context.MakeCurrent('TSolidModel.Setup'); if FVAO = 0 then glGenVertexArrays(1, @FVAO); glBindVertexArray(FVAO); try if Stored then begin var Rec := Default(TSolidStoreRec); if Control.Context.FStoredSolids.TryGetValue(ModelClass, Rec) then begin FVertexData := Rec.VertexData; FCount := Rec.Count; Inc(Rec.RefCnt); Control.Context.FStoredSolids.AddOrSetValue(ModelClass, Rec); end else begin MakeBaseBuffer; if FVertexData = 0 then Exit; Rec.VertexData := FVertexData; Rec.Count := FCount; Rec.RefCnt := 1; Control.Context.FStoredSolids.Add(ModelClass, Rec); end; end else MakeBaseBuffer; if FVertexData = 0 then Exit; glBindBuffer(GL_ARRAY_BUFFER, FVertexData); glEnableVertexAttribArray(0); glEnableVertexAttribArray(2); glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 6*SizeOf(GLfloat), nil); glVertexAttribPointer(2, 3, GL_FLOAT, GL_FALSE, 6*SizeOf(GLfloat), Pointer(3*SizeOf(GLfloat))); finally glBindVertexArray(0); end; end; class function TSolidModel.TryFromString(const S: string; out AClass: TDrawable3DClass): Boolean; begin Result := Assigned(FSolidClasses) and FSolidClasses.TryGetValue(S, AClass); end; { TObjModel } procedure TObjModel.LoadModel(const AData: string); begin FSource := AData; FBuf := ObjFileParseObjData(AData.Split([#13#10])); Recreate; Changed; end; constructor TObjModel.Create(ACtl: TVisCtl3D); begin inherited; end; procedure TObjModel.LoadModel(const AData: TArray<string>); begin FSource := string.Join(#13#10, AData); FBuf := ObjFileParseObjData(AData); Recreate; Changed; end; procedure TObjModel.LoadModelFromFile(const AFileName: TFileName; const Encoding: TEncoding); begin LoadModel(TFile.ReadAllLines(AFileName, Encoding)); end; procedure TObjModel.LoadModelFromFile(const AFileName: TFileName); begin LoadModel(TFile.ReadAllLines(AFileName)); end; procedure TObjModel.MakeBaseBuffer; begin if FBuf = nil then Exit; if FVertexData = 0 then glGenBuffers(1, @FVertexData); glBindBuffer(GL_ARRAY_BUFFER, FVertexData); glBufferData(GL_ARRAY_BUFFER, Length(FBuf) * SizeOf(GLfloat6), Pointer(FBuf), GL_STATIC_DRAW); FCount := Length(FBuf); end; { TRawModel } function TRawModel.GetRawData: TArray<GLfloat6>; begin Result := nil; end; procedure TRawModel.MakeBaseBuffer; begin var RawBuffer := GetRawData; if RawBuffer = nil then Exit; if FVertexData = 0 then glGenBuffers(1, @FVertexData); glBindBuffer(GL_ARRAY_BUFFER, FVertexData); glBufferData(GL_ARRAY_BUFFER, Length(RawBuffer) * SizeOf(GLfloat6), Pointer(RawBuffer), GL_STATIC_DRAW); FCount := Length(RawBuffer); end; { TSolidCube } constructor TSolidCube.Create(ACtl: TVisCtl3D); begin inherited; Stored := True; end; function TSolidCube.GetRawData: TArray<GLfloat6>; const s = 0.5; Data: array[0..6*2*3-1] of GLfloat6 = ( // Front face, upper triangle // x, y, z, nx, ny, nz (+s, -s, +s, +1.0, 0.0, 0.0), (+s, -s, -s, +1.0, 0.0, 0.0), (+s, +s, +s, +1.0, 0.0, 0.0), // Front face, lower triangle // x, y, z, nx, ny, nz (+s, -s, -s, +1.0, 0.0, 0.0), (+s, +s, -s, +1.0, 0.0, 0.0), (+s, +s, +s, +1.0, 0.0, 0.0), // Right face, upper triangle // x, y, z, nx, ny, nz (+s, +s, +s, 0.0, +1.0, 0.0), (+s, +s, -s, 0.0, +1.0, 0.0), (-s, +s, +s, 0.0, +1.0, 0.0), // Right face, lower triangle // x, y, z, nx, ny, nz (+s, +s, -s, 0.0, +1.0, 0.0), (-s, +s, -s, 0.0, +1.0, 0.0), (-s, +s, +s, 0.0, +1.0, 0.0), // Back face, upper triangle // x, y, z, nx, ny, nz (-s, +s, -s, -1.0, 0.0, 0.0), (-s, -s, +s, -1.0, 0.0, 0.0), (-s, +s, +s, -1.0, 0.0, 0.0), // Back face, lower triangle // x, y, z, nx, ny, nz (-s, -s, -s, -1.0, 0.0, 0.0), (-s, -s, +s, -1.0, 0.0, 0.0), (-s, +s, -s, -1.0, 0.0, 0.0), // Left face, upper triangle // x, y, z, nx, ny, nz (-s, -s, -s, 0.0, -1.0, 0.0), (+s, -s, +s, 0.0, -1.0, 0.0), (-s, -s, +s, 0.0, -1.0, 0.0), // Left face, lower triangle // x, y, z, nx, ny, nz (-s, -s, -s, 0.0, -1.0, 0.0), (+s, -s, -s, 0.0, -1.0, 0.0), (+s, -s, +s, 0.0, -1.0, 0.0), // Top face, near triangle // x, y, z, nx, ny, nz (+s, -s, +s, 0.0, 0.0, +1.0), (+s, +s, +s, 0.0, 0.0, +1.0), (-s, +s, +s, 0.0, 0.0, +1.0), // Top face, far triangle // x, y, z, nx, ny, nz (-s, -s, +s, 0.0, 0.0, +1.0), (+s, -s, +s, 0.0, 0.0, +1.0), (-s, +s, +s, 0.0, 0.0, +1.0), // Bottom face, near triangle // x, y, z, nx, ny, nz (+s, -s, -s, 0.0, 0.0, -1.0), (-s, -s, -s, 0.0, 0.0, -1.0), (+s, +s, -s, 0.0, 0.0, -1.0), // Bottom face, far triangle // x, y, z, nx, ny, nz (-s, -s, -s, 0.0, 0.0, -1.0), (-s, +s, -s, 0.0, 0.0, -1.0), (+s, +s, -s, 0.0, 0.0, -1.0) ); begin SetLength(Result, Length(Data)); TArray.Copy<GLfloat6>(Data, Result, Length(Data)); end; { TSolidCylinder } constructor TSolidCylinder.Create(ACtl: TVisCtl3D); begin inherited; FAngle := 360; end; destructor TSolidCylinder.Destroy; begin inherited; end; procedure TSolidCylinder.ConstructBufferSinHole; begin if FVertexData <> 0 then raise Exception.Create('Vertex data already present.'); var LAngle: Single := EnsureRange(FAngle, 0.1, 360) * Pi / 180; var LSlice := LAngle < 2*Pi; const N = Ceil(128 * LAngle/2*Pi); var TrigVals: TArray<rglv2>; SetLength(TrigVals, N + 1); for var i := 0 to N do begin var s, c: Single; SinCos(LAngle / N * i, s, c); TrigVals[i] := vec2(c, s); end; FCounts := [2 + N, 2 + 2*N + 8*Ord(LSlice), 2 + N]; FTwoSides := False; var Data: TArray<GLfloat6>; SetLength(Data, FCounts[0] + FCounts[1] + FCounts[2]); var Idx := 0; // Top disk (triangle fan) GLr3n3v(Data[Idx]).r := vec(0.0, 0.0, 1.0); GLr3n3v(Data[Idx]).n := vec(0, 0, 1.0); Inc(Idx); for var i := 0 to N do begin GLr3n3v(Data[Idx]).r := vec(TrigVals[i].x, TrigVals[i].y, 1.0); GLr3n3v(Data[Idx]).n := vec(0, 0, 1.0); Inc(Idx); end; // Cylinder + edges (triangle strip) if LSlice then begin GLr3n3v(Data[Idx]).r := vec(0.0, 0.0, 1.0); GLr3n3v(Data[Idx]).n := vec(0.0, -1.0, 0.0); Inc(Idx); GLr3n3v(Data[Idx]).r := vec(0.0, 0.0, 0.0); GLr3n3v(Data[Idx]).n := vec(0.0, -1.0, 0.0); Inc(Idx); GLr3n3v(Data[Idx]).r := vec(TrigVals[0].x, TrigVals[0].y, 1.0); GLr3n3v(Data[Idx]).n := vec(0.0, -1.0, 0.0); Inc(Idx); GLr3n3v(Data[Idx]).r := vec(TrigVals[0].x, TrigVals[0].y, 0.0); GLr3n3v(Data[Idx]).n := vec(0.0, -1.0, 0.0); Inc(Idx); end; GLr3n3v(Data[Idx]).r := vec(TrigVals[0].x, TrigVals[0].y, 1.0); GLr3n3v(Data[Idx]).n := vec(TrigVals[0].x, TrigVals[0].y, 0.0); Inc(Idx); for var i := 0 to N - 1 do begin GLr3n3v(Data[Idx]).r := vec(TrigVals[i].x, TrigVals[i].y, 0.0); GLr3n3v(Data[Idx]).n := vec(TrigVals[i].x, TrigVals[i].y, 0.0); Inc(Idx); GLr3n3v(Data[Idx]).r := vec(TrigVals[Succ(i)].x, TrigVals[Succ(i)].y, 1.0); GLr3n3v(Data[Idx]).n := vec(TrigVals[Succ(i)].x, TrigVals[Succ(i)].y, 0.0); Inc(Idx); end; GLr3n3v(Data[Idx]).r := vec(TrigVals[High(TrigVals)].x, TrigVals[High(TrigVals)].y, 0.0); GLr3n3v(Data[Idx]).n := vec(TrigVals[High(TrigVals)].x, TrigVals[High(TrigVals)].y, 0.0); Inc(Idx); if LSlice then begin GLr3n3v(Data[Idx]).r := vec(TrigVals[High(TrigVals)].x, TrigVals[High(TrigVals)].y, 1.0); GLr3n3v(Data[Idx]).n := vec(-TrigVals[High(TrigVals)].y, TrigVals[High(TrigVals)].x, 0); Inc(Idx); GLr3n3v(Data[Idx]).r := vec(TrigVals[High(TrigVals)].x, TrigVals[High(TrigVals)].y, 0.0); GLr3n3v(Data[Idx]).n := vec(-TrigVals[High(TrigVals)].y, TrigVals[High(TrigVals)].x, 0); Inc(Idx); GLr3n3v(Data[Idx]).r := vec(0.0, 0.0, 1.0); GLr3n3v(Data[Idx]).n := vec(-TrigVals[High(TrigVals)].y, TrigVals[High(TrigVals)].x, 0); Inc(Idx); GLr3n3v(Data[Idx]).r := vec(0.0, 0.0, 0.0); GLr3n3v(Data[Idx]).n := vec(-TrigVals[High(TrigVals)].y, TrigVals[High(TrigVals)].x, 0); Inc(Idx); end; // Bottom disk (triangle fan) GLr3n3v(Data[Idx]).r := vec(0.0, 0.0, 0.0); GLr3n3v(Data[Idx]).n := vec(0, 0, -1.0); Inc(Idx); for var i := N downto 0 do begin GLr3n3v(Data[Idx]).r := vec(TrigVals[i].x, TrigVals[i].y, 0.0); GLr3n3v(Data[Idx]).n := vec(0, 0, -1.0); Inc(Idx); end; Assert(Idx = Length(Data)); glGenBuffers(1, @FVertexData); glBindBuffer(GL_ARRAY_BUFFER, FVertexData); glBufferData(GL_ARRAY_BUFFER, Length(Data) * SizeOf(GLfloat6), Pointer(Data), GL_STATIC_DRAW); end; procedure TSolidCylinder.ConstructBufferConHole; begin if FVertexData <> 0 then raise Exception.Create('Vertex data already present.'); var LAngle: Single := EnsureRange(FAngle, 0.1, 360) * Pi / 180; var LSlice := LAngle < 2*Pi; var LInnerRadius: Single := EnsureRange(FInnerRadius, 0.0, 1.0); var ρ := LInnerRadius; const N = Ceil(128 * LAngle/2*Pi); var TrigVals: TArray<rglv2>; SetLength(TrigVals, N + 1); for var i := 0 to N do begin var s, c: Single; SinCos(LAngle / N * i, s, c); TrigVals[i] := vec2(c, s); end; FCounts := [2 * (N + 1), 4 + 4*N + 8*Ord(LSlice), 2 * (N+1)]; FTwoSides := not LSlice; var Data: TArray<GLfloat6>; SetLength(Data, FCounts[0] + FCounts[1] + FCounts[2]); var Idx := 0; // Top annulus (triangle strip) for var i := 0 to N do begin GLr3n3v(Data[Idx]).r := vec(ρ * TrigVals[i].x, ρ * TrigVals[i].y, 1.0); GLr3n3v(Data[Idx]).n := vec(0, 0, 1.0); Inc(Idx); GLr3n3v(Data[Idx]).r := vec(TrigVals[i].x, TrigVals[i].y, 1.0); GLr3n3v(Data[Idx]).n := vec(0, 0, 1.0); Inc(Idx); end; // Cylinders + edges (triangle strip) if LSlice then begin GLr3n3v(Data[Idx]).r := vec(ρ * TrigVals[0].x, ρ * TrigVals[0].y, 1.0); GLr3n3v(Data[Idx]).n := vec(0.0, -1.0, 0.0); Inc(Idx); GLr3n3v(Data[Idx]).r := vec(ρ * TrigVals[0].x, ρ * TrigVals[0].y, 0.0); GLr3n3v(Data[Idx]).n := vec(0.0, -1.0, 0.0); Inc(Idx); GLr3n3v(Data[Idx]).r := vec(TrigVals[0].x, TrigVals[0].y, 1.0); GLr3n3v(Data[Idx]).n := vec(0.0, -1.0, 0.0); Inc(Idx); GLr3n3v(Data[Idx]).r := vec(TrigVals[0].x, TrigVals[0].y, 0.0); GLr3n3v(Data[Idx]).n := vec(0.0, -1.0, 0.0); Inc(Idx); end; GLr3n3v(Data[Idx]).r := vec(TrigVals[0].x, TrigVals[0].y, 1.0); GLr3n3v(Data[Idx]).n := vec(TrigVals[0].x, TrigVals[0].y, 0.0); Inc(Idx); for var i := 0 to N - 1 do begin GLr3n3v(Data[Idx]).r := vec(TrigVals[i].x, TrigVals[i].y, 0.0); GLr3n3v(Data[Idx]).n := vec(TrigVals[i].x, TrigVals[i].y, 0.0); Inc(Idx); GLr3n3v(Data[Idx]).r := vec(TrigVals[Succ(i)].x, TrigVals[Succ(i)].y, 1.0); GLr3n3v(Data[Idx]).n := vec(TrigVals[Succ(i)].x, TrigVals[Succ(i)].y, 0.0); Inc(Idx); end; GLr3n3v(Data[Idx]).r := vec(TrigVals[High(TrigVals)].x, TrigVals[High(TrigVals)].y, 0.0); GLr3n3v(Data[Idx]).n := vec(TrigVals[High(TrigVals)].x, TrigVals[High(TrigVals)].y, 0.0); Inc(Idx); if LSlice then begin GLr3n3v(Data[Idx]).r := vec(TrigVals[High(TrigVals)].x, TrigVals[High(TrigVals)].y, 1.0); GLr3n3v(Data[Idx]).n := vec(-TrigVals[High(TrigVals)].y, TrigVals[High(TrigVals)].x, 0); Inc(Idx); GLr3n3v(Data[Idx]).r := vec(TrigVals[High(TrigVals)].x, TrigVals[High(TrigVals)].y, 0.0); GLr3n3v(Data[Idx]).n := vec(-TrigVals[High(TrigVals)].y, TrigVals[High(TrigVals)].x, 0); Inc(Idx); GLr3n3v(Data[Idx]).r := vec(ρ * TrigVals[High(TrigVals)].x, ρ * TrigVals[High(TrigVals)].y, 1.0); GLr3n3v(Data[Idx]).n := vec(-TrigVals[High(TrigVals)].y, TrigVals[High(TrigVals)].x, 0); Inc(Idx); GLr3n3v(Data[Idx]).r := vec(ρ * TrigVals[High(TrigVals)].x, ρ * TrigVals[High(TrigVals)].y, 0.0); GLr3n3v(Data[Idx]).n := vec(-TrigVals[High(TrigVals)].y, TrigVals[High(TrigVals)].x, 0); Inc(Idx); end; GLr3n3v(Data[Idx]).r := vec(ρ * TrigVals[High(TrigVals)].x, ρ * TrigVals[High(TrigVals)].y, 1.0); GLr3n3v(Data[Idx]).n := vec(-TrigVals[High(TrigVals)].y, TrigVals[High(TrigVals)].x, 0); Inc(Idx); for var i := N - 1 downto 0 do begin GLr3n3v(Data[Idx]).r := vec(ρ * TrigVals[Succ(i)].x, ρ * TrigVals[Succ(i)].y, 0.0); GLr3n3v(Data[Idx]).n := vec(ρ * TrigVals[Succ(i)].x, ρ * TrigVals[Succ(i)].y, 0.0); Inc(Idx); GLr3n3v(Data[Idx]).r := vec(ρ * TrigVals[i].x, ρ * TrigVals[i].y, 1.0); GLr3n3v(Data[Idx]).n := vec(ρ * TrigVals[i].x, ρ * TrigVals[i].y, 0.0); Inc(Idx); end; GLr3n3v(Data[Idx]).r := vec(ρ * TrigVals[0].x, ρ * TrigVals[0].y, 0.0); GLr3n3v(Data[Idx]).n := vec(ρ * TrigVals[0].x, ρ * TrigVals[0].y, 0.0); Inc(Idx); // Bottom annulus (triangle strip) for var i := N downto 0 do begin GLr3n3v(Data[Idx]).r := vec(ρ * TrigVals[i].x, ρ * TrigVals[i].y, 0.0); GLr3n3v(Data[Idx]).n := vec(0, 0, -1.0); Inc(Idx); GLr3n3v(Data[Idx]).r := vec(TrigVals[i].x, TrigVals[i].y, 0.0); GLr3n3v(Data[Idx]).n := vec(0, 0, -1.0); Inc(Idx); end; Assert(Idx = Length(Data)); glGenBuffers(1, @FVertexData); glBindBuffer(GL_ARRAY_BUFFER, FVertexData); glBufferData(GL_ARRAY_BUFFER, Length(Data) * SizeOf(GLfloat6), Pointer(Data), GL_STATIC_DRAW); end; procedure TSolidCylinder.Draw(const AGlobalTime: Double); begin inherited; glBindVertexArray(FVAO); if FInnerRadius = 0.0 then begin glDrawArrays(GL_TRIANGLE_FAN, 0, FCounts[0]); glDrawArrays(GL_TRIANGLE_STRIP, FCounts[0], FCounts[1]); glDrawArrays(GL_TRIANGLE_FAN, FCounts[0] + FCounts[1], FCounts[2]); end else begin glDrawArrays(GL_TRIANGLE_STRIP, 0, FCounts[0]); if FTwoSides then begin glDrawArrays(GL_TRIANGLE_STRIP, FCounts[0], FCounts[1] div 2); glDrawArrays(GL_TRIANGLE_STRIP, FCounts[0] + FCounts[1] div 2, FCounts[1] div 2); end else glDrawArrays(GL_TRIANGLE_STRIP, FCounts[0], FCounts[1]); glDrawArrays(GL_TRIANGLE_STRIP, FCounts[0] + FCounts[1], FCounts[2]); end; glBindVertexArray(0); end; procedure TSolidCylinder.FreeGLResources; begin if TryContextCurrent then begin glDeleteVertexArrays(1, @FVAO); FVAO := 0; ReleaseBuffer; end else rglLog('TSolidCylinder.FreeGLResources: TryContextCurrent returned false'); inherited; end; function TSolidCylinder.GetAxisLengths: rglv2; begin Result.x := Scale.x; Result.y := Scale.y; end; function TSolidCylinder.GetHeight: Single; begin Result := Scale.z; end; function TSolidCylinder.GetRadius: Single; begin Result := FScale.x; end; procedure TSolidCylinder.GLRelease; begin FVertexData := 0; FCounts := nil; FVAO := 0; inherited; end; procedure TSolidCylinder.GetBufferData; begin var LBufferStore: TDictionary<TBufferStoreKey, TBufferStoreItem>; var Buf := TObject(nil); if Control.Context.FCustomBuffers.TryGetValue(TSolidCylinder, Buf) then LBufferStore := Buf as TDictionary<TBufferStoreKey, TBufferStoreItem> else begin LBufferStore := TDictionary<TBufferStoreKey, TBufferStoreItem>.Create; Control.Context.FCustomBuffers.AddOrSetValue(TSolidCylinder, LBufferStore); end; var Key := Default(TBufferStoreKey); Key.InnerRadius := FInnerRadius; Key.Angle := FAngle; var BSI := Default(TBufferStoreItem); if LBufferStore.TryGetValue(Key, BSI) then begin Assert(BSI.InnerRadius = FInnerRadius); Assert(BSI.Angle = FAngle); FVertexData := BSI.VertexData; FCounts := Copy(BSI.Counts); FTwoSides := BSI.TwoSides; Inc(BSI.RefCnt); LBufferStore.AddOrSetValue(Key, BSI); FBSK := Key; end else begin if FInnerRadius = 0.0 then ConstructBufferSinHole else ConstructBufferConHole; BSI.InnerRadius := FInnerRadius; BSI.Angle := FAngle; BSI.VertexData := FVertexData; BSI.Counts := Copy(FCounts); BSI.TwoSides := FTwoSides; BSI.RefCnt := 1; LBufferStore.Add(Key, BSI); FBSK := Key; end; end; procedure TSolidCylinder.Recreate; begin if FVertexData <> 0 then begin ReleaseBuffer; Setup; end; end; procedure TSolidCylinder.ReleaseBuffer; begin if FVertexData = 0 then Exit; if TryContextCurrent then begin var Key := FBSK; var BSI := Default(TBufferStoreItem); var bobj := TObject(nil); if Assigned(Control.Context.FCustomBuffers) and Control.Context.FCustomBuffers.TryGetValue(TSolidCylinder, bobj) and (bobj is TDictionary<TBufferStoreKey, TBufferStoreItem>) and TDictionary<TBufferStoreKey, TBufferStoreItem>(bobj).TryGetValue(Key, BSI) then begin var LBufferStore := TDictionary<TBufferStoreKey, TBufferStoreItem>(bobj); Assert(BSI.InnerRadius = Key.InnerRadius); Assert(BSI.Angle = Key.Angle); Assert(BSI.VertexData = FVertexData); Dec(BSI.RefCnt); if BSI.RefCnt = 0 then begin glDeleteBuffers(1, @FVertexData); FVertexData := 0; LBufferStore.Remove(Key); end else begin LBufferStore.AddOrSetValue(Key, BSI); FVertexData := 0; end; end else FVertexData := 0; end; end; procedure TSolidCylinder.SetAngle(const Value: Double); begin var LValue := EnsureRange(Value, 0, 360); if FAngle <> LValue then begin FAngle := LValue; Recreate; Changed; end; end; procedure TSolidCylinder.SetAxisLengths(const Value: rglv2); begin Scale := vec(Value.x, Value.y, FScale.z); end; procedure TSolidCylinder.SetHeight(const Value: Single); begin Scale := vec(FScale.x, FScale.y, Value); end; procedure TSolidCylinder.SetInnerRadiusFraction(const Value: Double); begin var LValue := EnsureRange(Value, 0.0, 1.0); if FInnerRadius <> LValue then begin FInnerRadius := LValue; Recreate; Changed; end; end; procedure TSolidCylinder.SetRadius(const Value: Single); begin Scale := vec(Value, Value, FScale.z); end; procedure TSolidCylinder.Setup; begin if (Control = nil) or (Control.Context = nil) then Exit; Control.Context.MakeCurrent('TSolidCylinder.Setup'); if FVAO = 0 then glGenVertexArrays(1, @FVAO); glBindVertexArray(FVAO); try GetBufferData; glBindBuffer(GL_ARRAY_BUFFER, FVertexData); glEnableVertexAttribArray(0); glEnableVertexAttribArray(2); glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 6*SizeOf(GLfloat), nil); glVertexAttribPointer(2, 3, GL_FLOAT, GL_FALSE, 6*SizeOf(GLfloat), Pointer(3*SizeOf(GLfloat))); finally glBindVertexArray(0); end; end; { TSolidCone } constructor TSolidCone.Create(ACtl: TVisCtl3D); begin inherited; end; destructor TSolidCone.Destroy; begin inherited; end; procedure TSolidCone.Draw(const AGlobalTime: Double); begin inherited; glBindVertexArray(FVAO); glDrawArrays(GL_TRIANGLE_FAN, 0, FCount + 2); glDrawArrays(GL_TRIANGLE_FAN, FCount + 2, FCount + 2); glBindVertexArray(0); end; procedure TSolidCone.FreeGLResources; begin if TryContextCurrent then begin glDeleteVertexArrays(1, @FVAO); FVAO := 0; if FVertexData <> 0 then begin var Rec := Default(TSolidStoreRec); if Assigned(Control.Context.FStoredSolids) and Control.Context.FStoredSolids.TryGetValue(TSolidCone, Rec) then begin Assert(Rec.VertexData = FVertexData); Assert(Rec.Count = FCount); Dec(Rec.RefCnt); if Rec.RefCnt = 0 then begin glDeleteBuffers(1, @FVertexData); FVertexData := 0; Control.Context.FStoredSolids.Remove(TSolidCone); end else begin Control.Context.FStoredSolids.AddOrSetValue(TSolidCone, Rec); FVertexData := 0; end; end else begin glDeleteBuffers(1, @FVertexData); FVertexData := 0; end; end; end else rglLog('TSolidCone.FreeGLResources: TryContextCurrent returned false'); inherited; end; function TSolidCone.GetAxisLengths: rglv2; begin Result.x := Scale.x; Result.y := Scale.y; end; function TSolidCone.GetHeight: Single; begin Result := Scale.z; end; function TSolidCone.GetRadius: Single; begin Result := FScale.x; end; procedure TSolidCone.GLRelease; begin FVertexData := 0; FCount := 0; FVAO := 0; inherited; end; procedure TSolidCone.MakeBaseBuffer; begin const N = 128; var TrigVals: TArray<rglv2>; SetLength(TrigVals, N + 1); for var i := 0 to N do begin var s, c: Single; SinCos(2*Pi / N * i, s, c); TrigVals[i] := vec2(c, s); end; FCount := N; var Data: TArray<GLfloat6>; SetLength(Data, N + 2 + N + 2); var Idx := 0; // Cone (triangle fan) GLr3n3v(Data[Idx]).r := vec(0.0, 0.0, 1.0); GLr3n3v(Data[Idx]).n := rglv.Zero; Inc(Idx); for var i := 0 to N do begin GLr3n3v(Data[Idx]).r := vec(TrigVals[i].x, TrigVals[i].y, 0.0); GLr3n3v(Data[Idx]).n := vec(TrigVals[i].x, TrigVals[i].y, 1.0).Normalized; Inc(Idx); end; // Bottom disk (triangle fan) GLr3n3v(Data[Idx]).r := vec(0.0, 0.0, 0.0); GLr3n3v(Data[Idx]).n := vec(0, 0, -1.0); Inc(Idx); for var i := N downto 0 do begin GLr3n3v(Data[Idx]).r := vec(TrigVals[i].x, TrigVals[i].y, 0.0); GLr3n3v(Data[Idx]).n := vec(0, 0, -1.0); Inc(Idx); end; Assert(Idx = Length(Data)); if FVertexData = 0 then glGenBuffers(1, @FVertexData); glBindBuffer(GL_ARRAY_BUFFER, FVertexData); glBufferData(GL_ARRAY_BUFFER, Length(Data) * SizeOf(GLfloat6), Pointer(Data), GL_STATIC_DRAW); end; procedure TSolidCone.Recreate; begin if FVertexData <> 0 then Setup; end; procedure TSolidCone.SetAxisLengths(const Value: rglv2); begin Scale := vec(Value.x, Value.y, FScale.z); end; procedure TSolidCone.SetHeight(const Value: Single); begin Scale := vec(FScale.x, FScale.y, Value); end; procedure TSolidCone.SetRadius(const Value: Single); begin Scale := vec(Value, Value, FScale.z); end; procedure TSolidCone.Setup; begin if (Control = nil) or (Control.Context = nil) then Exit; Control.Context.MakeCurrent('TSolidCone.Setup'); if FVAO = 0 then glGenVertexArrays(1, @FVAO); glBindVertexArray(FVAO); try if FVertexData = 0 then begin var Rec := Default(TSolidStoreRec); if Control.Context.FStoredSolids.TryGetValue(TSolidCone, Rec) then begin FVertexData := Rec.VertexData; FCount := Rec.Count; Inc(Rec.RefCnt); Control.Context.FStoredSolids.AddOrSetValue(TSolidCone, Rec); end else begin MakeBaseBuffer; if FVertexData = 0 then Exit; Rec.VertexData := FVertexData; Rec.Count := FCount; Rec.RefCnt := 1; Control.Context.FStoredSolids.Add(TSolidCone, Rec); end; end; glBindBuffer(GL_ARRAY_BUFFER, FVertexData); glEnableVertexAttribArray(0); glEnableVertexAttribArray(2); glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 6*SizeOf(GLfloat), nil); glVertexAttribPointer(2, 3, GL_FLOAT, GL_FALSE, 6*SizeOf(GLfloat), Pointer(3*SizeOf(GLfloat))); finally glBindVertexArray(0); end; end; { TSolidTetrahedron } constructor TSolidTetrahedron.Create(ACtl: TVisCtl3D); begin inherited; Stored := True; end; function TSolidTetrahedron.GetRawData: TArray<GLfloat6>; const Data: array[0..71] of GLfloat = ( 0.942809045, 0, -1/3, 0.471404523, 0.816496611, 1/3, -0.471404523, 0.816496611, -1/3, 0.471404523, 0.816496611, 1/3, 0, 0, 1, 0.471404523, 0.816496611, 1/3, -0.471404523, 0.816496611, -1/3, -0.942809045, 0, 1/3, -0.471404523, -0.816496611, -1/3, -0.942809045, 0, 1/3, 0, 0, 1, -0.942809045, 0, 1/3, -0.471404523, -0.816496611, -1/3, 0.471404523, -0.816496611, 1/3, 0.942809045, 0, -1/3, 0.471404523, -0.816496611, 1/3, 0, 0, 1, 0.471404523, -0.816496611, 1/3, 0.942809045, 0, -1/3, 0, 0, -1, -0.471404523, -0.816496611, -1/3, 0, 0, -1, -0.471404523, 0.816496611, -1/3, 0, 0, -1 ); begin SetLength(Result, Length(Data) div 6); CopyMemory(Pointer(Result), @Data, SizeOf(Data)); end; { TSolidOctahedron } constructor TSolidOctahedron.Create(ACtl: TVisCtl3D); begin inherited; Stored := True; end; function TSolidOctahedron.GetRawData: TArray<GLfloat6>; const x = 0.577350269189625764; Data: array[0..143] of GLfloat = ( 0, 0, 1, x, x, x, 1, 0, 0, x, x, x, 0, 1, 0, x, x, x, 0, 0, 1, -x, -x, x, -1, 0, 0, -x, -x, x, 0, -1, 0, -x, -x, x, 0, 0, 1, x, -x, x, 0, -1, 0, x, -x, x, 1, 0, 0, x, -x, x, 0, 0, 1, -x, x, x, 0, 1, 0, -x, x, x, -1, 0, 0, -x, x, x, 1, 0, 0, x, x, -x, 0, 0, -1, x, x, -x, 0, 1, 0, x, x, -x, 0, 0, -1, -x, -x, -x, 0, -1, 0, -x, -x, -x, -1, 0, 0, -x, -x, -x, 1, 0, 0, x, -x, -x, 0, -1, 0, x, -x, -x, 0, 0, -1, x, -x, -x, -1, 0, 0, -x, x, -x, 0, 1, 0, -x, x, -x, 0, 0, -1, -x, x, -x ); begin SetLength(Result, Length(Data) div 6); CopyMemory(Pointer(Result), @Data, SizeOf(Data)); end; { TSolidDodecahedron } constructor TSolidDodecahedron.Create(ACtl: TVisCtl3D); begin inherited; Stored := True; end; function TSolidDodecahedron.GetRawData: TArray<GLfloat6>; const Data: array[0..647] of GLfloat = ( 0, -1.6180340, 0.6180340, 0.5257311, -0.8506507, 0, 0, -1.6180340, -0.6180340, 0.5257311, -0.8506507, 0, 1, -1, -1, 0.5257311, -0.8506507, 0, 0, -1.6180340, 0.6180340, 0.5257311, -0.8506508, 0, 1, -1, -1, 0.5257311, -0.8506508, 0, 1.6180340, -0.6180340, 0, 0.5257311, -0.8506508, 0, 0, -1.6180340, 0.6180340, 0.5257311, -0.8506507, 0, 1.6180340, -0.6180340, 0, 0.5257311, -0.8506507, 0, 1, -1, 1, 0.5257311, -0.8506507, 0, 1, -1, 1, 0.8506507, 0, 0.5257311, 1.6180340, -0.6180340, 0, 0.8506507, 0, 0.5257311, 1.6180340, 0.6180340, 0, 0.8506507, 0, 0.5257311, 1, -1, 1, 0.8506508, 0, 0.5257311, 1.6180340, 0.6180340, 0, 0.8506508, 0, 0.5257311, 1, 1, 1, 0.8506508, 0, 0.5257311, 1, -1, 1, 0.8506508, 0, 0.5257311, 1, 1, 1, 0.8506508, 0, 0.5257311, 0.6180340, 0, 1.6180340, 0.8506508, 0, 0.5257311, 0, -1.6180340, 0.6180340, 0, -0.5257311, 0.8506507, 1, -1, 1, 0, -0.5257311, 0.8506507, 0.6180340, 0, 1.6180340, 0, -0.5257311, 0.8506507, 0, -1.6180340, 0.6180340, 0, -0.5257311, 0.8506508, 0.6180340, 0, 1.6180340, 0, -0.5257311, 0.8506508, -0.6180340, 0, 1.6180340, 0, -0.5257311, 0.8506508, 0, -1.6180340, 0.6180340, 0, -0.5257311, 0.8506507, -0.6180340, 0, 1.6180340, 0, -0.5257311, 0.8506507, -1, -1, 1, 0, -0.5257311, 0.8506507, 0, -1.6180340, -0.6180340, -0.5257311, -0.8506507, 0, 0, -1.6180340, 0.6180340, -0.5257311, -0.8506507, 0, -1, -1, 1, -0.5257311, -0.8506507, 0, 0, -1.6180340, -0.6180340, -0.5257311, -0.8506508, 0, -1, -1, 1, -0.5257311, -0.8506508, 0, -1.6180340, -0.6180340, 0, -0.5257311, -0.8506508, 0, 0, -1.6180340, -0.6180340, -0.5257311, -0.8506507, 0, -1.6180340, -0.6180340, 0, -0.5257311, -0.8506507, 0, -1, -1, -1, -0.5257311, -0.8506507, 0, 0, -1.6180340, -0.6180340, 0, -0.5257311, -0.8506507, -1, -1, -1, 0, -0.5257311, -0.8506507, -0.6180340, 0, -1.6180340, 0, -0.5257311, -0.8506507, 0, -1.6180340, -0.6180340, 0, -0.5257311, -0.8506508, -0.6180340, 0, -1.6180340, 0, -0.5257311, -0.8506508, 0.6180340, 0, -1.6180340, 0, -0.5257311, -0.8506508, 0, -1.6180340, -0.6180340, 0, -0.5257311, -0.8506507, 0.6180340, 0, -1.6180340, 0, -0.5257311, -0.8506507, 1, -1, -1, 0, -0.5257311, -0.8506507, 1, -1, -1, 0.8506508, 0, -0.5257311, 0.6180340, 0, -1.6180340, 0.8506508, 0, -0.5257311, 1, 1, -1, 0.8506508, 0, -0.5257311, 1, -1, -1, 0.8506508, 0, -0.5257311, 1, 1, -1, 0.8506508, 0, -0.5257311, 1.6180340, 0.6180340, 0, 0.8506508, 0, -0.5257311, 1, -1, -1, 0.8506507, 0, -0.5257311, 1.6180340, 0.6180340, 0, 0.8506507, 0, -0.5257311, 1.6180340, -0.6180340, 0, 0.8506507, 0, -0.5257311, -1, -1, 1, -0.8506508, 0, 0.5257311, -0.6180340, 0, 1.6180340, -0.8506508, 0, 0.5257311, -1, 1, 1, -0.8506508, 0, 0.5257311, -1, -1, 1, -0.8506508, 0, 0.5257311, -1, 1, 1, -0.8506508, 0, 0.5257311, -1.6180340, 0.6180340, 0, -0.8506508, 0, 0.5257311, -1, -1, 1, -0.8506507, 0, 0.5257311, -1.6180340, 0.6180340, 0, -0.8506507, 0, 0.5257311, -1.6180340, -0.6180340, 0, -0.8506507, 0, 0.5257311, -1, 1, 1, 0, 0.5257311, 0.8506507, -0.6180340, 0, 1.6180340, 0, 0.5257311, 0.8506507, 0.6180340, 0, 1.6180340, 0, 0.5257311, 0.8506507, -1, 1, 1, 0, 0.5257311, 0.8506508, 0.6180340, 0, 1.6180340, 0, 0.5257311, 0.8506508, 1, 1, 1, 0, 0.5257311, 0.8506508, -1, 1, 1, 0, 0.5257311, 0.8506508, 1, 1, 1, 0, 0.5257311, 0.8506508, 0, 1.6180340, 0.6180340, 0, 0.5257311, 0.8506508, 1, 1, 1, 0.5257311, 0.8506508, 0, 1.6180340, 0.6180340, 0, 0.5257311, 0.8506508, 0, 1, 1, -1, 0.5257311, 0.8506508, 0, 1, 1, 1, 0.5257311, 0.8506508, 0, 1, 1, -1, 0.5257311, 0.8506508, 0, 0, 1.6180340, -0.6180340, 0.5257311, 0.8506508, 0, 1, 1, 1, 0.5257311, 0.8506507, 0, 0, 1.6180340, -0.6180340, 0.5257311, 0.8506507, 0, 0, 1.6180340, 0.6180340, 0.5257311, 0.8506507, 0, -1, 1, 1, -0.5257311, 0.8506507, 0, 0, 1.6180340, 0.6180340, -0.5257311, 0.8506507, 0, 0, 1.6180340, -0.6180340, -0.5257311, 0.8506507, 0, -1, 1, 1, -0.5257311, 0.8506508, 0, 0, 1.6180340, -0.6180340, -0.5257311, 0.8506508, 0, -1, 1, -1, -0.5257311, 0.8506508, 0, -1, 1, 1, -0.5257311, 0.8506508, 0, -1, 1, -1, -0.5257311, 0.8506508, 0, -1.6180340, 0.6180340, 0, -0.5257311, 0.8506508, 0, -1, 1, -1, -0.8506508, 0, -0.5257311, -0.6180340, 0, -1.6180340, -0.8506508, 0, -0.5257311, -1, -1, -1, -0.8506508, 0, -0.5257311, -1, 1, -1, -0.8506508, 0, -0.5257311, -1, -1, -1, -0.8506508, 0, -0.5257311, -1.6180340, -0.6180340, 0, -0.8506508, 0, -0.5257311, -1, 1, -1, -0.8506507, 0, -0.5257311, -1.6180340, -0.6180340, 0, -0.8506507, 0, -0.5257311, -1.6180340, 0.6180340, 0, -0.8506507, 0, -0.5257311, 1, 1, -1, 0, 0.5257311, -0.8506507, 0.6180340, 0, -1.6180340, 0, 0.5257311, -0.8506507, -0.6180340, 0, -1.6180340, 0, 0.5257311, -0.8506507, 1, 1, -1, 0, 0.5257311, -0.8506508, -0.6180340, 0, -1.6180340, 0, 0.5257311, -0.8506508, -1, 1, -1, 0, 0.5257311, -0.8506508, 1, 1, -1, 0, 0.5257311, -0.8506508, -1, 1, -1, 0, 0.5257311, -0.8506508, 0, 1.6180340, -0.6180340, 0, 0.5257311, -0.8506508 ); begin SetLength(Result, Length(Data) div 6); CopyMemory(Pointer(Result), @Data, SizeOf(Data)); end; { TSolidIcosahedron } constructor TSolidIcosahedron.Create(ACtl: TVisCtl3D); begin inherited; Stored := True; end; function TSolidIcosahedron.GetRawData: TArray<GLfloat6>; const Data: array[0..359] of GLfloat = ( 1.61803401, 0, 1, 0.57735026, 0.57735026, 0.57735026, 1, 1.61803401, 0, 0.57735026, 0.57735026, 0.57735026, 0, 1, 1.61803401, 0.57735026, 0.57735026, 0.57735026, 1.61803401, 0, 1, 0.93417233, 0.35682210, 0, 1.61803401, 0, -1, 0.93417233, 0.35682210, 0, 1, 1.61803401, 0, 0.93417233, 0.35682210, 0, 0, 1, 1.61803401, 0, 0.93417233, 0.35682210, 1, 1.61803401, 0, 0, 0.93417233, 0.35682210, -1, 1.61803401, 0, 0, 0.93417233, 0.35682210, 1, 1.61803401, 0, 0, 0.93417233, -0.35682210, 0, 1, -1.61803401, 0, 0.93417233, -0.35682210, -1, 1.61803401, 0, 0, 0.93417233, -0.35682210, 1.61803401, 0, -1, 0.57735026, 0.57735026, -0.57735026, 0, 1, -1.61803401, 0.57735026, 0.57735026, -0.57735026, 1, 1.61803401, 0, 0.57735026, 0.57735026, -0.57735026, 0, 1, 1.61803401, -0.57735026, 0.57735026, 0.57735026, -1, 1.61803401, 0, -0.57735026, 0.57735026, 0.57735026, -1.61803401, 0, 1, -0.57735026, 0.57735026, 0.57735026, -1, 1.61803401, 0, -0.93417233, 0.35682210, 0, -1.61803401, 0, -1, -0.93417233, 0.35682210, 0, -1.61803401, 0, 1, -0.93417233, 0.35682210, 0, -1.61803401, 0, 1, -0.93417233, -0.35682210, 0, -1.61803401, 0, -1, -0.93417233, -0.35682210, 0, -1, -1.61803401, 0, -0.93417233, -0.35682210, 0, -1.61803401, 0, 1, -0.57735026, -0.57735026, 0.57735026, -1, -1.61803401, 0, -0.57735026, -0.57735026, 0.57735026, 0, -1, 1.61803401, -0.57735026, -0.57735026, 0.57735026, -1, -1.61803401, 0, 0, -0.93417233, 0.35682210, 1, -1.61803401, 0, 0, -0.93417233, 0.35682210, 0, -1, 1.61803401, 0, -0.93417233, 0.35682210, -1, -1.61803401, 0, 0, -0.93417233, -0.35682210, 0, -1, -1.61803401, 0, -0.93417233, -0.35682210, 1, -1.61803401, 0, 0, -0.93417233, -0.35682210, 1, -1.61803401, 0, 0.57735026, -0.57735026, -0.57735026, 0, -1, -1.61803401, 0.57735026, -0.57735026, -0.57735026, 1.61803401, 0, -1, 0.57735026, -0.57735026, -0.57735026, -1.61803401, 0, -1, -0.35682210, 0, -0.93417233, 0, 1, -1.61803401, -0.35682210, 0, -0.93417233, 0, -1, -1.61803401, -0.35682210, 0, -0.93417233, 0, -1, -1.61803401, 0.35682210, 0, -0.93417233, 0, 1, -1.61803401, 0.35682210, 0, -0.93417233, 1.61803401, 0, -1, 0.35682210, 0, -0.93417233, -1, 1.61803401, 0, -0.57735026, 0.57735026, -0.57735026, 0, 1, -1.61803401, -0.57735026, 0.57735026, -0.57735026, -1.61803401, 0, -1, -0.57735026, 0.57735026, -0.57735026, 0, 1, 1.61803401, -0.35682210, 0, 0.93417233, -1.61803401, 0, 1, -0.35682210, 0, 0.93417233, 0, -1, 1.61803401, -0.35682210, 0, 0.93417233, 0, 1, 1.61803401, 0.35682210, 0, 0.93417233, 0, -1, 1.61803401, 0.35682210, 0, 0.93417233, 1.61803401, 0, 1, 0.35682210, 0, 0.93417233, 1, -1.61803401, 0, 0.93417233, -0.35682210, 0, 1.61803401, 0, -1, 0.93417233, -0.35682210, 0, 1.61803401, 0, 1, 0.93417233, -0.35682210, 0, 0, -1, 1.61803401, 0.57735026, -0.57735026, 0.57735026, 1, -1.61803401, 0, 0.57735026, -0.57735026, 0.57735026, 1.61803401, 0, 1, 0.57735026, -0.57735026, 0.57735026, -1.61803401, 0, -1, -0.57735026, -0.57735026, -0.57735026, 0, -1, -1.61803401, -0.57735026, -0.57735026, -0.57735026, -1, -1.61803401, 0, -0.57735026, -0.57735026, -0.57735026 ); begin SetLength(Result, Length(Data) div 6); CopyMemory(Pointer(Result), @Data, SizeOf(Data)); end; { TSolidPyramid } constructor TSolidPyramid.Create(ACtl: TVisCtl3D); begin inherited; Stored := True; end; function TSolidPyramid.GetRawData: TArray<GLfloat6>; const Data: array[0..107] of GLfloat = ( -0.5, -0.5, -0.5, 0, 0, -1, -0.5, 0.5, -0.5, 0, 0, -1, 0.5, 0.5, -0.5, 0, 0, -1, -0.5, -0.5, -0.5, 0, 0, -1, 0.5, 0.5, -0.5, 0, 0, -1, 0.5, -0.5, -0.5, 0, 0, -1, 0, 0, 0.5, 0.89442718, 0, 0.44721359, 0.5, -0.5, -0.5, 0.89442718, 0, 0.44721359, 0.5, 0.5, -0.5, 0.89442718, 0, 0.44721359, 0, 0, 0.5, 0, -0.89442718, 0.44721359, -0.5, -0.5, -0.5, 0, -0.89442718, 0.44721359, 0.5, -0.5, -0.5, 0, -0.89442718, 0.44721359, 0, 0, 0.5, -0.89442718, 0, 0.44721359, -0.5, 0.5, -0.5, -0.89442718, 0, 0.44721359, -0.5, -0.5, -0.5, -0.89442718, 0, 0.44721359, 0, 0, 0.5, 0, 0.89442718, 0.44721359, 0.5, 0.5, -0.5, 0, 0.89442718, 0.44721359, -0.5, 0.5, -0.5, 0, 0.89442718, 0.44721359 ); begin SetLength(Result, Length(Data) div 6); CopyMemory(Pointer(Result), @Data, SizeOf(Data)); end; { TPublishedCoordinates } constructor TPublishedCoordinates.Create(AVector: Prglv; const ANotifier: TProc); begin if AVector = nil then raise Exception.Create('TPublishedCoordinates: Nil pointer.'); inherited Create; FVectorPtr := AVector; FNotifier := ANotifier; end; function TPublishedCoordinates.GetCoordinate(AIndex: Integer): GLfloat; begin if Assigned(FVectorPtr) and (AIndex in [0, 1, 2]) then Result := FVectorPtr.elem[AIndex] else Result := 0.0; end; procedure TPublishedCoordinates.SetCoordinate(AIndex: Integer; const Value: GLfloat); begin if Assigned(FVectorPtr) and (AIndex in [0, 1, 2]) then begin FVectorPtr.elem[AIndex] := Value; if Assigned(FNotifier) then FNotifier(); end; end; { TView3D } procedure TView3D.FixCamera(const ACenter: rglv; const ARelativePosition: rglv); begin if Control = nil then Exit; if Control.Context = nil then Exit; if (Control.CameraPos <> ARelativePosition) or (Control.LookAt <> ACenter) then begin with TSphericalCoordinates(ARelativePosition) do begin Control.r := r; Control.θ := θ; Control.φ := φ; Control.LookAt := ACenter; end; Changed; end; end; procedure TView3D.FixCamera(const ACameraPose: TCameraPose); begin FixCamera(ACameraPose.SceneCenter, ACameraPose.RelativePosition); end; procedure TView3D.FinishAnimation; begin FAnimationEnd := Now; end; procedure TView3D.FixCamera(const ACameraPose: TCameraPoseSp); begin FixCamera(ACameraPose.SceneCenter, ACameraPose.RelativePosition); end; procedure TView3D.AnimateTo(const ATargetCenter: rglv; const ATargetRelativePosition: rglv); const Gamma = 4.0; var i: Integer; F, t: Double; begin if Control = nil then Exit; if Control.Context = nil then Exit; var LTargetCenter := ATargetCenter; var LCurrentCenter := Control.LookAt; var LTargetRelativePosition := rθφ(ATargetRelativePosition); var LCurrentRelativePosition := CameraRelativePositionSp; if Abs(LTargetRelativePosition.φ - LCurrentRelativePosition.φ) > Pi then if LTargetRelativePosition.φ > LCurrentRelativePosition.φ then LTargetRelativePosition.φ := LTargetRelativePosition.φ - 2*Pi else LCurrentRelativePosition.φ := LCurrentRelativePosition.φ - 2*Pi; F := 1 / ArcTan(Gamma); for i := 0 to High(FAnimation) do begin t := i / High(FAnimation); // [0, 1] t := 2 * t - 1; // [-1, 1] t := F * ArcTan(Gamma * t); // Sigmoid transformation t := (t + 1) / 2; // [0, 1] FAnimation[i].SceneCenter := (1 - t) * LCurrentCenter + t * LTargetCenter; FAnimation[i].RelativePosition := (1 - t) * LCurrentRelativePosition + t * LTargetRelativePosition; end; FAnimationBegin := Now; FAnimationEnd := IncMilliSecond(FAnimationBegin, Round(1000 * AnimationDurationSec)); FAnimationTimer.Enabled := True; end; procedure TView3D.AnimateTo(const ATargetCenter: rglv); begin AnimateTo(ATargetCenter, CameraRelativePosition); end; procedure TView3D.AnimateTo(const ACameraPose: TCameraPose); begin AnimateTo(ACameraPose.SceneCenter, ACameraPose.RelativePosition); end; procedure TView3D.AnimateTo(const ACameraPose: TCameraPoseSp); begin AnimateTo(ACameraPose.SceneCenter, ACameraPose.RelativePosition); end; procedure TView3D.AnimationTimerTimer(Sender: TObject); begin if CompareTime(Now, FAnimationEnd) = GreaterThanValue then begin FAnimationTimer.Enabled := False; end; var f: Double := EnsureRange( MilliSecondsBetween(FAnimationBegin, Now) / MilliSecondsBetween(FAnimationBegin, FAnimationEnd), 0, 1 ); var i: Integer := EnsureRange( Round(f * High(FAnimation)), Low(FAnimation), High(FAnimation) ); FixCamera(FAnimation[i]); end; constructor TView3D.Create(ACtl: TVisCtl3D); begin inherited; FTargetCenterIntf := TPublishedCoordinates.Create(@ACtl.LookAt, Changed); FAnimationTimer := TAnimationTimer.Create(nil); FAnimationTimer.Control := Control; FAnimationTimer.Enabled := False; FAnimationTimer.OnTimer := AnimationTimerTimer; end; destructor TView3D.Destroy; begin FreeAndNil(FAnimationTimer); FreeAndNil(FTargetCenterIntf); inherited; end; function TView3D.GetCameraRelativePosition: rglv; begin Result := Control.CameraRelativePos; end; function TView3D.GetCameraRelativePositionSp: TSphericalCoordinates; begin Result.r := Control.r; Result.θ := Control.θ; Result.φ := Control.φ; end; function TView3D.GetPhi: Double; begin Result := 180 / Pi * Control.φ; end; function TView3D.GetPhiRad: Double; begin Result := Control.φ; end; function TView3D.GetR: Double; begin Result := Control.r; end; function TView3D.GetTheta: Double; begin Result := 180 / Pi * Control.θ; end; function TView3D.GetThetaRad: Double; begin Result := Control.θ; end; function TView3D.GetTargetCenter: rglv; begin Result := Control.LookAt; end; procedure TView3D.SetCameraRelativePosition(const Value: rglv); begin if Control.CameraPos <> Value then begin with TSphericalCoordinates(Value) do begin Control.r := r; Control.θ := θ; Control.φ := φ; end; Changed; end; end; procedure TView3D.SetCameraRelativePositionSp(const Value: TSphericalCoordinates); begin if (Value.r <> Control.r) or (Value.θ <> Control.θ) or (Value.φ <> Control.φ) then begin Control.r := EnsureRange(Value.r, 0.01, 900); Control.θ := EnsureRange(Value.θ, 0, Pi); Control.φ := rmod(Value.φ, 2*Pi); Changed; end; end; procedure TView3D.SetPhi(const Value: Double); begin var LValue := Pi / 180 * Value; if not SameValue(Control.φ, LValue) then begin Control.φ := rmod(LValue, 2*Pi); Changed; end; end; procedure TView3D.SetPhiRad(const Value: Double); begin if not SameValue(Control.φ, Value) then begin Control.φ := rmod(Value, 2*Pi); Changed; end; end; procedure TView3D.SetR(const Value: Double); begin if not SameValue(Control.r, Value) then begin Control.r := EnsureRange(Value, 0.01, 900); Changed; end; end; procedure TView3D.SetTheta(const Value: Double); begin var LValue := Pi / 180 * Value; if not SameValue(Control.θ, LValue) then begin Control.θ := EnsureRange(LValue, 0, Pi); Changed; end; end; procedure TView3D.SetThetaRad(const Value: Double); begin if not SameValue(Control.θ, Value) then begin Control.θ := EnsureRange(Value, 0, Pi); Changed; end; end; procedure TView3D.SetTargetCenter(const Value: rglv); begin if Control.LookAt <> Value then begin Control.LookAt := Value; Changed; end; end; { TSphericalCoordinates } constructor TSphericalCoordinates.Create(const r: Single; const θ: Single; const φ: Single); begin Self.r := r; Self.θ := θ; Self.φ := φ; end; class operator TSphericalCoordinates.Add(const Left, Right: TSphericalCoordinates): TSphericalCoordinates; begin Result.r := Left.r + Right.r; Result.θ := Left.θ + Right.θ; Result.φ := Left.φ + Right.φ; end; class operator TSphericalCoordinates.Equal(const Left, Right: TSphericalCoordinates): Boolean; begin Result := (Left.r = Right.r) and (Left.θ = Right.θ) and (Left.φ = Right.φ); end; class operator TSphericalCoordinates.Implicit( const ACoords: TCartesianCoordinates): TSphericalCoordinates; begin Result.r := EnsureRange(ACoords.Norm, 0.01, 900); if IsZero(ACoords.xy.Norm) then Result.θ := Pi * Ord(ACoords.z < 0) else Result.θ := EnsureRange(Pi/2 - ArcTan(ACoords.z / ACoords.xy.Norm), 0, Pi); Result.φ := rmod(ArcTan2(ACoords.y, ACoords.x), 2*Pi); end; class operator TSphericalCoordinates.Multiply(const Left: Single; Right: TSphericalCoordinates): TSphericalCoordinates; begin Result.r := Left * Right.r; Result.θ := Left * Right.θ; Result.φ := Left * Right.φ; end; class operator TSphericalCoordinates.NotEqual(const Left, Right: TSphericalCoordinates): Boolean; begin Result := not (Left = Right); end; class operator TSphericalCoordinates.Implicit( const ACoords: TSphericalCoordinates): TCartesianCoordinates; begin with ACoords do begin var Sinθ, Cosθ, Sinφ, Cosφ: Single; SinCos(θ, Sinθ, Cosθ); SinCos(φ, Sinφ, Cosφ); Result.x := r * Sinθ * Cosφ; Result.y := r * Sinθ * Sinφ; Result.z := r * Cosθ; end; end; { TCameraPose } class operator TCameraPose.Equal(const Left: TCameraPose; const Right: TCameraPose): Boolean; begin Result := (Left.SceneCenter = Right.SceneCenter) and (Left.RelativePosition = Right.RelativePosition); end; class operator TCameraPose.NotEqual(const Left: TCameraPose; const Right: TCameraPose): Boolean; begin Result := not (Left = Right); end; { TCameraPoseSp } class operator TCameraPoseSp.Equal(const Left: TCameraPoseSp; const Right: TCameraPoseSp): Boolean; begin Result := (Left.SceneCenter = Right.SceneCenter) and (Left.RelativePosition = Right.RelativePosition); end; class operator TCameraPoseSp.NotEqual(const Left: TCameraPoseSp; const Right: TCameraPoseSp): Boolean; begin Result := not (Left = Right); end; { TAnimationTimer } class procedure TAnimationTimer.AppIdle(Sender: TObject; var Done: Boolean); begin Done := True; if Assigned(FInstances) then for var Timer in FInstances do begin if not Timer.Enabled or (Timer.Control = nil) or not Assigned(TImer.OnTimer) then Continue; Timer.OnTimer(Sender); Timer.Control.Update; Timer.Interval := 1000; Done := False; end; end; class constructor TAnimationTimer.ClassCreate; begin FInstances := TList<TAnimationTimer>.Create; FAppEvents := TApplicationEvents.Create(nil); end; class destructor TAnimationTimer.ClassDestroy; begin FreeAndNil(FAppEvents); FreeAndNil(FInstances); end; constructor TAnimationTimer.Create(AOwner: TComponent); begin inherited; if FPrioritize then Interval := 1000 else Interval := 30; if Assigned(FInstances) then FInstances.Add(Self); end; destructor TAnimationTimer.Destroy; begin if Assigned(FInstances) then FInstances.Remove(Self); inherited; end; class procedure TAnimationTimer.SetPrioritize(const Value: Boolean); begin if FPrioritize <> Value then begin FPrioritize := Value; if Value then begin FAppEvents.OnIdle := AppIdle; if Assigned(FInstances) then for var Timer in FInstances do Timer.Interval := 1000; end else begin FAppEvents.OnIdle := nil; if Assigned(FInstances) then for var Timer in FInstances do Timer.Interval := 30; end; end; end; { TTransparentColorModeHelper } class function TTransparentColorModeHelper.FromString( const S: string): TTransparentColorMode; begin if S = 'opaque' then Result := tcmOff else if S = 'equal' then Result := tcmEqual else if S = 'distance' then Result := tcmDistance else if S = 'bipolar' then Result := tcmBipolar else raise ERglError.CreateFmt('Unknown transparency mode: "%".', [S]); end; function TTransparentColorModeHelper.ToString: string; begin case Self of tcmOff: Result := 'Opaque'; tcmEqual: Result := 'Single-colour transparency'; tcmDistance: Result := 'Unipolar interpolation'; tcmBipolar: Result := 'Bipolar interpolation'; else Result := ''; end; end; { TProjectionHelper } class function TProjectionHelper.FromString(const S: string): TProjection; begin for var p := Low(TProjection) to High(TProjection) do if SameText(p.ToString, S) then Exit(p); raise ERglError.CreateFmt('Unknown projection: "%s".', [S]); end; function TProjectionHelper.ToString: string; begin case Self of Orthographic: Result := 'orthographic'; Perspective: Result := 'perspective'; else Result := 'unknown projection'; end; end; { TArrow } constructor TArrow.Create(ACtl: TVisCtl3D); begin inherited; FLineWidth := 0.02; FHeadSize := 0.2; Q := 0.5; FHead := CreateChild<TSolidCone>; FLine := CreateChild<TSolidCylinder>; end; function TArrow.GetColor: TColor; begin if Assigned(FLine) then Result := FLine.Color else Result := inherited; end; function TArrow.GetLineWidth: Single; begin if Assigned(FLine) then Result := FLine.Radius else Result := inherited; end; procedure TArrow.Recreate; begin if FSetup then Setup; end; procedure TArrow.SetAspect(const Value: Single); begin if Q <> Value then begin Q := Value; Recreate; Changed; end; end; procedure TArrow.SetColor(const Value: TColor); begin inherited; if Assigned(FLine) then FLine.Color := Value; if Assigned(FHead) then FHead.Color := Value; end; procedure TArrow.SetHeadSize(const Value: Single); begin if FHeadSize <> Value then begin FHeadSize := Value; Recreate; Changed; end; end; procedure TArrow.SetLineWidth(const Value: Single); begin inherited; if Assigned(FLine) then FLine.Radius := Value; end; procedure TArrow.Setup; begin if (Control = nil) or (Control.Context = nil) then Exit; Control.Context.MakeCurrent('TArrow.Setup'); var LVectorNorm := v.Norm; var LIsZeroVector := LVectorNorm < FHeadSize / 10; FHead.Height := FHeadSize; FHead.Radius := Q*FHeadSize; FHead.Position := vec(0, 0, LVectorNorm - FHead.Height); FHead.Visible := not LIsZeroVector; FLine.Height := LVectorNorm - FHead.Height; FLine.Radius := FLineWidth; FLine.Visible := not LIsZeroVector; FSetup := True; end; procedure TArrow.SetVector(const Value: rglv); begin if v <> Value then begin v := Value; Direction := v; Recreate; Changed; end; end; function Tokenize(const S: string; ADefs: TDictionary<string, string>): TArray<string>; function TDA(const AToken: string): string; begin if (ADefs = nil) or (ADefs.Count = 0) or not ADefs.TryGetValue(AToken, Result) then Result := AToken; end; begin var ActualLength := 0; SetLength(Result, 32); var InToken := False; var p := 0; for var i := 1 to S.Length do begin if InToken then begin if S[i].IsWhiteSpace then begin InToken := False; Result[ActualLength] := TDA(Copy(S, p, i - p)); Inc(ActualLength); if ActualLength = Length(Result) then Break; end; end else begin if not S[i].IsWhiteSpace then begin InToken := True; p := i; end; end; end; if InToken and (ActualLength < Length(Result)) then begin Result[ActualLength] := TDA(Copy(S, p)); Inc(ActualLength); end; SetLength(Result, ActualLength); end; procedure ParseVec(AList: TList<rglv>; const AData: TArray<string>; ANormalize: Boolean); begin if Length(AData) < 4 then raise ERglObjFileError.Create('Vector is of too small dimension.'); var v := vec( StrToFloat(AData[1], InvFS), StrToFloat(AData[2], InvFS), StrToFloat(AData[3], InvFS) ); if ANormalize and not IsZero(v.Norm) then v := v.Normalized; if Assigned(AList) then AList.Add(v); end; type TVertexData = record VertexIndex: Integer; TextureIndex: Integer; NormalIndex: Integer; FaceData: GLr3n3v; end; function ParseVertex(const AData: string): TVertexData; begin Result := Default(TVertexData); var P := AData.Split(['/']); if Length(P) >= 1 then Result.VertexIndex := StrToInt(P[0]); if Length(P) >= 2 then begin if (Length(P) = 2) or not P[1].IsEmpty then Result.TextureIndex := StrToInt(P[1]); end; if Length(P) >= 3 then Result.NormalIndex := StrToInt(P[2]); end; procedure TranslateVertex(var Vertex: TVertexData; AVertices: TList<rglv>); begin case Sign(Vertex.VertexIndex) of NegativeValue: Vertex.FaceData.r := AVertices[AVertices.Count + Vertex.VertexIndex]; PositiveValue: Vertex.FaceData.r := AVertices[Vertex.VertexIndex - 1]; else raise ERglObjFileError.Create('Invalid vertex index.'); end; end; procedure TranslateNormal(var Vertex: TVertexData; ANormals: TList<rglv>; const ADefNormal: rglv); begin case Sign(Vertex.NormalIndex) of NegativeValue: Vertex.FaceData.n := ANormals[ANormals.Count + Vertex.NormalIndex]; PositiveValue: Vertex.FaceData.n := ANormals[Vertex.NormalIndex - 1]; else Vertex.FaceData.n := ADefNormal; end; end; procedure ParseFace(AVertices, ANormals: TList<rglv>; AFaces: TList<GLfloat6>; AData: TArray<string>); begin if Length(AData) < 4 then raise ERglObjFileError.Create('Vector is of too small dimension.'); if Length(AData) >= 5 then begin ParseFace(AVertices, ANormals, AFaces, Copy(AData, 0, 4)); for var i := 3 to High(AData) - 1 do ParseFace(AVertices, ANormals, AFaces, ['f', AData[1], AData[i], AData[Succ(i)]]); Exit; end; var A := ParseVertex(AData[1]); var B := ParseVertex(AData[2]); var C := ParseVertex(AData[3]); TranslateVertex(A, AVertices); TranslateVertex(B, AVertices); TranslateVertex(C, AVertices); var LComputedNormal := rglv.Zero; if (A.NormalIndex = 0) or (B.NormalIndex = 0) or (C.NormalIndex = 0) then begin var u := B.FaceData.r - A.FaceData.r; var v := C.FaceData.r - B.FaceData.r; LComputedNormal := u xor v; if not IsZero(LComputedNormal.Norm) then LComputedNormal := LComputedNormal.Normalized; end; TranslateNormal(A, ANormals, LComputedNormal); TranslateNormal(B, ANormals, LComputedNormal); TranslateNormal(C, ANormals, LComputedNormal); if Assigned(AFaces) then begin AFaces.Add(GLfloat6(A.FaceData)); AFaces.Add(GLfloat6(B.FaceData)); AFaces.Add(GLfloat6(C.FaceData)); end; end; function ObjFileParseObjData(const AData: TArray<string>): TArray<GLfloat6>; begin var Defines := TDictionary<string, string>.Create; try var Vertices := TList<rglv>.Create; try var Normals := TList<rglv>.Create; try var Faces := TList<GLfloat6>.Create; try var i := 0; try while i <= High(AData) do begin var L := AData[i].Trim; Inc(i); if L.IsEmpty or L.StartsWith('#') then Continue; var P := Tokenize(L, Defines); if Length(P) = 0 then Continue; if P[0] = 'v' then ParseVec(Vertices, P, False) else if P[0] = 'vn' then ParseVec(Normals, P, True) else if P[0] = 'f' then ParseFace(Vertices, Normals, Faces, P) else if (P[0] = 'def') and (Length(P) >= 2) then Defines.Add(P[1], string.Join(#32, Copy(P, 2))); end; except on E: Exception do raise ERglObjFileError.CreateFmt('Couldn''t parse OBJ file at line %d.'#32 + E.Message, [i]); end; Result := Faces.ToArray; finally Faces.Free; end; finally Normals.Free; end; finally Vertices.Free; end; finally Defines.Free; end; end; type PFloat = ^Single; function FormatFloat(p: Single): string; begin Result := SysUtils.FormatFloat('0.########', p, InvFS); end; function FormatFloat6(p: PFloat; const ASep: string): string; begin var LSep := ASep; if LSep = '' then LSep := #32; Result := FormatFloat(p^); Inc(p); Result := Result + LSep + FormatFloat(p^); Inc(p); Result := Result + LSep + FormatFloat(p^); Inc(p); Result := Result + LSep + FormatFloat(p^); Inc(p); Result := Result + LSep + FormatFloat(p^); Inc(p); Result := Result + LSep + FormatFloat(p^) + LSep.TrimRight; end; function ObjFilePrintRawBuffer(ABuffer: Pointer; ALength: Integer; const ASep: string): string; begin const FloatCount = ALength div SizeOf(Single); const VertexCount = FloatCount div 6; var L: TArray<string> := nil; SetLength(L, VertexCount); var p: PFloat := PFloat(ABuffer); for var i := 0 to High(L) do begin L[i] := FormatFloat6(p, ASep); Inc(p, 6); end; Result := string.Join(#13#10, L); end; function ObjFilePrintRawBuffer(ABuffer: TArray<GLfloat6>; const ASep: string): string; begin Result := ObjFilePrintRawBuffer(Pointer(ABuffer), Length(ABuffer) * SizeOf(GLfloat6), ASep); end; procedure Register; begin RegisterComponents('Rejbrand', [TVisCtl3D]); end; initialization InvFS := TFormatSettings.Invariant; end.
Downloads
Licence
Rejbrand Algosim 3D Visualisation Control
Copyright (c) 2026 Andreas Rejbrand
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.