// // The graphics engine GLScene // unit GLS.Extrusion; (* Extrusion objects are solids defined by the surface described by a moving curve. Suggestion: All extrusion objects use actually the same kind of "parts", one common type should do. The registered class is: [TGLRevolutionSolid, TGLExtrusionSolid, TGLPipe] *) interface {$I Stage.Defines.inc} uses Winapi.OpenGL, System.Classes, System.SysUtils, System.Math, Stage.OpenGLTokens, GLS.Context, GLS.Objects, GLS.Scene, GLS.MultiPolygon, GLS.Color, Stage.VectorGeometry, GLS.RenderContextInfo, GLS.Nodes, GLS.State, Stage.VectorTypes; type TGLExtrusionSolidPart = (espOutside, espInside, espStartPolygon, espStopPolygon); TGLExtrusionSolidParts = set of TGLExtrusionSolidPart; TGLRevolutionSolidPart = (rspOutside, rspInside, rspStartPolygon, rspStopPolygon); TGLRevolutionSolidParts = set of TGLRevolutionSolidPart; (* A solid object generated by rotating a curve along the Y axis. The curve is described by the Nodes and SplineMode properties, and it is rotated in the trigonometrical direction (CCW when seen from Y->INF). The TGLRevolutionSolid can also be used to render regular helicoidions, by setting a non-null YOffsetPerTurn, and adjusting start/finish angles to make more than one revolution. If you want top/bottom caps, just add a first/last node that will make the curve start/finish on the Y axis. *) TGLRevolutionSolid = class(TGLPolygonBase) private FSlices: Integer; FStartAngle, FStopAngle: Single; FNormals: TGLNormalSmoothing; FYOffsetPerTurn: Single; FTriangleCount: Integer; FNormalDirection: TGLNormalDirection; FParts: TGLRevolutionSolidParts; FAxisAlignedDimensionsCache: TGLVector; protected procedure SetStartAngle(const val: Single); procedure SetStopAngle(const val: Single); function StoreStopAngle: Boolean; procedure SetSlices(const val: Integer); procedure SetNormals(const val: TGLNormalSmoothing); procedure SetYOffsetPerTurn(const val: Single); procedure SetNormalDirection(const val: TGLNormalDirection); procedure SetParts(const val: TGLRevolutionSolidParts); public constructor Create(AOwner: TComponent); override; destructor Destroy; override; procedure Assign(Source: TPersistent); override; procedure BuildList(var rci: TGLRenderContextInfo); override; // Number of triangles used for rendering. property TriangleCount: Integer read FTriangleCount; function AxisAlignedDimensionsUnscaled: TGLVector; override; procedure StructureChanged; override; published (* Parts of the rotation solid to be generated for rendering. rspInside and rspOutside are generated from the curve and make the inside/outside as long as NormalDirection=ndOutside and the solid is described by the curve that goes from top to bottom. Start/StopPolygon are tesselated from the curve (considered as closed). *) property Parts: TGLRevolutionSolidParts read FParts write SetParts default [rspOutside]; property StartAngle: Single read FStartAngle write SetStartAngle; property StopAngle: Single read FStopAngle write SetStopAngle stored StoreStopAngle; (* Y offset applied to the curve position for each turn. This amount is applied proportionnally, for instance if your curve is a small circle, off from the Y axis, with a YOffset set to 0 (zero), you will get a torus, but with a non null value, you will get a small helicoidal spring. This can be useful for rendering, lots of helicoidal objects from screws, to nails to stairs etc. *) property YOffsetPerTurn: Single read FYOffsetPerTurn write SetYOffsetPerTurn; // Number of slices per turn (360deg). property Slices: Integer read FSlices write SetSlices default 16; property Normals: TGLNormalSmoothing read FNormals write SetNormals default nsFlat; property NormalDirection: TGLNormalDirection read FNormalDirection write SetNormalDirection default ndOutside; end; (* Extrudes a complex Polygon into Z direction. For contour description see TGLMultiPolygonBase. properties Parts, Height (or should we better cal it Depth, because its in Z?), Stacks, Normals and NormalDirection are equivalent to TGLRevolutionSolid. If Normals=nsSmooth and the angle between two consecutive normals along the contour is less than MinSmoothAngle, smoothing is done, otherweise flat normals are used. This makes it possible to have smooth normals on sharp edged contours. *) TGLExtrusionSolid = class(TGLMultiPolygonBase) private FStacks: Integer; FNormals: TGLNormalSmoothing; FTriangleCount: Integer; FNormalDirection: TGLNormalDirection; FParts: TGLExtrusionSolidParts; FHeight: TGLFloat; FMinSmoothAngle: Single; FMinSmoothAngleCos: Single; FAxisAlignedDimensionsCache: TGLVector; procedure SetHeight(const Value: TGLFloat); procedure SetMinSmoothAngle(const Value: Single); protected procedure SetStacks(const val: Integer); procedure SetNormals(const val: TGLNormalSmoothing); procedure SetNormalDirection(const val: TGLNormalDirection); procedure SetParts(const val: TGLExtrusionSolidParts); public constructor Create(AOwner: TComponent); override; destructor Destroy; override; procedure Assign(Source: TPersistent); override; procedure BuildList(var rci: TGLRenderContextInfo); override; // Number of triangles used for rendering. property TriangleCount: Integer read FTriangleCount; function AxisAlignedDimensionsUnscaled: TGLVector; override; procedure StructureChanged; override; published property Parts: TGLExtrusionSolidParts read FParts write SetParts default [espOutside]; property Height: TGLFloat read FHeight write SetHeight; property Stacks: Integer read FStacks write SetStacks default 1; property Normals: TGLNormalSmoothing read FNormals write SetNormals default nsFlat; property NormalDirection: TGLNormalDirection read FNormalDirection write SetNormalDirection default ndOutside; property MinSmoothAngle: Single read FMinSmoothAngle write SetMinSmoothAngle; end; TGLPipeNode = class(TGLNode) private FRadiusFactor: Single; FColor: TGLColor; FTexCoordT: Single; protected function GetDisplayName: string; override; procedure SetRadiusFactor(const val: Single); function StoreRadiusFactor: Boolean; procedure SetColor(const val: TGLColor); procedure ColorChanged(sender: TObject); function StoreTexCoordT: Boolean; public constructor Create(Collection: TCollection); override; destructor Destroy; override; procedure Assign(Source: TPersistent); override; published property RadiusFactor: Single read FRadiusFactor write SetRadiusFactor stored StoreRadiusFactor; property Color: TGLColor read FColor write SetColor; property TexCoordT: Single read FTexCoordT write FTexCoordT stored StoreTexCoordT; end; TGLPipeNodes = class(TGLLinesNodes) protected procedure SetItems(index: Integer; const val: TGLPipeNode); function GetItems(index: Integer): TGLPipeNode; public constructor Create(AOwner: TComponent); function Add: TGLPipeNode; function FindItemID(ID: Integer): TGLPipeNode; property Items[index: Integer]: TGLPipeNode read GetItems write SetItems; default; end; TPipePart = (ppOutside, ppInside, ppStartDisk, ppStopDisk); TPipeParts = set of TPipePart; TPipeNodesColorMode = (pncmNone, pncmEmission, pncmAmbient, pncmDiffuse, pncmAmbientAndDiffuse); TPipeTexCoordMode = (ptcmDefault, ptcmManual); TPipeNormalMode = (pnmDefault, pnmAdvanced); (* A solid object generated by extruding a circle along a trajectory. Texture coordinates NOT supported yet. *) TGLPipe = class(TGLPolygonBase) private FSlices: Integer; FParts: TPipeParts; FTriangleCount: Integer; FRadius: Single; FNodesColorMode: TPipeNodesColorMode; FTextCoordMode: TPipeTexCoordMode; FTextCoordTileS: Single; FTextCoordTileT: Single; FNormalMode: TPipeNormalMode; FNormalSmoothAngle: Single; protected procedure CreateNodes; override; procedure SetSlices(const val: Integer); procedure SetParts(const val: TPipeParts); procedure SetRadius(const val: Single); function StoreRadius: Boolean; procedure SetNodesColorMode(const val: TPipeNodesColorMode); procedure SetTextCoordMode(const val: TPipeTexCoordMode); procedure SetTextCoordTileS(const val: Single); procedure SetTextCoordTileT(const val: Single); function StoreTextCoordTileS: Boolean; function StoreTextCoordTileT: Boolean; procedure SetNormalMode(const val: TPipeNormalMode); procedure SetNormalSmoothAngle(const val: Single); public constructor Create(AOwner: TComponent); override; destructor Destroy; override; procedure Assign(Source: TPersistent); override; procedure BuildList(var rci: TGLRenderContextInfo); override; // Number of triangles used for rendering. property TriangleCount: Integer read FTriangleCount; published property Parts: TPipeParts read FParts write SetParts default [ppOutside]; property Slices: Integer read FSlices write SetSlices default 16; property Radius: Single read FRadius write SetRadius; property NodesColorMode: TPipeNodesColorMode read FNodesColorMode write SetNodesColorMode default pncmNone; property TexCoordMode: TPipeTexCoordMode read FTextCoordMode write SetTextCoordMode default ptcmDefault; property TexCoordTileS: Single read FTextCoordTileS write SetTextCoordTileS stored StoreTextCoordTileS; property TexCoordTileT: Single read FTextCoordTileT write SetTextCoordTileT stored StoreTextCoordTileT; property NormalMode: TPipeNormalMode read FNormalMode write SetNormalMode default pnmDefault; property NormalSmoothAngle: Single read FNormalSmoothAngle write SetNormalSmoothAngle; end; // ------------------------------------------------------------------ implementation // ------------------------------------------------------------------ uses Stage.Spline, GLS.VectorLists, GLS.XOpenGL; // ------------------ // ------------------ TGLRevolutionSolid ------------------ // ------------------ constructor TGLRevolutionSolid.Create(AOwner: TComponent); begin inherited Create(AOwner); FStartAngle := 0; FStopAngle := 360; FSlices := 16; FNormals := nsFlat; FNormalDirection := ndOutside; FParts := [rspOutside]; end; destructor TGLRevolutionSolid.Destroy; begin inherited Destroy; end; procedure TGLRevolutionSolid.SetStartAngle(const val: Single); begin if FStartAngle <> val then begin FStartAngle := val; if FStartAngle > FStopAngle then FStopAngle := FStartAngle; StructureChanged; end; end; procedure TGLRevolutionSolid.SetStopAngle(const val: Single); begin if FStopAngle <> val then begin FStopAngle := val; if FStopAngle < FStartAngle then FStartAngle := FStopAngle; StructureChanged; end; end; function TGLRevolutionSolid.StoreStopAngle: Boolean; begin Result := (FStopAngle <> 360); end; procedure TGLRevolutionSolid.SetSlices(const val: Integer); begin if (val <> FSlices) and (val > 0) then begin FSlices := val; StructureChanged; end; end; procedure TGLRevolutionSolid.SetNormals(const val: TGLNormalSmoothing); begin if FNormals <> val then begin FNormals := val; StructureChanged; end; end; procedure TGLRevolutionSolid.SetYOffsetPerTurn(const val: Single); begin if FYOffsetPerTurn <> val then begin FYOffsetPerTurn := val; StructureChanged; end; end; procedure TGLRevolutionSolid.SetNormalDirection(const val: TGLNormalDirection); begin if FNormalDirection <> val then begin FNormalDirection := val; StructureChanged; end; end; procedure TGLRevolutionSolid.SetParts(const val: TGLRevolutionSolidParts); begin if FParts <> val then begin FParts := val; StructureChanged; end; end; procedure TGLRevolutionSolid.Assign(Source: TPersistent); begin if Source is TGLRevolutionSolid then begin FStartAngle := TGLRevolutionSolid(Source).FStartAngle; FStopAngle := TGLRevolutionSolid(Source).FStopAngle; FSlices := TGLRevolutionSolid(Source).FSlices; FNormals := TGLRevolutionSolid(Source).FNormals; FYOffsetPerTurn := TGLRevolutionSolid(Source).FYOffsetPerTurn; FNormalDirection := TGLRevolutionSolid(Source).FNormalDirection; FParts := TGLRevolutionSolid(Source).FParts; end; inherited Assign(Source); end; procedure TGLRevolutionSolid.BuildList(var rci: TGLRenderContextInfo); var deltaAlpha, startAlpha, stopAlpha, alpha: Single; deltaS: Single; deltaYOffset, yOffset, startYOffset: Single; lastNormals: PAffineVectorArray; firstStep, gotYDeltaOffset: Boolean; procedure CalcNormal(const ptTop, ptBottom: PAffineVector; var normal: TAffineVector); var tb: TAffineVector; mx, mz: Single; begin mx := ptBottom^.X + ptTop^.X; mz := ptBottom^.Z + ptTop^.Z; VectorSubtract(ptBottom^, ptTop^, tb); normal.X := -tb.Y * mx; normal.Y := mx * tb.X + mz * tb.Z; normal.Z := -mz * tb.Y; NormalizeVector(normal); end; procedure BuildStep(ptTop, ptBottom: PAffineVector; invertNormals: Boolean; topT, bottomT: Single); var i: Integer; topBase, topNext, bottomBase, bottomNext, normal, topNormal, bottomNormal: TAffineVector; topTPBase, topTPNext, bottomTPBase, bottomTPNext: TTexPoint; nextAlpha: Single; ptBuffer: PAffineVector; procedure SetLocalNormals; begin if (FNormals = nsFlat) or FirstStep then begin topNormal := normal; bottomNormal := normal; if (FNormals = nsSmooth) then lastNormals^[i] := normal; end else if (FNormals = nsSmooth) then begin if invertNormals then begin topNormal := normal; bottomNormal := lastNormals^[i]; end else begin topNormal := lastNormals^[i]; bottomNormal := normal; end; lastNormals^[i] := normal; end; end; begin // to invert normals, we just need to flip top & bottom if invertNormals then begin ptBuffer := ptTop; ptTop := ptBottom; ptBottom := ptBuffer; end; // generate triangle strip for a level // TODO : support for triangle fans (when ptTop or ptBottom is on the Y Axis) alpha := startAlpha; i := 0; yOffset := startYOffset; topTPBase.S := 0; bottomTPBase.S := 0; topTPBase.T := topT; bottomTPBase.T := bottomT; VectorRotateAroundY(ptTop^, alpha, topBase); VectorRotateAroundY(ptBottom^, alpha, bottomBase); if gotYDeltaOffset then begin topBase.Y := topBase.Y + yOffset; bottomBase.Y := bottomBase.Y + yOffset; yOffset := yOffset + deltaYOffset; end; CalcNormal(@topBase, @bottomBase, normal); SetLocalNormals; inc(i); topTPNext := topTPBase; bottomTPNext := bottomTPBase; gl.Begin_(GL_TRIANGLE_STRIP); gl.Normal3fv(@topNormal); xgl.TexCoord2fv(@topTPBase); gl.Vertex3fv(@topBase); while alpha < stopAlpha do begin gl.Normal3fv(@bottomNormal); xgl.TexCoord2fv(@bottomTPBase); gl.Vertex3fv(@bottomBase); nextAlpha := alpha + deltaAlpha; topTPNext.S := topTPNext.S + deltaS; bottomTPNext.S := bottomTPNext.S + deltaS; VectorRotateAroundY(ptTop^, nextAlpha, topNext); VectorRotateAroundY(ptBottom^, nextAlpha, bottomNext); if gotYDeltaOffset then begin topNext.Y := topNext.Y + yOffset; bottomNext.Y := bottomNext.Y + yOffset; yOffset := yOffset + deltaYOffset end; CalcNormal(@topNext, @bottomNext, normal); SetLocalNormals; inc(i); xgl.TexCoord2fv(@topTPNext); gl.Normal3fv(@topNormal); gl.Vertex3fv(@topNext); alpha := nextAlpha; topBase := topNext; topTPBase := topTPNext; bottomBase := bottomNext; bottomTPBase := bottomTPNext; end; gl.Normal3fv(@bottomNormal); xgl.TexCoord2fv(@bottomTPBase); gl.Vertex3fv(@bottomBase); gl.End_; firstStep := False; end; var i, nbSteps, nbDivisions: Integer; splinePos, lastSplinePos, bary, polygonNormal: TAffineVector; f: Single; spline: TCubicSpline; invertedNormals: Boolean; polygon: TGLNodes; begin if (Nodes.Count > 1) and (FStopAngle > FStartAngle) then begin startAlpha := FStartAngle * cPIdiv180; stopAlpha := FStopAngle * cPIdiv180; nbSteps := Round(((stopAlpha - startAlpha) / (2 * PI)) * FSlices); // drop 0.1% to slice count to care for precision losses deltaAlpha := (stopAlpha - startAlpha) / (nbSteps * 0.999); deltaS := (stopAlpha - startAlpha) / (2 * PI * nbSteps); gotYDeltaOffset := FYOffsetPerTurn <> 0; if gotYDeltaOffset then deltaYOffset := (FYOffsetPerTurn * (stopAlpha - startAlpha) / (2 * PI)) / nbSteps else deltaYOffset := 0; startYOffset := YOffsetPerTurn * startAlpha / (2 * PI); invertedNormals := (FNormalDirection = ndInside); FTriangleCount := 0; // generate sides if (rspInside in FParts) or (rspOutside in FParts) then begin // allocate lastNormals buffer (if smoothing) if FNormals = nsSmooth then begin GetMem(lastNormals, (FSlices + 2) * SizeOf(TAffineVector)); firstStep := True; end; // start working if rspInside in Parts then begin firstStep := True; if (Division < 2) or (SplineMode = lsmLines) then begin // standard line(s), draw directly for i := 0 to Nodes.Count - 2 do with Nodes[i] do begin BuildStep(PAffineVector(Nodes[i].AsAddress), PAffineVector(Nodes[i + 1].AsAddress), not invertedNormals, i / (Nodes.Count - 1), (i + 1) / (Nodes.Count - 1)); end; FTriangleCount := nbSteps * Nodes.Count * 2; end else begin // cubic spline Spline := Nodes.CreateNewCubicSpline; Spline.SplineAffineVector(0, lastSplinePos); f := 1 / Division; nbDivisions := (Nodes.Count - 1) * Division; for i := 1 to nbDivisions do begin Spline.SplineAffineVector(i * f, splinePos); BuildStep(@lastSplinePos, @splinePos, not invertedNormals, (i - 1) / nbDivisions, i / nbDivisions); lastSplinePos := splinePos; end; Spline.Free; FTriangleCount := nbSteps * nbDivisions * 2; end; end; if rspOutside in Parts then begin firstStep := True; if (Division < 2) or (SplineMode = lsmLines) then begin // standard line(s), draw directly for i := 0 to Nodes.Count - 2 do with Nodes[i] do begin BuildStep(PAffineVector(Nodes[i].AsAddress), PAffineVector(Nodes[i + 1].AsAddress), invertedNormals, i / (Nodes.Count - 1), (i + 1) / (Nodes.Count - 1)); end; FTriangleCount := nbSteps * Nodes.Count * 2; end else begin // cubic spline Spline := Nodes.CreateNewCubicSpline; Spline.SplineAffineVector(0, lastSplinePos); f := 1 / Division; nbDivisions := (Nodes.Count - 1) * Division; for i := 1 to nbDivisions do begin Spline.SplineAffineVector(i * f, splinePos); BuildStep(@lastSplinePos, @splinePos, invertedNormals, (i - 1) / nbDivisions, i / nbDivisions); lastSplinePos := splinePos; end; Spline.Free; FTriangleCount := nbSteps * nbDivisions * 2; end; end; if (rspInside in FParts) and (rspOutside in FParts) then FTriangleCount := FTriangleCount * 2; xgl.TexCoord2fv(@NullTexPoint); // release lastNormals buffer (if smoothing) if FNormals = nsSmooth then FreeMem(lastNormals); end; // tessellate start/stop polygons if (rspStartPolygon in FParts) or (rspStopPolygon in FParts) then begin bary := Nodes.Barycenter; bary.Y := 0; NormalizeVector(bary); // tessellate start polygon if rspStartPolygon in FParts then begin polygon := Nodes.CreateCopy(nil); with polygon do begin RotateAroundY(RadToDeg(startAlpha)); Translate(AffineVectorMake(0, startYOffset, 0)); if invertedNormals then alpha := startAlpha + PI / 2 else alpha := startAlpha + PI + PI / 2; polygonNormal := VectorRotateAroundY(bary, alpha); if SplineMode = lsmLines then RenderTesselatedPolygon(False, @polygonNormal, 1) else RenderTesselatedPolygon(False, @polygonNormal, Division); Free; end; // estimated count FTriangleCount := FTriangleCount + Nodes.Count + (Nodes.Count shr 1); end; // tessellate stop polygon if rspStopPolygon in FParts then begin polygon := Nodes.CreateCopy(nil); with polygon do begin RotateAroundY(RadToDeg(stopAlpha)); Translate(AffineVectorMake(0, startYOffset + (stopAlpha - startAlpha) * YOffsetPerTurn / (2 * PI), 0)); if invertedNormals then alpha := stopAlpha + PI + PI / 2 else alpha := stopAlpha + PI / 2; polygonNormal := VectorRotateAroundY(bary, alpha); if SplineMode = lsmLines then RenderTesselatedPolygon(False, @polygonNormal, 1) else RenderTesselatedPolygon(False, @polygonNormal, Division); Free; end; // estimated count FTriangleCount := FTriangleCount + Nodes.Count + (Nodes.Count shr 1); end; end; end; end; function TGLRevolutionSolid.AxisAlignedDimensionsUnscaled: TGLVector; var maxRadius: Single; maxHeight: Single; i: integer; begin maxRadius := 0; maxHeight := 0; if FAxisAlignedDimensionsCache.X < 0 then begin for i := 0 to Nodes.Count - 1 do begin maxHeight := MaxFloat(maxHeight, Abs(Nodes[i].Y)); maxRadius := MaxFloat(maxRadius, Sqr(Nodes[i].X) + Sqr(Nodes[i].Z)); end; maxRadius := sqrt(maxRadius); FAxisAlignedDimensionsCache.X := maxRadius; FAxisAlignedDimensionsCache.Y := maxHeight; FAxisAlignedDimensionsCache.Z := maxRadius; end; SetVector(Result, FAxisAlignedDimensionsCache); end; procedure TGLRevolutionSolid.StructureChanged; begin FAxisAlignedDimensionsCache.X := -1; inherited; end; // ------------------ // ------------------ TGLPipeNode ------------------ // ------------------ constructor TGLPipeNode.Create(Collection: TCollection); begin inherited Create(Collection); FRadiusFactor := 1.0; FColor := TGLColor.CreateInitialized(Self, clrBlack, ColorChanged); FTexCoordT := 1.0; end; destructor TGLPipeNode.Destroy; begin FColor.Free; inherited Destroy; end; procedure TGLPipeNode.Assign(Source: TPersistent); begin if Source is TGLPipeNode then begin RadiusFactor := TGLPipeNode(Source).FRadiusFactor; Color.DirectColor := TGLPipeNode(Source).Color.DirectColor; TexCoordT := TGLPipeNode(Source).FTexCoordT; end; inherited; end; function TGLPipeNode.GetDisplayName: string; begin Result := Format('%s / rf = %.3f', [inherited GetDisplayName, RadiusFactor]); ; end; procedure TGLPipeNode.SetRadiusFactor(const val: Single); begin if FRadiusFactor <> val then begin FRadiusFactor := val; Changed(false); //(Collection as TGLNodes).NotifyChange; end; end; function TGLPipeNode.StoreRadiusFactor: Boolean; begin Result := (FRadiusFactor <> 1.0); end; function TGLPipeNode.StoreTexCoordT: Boolean; begin Result := (FTexCoordT <> 1.0); end; procedure TGLPipeNode.SetColor(const val: TGLColor); begin FColor.Assign(val); end; procedure TGLPipeNode.ColorChanged(sender: TObject); begin TGLPipeNodes(Collection).NotifyChange; end; // ------------------ // ------------------ TGLPipeNodes ------------------ // ------------------ constructor TGLPipeNodes.Create(AOwner: TComponent); begin inherited Create(AOwner, TGLPipeNode); end; procedure TGLPipeNodes.SetItems(index: Integer; const val: TGLPipeNode); begin inherited Items[index] := val; end; function TGLPipeNodes.GetItems(index: Integer): TGLPipeNode; begin Result := TGLPipeNode(inherited Items[index]); end; function TGLPipeNodes.Add: TGLPipeNode; begin Result := (inherited Add) as TGLPipeNode; end; function TGLPipeNodes.FindItemID(ID: Integer): TGLPipeNode; begin Result := (inherited FindItemID(ID)) as TGLPipeNode; end; // ------------------ // ------------------ TGLPipe ------------------ // ------------------ constructor TGLPipe.Create(AOwner: TComponent); begin inherited Create(AOwner); FSlices := 16; FParts := [ppOutside]; FRadius := 1.0; FTriangleCount := 0; FTextCoordMode := ptcmDefault; FTextCoordTileS := 1; FTextCoordTileT := 1; FNormalMode := pnmDefault; FNormalSmoothAngle := 0; end; procedure TGLPipe.CreateNodes; begin FNodes := TGLPipeNodes.Create(Self); end; destructor TGLPipe.Destroy; begin inherited Destroy; end; procedure TGLPipe.SetSlices(const val: Integer); begin if (val <> FSlices) and (val > 0) then begin FSlices := val; StructureChanged; end; end; procedure TGLPipe.SetParts(const val: TPipeParts); begin if FParts <> val then begin FParts := val; StructureChanged; end; end; procedure TGLPipe.SetRadius(const val: Single); begin if FRadius <> val then begin FRadius := val; StructureChanged; end; end; function TGLPipe.StoreRadius: Boolean; begin Result := (FRadius <> 1.0); end; function TGLPipe.StoreTextCoordTileS: Boolean; begin Result := (FTextCoordTileS <> 1.0); end; function TGLPipe.StoreTextCoordTileT: Boolean; begin Result := (FTextCoordTileT <> 1.0); end; procedure TGLPipe.SetNodesColorMode(const val: TPipeNodesColorMode); begin if val <> FNodesColorMode then begin FNodesColorMode := val; StructureChanged; end; end; procedure TGLPipe.SetTextCoordMode(const val: TPipeTexCoordMode); begin if val <> FTextCoordMode then begin FTextCoordMode := val; StructureChanged; end; end; procedure TGLPipe.SetTextCoordTileS(const val: Single); begin if val <> FTextCoordTileS then begin FTextCoordTileS := val; StructureChanged; end; end; procedure TGLPipe.SetTextCoordTileT(const val: Single); begin if val <> FTextCoordTileT then begin FTextCoordTileT := val; StructureChanged; end; end; procedure TGLPipe.SetNormalMode(const val: TPipeNormalMode); begin if val <> FNormalMode then begin FNormalMode := val; StructureChanged; end; end; procedure TGLPipe.SetNormalSmoothAngle(const val: Single); begin if val <> FNormalSmoothAngle then begin FNormalSmoothAngle := val; if NormalMode = pnmAdvanced then StructureChanged; end; end; procedure TGLPipe.Assign(Source: TPersistent); begin if Source is TGLPipe then begin Slices := TGLPipe(Source).Slices; Parts := TGLPipe(Source).Parts; Radius := TGLPipe(Source).Radius; NodesColorMode := TGLPipe(Source).NodesColorMode; TexCoordMode := TGLPipe(Source).TexCoordMode; TexCoordTileS := TGLPipe(Source).TexCoordTileS; TexCoordTileT := TGLPipe(Source).TexCoordTileT; end; inherited; end; var vSinCache, vCosCache: array of Single; procedure TGLPipe.BuildList(var rci: TGLRenderContextInfo); type TNodeData = record pos: TAffineVector; normal: TAffineVector; innormal: TAffineVector; sidedir: TVector3f; end; TRowData = record node: array of TNodeData; color: TGLColorVector; center: TVector3f; textcoordT: Single; end; PRowData = ^TRowData; const cPNCMtoEnum: array[pncmEmission..pncmAmbientAndDiffuse] of Cardinal = (GL_EMISSION, GL_AMBIENT, GL_DIFFUSE, GL_AMBIENT_AND_DIFFUSE); procedure CalculateRow(row: PRowData; const center, normal: TAffineVector; radius: Single); var i: Integer; vx, vy: TAffineVector; begin // attempt to use object's Z as Y vector VectorCrossProduct(ZVector, normal, vx); if VectorNorm(vx) < 1e-7 then begin // bad luck, the X vector will do (unless it's or normal that was null) if VectorNorm(normal) < 1e-7 then begin SetVector(vx, XVector); SetVector(vy, ZVector); end else begin VectorCrossProduct(XVector, normal, vx); NormalizeVector(vx); VectorCrossProduct(normal, vx, vy); end; end else begin NormalizeVector(vx); VectorCrossProduct(normal, vx, vy); end; NormalizeVector(vy); ScaleVector(vx, FRadius); ScaleVector(vy, FRadius); // generate the circle for i := 0 to High(row^.node) do begin row^.node[i].normal := VectorCombine(vx, vy, vCosCache[i], vSinCache[i]); row^.node[i].pos := VectorCombine(PAffineVector(@center)^, row^.node[i].normal, 1, radius); SetVector(row^.node[i].sidedir, 0, 0, 0); end; row^.center := center; end; procedure RenderDisk(row: PRowData; const center: TGLVector; const normal: TAffineVector; invert: Boolean; TextCoordTileS: Single); var i: Integer; begin begin if NodesColorMode <> pncmNone then gl.Color4fv(@row^.color); // it was necessary to change build process to generate textcoords gl.Begin_(GL_TRIANGLE_STRIP); gl.Normal3fv(@normal); case TexCoordMode of ptcmDefault, ptcmManual: begin if invert then begin for i := 0 to High(row^.node) - 1 do begin gl.TexCoord2f(i / (High(row^.node)) * TextCoordTileS, 1); gl.Vertex3fv(@row^.node[i].pos); gl.TexCoord2f(i / (High(row^.node)) * TextCoordTileS, 0); gl.Vertex3fv(@center); end; gl.TexCoord2f(TextCoordTileS, 1); gl.Vertex3fv(@row^.node[High(row^.node)].pos); end else begin for i := High(row^.node) downto 1 do begin gl.TexCoord2f(i / (High(row^.node)) * TextCoordTileS, 0); gl.Vertex3fv(@row^.node[i].pos); gl.TexCoord2f(i / (High(row^.node)) * TextCoordTileS, 1); gl.Vertex3fv(@center); end; gl.TexCoord2f(0, 0); gl.Vertex3fv(@row^.node[0].pos); end; end; end; gl.End_; end; end; procedure CalculateSides(prevRow, curRow: PRowData; const trajvec: TVector3f); var j, k, m, n: Integer; deltaNormal, deltaPos: array of Double; smoothanglerad: Single; begin SetLength(deltanormal, Slices); SetLength(deltapos, Slices); for k := 0 to Slices - 1 do begin //rotate index for curRow deltanormal[k] := 0; //sum of difference for normal vector deltapos[k] := 0; //sum of difference for pos vector for j := 0 to Slices - 1 do begin //over all places n := (j + k) mod Slices; deltanormal[k] := deltanormal[k] + VectorSpacing(curRow^.node[n].normal, prevRow^.node[j].normal); deltapos[k] := deltapos[k] + VectorSpacing(curRow^.node[n].pos, prevRow^.node[j].pos); end; end; //Search minimum // only search in deltapos, if i would search in deltanormal, // the same index of minimum would be found m := 0; for k := 1 to Slices - 1 do if deltapos[m] > deltapos[k] then m := k; // rotate count for k := 1 to m do begin // rotate the values of curRow curRow^.node[Slices] := curRow^.node[0]; System.Move(curRow^.node[1], curRow^.node[0], SizeOf(TNodeData) * Slices); curRow^.node[Slices] := curRow^.node[0]; end; case NormalMode of pnmDefault: begin for j := 0 to Slices do begin curRow.node[j].innormal := VectorNegate(curRow.node[j].normal); prevRow.node[j].innormal := VectorNegate(prevRow.node[j].normal); end; end; pnmAdvanced: begin smoothanglerad := DegToRadian(NormalSmoothAngle); for j := 0 to Slices do begin curRow.node[j].sidedir := VectorNormalize(VectorSubtract(curRow.node[j].pos, prevRow.node[j].pos)); if VectorDotProduct(curRow.node[j].sidedir, prevRow.node[j].sidedir) < Cos(smoothanglerad) then begin if VectorDotProduct(curRow.node[j].sidedir, VectorNormalize( VectorSubtract(curRow.node[j].pos, curRow.center))) > 0.99 then begin curRow.node[j].normal := VectorCrossProduct(curRow.node[j].sidedir, VectorCrossProduct(curRow.node[j].sidedir, VectorNormalize(trajvec))); prevRow.node[j].normal := VectorCrossProduct(curRow.node[j].sidedir, VectorCrossProduct(curRow.node[j].sidedir, VectorNormalize(trajvec))); end else begin if VectorDotProduct(curRow.node[j].sidedir, VectorNormalize( VectorSubtract(curRow.node[j].pos, curRow.center))) < -0.99 then begin curRow.node[j].normal := VectorCrossProduct(VectorCrossProduct (curRow.node[j].sidedir, VectorNormalize(trajvec)), curRow.node[j].sidedir); prevRow.node[j].normal := VectorCrossProduct(VectorCrossProduct (curRow.node[j].sidedir, VectorNormalize(trajvec)), curRow.node[j].sidedir); end else begin if VectorDotProduct(trajvec, curRow.node[j].sidedir) < 0 then begin curRow.node[j].normal := VectorCrossProduct(VectorNormalize(VectorCrossProduct (VectorNormalize(VectorSubtract(curRow.node[j].pos, curRow.center)), curRow.node[j].sidedir)), curRow.node[j].sidedir); prevRow.node[j].normal := VectorCrossProduct(VectorNormalize(VectorCrossProduct (VectorNormalize(VectorSubtract(prevRow.node[j].pos, prevRow.center)), curRow.node[j].sidedir)), curRow.node[j].sidedir); end else begin curRow.node[j].normal := VectorCrossProduct(curRow.node[j].sidedir, VectorNormalize (VectorCrossProduct(VectorNormalize(VectorSubtract(curRow.node[j].pos, curRow.center)), curRow.node[j].sidedir))); prevRow.node[j].normal := VectorCrossProduct(curRow.node[j].sidedir, VectorNormalize (VectorCrossProduct(VectorNormalize(VectorSubtract(prevRow.node[j].pos, prevRow.center)), curRow.node[j].sidedir))); end; end; if VectorLength(curRow.node[j].normal) = 0 then curRow.node[j].normal := prevRow.node[j].normal; if VectorLength(prevRow.node[j].normal) = 0 then prevRow.node[j].normal := curRow.node[j].normal; //compute inside normales curRow.node[j].innormal := VectorNegate(curRow.node[j].normal); prevRow.node[j].innormal := VectorNegate(prevRow.node[j].normal); end; end else begin if VectorDotProduct(curRow.node[j].sidedir, VectorNormalize(VectorSubtract (curRow.node[j].pos, curRow.center))) > 0.99 then begin curRow.node[j].normal := VectorCrossProduct(curRow.node[j].sidedir, VectorCrossProduct(curRow.node[j].sidedir, VectorNormalize(trajvec))); end else begin if VectorDotProduct(curRow.node[j].sidedir, VectorNormalize( VectorSubtract(curRow.node[j].pos, curRow.center))) < -0.99 then begin curRow.node[j].normal := VectorCrossProduct(VectorCrossProduct (curRow.node[j].sidedir, VectorNormalize(trajvec)), curRow.node[j].sidedir); end else begin if VectorDotProduct(trajvec, curRow.node[j].sidedir) < 0 then begin curRow.node[j].normal := VectorCrossProduct(VectorNormalize(VectorCrossProduct (VectorNormalize(VectorSubtract(curRow.node[j].pos, curRow.center)), curRow.node[j].sidedir)), curRow.node[j].sidedir); end else begin curRow.node[j].normal := VectorCrossProduct(curRow.node[j].sidedir, VectorNormalize (VectorCrossProduct(VectorNormalize(VectorSubtract(curRow.node[j].pos, curRow.center)), curRow.node[j].sidedir))); end; end; //compute inside normales curRow.node[j].innormal := VectorNegate(curRow.node[j].normal); end; end; end; end; end; end; procedure RenderSides(prevRow, curRow: PRowData; TextCoordTileS, TextCoordTileT: Single; outside: Boolean); var j: Integer; begin begin gl.Begin_(GL_TRIANGLE_STRIP); if outside then begin if NodesColorMode <> pncmNone then gl.Color4fv(@curRow^.color); gl.TexCoord2f(0, curRow^.textcoordT * TextCoordTileT); gl.Normal3fv(@curRow^.node[0].normal); gl.Vertex3fv(@curRow^.node[0].pos); for j := 0 to Slices - 1 do begin if NodesColorMode <> pncmNone then gl.Color4fv(@prevRow^.color); gl.TexCoord2f(j / Slices * TextCoordTileS, prevRow^.textcoordT * TextCoordTileT); gl.Normal3fv(@prevRow^.node[j].normal); gl.Vertex3fv(@prevRow^.node[j].pos); if NodesColorMode <> pncmNone then gl.Color4fv(@curRow^.color); gl.TexCoord2f((j + 1) / Slices * TextCoordTileS, curRow^.textcoordT * TextCoordTileT); gl.Normal3fv(@curRow^.node[j + 1].normal); gl.Vertex3fv(@curRow^.node[j + 1].pos); end; if NodesColorMode <> pncmNone then gl.Color4fv(@prevRow^.color); gl.TexCoord2f(TextCoordTileS, prevRow^.textcoordT * TextCoordTileT); gl.Normal3fv(@prevRow^.node[Slices].normal); gl.Vertex3fv(@prevRow^.node[Slices].pos); end else begin for j := 0 to Slices do begin curRow.node[j].innormal := VectorNegate(curRow.node[j].normal); prevRow.node[j].innormal := VectorNegate(prevRow.node[j].normal); end; if NodesColorMode <> pncmNone then gl.Color4fv(@prevRow^.color); gl.TexCoord2f(0, prevRow^.textcoordT * TextCoordTileT); gl.Normal3fv(@prevRow^.node[0].innormal); gl.Vertex3fv(@prevRow^.node[0].pos); for j := 0 to Slices - 1 do begin if NodesColorMode <> pncmNone then gl.Color4fv(@curRow^.color); gl.TexCoord2f(j / Slices * TextCoordTileS, curRow^.textcoordT * TextCoordTileT); gl.Normal3fv(@curRow^.node[j].innormal); gl.Vertex3fv(@curRow^.node[j].pos); if NodesColorMode <> pncmNone then gl.Color4fv(@prevRow^.color); gl.TexCoord2f((j + 1) / Slices * TextCoordTileS, prevRow^.textcoordT * TextCoordTileT); gl.Normal3fv(@prevRow^.node[j + 1].innormal); gl.Vertex3fv(@prevRow^.node[j + 1].pos); end; if NodesColorMode <> pncmNone then gl.Color4fv(@curRow^.color); gl.TexCoord2f(TextCoordTileS, curRow^.textcoordT * TextCoordTileT); gl.Normal3fv(@curRow^.node[Slices].innormal); gl.Vertex3fv(@curRow^.node[Slices].pos); end; gl.End_; end; end; var i, curRow, nbDivisions, k: Integer; normal, splinePos: TAffineVector; rows: array[0..1] of TRowData; ra: PFloatArray; posSpline, rSpline: TCubicSpline; f, t: Single; begin FTriangleCount := 0; if Nodes.Count = 0 then Exit; SetLength(rows[0].node, Slices + 1); SetLength(rows[1].node, Slices + 1); if (Length(vSinCache) <> Slices + 1) or (Length(vCosCache) <> Slices + 1) then begin SetLength(vSinCache, Slices + 1); SetLength(vCosCache, Slices + 1); PrepareSinCosCache(vSinCache, vCosCache, 0, 360); end; if (SplineMode = lsmCubicSpline) and (Nodes.Count > 1) then begin // creates position spline posSpline := Nodes.CreateNewCubicSpline; // creates radius spline GetMem(ra, SizeOf(TGLFloat) * Nodes.Count); for i := 0 to Nodes.Count - 1 do ra^[i] := TGLPipeNode(Nodes[i]).RadiusFactor; rSpline := TCubicSpline.Create(ra, nil, nil, nil, Nodes.Count); FreeMem(ra); normal := posSpline.SplineSlopeVector(0); end else begin normal := Nodes.Vector(0); posSpline := nil; rSpline := nil; end; if NodesColorMode <> pncmNone then begin gl.ColorMaterial(GL_FRONT_AND_BACK, cPNCMtoEnum[NodesColorMode]); rci.GLStates.Enable(stColorMaterial); end else rci.GLStates.Disable(stColorMaterial); CalculateRow(@rows[0], PAffineVector(@Nodes[0].AsVector)^, normal, TGLPipeNode(Nodes[0]).RadiusFactor); rows[0].color := TGLPipeNodes(Nodes)[0].Color.Color; case TexCoordMode of ptcmDefault: rows[0].textcoordT := 0; ptcmManual: rows[0].textcoordT := TGLPipeNode(Nodes[0]).TexCoordT; end; if ppStartDisk in Parts then begin NegateVector(normal); if ppOutside in Parts then begin RenderDisk(@rows[0], Nodes[0].AsVector, normal, True, TexCoordTileS); FTriangleCount := FTriangleCount + Slices * 2; //Slices+1; end; if ppInside in Parts then begin RenderDisk(@rows[0], Nodes[0].AsVector, VectorNegate(normal), False, TexCoordTileS); FTriangleCount := FTriangleCount + Slices * 2; //Slices+1; end; end; if (Nodes.Count > 1) then begin if SplineMode = lsmCubicSpline then begin f := 1 / Division; nbDivisions := (Nodes.Count - 1) * Division; for i := 1 to nbDivisions do begin t := i * f; posSpline.SplineAffineVector(t, splinePos); normal := posSpline.SplineSlopeVector(t); NormalizeVector(normal); curRow := (i and 1); CalculateRow(@rows[curRow], splinePos, normal, rSpline.SplineX(t)); if NodesColorMode <> pncmNone then begin k := Trunc(t); if k < Nodes.Count - 1 then rows[curRow].color := VectorLerp(TGLPipeNodes(Nodes)[k].Color.Color, TGLPipeNodes(Nodes)[k + 1].Color.Color, Frac(t)) else rows[curRow].color := TGLPipeNodes(Nodes)[k].Color.Color; end; // case TexCoordMode of ptcmDefault: begin k := Trunc(t); if k < Nodes.Count - 1 then rows[curRow].textcoordT := Lerp(k, k + 1, Frac(t)) else rows[curRow].textcoordT := k; end; ptcmManual: begin k := Trunc(t); if k < Nodes.Count - 1 then rows[curRow].textcoordT := Lerp(TGLPipeNode(Nodes[k]).TexCoordT, TGLPipeNode(Nodes[k + 1]).TexCoordT, Frac(t)) else rows[curRow].textcoordT := TGLPipeNode(Nodes[k]).TexCoordT; end; end; if (ppOutside in Parts) or (ppInside in Parts) then CalculateSides(@rows[curRow xor 1], @rows[curRow], normal); if ppOutside in Parts then RenderSides(@rows[curRow xor 1], @rows[curRow], TexCoordTileS, TexCoordTileT, True); if ppInside in Parts then RenderSides(@rows[curRow xor 1], @rows[curRow], TexCoordTileS, TexCoordTileT, False); end; i := nbDivisions * (Slices + 1) * 2; if ppOutside in Parts then Inc(FTriangleCount, i); if ppInside in Parts then Inc(FTriangleCount, i); end else begin for i := 1 to Nodes.Count - 1 do begin curRow := (i and 1); //Initialize Texture coordinates case TexCoordMode of ptcmDefault: rows[curRow].textcoordT := i; ptcmManual: rows[curRow].textcoordT := TGLPipeNode(Nodes[i]).TexCoordT; end; CalculateRow(@rows[curRow], PAffineVector(@Nodes[i].AsVector)^, Nodes.Vector(i), TGLPipeNode(Nodes[i]).RadiusFactor); rows[curRow].color := TGLPipeNodes(Nodes)[i].Color.Color; if (ppOutside in Parts) or (ppInside in Parts) then CalculateSides(@rows[curRow xor 1], @rows[curRow], Nodes.Vector(i)); if ppOutside in Parts then RenderSides(@rows[curRow xor 1], @rows[curRow], TexCoordTileS, TexCoordTileT, True); if ppInside in Parts then RenderSides(@rows[curRow xor 1], @rows[curRow], TexCoordTileS, TexCoordTileT, False); end; i := Nodes.Count * (Slices + 1) * 2; if ppOutside in Parts then Inc(FTriangleCount, i); if ppInside in Parts then Inc(FTriangleCount, i); end; end; if ppStopDisk in Parts then begin i := Nodes.Count - 1; if SplineMode = lsmCubicSpline then normal := posSpline.SplineSlopeVector(Nodes.Count - 1) else normal := Nodes.Vector(i); CalculateRow(@rows[0], PAffineVector(@Nodes[i].AsVector)^, normal, TGLPipeNode(Nodes[i]).RadiusFactor); rows[0].color := TGLPipeNodes(Nodes)[i].Color.Color; if ppOutside in Parts then begin RenderDisk(@rows[0], Nodes[i].AsVector, normal, False, TexCoordTileS); FTriangleCount := FTriangleCount + Slices * 2; //Slices+1; end; if ppInside in Parts then begin RenderDisk(@rows[0], Nodes[i].AsVector, VectorNegate(normal), True, TexCoordTileS); FTriangleCount := FTriangleCount + Slices * 2; //Slices+1; end; end; if SplineMode = lsmCubicSpline then begin posSpline.Free; rSpline.Free; end; end; // ------------------ // ------------------ TGLExtrusionSolid ------------------ // ------------------ procedure TGLExtrusionSolid.Assign(Source: TPersistent); begin if Source is TGLExtrusionSolid then begin FStacks := TGLExtrusionSolid(Source).FStacks; FNormals := TGLExtrusionSolid(Source).FNormals; FNormalDirection := TGLExtrusionSolid(Source).FNormalDirection; FParts := TGLExtrusionSolid(Source).FParts; end; inherited; end; procedure TGLExtrusionSolid.BuildList(var rci: TGLRenderContextInfo); var {deltaS,}deltaZ: Single; lastNormal: TAffineVector; procedure CalcNormal(const Top, Bottom: TAffineVector; var normal: TAffineVector); { extrusion is in Z direction, so the Z component of the normal vector is always zero. } {var p : TAffineVector;} begin normal.X := Bottom.Y - Top.Y; normal.Y := Top.X - Bottom.X; normal.Z := 0; NormalizeVector(normal); if FHeight < 0 then NegateVector(normal); (* p:=Top; p[2]:=p[2] + FHeight; CalcPlaneNormal(top,bottom,p,normal); *) end; procedure BuildStep(ptTop, ptBottom: TAffineVector; invertNormals: Boolean; topT, bottomT: Single); var step: Integer; topBase, topNext, bottomBase, bottomNext, normal, normTop, normBottom: TAffineVector; topTPBase, topTPNext, bottomTPBase, bottomTPNext: TTexPoint; ptBuffer: TAffineVector; angle: Double; dir: TAffineVector; begin // to invert normals, we just need to flip top & bottom if invertNormals then begin ptBuffer := ptTop; ptTop := ptBottom; ptBottom := ptBuffer; end; // generate triangle strip for a level // TODO : support for triangle fans (when ptTop or ptBottom is on the Y Axis) /// topTPBase.S:=0; bottomTPBase.S:=0; topTPBase.T := topT; bottomTPBase.T := bottomT; topBase := ptTop; bottomBase := ptBottom; CalcNormal(topBase, bottomBase, normal); if (FNormals = nsFlat) then lastNormal := normal else if (FNormals = nsSmooth) then begin angle := VectorDotProduct(normal, lastNormal); if (angle < FMinSmoothAngleCos) then begin lastNormal := normal; end; end; if invertNormals then begin normTop := Normal; normBottom := lastnormal; end else begin normTop := lastNormal; normBottom := normal; end; dir := VectorNormalize(VectorSubtract(bottomBase, topBase)); topTPBase.S := VectorDotProduct(topBase, dir); topTPBase.T := topBase.Z; bottomTPBase.S := VectorDotProduct(bottomBase, dir); bottomTPBase.T := bottomBase.Z; lastNormal := normal; topNext := topBase; bottomNext := bottomBase; topTPNext := topTPBase; bottomTPNext := bottomTPBase; gl.Begin_(GL_TRIANGLE_STRIP); gl.Normal3fv(@normTop); xgl.TexCoord2fv(@topTPBase); gl.Vertex3fv(@topBase); for step := 1 to FStacks do begin gl.Normal3fv(@normBottom); xgl.TexCoord2fv(@bottomTPBase); gl.Vertex3fv(@bottomBase); topNext.Z := step * DeltaZ; bottomNext.Z := topNext.Z; topTPNext.T := topNext.Z; bottomTPNext.T := bottomNext.Z; xgl.TexCoord2fv(@topTPNext); gl.Normal3fv(@normTop); gl.Vertex3fv(@topNext); topBase := topNext; topTPBase := topTPNext; bottomBase := bottomNext; bottomTPBase := bottomTPNext; end; gl.Normal3fv(@normBottom); xgl.TexCoord2fv(@bottomTPBase); gl.Vertex3fv(@bottomBase); gl.End_; end; var n, i: Integer; invertedNormals: Boolean; normal: TAffineVector; begin if Outline.Count < 1 then Exit; deltaZ := FHeight / FStacks; // deltaS:=1/FStacks; invertedNormals := (FNormalDirection = ndInside); FTriangleCount := 0; // generate sides if (FHeight <> 0) and ((espInside in FParts) or (espOutside in FParts)) then begin for n := 0 to Outline.Count - 1 do begin with Outline.List[n] do if count > 1 then begin if espInside in Parts then begin CalcNormal(List^[count - 1], List^[0], lastNormal); if not InvertedNormals then NegateVector(lastNormal); for i := 0 to Count - 2 do begin BuildStep(List^[i], List^[i + 1], not invertedNormals, i / (Count - 1), (i + 1) / (Count - 1)); end; BuildStep(List^[count - 1], List^[0], not invertedNormals, 1, 0); end; if espOutside in Parts then begin CalcNormal(List^[count - 1], List^[0], lastNormal); if InvertedNormals then NegateVector(lastNormal); for i := 0 to Count - 2 do begin BuildStep(List^[i], List^[i + 1], invertedNormals, i / (Count - 1), (i + 1) / (Count - 1)); end; BuildStep(List^[count - 1], List^[0], invertedNormals, 1, 0); end; end; end; xgl.TexCoord2fv(@NullTexPoint); end; // tessellate start/stop polygons if (espStartPolygon in FParts) or (espStopPolygon in FParts) then begin normal := ContoursNormal; // tessellate stop polygon if espStopPolygon in FParts then begin gl.PushMatrix; gl.Translatef(0, 0, FHeight); RenderTesselatedPolygon(true, @normal, invertedNormals); gl.PopMatrix; end; // tessellate start polygon if espStartPolygon in FParts then begin NegateVector(normal); RenderTesselatedPolygon(true, @normal, not invertedNormals); end; end; end; constructor TGLExtrusionSolid.Create(AOwner: TComponent); begin inherited; FHeight := 1; FStacks := 1; FNormals := nsFlat; FNormalDirection := ndOutside; FParts := [espOutside]; MinSmoothAngle := 5; FAxisAlignedDimensionsCache.X := -1; end; destructor TGLExtrusionSolid.Destroy; begin inherited; end; procedure TGLExtrusionSolid.SetHeight(const Value: TGLFloat); begin if (Value <> FHeight) then begin FHeight := Value; StructureChanged; end; end; procedure TGLExtrusionSolid.SetMinSmoothAngle(const Value: Single); var s, c: Single; begin FMinSmoothAngle := Value; SinCosine(Value * cPidiv180, s, c); FMinSmoothAngleCos := c; end; procedure TGLExtrusionSolid.SetNormalDirection(const val: TGLNormalDirection); begin if FNormalDirection <> val then begin FNormalDirection := val; StructureChanged; end; end; procedure TGLExtrusionSolid.SetNormals(const val: TGLNormalSmoothing); begin if FNormals <> val then begin FNormals := val; StructureChanged; end; end; procedure TGLExtrusionSolid.SetParts(const val: TGLExtrusionSolidParts); begin if FParts <> val then begin FParts := val; StructureChanged; end; end; procedure TGLExtrusionSolid.SetStacks(const val: Integer); begin if (val <> FStacks) and (val > 0) then begin FStacks := val; StructureChanged; end; end; function TGLExtrusionSolid.AxisAlignedDimensionsUnscaled: TGLVector; var dMin, dMax: TAffineVector; begin if FAxisAlignedDimensionsCache.X < 0 then begin Contours.GetExtents(dMin, dMax); FAxisAlignedDimensionsCache.X := MaxFloat(Abs(dMin.X), Abs(dMax.X)); FAxisAlignedDimensionsCache.Y := MaxFloat(Abs(dMin.Y), Abs(dMax.Y)); FAxisAlignedDimensionsCache.Z := MaxFloat(Abs(dMin.Z), Abs(dMax.Z + Height)); end; SetVector(Result, FAxisAlignedDimensionsCache); end; procedure TGLExtrusionSolid.StructureChanged; begin FAxisAlignedDimensionsCache.X := -1; inherited; end; // ------------------------------------------------------------------ initialization // ------------------------------------------------------------------ RegisterClasses([TGLRevolutionSolid, TGLExtrusionSolid, TGLPipe]); end.