/usr/src/castle-game-engine-5.2.0/x3d/x3dnodes_extrusion.inc is in castle-game-engine-src 5.2.0-3.
This file is owned by root:root, with mode 0o644.
The actual contents of the file can be viewed below.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 | {
Copyright 2008-2014 Michalis Kamburelis.
This file is part of "Castle Game Engine".
"Castle Game Engine" is free software; see the file COPYING.txt,
included in this distribution, for details about the copyright.
"Castle Game Engine" is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
----------------------------------------------------------------------------
}
type
{ Utility for dealing with Extrusion.
Calculating vertices (and other properties) of Extrusion
is non-trivial enough to be separated into this file. We want to reuse
this by TExtrusionNode.BoundingBox calculation,
TExtrusionNode.Triangulate etc. }
TVRMLExtrusion = class
private
FHigh: Integer;
FNode: TExtrusionNode;
FSpineClosed, FCrossSectionClosed: boolean;
FCrossSectionOmit: Cardinal;
FBeginEndCapsMatching: boolean;
procedure SetNode(Value: TExtrusionNode);
public
constructor Create;
{ Node used. Always assign something non-nil, and generally
use rest of this class only when this is assigned. }
property Node: TExtrusionNode read FNode write SetNode;
{ You have spines from 0 to High.
Remember that this can be -1, if no spine points are defined.
You can safely ask SpineXxxTransform about various values between
0..High. }
property High: Integer read FHigh;
{ Same thing as @link(TExtrusionNode.SpineClosed Node.SpineClosed) and
@link(TExtrusionNode.CrossSectionClosed Node.CrossSectionClosed),
just calculated once (when setting Node) for speed.
@groupBegin }
property SpineClosed: boolean read FSpineClosed;
property CrossSectionClosed: boolean read FCrossSectionClosed;
{ @groupEnd }
{ If we should omit a first (or last) vertex when rendering caps,
this is 1, otherwise it is 0.
When crossSection is closed, we really should do it,
otherwise our concave triangulation may get confused ---
see extrusion_concave_cap.wrl testcase. }
property CrossSectionOmit: Cardinal read FCrossSectionOmit;
{ Are begin and end caps at the same place.
This is a stronger condition
than just a SpineClosed: whole SpineTransformTo1st must be
guaranteed the same at the beginning and end. The Extrusion rules are such
that closed spine -> always produces the same automatic orientation
calculated at the beginning and end (X, Y, Z vectors in
TVRMLExtrusion.SpineTransformTo1st implementation). But we also
have to compare Orientation and Scale factors --- only when they
also match, the caps match. }
property BeginEndCapsMatching: boolean read FBeginEndCapsMatching;
{ If Spine > 0, LastY and LastZ must contain what was set here by calling
SpineTransformTo1st(Spine - 1, LastY, LastZ). }
procedure SpineTransformTo1st(Spine: Cardinal;
var LastY, LastZ: TVector3Single;
out Transform: TMatrix4Single);
procedure SpineScaleTo1st(Spine: Cardinal;
out Scale: TVector2Single);
procedure SpineOrientationTo1st(Spine: Cardinal;
out Orientation: TVector4Single);
end;
constructor TVRMLExtrusion.Create;
begin
inherited;
end;
procedure TVRMLExtrusion.SetNode(Value: TExtrusionNode);
var
BeginOrientation, EndOrientation: TVector4Single;
BeginScale, EndScale: TVector2Single;
begin
FNode := Value;
{ calculate FHigh }
if Node.FdSpine.Count = 0 then
begin
FHigh := -1;
{ ... and don't even check scale/orientation }
end else
if (Node.FdScale.Count = 0) or
(Node.FdOrientation.Count = 0) then
begin
OnWarning(wtMajor, 'VRML/X3D', 'Extrusion has no scale or orientation specified');
FHigh := -1;
end else
begin
FHigh := Node.FdSpine.Count - 1;
{ We will handle all other scale/orientation counts.
Excessive scale/orientation values will be ignored.
If not enough will be available then we'll only use the first one
(spec says the behavior is undefined then).
We know that at least one is available, we checked this above. }
if (Node.FdScale.Count > 1) and
(Node.FdScale.Count < Node.FdSpine.Count) then
OnWarning(wtMajor, 'VRML/X3D', 'Extrusion has more scales than 1, but not as much as spines. ' +
'We''ll use only the first scale.');
if (Node.FdOrientation.Count > 1) and
(Node.FdOrientation.Count < Node.FdSpine.Count) then
OnWarning(wtMajor, 'VRML/X3D', 'Extrusion has more orientations than 1, but not as much as spines. ' +
'We''ll use only the first orientation.');
end;
FSpineClosed := Node.SpineClosed;
FCrossSectionClosed := Node.CrossSectionClosed;
if FCrossSectionClosed then
FCrossSectionOmit := 1 else
FCrossSectionOmit := 0;
if SpineClosed then
begin
SpineScaleTo1st(0, BeginScale);
SpineScaleTo1st(High, EndScale);
SpineOrientationTo1st(0, BeginOrientation);
SpineOrientationTo1st(High, EndOrientation);
FBeginEndCapsMatching :=
VectorsPerfectlyEqual(BeginOrientation, EndOrientation) and
VectorsPerfectlyEqual(BeginScale, EndScale);
end else
FBeginEndCapsMatching := false;
end;
procedure TVRMLExtrusion.SpineScaleTo1st(Spine: Cardinal;
out Scale: TVector2Single);
begin
Assert(Between(Spine, 0, High));
{ So High must be >= 0, by the way.
Also, High checked that we have at least one scale. }
if Node.FdScale.Count < Node.FdSpine.Count then
Scale := Node.FdScale.Items.L[0] else
Scale := Node.FdScale.Items.L[Spine];
end;
procedure TVRMLExtrusion.SpineOrientationTo1st(Spine: Cardinal;
out Orientation: TVector4Single);
begin
Assert(Between(Spine, 0, High));
{ So High must be >= 0, by the way.
Also, High checked that we have at least one orientation. }
if Node.FdOrientation.Count < Node.FdSpine.Count then
Orientation := Node.FdOrientation.Items.L[0] else
Orientation := Node.FdOrientation.Items.L[Spine];
end;
procedure TVRMLExtrusion.SpineTransformTo1st(Spine: Cardinal;
var LastY, LastZ: TVector3Single;
out Transform: TMatrix4Single);
var
SpinePoints: TVector3SingleList;
{ Calculate Z by searching for the first non-colinear three spine
points. @false if not found. }
function FindFirstNonColinear(out Z: TVector3Single): boolean;
var
I: Integer;
begin
Result := false;
for I := 1 to High - 1 do
begin
{ Try to find first spine point with Z defined as non-zero.
This follows X3D / VRML spec ("If the Z-axis of the
first point is undefined..."). }
Z := VectorProduct(
VectorSubtract(SpinePoints.L[I + 1], SpinePoints.L[I]),
VectorSubtract(SpinePoints.L[I - 1], SpinePoints.L[I]));
if not ZeroVector(Z) then
begin
Result := true;
break;
end;
end;
end;
{ Calculate Y by searching for the first non-coincident two spine
points. Sets to (0, 1, 0) if not found.
Y is always normalized. }
procedure FindFirstNonCoincident(out Y: TVector3Single);
var
I: Integer;
begin
for I := 1 to High do
begin
Y := VectorSubtract(SpinePoints.L[I], SpinePoints.L[I - 1]);
if not ZeroVector(Y) then
begin
NormalizeTo1st(Y);
Exit;
end;
end;
Y := UnitVector3Single[1];
end;
{ Calculate Z based on Y (already normalized) if all spine points
are colinear. }
function AllColinear(const Y: TVector3Single): TVector3Single;
var
AngleRad: Single;
Rotation, ForPositiveAngleRad, ForNegativeAngleRad: TVector3Single;
begin
{ Spec: "If the entire spine is collinear,
the SCP is computed by finding the rotation of a
vector along the positive Y-axis (v1) to the vector
formed by the spine points (v2). The Y=0 plane is then rotated
by this value."
v2 is actually just our Y variable. }
AngleRad := AngleRadBetweenNormals(UnitVector3Single[1], Y);
Rotation := VectorProduct(UnitVector3Single[1], Y);
if ZeroVector(Rotation) then
begin
{ This means that Y is actually just equal (0, 1, 0).
So Z is just (0, 0, 1). }
Result := UnitVector3Single[2];
end else
begin
{ Is the rotation by AngleRad or -AngleRad?
Lame way of checking this below, we just check which result
produces back Y when rotated.
Note: the first implementation was like
if not VectorsEqual(ForPositiveAngleRad, Y) then
begin
AngleRad := -AngleRad;
Assert(VectorsEqual(ForNegativeAngleRad, Y));
end;
but this is obviously unsafe because of floating point errors,
there was always a chance that both VectorsEqual fail.
Fixed below to just choose the best one. }
ForPositiveAngleRad := RotatePointAroundAxisRad( AngleRad, UnitVector3Single[1], Rotation);
ForNegativeAngleRad := RotatePointAroundAxisRad(-AngleRad, UnitVector3Single[1], Rotation);
if PointsDistanceSqr(ForPositiveAngleRad, Y) >
PointsDistanceSqr(ForNegativeAngleRad, Y) then
AngleRad := -AngleRad;
Result := RotatePointAroundAxisRad(AngleRad, UnitVector3Single[2], Rotation);
end;
end;
procedure CalculateYZForClosed(out Y, Z: TVector3Single);
begin
{ Same for Spine = 0 and High, as this is the same point actually. }
Y := VectorSubtract(SpinePoints.L[1], SpinePoints.L[High - 1]);
if not ZeroVector(Y) then
NormalizeTo1st(Y) else
FindFirstNonCoincident(Y);
Z := VectorProduct(
VectorSubtract(SpinePoints.L[1], SpinePoints.L[0]),
VectorSubtract(SpinePoints.L[High - 1], SpinePoints.L[0]));
if not ZeroVector(Z) then
NormalizeTo1st(Z) else
if FindFirstNonColinear(Z) then
NormalizeTo1st(Z) else
Z := AllColinear(Y);
end;
var
X, Y, Z: TVector3Single;
Scale: TVector2Single;
Orientation: TVector4Single;
begin
Assert(Between(Spine, 0, High));
SpinePoints := Node.FdSpine.Items;
if High = 0 then
begin
Y := UnitVector3Single[1];
Z := UnitVector3Single[2];
end else
if Spine = 0 then
begin
if SpineClosed then
CalculateYZForClosed(Y, Z) else
begin
Y := VectorSubtract(SpinePoints.L[1], SpinePoints.L[0]);
if not ZeroVector(Y) then
NormalizeTo1st(Y) else
FindFirstNonCoincident(Y);
if FindFirstNonColinear(Z) then
NormalizeTo1st(Z) else
Z := AllColinear(Y);
end;
end else
if Integer(Spine) = High then
begin
if SpineClosed then
CalculateYZForClosed(Y, Z) else
begin
Y := VectorSubtract(SpinePoints.L[High], SpinePoints.L[High - 1]);
if not ZeroVector(Y) then
NormalizeTo1st(Y) else
Y := LastY;
Z := LastZ;
end;
end else
begin
{ Note that I avoided wasting time on checking SpineClosed
in this case, and that's good. This is the most common case
for this routine. }
Y := VectorSubtract(SpinePoints.L[Spine + 1], SpinePoints.L[Spine - 1]);
if not ZeroVector(Y) then
NormalizeTo1st(Y) else
Y := LastY;
Z := VectorProduct(
VectorSubtract(SpinePoints.L[Spine + 1], SpinePoints.L[Spine]),
VectorSubtract(SpinePoints.L[Spine - 1], SpinePoints.L[Spine]));
if not ZeroVector(Z) then
NormalizeTo1st(Z) else
Z := LastZ;
end;
if (Spine > 0) and (VectorDotProduct(LastZ, Z) < 0) then
begin
VectorNegateTo1st(Z);
end;
LastY := Y;
LastZ := Z;
X := VectorProduct(Y, Z);
SpineScaleTo1st(Spine, Scale);
SpineOrientationTo1st(Spine, Orientation);
Transform := MatrixMult(
TransformToCoordsMatrix(SpinePoints.L[Spine], X, Y, Z),
MatrixMult(RotationMatrixRad(Orientation[3],
Orientation[0], Orientation[1], Orientation[2]),
ScalingMatrix(Vector3Single(Scale[0], 1, Scale[1]))));
end;
|