// // The graphics engine GLXEngine. The unit of GXScene for Delphi // unit GXS.Extrusion; (* Extrusion objects are solids defined by the surface described by a moving curve. TODO: ur: Suggestion: All extrusion objects use actually the same kind of "parts", one common type should do. *) interface {$I Stage.Defines.inc} uses Winapi.OpenGL, Winapi.OpenGLext, System.SysUtils, System.Classes, System.Math, GXS.XOpenGL, Stage.VectorTypes, GXS.VectorLists, Stage.VectorGeometry, Stage.Spline, GXS.Context, GXS.Objects, GXS.Scene, GXS.MultiPolygon, GXS.Color, GXS.RenderContextInfo, GXS.Nodes, GXS.State; type TgxExtrusionSolidPart = (espOutside, espInside, espStartPolygon, espStopPolygon); TgxExtrusionSolidParts = set of TgxExtrusionSolidPart; TgxRevolutionSolidPart = (rspOutside, rspInside, rspStartPolygon, rspStopPolygon); TgxRevolutionSolidParts = set of TgxRevolutionSolidPart; (* 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 TgxRevolutionSolid 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. *) TgxRevolutionSolid = class(TgxPolygonBase) private FSlices: Integer; FStartAngle, FStopAngle: Single; FNormals: TgxNormalSmoothing; FYOffsetPerTurn: Single; FTriangleCount: Integer; FNormalDirection: TgxNormalDirection; FParts: TgxRevolutionSolidParts; FAxisAlignedDimensionsCache: TVector4f; protected procedure SetStartAngle(const val: Single); procedure SetStopAngle(const val: Single); function StoreStopAngle: Boolean; procedure SetSlices(const val: Integer); procedure SetNormals(const val: TgxNormalSmoothing); procedure SetYOffsetPerTurn(const val: Single); procedure SetNormalDirection(const val: TgxNormalDirection); procedure SetParts(const val: TgxRevolutionSolidParts); public constructor Create(AOwner: TComponent); override; destructor Destroy; override; procedure Assign(Source: TPersistent); override; procedure BuildList(var rci: TgxRenderContextInfo); override; // Number of triangles used for rendering. property TriangleCount: Integer read FTriangleCount; function AxisAlignedDimensionsUnscaled: TVector4f; 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: TgxRevolutionSolidParts 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: TgxNormalSmoothing read FNormals write SetNormals default nsFlat; property NormalDirection: TgxNormalDirection read FNormalDirection write SetNormalDirection default ndOutside; end; (*Extrudes a complex Polygon into Z direction. For contour description see TgxMultiPolygonBase. properties Parts, Height (or should we better cal it Depth, because its in Z?), Stacks, Normals and NormalDirection are equivalent to TgxRevolutionSolid. 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. *) TgxExtrusionSolid = class(TgxMultiPolygonBase) private FStacks: Integer; FNormals: TgxNormalSmoothing; FTriangleCount: Integer; FNormalDirection: TgxNormalDirection; FParts: TgxExtrusionSolidParts; FHeight: Single; FMinSmoothAngle: Single; FMinSmoothAngleCos: Single; FAxisAlignedDimensionsCache: TVector4f; procedure SetHeight(const Value: Single); procedure SetMinSmoothAngle(const Value: Single); protected procedure SetStacks(const val: Integer); procedure SetNormals(const val: TgxNormalSmoothing); procedure SetNormalDirection(const val: TgxNormalDirection); procedure SetParts(const val: TgxExtrusionSolidParts); public constructor Create(AOwner: TComponent); override; destructor Destroy; override; procedure Assign(Source: TPersistent); override; procedure BuildList(var rci: TgxRenderContextInfo); override; // Number of triangles used for rendering. property TriangleCount: Integer read FTriangleCount; function AxisAlignedDimensionsUnscaled: TVector4f; override; procedure StructureChanged; override; published property Parts: TgxExtrusionSolidParts read FParts write SetParts default [espOutside]; property Height: Single read FHeight write SetHeight; property Stacks: Integer read FStacks write SetStacks default 1; property Normals: TgxNormalSmoothing read FNormals write SetNormals default nsFlat; property NormalDirection: TgxNormalDirection read FNormalDirection write SetNormalDirection default ndOutside; property MinSmoothAngle: Single read FMinSmoothAngle write SetMinSmoothAngle; end; TgxPipeNode = class(TgxNode) private FRadiusFactor: Single; FColor: TgxColor; FTexCoordT: Single; protected function GetDisplayName: string; override; procedure SetRadiusFactor(const val: Single); function StoreRadiusFactor: Boolean; procedure SetColor(const val: TgxColor); 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: TgxColor read FColor write SetColor; property TexCoordT: Single read FTexCoordT write FTexCoordT stored StoreTexCoordT; end; TgxPipeNodes = class(TgxLinesNodes) protected procedure SetItems(index: Integer; const val: TgxPipeNode); function GetItems(index: Integer): TgxPipeNode; public constructor Create(AOwner: TComponent); function Add: TgxPipeNode; function FindItemID(ID: Integer): TgxPipeNode; property Items[index: Integer]: TgxPipeNode 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. *) TgxPipe = class(TgxPolygonBase) 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: TgxRenderContextInfo); 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 // ------------------------------------------------------------------ // ------------------ // ------------------ TgxRevolutionSolid ------------------ // ------------------ constructor TgxRevolutionSolid.Create(AOwner: TComponent); begin inherited Create(AOwner); FStartAngle := 0; FStopAngle := 360; FSlices := 16; FNormals := nsFlat; FNormalDirection := ndOutside; FParts := [rspOutside]; end; destructor TgxRevolutionSolid.Destroy; begin inherited Destroy; end; procedure TgxRevolutionSolid.SetStartAngle(const val: Single); begin if FStartAngle <> val then begin FStartAngle := val; if FStartAngle > FStopAngle then FStopAngle := FStartAngle; StructureChanged; end; end; procedure TgxRevolutionSolid.SetStopAngle(const val: Single); begin if FStopAngle <> val then begin FStopAngle := val; if FStopAngle < FStartAngle then FStartAngle := FStopAngle; StructureChanged; end; end; function TgxRevolutionSolid.StoreStopAngle: Boolean; begin Result := (FStopAngle <> 360); end; procedure TgxRevolutionSolid.SetSlices(const val: Integer); begin if (val <> FSlices) and (val > 0) then begin FSlices := val; StructureChanged; end; end; procedure TgxRevolutionSolid.SetNormals(const val: TgxNormalSmoothing); begin if FNormals <> val then begin FNormals := val; StructureChanged; end; end; procedure TgxRevolutionSolid.SetYOffsetPerTurn(const val: Single); begin if FYOffsetPerTurn <> val then begin FYOffsetPerTurn := val; StructureChanged; end; end; procedure TgxRevolutionSolid.SetNormalDirection(const val: TgxNormalDirection); begin if FNormalDirection <> val then begin FNormalDirection := val; StructureChanged; end; end; procedure TgxRevolutionSolid.SetParts(const val: TgxRevolutionSolidParts); begin if FParts <> val then begin FParts := val; StructureChanged; end; end; procedure TgxRevolutionSolid.Assign(Source: TPersistent); begin if Source is TgxRevolutionSolid then begin FStartAngle := TgxRevolutionSolid(Source).FStartAngle; FStopAngle := TgxRevolutionSolid(Source).FStopAngle; FSlices := TgxRevolutionSolid(Source).FSlices; FNormals := TgxRevolutionSolid(Source).FNormals; FYOffsetPerTurn := TgxRevolutionSolid(Source).FYOffsetPerTurn; FNormalDirection := TgxRevolutionSolid(Source).FNormalDirection; FParts := TgxRevolutionSolid(Source).FParts; end; inherited Assign(Source); end; procedure TgxRevolutionSolid.BuildList(var rci: TgxRenderContextInfo); 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; glBegin(GL_TRIANGLE_STRIP); glNormal3fv(@topNormal); glTexCoord2fv(@topTPBase); glVertex3fv(@topBase); while alpha < stopAlpha do begin glNormal3fv(@bottomNormal); glTexCoord2fv(@bottomTPBase); glVertex3fv(@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); glTexCoord2fv(@topTPNext); glNormal3fv(@topNormal); glVertex3fv(@topNext); alpha := nextAlpha; topBase := topNext; topTPBase := topTPNext; bottomBase := bottomNext; bottomTPBase := bottomTPNext; end; glNormal3fv(@bottomNormal); glTexCoord2fv(@bottomTPBase); glVertex3fv(@bottomBase); glEnd; firstStep := False; end; var i, nbSteps, nbDivisions: Integer; splinePos, lastSplinePos, bary, polygonNormal: TAffineVector; f: Single; Spline: TCubicSpline; invertedNormals: Boolean; polygon: TgxNodes; 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; glTexCoord2fv(@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(RadianToDeg(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(RadianToDeg(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 TgxRevolutionSolid.AxisAlignedDimensionsUnscaled: TVector4f; 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 TgxRevolutionSolid.StructureChanged; begin FAxisAlignedDimensionsCache.X := -1; inherited; end; // ------------------ // ------------------ TgxPipeNode ------------------ // ------------------ constructor TgxPipeNode.Create(Collection: TCollection); begin inherited Create(Collection); FRadiusFactor := 1.0; FColor := TgxColor.CreateInitialized(Self, clrBlack, ColorChanged); FTexCoordT := 1.0; end; destructor TgxPipeNode.Destroy; begin FColor.Free; inherited Destroy; end; procedure TgxPipeNode.Assign(Source: TPersistent); begin if Source is TgxPipeNode then begin RadiusFactor := TgxPipeNode(Source).FRadiusFactor; Color.DirectColor := TgxPipeNode(Source).Color.DirectColor; TexCoordT := TgxPipeNode(Source).FTexCoordT; end; inherited; end; function TgxPipeNode.GetDisplayName: string; begin Result := Format('%s / rf = %.3f', [inherited GetDisplayName, RadiusFactor]);; end; procedure TgxPipeNode.SetRadiusFactor(const val: Single); begin if FRadiusFactor <> val then begin FRadiusFactor := val; Changed(False); // (Collection as TgxNodes).NotifyChange; end; end; function TgxPipeNode.StoreRadiusFactor: Boolean; begin Result := (FRadiusFactor <> 1.0); end; function TgxPipeNode.StoreTexCoordT: Boolean; begin Result := (FTexCoordT <> 1.0); end; procedure TgxPipeNode.SetColor(const val: TgxColor); begin FColor.Assign(val); end; procedure TgxPipeNode.ColorChanged(sender: TObject); begin TgxPipeNodes(Collection).NotifyChange; end; // ------------------ // ------------------ TgxPipeNodes ------------------ // ------------------ constructor TgxPipeNodes.Create(AOwner: TComponent); begin inherited Create(AOwner, TgxPipeNode); end; procedure TgxPipeNodes.SetItems(index: Integer; const val: TgxPipeNode); begin inherited Items[index] := val; end; function TgxPipeNodes.GetItems(index: Integer): TgxPipeNode; begin Result := TgxPipeNode(inherited Items[index]); end; function TgxPipeNodes.Add: TgxPipeNode; begin Result := (inherited Add) as TgxPipeNode; end; function TgxPipeNodes.FindItemID(ID: Integer): TgxPipeNode; begin Result := (inherited FindItemID(ID)) as TgxPipeNode; end; // ------------------ // ------------------ TgxPipe ------------------ // ------------------ constructor TgxPipe.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 TgxPipe.CreateNodes; begin FNodes := TgxPipeNodes.Create(Self); end; destructor TgxPipe.Destroy; begin inherited Destroy; end; procedure TgxPipe.SetSlices(const val: Integer); begin if (val <> FSlices) and (val > 0) then begin FSlices := val; StructureChanged; end; end; procedure TgxPipe.SetParts(const val: TPipeParts); begin if FParts <> val then begin FParts := val; StructureChanged; end; end; procedure TgxPipe.SetRadius(const val: Single); begin if FRadius <> val then begin FRadius := val; StructureChanged; end; end; function TgxPipe.StoreRadius: Boolean; begin Result := (FRadius <> 1.0); end; function TgxPipe.StoreTextCoordTileS: Boolean; begin Result := (FTextCoordTileS <> 1.0); end; function TgxPipe.StoreTextCoordTileT: Boolean; begin Result := (FTextCoordTileT <> 1.0); end; procedure TgxPipe.SetNodesColorMode(const val: TPipeNodesColorMode); begin if val <> FNodesColorMode then begin FNodesColorMode := val; StructureChanged; end; end; procedure TgxPipe.SetTextCoordMode(const val: TPipeTexCoordMode); begin if val <> FTextCoordMode then begin FTextCoordMode := val; StructureChanged; end; end; procedure TgxPipe.SetTextCoordTileS(const val: Single); begin if val <> FTextCoordTileS then begin FTextCoordTileS := val; StructureChanged; end; end; procedure TgxPipe.SetTextCoordTileT(const val: Single); begin if val <> FTextCoordTileT then begin FTextCoordTileT := val; StructureChanged; end; end; procedure TgxPipe.SetNormalMode(const val: TPipeNormalMode); begin if val <> FNormalMode then begin FNormalMode := val; StructureChanged; end; end; procedure TgxPipe.SetNormalSmoothAngle(const val: Single); begin if val <> FNormalSmoothAngle then begin FNormalSmoothAngle := val; if NormalMode = pnmAdvanced then StructureChanged; end; end; procedure TgxPipe.Assign(Source: TPersistent); begin if Source is TgxPipe then begin Slices := TgxPipe(Source).Slices; Parts := TgxPipe(Source).Parts; Radius := TgxPipe(Source).Radius; NodesColorMode := TgxPipe(Source).NodesColorMode; TexCoordMode := TgxPipe(Source).TexCoordMode; TexCoordTileS := TgxPipe(Source).TexCoordTileS; TexCoordTileT := TgxPipe(Source).TexCoordTileT; end; inherited; end; var vSinCache, vCosCache: array of Single; procedure TgxPipe.BuildList(var rci: TgxRenderContextInfo); type TNodeData = record pos: TAffineVector; normal: TAffineVector; innormal: TAffineVector; sidedir: TVector3f; end; TRowData = record node: array of TNodeData; Color: TgxColorVector; center: TVector3f; textcoordT: Single; end; PRowData = ^TRowData; const cPNCMtoEnum: array [pncmEmission .. pncmAmbientAndDiffuse] of GLEnum = (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: TVector4f; const normal: TAffineVector; invert: Boolean; TextCoordTileS: Single); var i: Integer; begin begin if NodesColorMode <> pncmNone then glColor4fv(@row^.Color); // it was necessary to change build process to generate textcoords glBegin(GL_TRIANGLE_STRIP); glNormal3fv(@normal); case TexCoordMode of ptcmDefault, ptcmManual: begin if invert then begin for i := 0 to High(row^.node) - 1 do begin glTexCoord2f(i / (High(row^.node)) * TextCoordTileS, 1); glVertex3fv(@row^.node[i].pos); glTexCoord2f(i / (High(row^.node)) * TextCoordTileS, 0); glVertex3fv(@center); end; glTexCoord2f(TextCoordTileS, 1); glVertex3fv(@row^.node[High(row^.node)].pos); end else begin for i := High(row^.node) downto 1 do begin glTexCoord2f(i / (High(row^.node)) * TextCoordTileS, 0); glVertex3fv(@row^.node[i].pos); glTexCoord2f(i / (High(row^.node)) * TextCoordTileS, 1); glVertex3fv(@center); end; glTexCoord2f(0, 0); glVertex3fv(@row^.node[0].pos); end; end; end; glEnd; end; end; procedure CalculateSides(prevRow, curRow: PRowData; 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 glBegin(GL_TRIANGLE_STRIP); if outside then begin if NodesColorMode <> pncmNone then glColor4fv(@curRow^.Color); glTexCoord2f(0, curRow^.textcoordT * TextCoordTileT); glNormal3fv(@curRow^.node[0].normal); glVertex3fv(@curRow^.node[0].pos); for j := 0 to Slices - 1 do begin if NodesColorMode <> pncmNone then glColor4fv(@prevRow^.Color); glTexCoord2f(j / Slices * TextCoordTileS, prevRow^.textcoordT * TextCoordTileT); glNormal3fv(@prevRow^.node[j].normal); glVertex3fv(@prevRow^.node[j].pos); if NodesColorMode <> pncmNone then glColor4fv(@curRow^.Color); glTexCoord2f((j + 1) / Slices * TextCoordTileS, curRow^.textcoordT * TextCoordTileT); glNormal3fv(@curRow^.node[j + 1].normal); glVertex3fv(@curRow^.node[j + 1].pos); end; if NodesColorMode <> pncmNone then glColor4fv(@prevRow^.Color); glTexCoord2f(TextCoordTileS, prevRow^.textcoordT * TextCoordTileT); glNormal3fv(@prevRow^.node[Slices].normal); glVertex3fv(@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 glColor4fv(@prevRow^.Color); glTexCoord2f(0, prevRow^.textcoordT * TextCoordTileT); glNormal3fv(@prevRow^.node[0].innormal); glVertex3fv(@prevRow^.node[0].pos); for j := 0 to Slices - 1 do begin if NodesColorMode <> pncmNone then glColor4fv(@curRow^.Color); glTexCoord2f(j / Slices * TextCoordTileS, curRow^.textcoordT * TextCoordTileT); glNormal3fv(@curRow^.node[j].innormal); glVertex3fv(@curRow^.node[j].pos); if NodesColorMode <> pncmNone then glColor4fv(@prevRow^.Color); glTexCoord2f((j + 1) / Slices * TextCoordTileS, prevRow^.textcoordT * TextCoordTileT); glNormal3fv(@prevRow^.node[j + 1].innormal); glVertex3fv(@prevRow^.node[j + 1].pos); end; if NodesColorMode <> pncmNone then glColor4fv(@curRow^.Color); glTexCoord2f(TextCoordTileS, curRow^.textcoordT * TextCoordTileT); glNormal3fv(@curRow^.node[Slices].innormal); glVertex3fv(@curRow^.node[Slices].pos); end; glEnd; 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 // create position spline posSpline := Nodes.CreateNewCubicSpline; // create radius spline GetMem(ra, SizeOf(Single) * Nodes.Count); for i := 0 to Nodes.Count - 1 do ra^[i] := TgxPipeNode(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 glColorMaterial(GL_FRONT_AND_BACK, cPNCMtoEnum[NodesColorMode]); rci.gxStates.Enable(stColorMaterial); end else rci.gxStates.Disable(stColorMaterial); CalculateRow(@rows[0], PAffineVector(@Nodes[0].AsVector)^, normal, TgxPipeNode(Nodes[0]).RadiusFactor); rows[0].Color := TgxPipeNodes(Nodes)[0].Color.Color; case TexCoordMode of ptcmDefault: rows[0].textcoordT := 0; ptcmManual: rows[0].textcoordT := TgxPipeNode(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(TgxPipeNodes(Nodes)[k].Color.Color, TgxPipeNodes(Nodes)[k + 1].Color.Color, Frac(T)) else rows[curRow].Color := TgxPipeNodes(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(TgxPipeNode(Nodes[k]).TexCoordT, TgxPipeNode(Nodes[k + 1]).TexCoordT, Frac(T)) else rows[curRow].textcoordT := TgxPipeNode(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 := TgxPipeNode(Nodes[i]).TexCoordT; end; CalculateRow(@rows[curRow], PAffineVector(@Nodes[i].AsVector)^, Nodes.Vector(i), TgxPipeNode(Nodes[i]).RadiusFactor); rows[curRow].Color := TgxPipeNodes(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, TgxPipeNode(Nodes[i]).RadiusFactor); rows[0].Color := TgxPipeNodes(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; // ------------------ // ------------------ TgxExtrusionSolid ------------------ // ------------------ procedure TgxExtrusionSolid.Assign(Source: TPersistent); begin if Source is TgxExtrusionSolid then begin FStacks := TgxExtrusionSolid(Source).FStacks; FNormals := TgxExtrusionSolid(Source).FNormals; FNormalDirection := TgxExtrusionSolid(Source).FNormalDirection; FParts := TgxExtrusionSolid(Source).FParts; end; inherited; end; procedure TgxExtrusionSolid.BuildList(var rci: TgxRenderContextInfo); 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; glBegin(GL_TRIANGLE_STRIP); glNormal3fv(@normTop); glTexCoord2fv(@topTPBase); glVertex3fv(@topBase); for step := 1 to FStacks do begin glNormal3fv(@normBottom); glTexCoord2fv(@bottomTPBase); glVertex3fv(@bottomBase); topNext.Z := step * deltaZ; bottomNext.Z := topNext.Z; topTPNext.T := topNext.Z; bottomTPNext.T := bottomNext.Z; glTexCoord2fv(@topTPNext); glNormal3fv(@normTop); glVertex3fv(@topNext); topBase := topNext; topTPBase := topTPNext; bottomBase := bottomNext; bottomTPBase := bottomTPNext; end; glNormal3fv(@normBottom); glTexCoord2fv(@bottomTPBase); glVertex3fv(@bottomBase); glEnd; 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; glTexCoord2fv(@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 glPushMatrix; glTranslatef(0, 0, FHeight); RenderTesselatedPolygon(True, @normal, invertedNormals); glPopMatrix; end; // tessellate start polygon if espStartPolygon in FParts then begin NegateVector(normal); RenderTesselatedPolygon(True, @normal, not invertedNormals); end; end; end; constructor TgxExtrusionSolid.Create(AOwner: TComponent); begin inherited; FHeight := 1; FStacks := 1; FNormals := nsFlat; FNormalDirection := ndOutside; FParts := [espOutside]; MinSmoothAngle := 5; FAxisAlignedDimensionsCache.X := -1; end; destructor TgxExtrusionSolid.Destroy; begin inherited; end; procedure TgxExtrusionSolid.SetHeight(const Value: Single); begin if (Value <> FHeight) then begin FHeight := Value; StructureChanged; end; end; procedure TgxExtrusionSolid.SetMinSmoothAngle(const Value: Single); var S, c: Single; begin FMinSmoothAngle := Value; SinCosine(Value * cPIdiv180, S, c); FMinSmoothAngleCos := c; end; procedure TgxExtrusionSolid.SetNormalDirection(const val: TgxNormalDirection); begin if FNormalDirection <> val then begin FNormalDirection := val; StructureChanged; end; end; procedure TgxExtrusionSolid.SetNormals(const val: TgxNormalSmoothing); begin if FNormals <> val then begin FNormals := val; StructureChanged; end; end; procedure TgxExtrusionSolid.SetParts(const val: TgxExtrusionSolidParts); begin if FParts <> val then begin FParts := val; StructureChanged; end; end; procedure TgxExtrusionSolid.SetStacks(const val: Integer); begin if (val <> FStacks) and (val > 0) then begin FStacks := val; StructureChanged; end; end; function TgxExtrusionSolid.AxisAlignedDimensionsUnscaled: TVector4f; 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 TgxExtrusionSolid.StructureChanged; begin FAxisAlignedDimensionsCache.X := -1; inherited; end; // ------------------------------------------------------------------ initialization // ------------------------------------------------------------------ RegisterClasses([TgxRevolutionSolid, TgxExtrusionSolid, TgxPipe]); end.