2
// This unit is part of the GLScene Engine https://github.com/glscene
5
Custom ODE collider implementations.
8
Heightfield collider code originally adapted from Mattias Fagerlund's
9
DelphiODE terrain collision demo.
10
Website: http://www.cambrianlabs.com/Mattias/DelphiODE
14
19/06/14 - PW - Changed some types from Single to TdReal to permit ODE be double based in ODEImport.pas
15
10/11/12 - PW - Added CPP compatibility: restored records with arrays instead of vector arrays
16
23/08/10 - Yar - Added OpenGLTokens to uses, replaced OpenGL1x functions to OpenGLAdapter
17
22/04/10 - Yar - Fixes after GLState revision
18
05/03/10 - DanB - More state added to TGLStateCache
19
17/11/09 - DaStr - Improved Unix compatibility
20
(thanks Predator) (BugtrackerID = 2893580)
21
08/11/09 - DaStr - Improved FPC compatibility
22
(thanks Predator) (BugtrackerID = 2893580)
23
12/04/08 - DaStr - Cleaned up uses section
24
(thanks Sandor Domokos) (BugtrackerID = 1808373)
25
06/02/08 - Mrqzzz - Upgrade to ODE 0.9 (by Paul Robello)
26
Fixed reference to odeimport
27
25/12/07 - DaStr - Fixed memory leak in TGLODECustomCollider.Destroy()
28
(thanks Sandor Domokos) (BugtrackerID = 1808373)
29
10/10/07 - Mrqzzz - Fixed reference ODEGL.ODERToGLSceneMatrix
30
07/10/07 - Mrqzzz - Added reference to ODEGL
31
11/09/07 - Mrqzzz - Added reference to ODEImport
32
07/06/07 - DaStr - Added GLColor to uses (BugtrackerID = 1732211)
34
08/12/04 - SG - Added contact point rendering to TGLODECustomCollider.
35
07/12/04 - SG - Added new TGLODECustomCollider class,
36
Geom collide code now uses Resolution to determine the
37
number of contact points to generate.
38
19/11/04 - SG - Changed TGLODETerrainCollider to TGLODEHeightField
39
which now inherits from TGLODEBehaviour and works for
40
both TGLTerrainRenderer and TGLHeightField objects.
41
Added Capsule, Cylinder and Cone collider code for
42
the heightfield collider.
43
23/04/04 - SG - Removed freeform static collider
44
29/10/03 - SG - Fix for GLODETerrainCollider (Matheus Degiovani)
45
30/07/03 - SG - Creation.
48
unit GLODECustomColliders;
78
Normal: TAffineVector;
82
// TGLODECustomCollider
84
{ The custom collider is designed for generic contact handling. There is a
85
contact point generator for sphere, box, capped cylinder, cylinder and
88
Once the contact points for a collision are generated the abstract Collide
89
function is called to generate the depth and the contact position and
90
normal. These points are then sorted and the deepest are applied to ODE. }
91
TGLODECustomCollider = class(TGLODEBehaviour)
98
FContactResolution: Single;
100
FRenderContacts: Boolean;
101
FContactRenderPoints: TAffineVectorList;
103
FContactColor: TGLColor;
107
procedure Initialize; override;
108
procedure Finalize; override;
110
procedure WriteToFiler(writer: TWriter); override;
111
procedure ReadFromFiler(reader: TReader); override;
113
// Test a position for a collision and fill out the contact information.
114
function Collide(aPos: TAffineVector; var Depth: Single;
115
var cPos, cNorm: TAffineVector): Boolean; virtual; abstract;
117
// Clears the contact list so it's ready for another collision.
118
procedure ClearContacts;
120
// Add a contact point to the list for ApplyContacts to processes.
121
procedure AddContact(x, y, z: TdReal); overload;
122
procedure AddContact(pos: TAffineVector); overload;
124
// Sort the current contact list and apply the deepest to ODE.
125
function ApplyContacts(o1, o2: PdxGeom; flags: Integer;
126
contact: PdContactGeom; skip: Integer): Integer;
128
{ Set the transform used that transforms contact points generated with
130
procedure SetTransform(ATransform: TMatrix);
132
procedure SetContactResolution(const Value: Single);
133
procedure SetRenderContacts(const Value: Boolean);
134
procedure SetPointSize(const Value: Single);
135
procedure SetContactColor(const Value: TGLColor);
139
constructor Create(AOwner: TGLXCollection); override;
140
destructor Destroy; override;
142
procedure Render(var rci: TGLRenderContextInfo); override;
144
property Geom: PdxGeom read FGeom;
147
{ Defines the resolution of the contact points created for the colliding
148
Geom. The number of contact points generated change base don the size
149
of the object and the ContactResolution. Lower values generate higher
150
resolution contact boundaries, and thus smoother but slower collisions. }
151
property ContactResolution: Single read FContactResolution write SetContactResolution;
152
{ Toggle contact point rendering on and off. (Rendered through the assigned
153
Manager.RenderPoint. }
154
property RenderContacts: Boolean read FRenderContacts write SetRenderContacts;
155
// Contact point rendering size (in pixels).
156
property PointSize: Single read FPointSize write SetPointSize;
157
// Contact point rendering color.
158
property ContactColor: TGLColor read FContactColor write SetContactColor;
164
{ Add this behaviour to a TGLHeightField or TGLTerrainRenderer to enable
165
height based collisions for spheres, boxes, capped cylinders, cylinders
167
TGLODEHeightField = class(TGLODECustomCollider)
170
procedure WriteToFiler(writer: TWriter); override;
171
procedure ReadFromFiler(reader: TReader); override;
173
function Collide(aPos: TAffineVector; var Depth: Single;
174
var cPos, cNorm: TAffineVector): Boolean; override;
178
constructor Create(AOwner: TGLXCollection); override;
180
class function FriendlyName: string; override;
181
class function FriendlyDescription: string; override;
182
class function UniqueItem: Boolean; override;
183
class function CanAddTo(collection: TGLXCollection): Boolean; override;
187
function GetODEHeightField(obj: TGLBaseSceneObject): TGLODEHeightField;
188
function GetOrCreateODEHeightField(obj: TGLBaseSceneObject): TGLODEHeightField;
193
vCustomColliderClass: TdGeomClass;
194
vCustomColliderClassNum: Integer;
199
function GetODEHeightField(obj: TGLBaseSceneObject): TGLODEHeightField;
201
result := TGLODEHeightField(obj.Behaviours.GetByClass(TGLODEHeightField));
204
// GetOrCreateODEHeightField
207
function GetOrCreateODEHeightField(obj: TGLBaseSceneObject): TGLODEHeightField;
209
result := TGLODEHeightField(obj.GetOrCreateBehaviour(TGLODEHeightField));
212
// GetColliderFromGeom
215
function GetColliderFromGeom(aGeom: PdxGeom): TGLODECustomCollider;
220
temp := dGeomGetData(aGeom);
221
if Assigned(temp) then
222
if temp is TGLODECustomCollider then
223
Result := TGLODECustomCollider(temp);
229
function ContactSort(Item1, Item2: Pointer): Integer;
231
c1, c2: TContactPoint;
233
c1 := TContactPoint(Item1);
234
c2 := TContactPoint(Item2);
235
if c1.Depth > c2.Depth then
237
else if c1.Depth = c2.Depth then
246
function CollideSphere(o1, o2: PdxGeom; flags: Integer;
247
contact: PdContactGeom; skip: Integer): Integer; cdecl;
249
Collider: TGLODECustomCollider;
254
rad, dx, dy, dz: TdReal;
257
Collider := GetColliderFromGeom(o1);
258
if not Assigned(Collider) then
261
pos := dGeomGetPosition(o2);
262
R := dGeomGetRotation(o2);
263
ODEGL.ODERToGLSceneMatrix(mat, R^, pos^);
264
Collider.SetTransform(mat);
266
rad := dGeomSphereGetRadius(o2);
268
res := Round(10 * rad / Collider.ContactResolution);
272
Collider.AddContact(0, 0, -rad);
273
Collider.AddContact(0, 0, rad);
274
rmat := CreateRotationMatrixZ(2 * Pi / res);
275
for i := 0 to res - 1 do
277
mat := MatrixMultiply(rmat, mat);
278
Collider.SetTransform(mat);
279
for j := -(res div 2) + 1 to (res div 2) - 1 do
281
dx := rad * cos(j * Pi / res);
283
dz := rad * sin(j * Pi / res);
284
Collider.AddContact(dx, dy, dz);
288
Result := Collider.ApplyContacts(o1, o2, flags, contact, skip);
289
Collider.SetTransform(IdentityHMGMatrix);
295
function CollideBox(o1, o2: PdxGeom; flags: Integer;
296
contact: PdContactGeom; skip: Integer): Integer; cdecl;
298
Collider: TGLODECustomCollider;
300
rcpres, len1, len2: Single;
307
Collider := GetColliderFromGeom(o1);
308
if not Assigned(Collider) then
311
pos := dGeomGetPosition(o2);
312
R := dGeomGetRotation(o2);
313
ODEGL.ODERToGLSceneMatrix(mat, R^, pos^);
314
Collider.SetTransform(mat);
316
dGeomBoxGetLengths(o2, s);
318
res := Round(Sqrt(MaxFloat([s[0], s[1], s[2]])) / Collider.ContactResolution);
330
AddContact(s[0], s[1], s[2]);
331
AddContact(s[0], s[1], -s[2]);
332
AddContact(s[0], -s[1], s[2]);
333
AddContact(s[0], -s[1], -s[2]);
334
AddContact(-s[0], s[1], s[2]);
335
AddContact(-s[0], s[1], -s[2]);
336
AddContact(-s[0], -s[1], s[2]);
337
AddContact(-s[0], -s[1], -s[2]);
340
for i := -(res - 1) to (res - 1) do
342
len1 := i * rcpres * s[0];
343
AddContact(len1, s[1], s[2]);
344
AddContact(len1, s[1], -s[2]);
345
AddContact(len1, -s[1], s[2]);
346
AddContact(len1, -s[1], -s[2]);
347
len1 := i * rcpres * s[1];
348
AddContact(s[0], len1, s[2]);
349
AddContact(s[0], len1, -s[2]);
350
AddContact(-s[0], len1, s[2]);
351
AddContact(-s[0], len1, -s[2]);
352
len1 := i * rcpres * s[2];
353
AddContact(s[0], s[1], len1);
354
AddContact(s[0], -s[1], len1);
355
AddContact(-s[0], s[1], len1);
356
AddContact(-s[0], -s[1], len1);
360
for i := -(res - 1) to (res - 1) do
361
for j := -(res - 1) to (res - 1) do
363
len1 := i * rcpres * s[0];
364
len2 := j * rcpres * s[1];
365
AddContact(len1, len2, s[2]);
366
AddContact(len1, len2, -s[2]);
367
len2 := j * rcpres * s[2];
368
AddContact(len1, s[1], len2);
369
AddContact(len1, -s[1], len2);
370
len1 := i * rcpres * s[1];
371
AddContact(s[0], len1, len2);
372
AddContact(-s[0], len1, len2);
376
Result := Collider.ApplyContacts(o1, o2, flags, contact, skip);
377
Collider.SetTransform(IdentityHMGMatrix);
383
function CollideCapsule(o1, o2: PdxGeom; flags: Integer;
384
contact: PdContactGeom; skip: Integer): Integer; cdecl;
386
Collider: TGLODECustomCollider;
391
rad, len, dx, dy, dz: TdReal;
395
Collider := GetColliderFromGeom(o1);
396
if not Assigned(Collider) then
399
pos := dGeomGetPosition(o2);
400
R := dGeomGetRotation(o2);
401
ODEGL.ODERToGLSceneMatrix(mat, R^, pos^);
402
Collider.SetTransform(mat);
404
dGeomCapsuleGetParams(o2, rad, len);
406
res := Round(5 * MaxFloat(4 * rad, len) / Collider.ContactResolution);
410
rmat := CreateRotationMatrixZ(2 * Pi / res);
413
AddContact(0, 0, -rad - 0.5 * len);
414
AddContact(0, 0, rad + 0.5 * len);
415
for i := 0 to res - 1 do
417
mat := MatrixMultiply(rmat, mat);
421
AddContact(rad, 0, len * (j / res - 0.5));
423
for j := 1 to (res div 2) - 1 do
425
dx := rad * cos(j * Pi / res);
427
dz := rad * sin(j * Pi / res);
428
Collider.AddContact(dx, dy, -dz - 0.5 * len);
429
Collider.AddContact(dx, dy, dz + 0.5 * len);
434
Result := Collider.ApplyContacts(o1, o2, flags, contact, skip);
435
Collider.SetTransform(IdentityHMGMatrix);
441
function CollideCylinder(o1, o2: PdxGeom; flags: Integer;
442
contact: PdContactGeom; skip: Integer): Integer; cdecl;
444
Collider: TGLODECustomCollider;
449
rad, len, dx, dy: TdReal;
453
Collider := GetColliderFromGeom(o1);
454
if not Assigned(Collider) then
457
pos := dGeomGetPosition(o2);
458
R := dGeomGetRotation(o2);
459
ODEGL.ODERToGLSceneMatrix(mat, R^, pos^);
460
Collider.SetTransform(mat);
462
dGeomCylinderGetParams(o2, rad, len);
464
res := Round(5 * MaxFloat(4 * rad, len) / Collider.ContactResolution);
470
AddContact(0, -0.5 * len, 0);
471
AddContact(0, 0.5 * len, 0);
472
for i := 0 to res - 1 do
474
SinCos(2 * Pi * i / res, rad, dy, dx);
475
AddContact(dx, -0.5 * len, dy);
476
AddContact(dx, 0, dy);
477
AddContact(dx, 0.5 * len, dy);
480
AddContact(dx, len * (j / res - 0.5), dy);
482
for j := 1 to (res div 2) - 1 do
484
SinCos(2 * Pi * i / res, rad * j / (res div 2), dy, dx);
485
AddContact(dx, -0.5 * len, dy);
486
AddContact(dx, 0.5 * len, dy);
491
Result := Collider.ApplyContacts(o1, o2, flags, contact, skip);
492
Collider.SetTransform(IdentityHMGMatrix);
495
// GetCustomColliderFn
498
function GetCustomColliderFn(num: Integer): TdColliderFn; cdecl;
500
if num = dSphereClass then
501
Result := CollideSphere
502
else if num = dBoxClass then
504
else if num = dCapsuleClass then
505
Result := CollideCapsule
506
else if num = dCylinderClass then
507
Result := CollideCylinder
513
// --------------- TGLODECustomCollider --------------
519
constructor TGLODECustomCollider.Create(AOwner: TGLXCollection);
523
FContactList := TList.Create;
524
FContactCache := TList.Create;
526
FContactResolution := 1;
528
FRenderContacts := False;
529
FContactRenderPoints := TAffineVectorList.Create;
530
FContactColor := TGLColor.CreateInitialized(Self, clrRed, NotifyChange);
537
destructor TGLODECustomCollider.Destroy;
542
for i := 0 to FContactCache.Count - 1 do
543
TContactPoint(FContactCache[i]).Free;
545
FContactRenderPoints.Free;
553
procedure TGLODECustomCollider.Initialize;
555
if not Assigned(Manager) then
557
if not Assigned(Manager.Space) then
559
if vCustomColliderClassNum = 0 then
561
with vCustomColliderClass do
564
collider := GetCustomColliderFn;
565
aabb := dInfiniteAABB;
569
vCustomColliderClassNum := dCreateGeomClass(vCustomColliderClass);
571
FGeom := dCreateGeom(vCustomColliderClassNum);
572
dGeomSetData(FGeom, Self);
573
dSpaceAdd(Manager.Space, FGeom);
580
procedure TGLODECustomCollider.Finalize;
582
if not Initialized then
584
if Assigned(FGeom) then
595
procedure TGLODECustomCollider.WriteToFiler(writer: TWriter);
600
WriteInteger(0); // Archive version
601
WriteFloat(FContactResolution);
602
WriteBoolean(FRenderContacts);
603
WriteFloat(FPointSize);
604
Write(PByte(FContactColor.AsAddress)^, 4);
611
procedure TGLODECustomCollider.ReadFromFiler(reader: TReader);
613
archiveVersion: Integer;
618
archiveVersion := ReadInteger;
619
Assert(archiveVersion = 0); // Archive version
620
FContactResolution := ReadFloat;
621
FRenderContacts := ReadBoolean;
622
FPointSize := ReadFloat;
623
Read(PByte(FContactColor.AsAddress)^, 4);
630
procedure TGLODECustomCollider.ClearContacts;
638
procedure TGLODECustomCollider.AddContact(x, y, z: TdReal);
640
AddContact(AffineVectorMake(x, y, z));
646
procedure TGLODECustomCollider.AddContact(pos: TAffineVector);
648
absPos, colPos, colNorm: TAffineVector;
650
ContactPoint: TContactPoint;
652
absPos := AffineVectorMake(VectorTransform(PointMake(pos), FTransform));
653
if Collide(absPos, depth, colPos, colNorm) then
655
if FContactList.Count < FContactCache.Count then
656
ContactPoint := FContactCache[FContactList.Count]
659
ContactPoint := TContactPoint.Create;
660
FContactCache.Add(ContactPoint);
662
ContactPoint.Position := colPos;
663
ContactPoint.Normal := colNorm;
664
ContactPoint.Depth := depth;
665
FContactList.Add(ContactPoint);
667
if FRenderContacts and Manager.Visible and Manager.VisibleAtRunTime then
668
FContactRenderPoints.Add(absPos);
674
function TGLODECustomCollider.ApplyContacts(o1, o2: PdxGeom;
675
flags: Integer; contact: PdContactGeom; skip: Integer): Integer;
677
i, maxContacts: integer;
679
FContactList.Sort(ContactSort);
681
maxContacts := flags and $FFFF;
683
for i := 0 to FContactList.Count - 1 do
685
if Result >= maxContacts then
687
with TContactPoint(FContactList[i]) do
689
contact.depth := Depth;
690
contact.pos[0] := Position.V[0];
691
contact.pos[1] := Position.V[1];
692
contact.pos[2] := Position.V[2];
694
contact.normal[0] := -Normal.V[0];
695
contact.normal[1] := -Normal.V[1];
696
contact.normal[2] := -Normal.V[2];
697
contact.normal[3] := 0;
701
contact := PdContactGeom(Integer(contact) + skip);
712
procedure TGLODECustomCollider.SetTransform(ATransform: TMatrix);
714
FTransform := ATransform;
717
// SetContactResolution
720
procedure TGLODECustomCollider.SetContactResolution(const Value: Single);
722
FContactResolution := Value;
723
if FContactResolution <= 0 then
724
FContactResolution := 0.01;
730
procedure TGLODECustomCollider.Render(var rci: TGLRenderContextInfo);
734
if FRenderContacts and (FContactRenderPoints.Count > 0) then
736
GL.Color3fv(FContactColor.AsAddress);
737
rci.GLStates.PointSize := FPointSize;
738
GL.Begin_(GL_POINTS);
739
for i := 0 to FContactRenderPoints.Count - 1 do
740
GL.Vertex3fv(@FContactRenderPoints.List[i]);
743
FContactRenderPoints.Clear;
749
procedure TGLODECustomCollider.SetRenderContacts(const Value: Boolean);
751
if Value <> FRenderContacts then
753
FRenderContacts := Value;
761
procedure TGLODECustomCollider.SetContactColor(const Value: TGLColor);
763
FContactColor.Assign(Value);
769
procedure TGLODECustomCollider.SetPointSize(const Value: Single);
771
if Value <> FPointSize then
779
// --------------- TGLODEHeightField --------------
785
constructor TGLODEHeightField.Create(AOwner: TGLXCollection);
790
if Assigned(AOwner) then
792
if Assigned(AOwner.Owner) then
794
if ((AOwner.Owner) is TGLTerrainRenderer)
795
or ((AOwner.Owner) is TGLHeightField) then
801
raise Exception.Create('This element must be a behaviour of a TGLTerrainRenderer or TGLHeightField');
803
inherited Create(AOwner);
809
procedure TGLODEHeightField.WriteToFiler(writer: TWriter);
814
WriteInteger(0); // Archive version
821
procedure TGLODEHeightField.ReadFromFiler(reader: TReader);
823
archiveVersion: Integer;
828
archiveVersion := ReadInteger;
829
Assert(archiveVersion = 0); // Archive version
836
class function TGLODEHeightField.FriendlyName: string;
838
Result := 'ODE HeightField Collider';
841
// FriendlyDescription
844
class function TGLODEHeightField.FriendlyDescription: string;
846
Result := 'A custom ODE collider powered by it''s parent TGLTerrainRenderer or TGLHeightField';
852
class function TGLODEHeightField.UniqueItem: Boolean;
860
class function TGLODEHeightField.CanAddTo(collection: TGLXCollection): Boolean;
863
if collection is TGLBehaviours then
864
if Assigned(TGLBehaviours(collection).Owner) then
865
if (TGLBehaviours(collection).Owner is TGLHeightField)
866
or (TGLBehaviours(collection).Owner is TGLTerrainRenderer) then
873
function TGLODEHeightField.Collide(aPos: TAffineVector;
874
var Depth: Single; var cPos, cNorm: TAffineVector): Boolean;
876
function AbsoluteToLocal(vec: TVector): TVector;
880
if Owner.Owner is TGLHeightField then
881
Result := TGLHeightField(Owner.Owner).AbsoluteToLocal(vec)
882
else if Owner.Owner is TGLTerrainRenderer then
884
mat := TGLTerrainRenderer(Owner.Owner).AbsoluteMatrix;
885
NormalizeMatrix(mat);
887
Result := VectorTransform(vec, mat);
893
function LocalToAbsolute(vec: TVector): TVector;
897
if Owner.Owner is TGLHeightField then
898
Result := TGLHeightField(Owner.Owner).LocalToAbsolute(vec)
899
else if Owner.Owner is TGLTerrainRenderer then
901
mat := TGLTerrainRenderer(Owner.Owner).AbsoluteMatrix;
902
NormalizeMatrix(mat);
903
Result := VectorTransform(vec, mat);
909
function GetHeight(pos: TVector; var height: Single): Boolean;
915
if Owner.Owner is TGLTerrainRenderer then
917
height := TGLTerrainRenderer(Owner.Owner).InterpolatedHeight(LocalToAbsolute(pos));
920
else if Owner.Owner is TGLHeightField then
922
if Assigned(TGLHeightField(Owner.Owner).OnGetHeight) then
924
TGLHeightField(Owner.Owner).OnGetHeight(pos.V[0], pos.V[1], height, dummy1, dummy2);
935
temp1, temp2: TAffineVector;
937
localPos := AbsoluteToLocal(PointMake(aPos));
938
if GetHeight(localPos, height) then
940
Depth := height - localPos.V[2];
941
Result := (Depth > 0);
944
localPos.V[2] := height;
945
cPos := AffineVectorMake(LocalToAbsolute(localPos));
946
temp1.V[0] := localPos.V[0] + cDelta;
947
temp1.V[1] := localPos.V[1];
948
temp1.V[2] := localPos.V[2];
949
GetHeight(PointMake(temp1), temp1.V[2]);
950
temp2.V[0] := localPos.V[0];
951
temp2.V[1] := localPos.V[1] + cDelta;
952
temp2.V[2] := localPos.V[2];
953
GetHeight(PointMake(temp2), temp2.V[2]);
954
cNorm := CalcPlaneNormal(AffineVectorMake(localPos), temp1, temp2);
955
cNorm := AffineVectorMake(LocalToAbsolute(VectorMake(cNorm)));
962
// ------------------------------------------------------------------
963
// ------------------------------------------------------------------
964
// ------------------------------------------------------------------
966
// ------------------------------------------------------------------
967
// ------------------------------------------------------------------
968
// ------------------------------------------------------------------
970
RegisterXCollectionItemClass(TGLODEHeightField);
972
// ------------------------------------------------------------------
973
// ------------------------------------------------------------------
974
// ------------------------------------------------------------------
976
// ------------------------------------------------------------------
977
// ------------------------------------------------------------------
978
// ------------------------------------------------------------------
980
UnregisterXCollectionItemClass(TGLODEHeightField);