GLS.Collision.pas 30 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966
  1. //
  2. // The graphics engine GLScene
  3. //
  4. unit GLS.Collision;
  5. (* Collision-detection management *)
  6. interface
  7. {$I Stage.Defines.inc}
  8. uses
  9. System.Classes,
  10. System.SysUtils,
  11. System.Types,
  12. Stage.VectorTypes,
  13. Stage.OpenGLTokens,
  14. Stage.VectorGeometry,
  15. Stage.Manager,
  16. GLS.Scene,
  17. GLS.XCollection,
  18. GLS.VectorLists,
  19. GLS.VectorFileObjects,
  20. GLS.GeometryBB;
  21. type
  22. TGLBCollision = class;
  23. TObjectCollisionEvent = procedure(Sender: TObject;
  24. object1, object2: TGLBaseSceneObject) of object;
  25. (* Defines how fine collision bounding is for a particular object.
  26. Possible values are :
  27. cbmPoint : the object is punctual and may only collide with volumes
  28. cbmSphere : the object is defined by its bounding sphere (radius is the max of axis-aligned dimensions)
  29. cbmEllipsoid the object is defined by its bounding axis-aligned ellipsoid
  30. cbmCube : the object is defined by a bounding axis-aligned "cube"
  31. cbmFaces : the object is defined by its faces (needs object-level support,
  32. if unavailable, uses cbmCube code) *)
  33. TCollisionBoundingMode = (cbmPoint, cbmSphere, cbmEllipsoid, cbmCube,
  34. cbmFaces);
  35. TFastCollisionChecker = function(obj1, obj2: TGLBaseSceneObject): Boolean;
  36. PFastCollisionChecker = ^TFastCollisionChecker;
  37. TGLCollisionManager = class(TComponent)
  38. private
  39. FClients: TList;
  40. FOnCollision: TObjectCollisionEvent;
  41. protected
  42. procedure RegisterClient(aClient: TGLBCollision);
  43. procedure DeRegisterClient(aClient: TGLBCollision);
  44. procedure DeRegisterAllClients;
  45. public
  46. constructor Create(AOwner: TComponent); override;
  47. destructor Destroy; override;
  48. procedure CheckCollisions;
  49. published
  50. property OnCollision: TObjectCollisionEvent read FOnCollision
  51. write FOnCollision;
  52. end;
  53. (* Collision detection behaviour.
  54. Allows an object to register to a TCollisionManager and be accounted for
  55. in collision-detection and distance calculation mechanisms.
  56. An object may have multiple TGLBCollision, registered to multiple collision
  57. managers, however if multiple behaviours share the same manager, only one
  58. of them will be accounted for, others will be ignored. *)
  59. TGLBCollision = class(TGLBehaviour)
  60. private
  61. FBoundingMode: TCollisionBoundingMode;
  62. FManager: TGLCollisionManager;
  63. FManagerName: String; // NOT persistent, temporarily used for persistence
  64. FGroupIndex: Integer;
  65. protected
  66. procedure SetGroupIndex(const value: Integer);
  67. procedure SetManager(const val: TGLCollisionManager);
  68. procedure WriteToFiler(writer: TWriter); override;
  69. procedure ReadFromFiler(reader: TReader); override;
  70. procedure Loaded; override;
  71. public
  72. constructor Create(AOwner: TXCollection); override;
  73. destructor Destroy; override;
  74. procedure Assign(Source: TPersistent); override;
  75. class function FriendlyName: String; override;
  76. class function FriendlyDescription: String; override;
  77. published
  78. { Refers the collision manager. }
  79. property Manager: TGLCollisionManager read FManager write SetManager;
  80. property BoundingMode: TCollisionBoundingMode read FBoundingMode
  81. write FBoundingMode;
  82. property GroupIndex: Integer read FGroupIndex write SetGroupIndex;
  83. end;
  84. // Fast Collision detection routines that are heavily specialized and just return a boolean
  85. function FastCheckPointVsPoint(obj1, obj2: TGLBaseSceneObject): Boolean;
  86. function FastCheckPointVsSphere(obj1, obj2: TGLBaseSceneObject): Boolean;
  87. function FastCheckPointVsEllipsoid(obj1, obj2: TGLBaseSceneObject): Boolean;
  88. function FastCheckPointVsCube(obj1, obj2: TGLBaseSceneObject): Boolean;
  89. function FastCheckSphereVsPoint(obj1, obj2: TGLBaseSceneObject): Boolean;
  90. function FastCheckSphereVsSphere(obj1, obj2: TGLBaseSceneObject): Boolean;
  91. function FastCheckSphereVsEllipsoid(obj1, obj2: TGLBaseSceneObject): Boolean;
  92. function FastCheckSphereVsCube(obj1, obj2: TGLBaseSceneObject): Boolean;
  93. function FastCheckEllipsoidVsPoint(obj1, obj2: TGLBaseSceneObject): Boolean;
  94. function FastCheckEllipsoidVsSphere(obj1, obj2: TGLBaseSceneObject): Boolean;
  95. function FastCheckEllipsoidVsEllipsoid(obj1, obj2: TGLBaseSceneObject): Boolean;
  96. function FastCheckEllipsoidVsCube(obj1, obj2: TGLBaseSceneObject): Boolean;
  97. function FastCheckCubeVsPoint(obj1, obj2: TGLBaseSceneObject): Boolean;
  98. function FastCheckCubeVsSphere(obj1, obj2: TGLBaseSceneObject): Boolean;
  99. function FastCheckCubeVsEllipsoid(obj1, obj2: TGLBaseSceneObject): Boolean;
  100. function FastCheckCubeVsCube(obj1, obj2: TGLBaseSceneObject): Boolean;
  101. function FastCheckCubeVsFace(obj1, obj2: TGLBaseSceneObject): Boolean;
  102. // experimental
  103. function FastCheckFaceVsCube(obj1, obj2: TGLBaseSceneObject): Boolean;
  104. // experimental
  105. function FastCheckFaceVsFace(obj1, obj2: TGLBaseSceneObject): Boolean;
  106. (* Returns true when the bounding box cubes does intersect the other.
  107. Also true when the one cube does contain the other completely. *)
  108. function IntersectCubes(obj1, obj2: TGLBaseSceneObject): Boolean; overload;
  109. (* Returns or creates the TGLBCollision within the given behaviours.
  110. This helper function is convenient way to access a TGLBCollision. *)
  111. function GetOrCreateCollision(behaviours: TGLBehaviours)
  112. : TGLBCollision; overload;
  113. (* Returns or creates the TGLBCollision within the given object's behaviours.
  114. This helper function is convenient way to access a TGLBCollision. *)
  115. function GetOrCreateCollision(obj: TGLBaseSceneObject): TGLBCollision; overload;
  116. // ------------------------------------------------------------------
  117. implementation
  118. // ------------------------------------------------------------------
  119. const
  120. cEpsilon: Single = 1E-6;
  121. const
  122. cFastCollisionChecker: array [cbmPoint .. cbmFaces, cbmPoint .. cbmFaces]
  123. of TFastCollisionChecker = ((FastCheckPointVsPoint, FastCheckPointVsSphere,
  124. FastCheckPointVsEllipsoid, FastCheckPointVsCube, FastCheckPointVsCube),
  125. (FastCheckSphereVsPoint, FastCheckSphereVsSphere,
  126. FastCheckSphereVsEllipsoid, FastCheckSphereVsCube, FastCheckSphereVsCube),
  127. (FastCheckEllipsoidVsPoint, FastCheckEllipsoidVsSphere,
  128. FastCheckEllipsoidVsEllipsoid, FastCheckEllipsoidVsCube,
  129. FastCheckEllipsoidVsCube), (FastCheckCubeVsPoint, FastCheckCubeVsSphere,
  130. FastCheckCubeVsEllipsoid, FastCheckCubeVsCube, FastCheckCubeVsFace),
  131. (FastCheckCubeVsPoint, FastCheckCubeVsSphere, FastCheckCubeVsEllipsoid,
  132. FastCheckFaceVsCube, FastCheckFaceVsFace));
  133. // Collision utility routines
  134. function FastCheckPointVsPoint(obj1, obj2: TGLBaseSceneObject): Boolean;
  135. begin
  136. Result := (obj2.SqrDistanceTo(obj1.AbsolutePosition) <= cEpsilon);
  137. end;
  138. function FastCheckPointVsSphere(obj1, obj2: TGLBaseSceneObject): Boolean;
  139. begin
  140. Result := (obj2.SqrDistanceTo(obj1.AbsolutePosition) <=
  141. Sqr(obj2.BoundingSphereRadius));
  142. end;
  143. function FastCheckPointVsEllipsoid(obj1, obj2: TGLBaseSceneObject): Boolean;
  144. var
  145. v: TGLVector;
  146. begin
  147. // calc vector expressed in local coordinates (for obj2)
  148. v := VectorTransform(obj1.AbsolutePosition, obj2.InvAbsoluteMatrix);
  149. // rescale to unit dimensions
  150. // DivideVector(v, obj2.Scale.AsVector); //DanB - Scale removed in VectorTransform
  151. DivideVector(v, obj2.AxisAlignedDimensionsUnscaled);
  152. // ScaleVector(v,obj2.Scale.AsVector);
  153. // ScaleVector();
  154. v.W := 0;
  155. // if norm is below 1, collision
  156. Result := (VectorNorm(v) <= 1 { Sqr(obj2.BoundingSphereRadius) } );
  157. // since radius*radius = 1/2*1/2 = 1/4 for unit sphere
  158. end;
  159. function FastCheckPointVsCube(obj1, obj2: TGLBaseSceneObject): Boolean;
  160. var
  161. v: TGLVector;
  162. begin
  163. // calc vector expressed in local coordinates (for obj2)
  164. v := VectorTransform(obj1.AbsolutePosition, obj2.InvAbsoluteMatrix);
  165. // rescale to unit dimensions
  166. DivideVector(v, obj2.AxisAlignedDimensionsUnscaled);
  167. // if abs() of all components are below 1, collision
  168. Result := (MaxAbsXYZComponent(v) <= 1);
  169. end;
  170. function FastCheckSphereVsPoint(obj1, obj2: TGLBaseSceneObject): Boolean;
  171. begin
  172. Result := (obj1.SqrDistanceTo(obj2.AbsolutePosition) <=
  173. Sqr(obj1.BoundingSphereRadius));
  174. end;
  175. function FastCheckSphereVsSphere(obj1, obj2: TGLBaseSceneObject): Boolean;
  176. begin
  177. Result := (obj1.SqrDistanceTo(obj2.AbsolutePosition) <=
  178. Sqr(obj1.BoundingSphereRadius + obj2.BoundingSphereRadius));
  179. end;
  180. function FastCheckSphereVsEllipsoid(obj1, obj2: TGLBaseSceneObject): Boolean;
  181. var
  182. v: TGLVector;
  183. aad: TGLVector;
  184. begin
  185. // express in local coordinates (for obj2)
  186. v := VectorTransform(obj1.AbsolutePosition, obj2.InvAbsoluteMatrix);
  187. // calc local vector, and rescale to unit dimensions
  188. // VectorSubstract(pt1, obj2.AbsolutePosition, v);
  189. aad := VectorAdd(obj2.AxisAlignedDimensions, obj1.BoundingSphereRadius);
  190. DivideVector(v, aad);
  191. ScaleVector(v, obj2.Scale.AsVector); // by DanB
  192. v.W := 0;
  193. // if norm is below 1, collision
  194. Result := (VectorNorm(v) <= 1);
  195. end;
  196. function FastCheckSphereVsCube(obj1, obj2: TGLBaseSceneObject): Boolean;
  197. var
  198. v: TGLVector;
  199. aad: TGLVector;
  200. r, r2: Single;
  201. begin
  202. // express in local coordinates (for cube "obj2")
  203. // v gives the vector from obj2 to obj1 expressed in obj2's local system
  204. v := VectorTransform(obj1.AbsolutePosition, obj2.InvAbsoluteMatrix);
  205. // because of symmetry we can make abs(v)
  206. v.X := abs(v.X);
  207. v.Y := abs(v.Y);
  208. v.Z := abs(v.Z);
  209. ScaleVector(v, obj2.Scale.AsVector);
  210. aad := obj2.AxisAlignedDimensions; // should be abs at all!
  211. VectorSubtract(v, aad, v); // v holds the distance in each axis
  212. v.W := 0;
  213. r := obj1.BoundingSphereRadius { UnScaled };
  214. r2 := Sqr(r);
  215. if (v.X > 0) then
  216. begin
  217. if (v.Y > 0) then
  218. begin
  219. if (v.Z > 0) then
  220. begin
  221. // v is outside axis parallel projection, so use distance to edge point
  222. Result := (VectorNorm(v) <= r2);
  223. end
  224. else
  225. begin
  226. // v is inside z axis projection, but outside x-y projection
  227. Result := (VectorNorm(v.X, v.Y) <= r2);
  228. end
  229. end
  230. else
  231. begin
  232. if (v.Z > 0) then
  233. begin
  234. // v is inside y axis projection, but outside x-z projection
  235. Result := (VectorNorm(v.X, v.Z) <= r2);
  236. end
  237. else
  238. begin
  239. // v is inside y-z axis projection, but outside x projection
  240. Result := (v.X <= r);
  241. end
  242. end
  243. end
  244. else
  245. begin
  246. if (v.Y > 0) then
  247. begin
  248. if (v.Z > 0) then
  249. begin
  250. // v is inside x axis projection, but outside y-z projection
  251. Result := (VectorNorm(v.Y, v.Z) <= r2);
  252. end
  253. else
  254. begin
  255. // v is inside x-z projection, but outside y projection
  256. Result := (v.Y <= r);
  257. end
  258. end
  259. else
  260. begin
  261. if (v.Z > 0) then
  262. begin
  263. // v is inside x-y axis projection, but outside z projection
  264. Result := (v.Z <= r);
  265. end
  266. else
  267. begin
  268. // v is inside all axes parallel projection, so it is inside cube
  269. Result := true;
  270. end;
  271. end
  272. end;
  273. end;
  274. function FastCheckEllipsoidVsPoint(obj1, obj2: TGLBaseSceneObject): Boolean;
  275. begin
  276. Result := FastCheckPointVsEllipsoid(obj2, obj1);
  277. end;
  278. function FastCheckEllipsoidVsSphere(obj1, obj2: TGLBaseSceneObject): Boolean;
  279. begin
  280. Result := FastCheckSphereVsEllipsoid(obj2, obj1);
  281. end;
  282. function FastCheckEllipsoidVsEllipsoid(obj1, obj2: TGLBaseSceneObject): Boolean;
  283. var
  284. v1, v2: TGLVector;
  285. begin
  286. // express in local coordinates (for obj2)
  287. v1 := VectorTransform(obj1.AbsolutePosition, obj2.InvAbsoluteMatrix);
  288. // calc local vector, and rescale to unit dimensions
  289. // VectorSubstract(pt, obj2.AbsolutePosition, v1);
  290. DivideVector(v1, obj2.AxisAlignedDimensions);
  291. v1.W := 0;
  292. // express in local coordinates (for obj1)
  293. v2 := VectorTransform(obj2.AbsolutePosition, obj1.InvAbsoluteMatrix);
  294. // calc local vector, and rescale to unit dimensions
  295. // VectorSubstract(pt, obj1.AbsolutePosition, v2);
  296. DivideVector(v2, obj1.AxisAlignedDimensions);
  297. v2.W := 0;
  298. // if sum of norms is below 2, collision
  299. Result := (VectorNorm(v1) + VectorNorm(v2) <= 2);
  300. end;
  301. function FastCheckEllipsoidVsCube(obj1, obj2: TGLBaseSceneObject): Boolean;
  302. { current implementation assumes Ellipsoid as Sphere }
  303. var
  304. v: TGLVector;
  305. aad: TGLVector;
  306. begin
  307. // express in local coordinates (for obj2)
  308. v := VectorTransform(obj1.AbsolutePosition, obj2.InvAbsoluteMatrix);
  309. // calc local vector, and rescale to unit dimensions
  310. aad := VectorAdd(obj2.AxisAlignedDimensionsUnscaled,
  311. obj1.BoundingSphereRadius);
  312. DivideVector(v, aad);
  313. v.W := 0;
  314. // if norm is below 1, collision
  315. Result := (VectorNorm(v) <= 1);
  316. end;
  317. function FastCheckCubeVsPoint(obj1, obj2: TGLBaseSceneObject): Boolean;
  318. begin
  319. Result := FastCheckPointVsCube(obj2, obj1);
  320. end;
  321. function FastCheckCubeVsSphere(obj1, obj2: TGLBaseSceneObject): Boolean;
  322. begin
  323. Result := FastCheckSphereVsCube(obj2, obj1);
  324. end;
  325. function FastCheckCubeVsEllipsoid(obj1, obj2: TGLBaseSceneObject): Boolean;
  326. begin
  327. Result := FastCheckEllipsoidVsCube(obj2, obj1);
  328. end;
  329. procedure InitArray(v: TGLVector; var pt: array of TGLVector);
  330. // calculate the cube edge points from the axis aligned dimension
  331. begin
  332. pt[0] := VectorMake(-v.X, -v.Y, -v.Z, 1);
  333. pt[1] := VectorMake(v.X, -v.Y, -v.Z, 1);
  334. pt[2] := VectorMake(v.X, v.Y, -v.Z, 1);
  335. pt[3] := VectorMake(-v.X, v.Y, -v.Z, 1);
  336. pt[4] := VectorMake(-v.X, -v.Y, v.Z, 1);
  337. pt[5] := VectorMake(v.X, -v.Y, v.Z, 1);
  338. pt[6] := VectorMake(v.X, v.Y, v.Z, 1);
  339. pt[7] := VectorMake(-v.X, v.Y, v.Z, 1);
  340. end;
  341. function DoCubesIntersectPrim(obj1, obj2: TGLBaseSceneObject): Boolean;
  342. // first check if any edge point of "cube" obj1 lies within "cube" obj2
  343. // else, for each "wire" in then wireframe of the "cube" obj1, check if it
  344. // intersects with one of the "planes" of "cube" obj2
  345. function CheckWire(p0, p1, pl: TGLVector): Boolean;
  346. // check "wire" line (p0,p1) for intersection with each plane, given from
  347. // axis aligned dimensions pl
  348. // - calculate "direction" d: p0 -> p1
  349. // - for each axis (0..2) do
  350. // - calculate line parameter t of intersection with plane pl[I]
  351. // - if not in range [0..1] (= not within p0->p1), no intersection
  352. // - else
  353. // - calculate intersection point s = p0 + t*d
  354. // - for both other axes check if coordinates are within range
  355. // - do the same for opposite plane -pl[I]
  356. var
  357. t: Single;
  358. d, s: TGLVector;
  359. i, j, k: Integer;
  360. begin
  361. Result := true;
  362. VectorSubtract(p1, p0, d); // d: direction p0 -> p1
  363. for i := 0 to 2 do
  364. begin
  365. if d.v[i] = 0 then
  366. begin // wire is parallel to plane
  367. // this case will be handled by the other planes
  368. end
  369. else
  370. begin
  371. j := (i + 1) mod 3;
  372. k := (j + 1) mod 3;
  373. t := (pl.v[i] - p0.v[i]) / d.v[i]; // t: line parameter of intersection
  374. if IsInRange(t, 0, 1) then
  375. begin
  376. s := p0;
  377. CombineVector(s, d, t); // calculate intersection
  378. // if the other two coordinates lie within the ranges, collision
  379. if IsInRange(s.v[j], -pl.v[j], pl.v[j]) and
  380. IsInRange(s.v[k], -pl.v[k], pl.v[k]) then
  381. Exit;
  382. end;
  383. t := (-pl.v[i] - p0.v[i]) / d.v[i]; // t: parameter of intersection
  384. if IsInRange(t, 0, 1) then
  385. begin
  386. s := p0;
  387. CombineVector(s, d, t); // calculate intersection
  388. // if the other two coordinates lie within the ranges, collision
  389. if IsInRange(s.v[j], -pl.v[j], pl.v[j]) and
  390. IsInRange(s.v[k], -pl.v[k], pl.v[k]) then
  391. Exit;
  392. end;
  393. end;
  394. end;
  395. Result := false;
  396. end;
  397. const
  398. cWires: array [0 .. 11, 0 .. 1] of Integer = ((0, 1), (1, 2), (2, 3), (3, 0),
  399. (4, 5), (5, 6), (6, 7), (7, 4), (0, 4), (1, 5), (2, 6), (3, 7));
  400. var
  401. pt1: array [0 .. 7] of TGLVector;
  402. M: TGLMatrix;
  403. i: Integer;
  404. aad: TGLVector;
  405. begin
  406. Result := true;
  407. aad := obj2.AxisAlignedDimensionsUnscaled; // DanB experiment
  408. InitArray(obj1.AxisAlignedDimensionsUnscaled, pt1);
  409. // calculate the matrix to transform obj1 into obj2
  410. MatrixMultiply(obj1.AbsoluteMatrix, obj2.InvAbsoluteMatrix, M);
  411. for i := 0 to 7 do
  412. begin // transform points of obj1
  413. pt1[i] := VectorTransform(pt1[i], M);
  414. // check if point lies inside "cube" obj2, collision
  415. if IsInCube(pt1[i], aad) then
  416. Exit;
  417. end;
  418. for i := 0 to 11 do
  419. begin
  420. if CheckWire(pt1[cWires[i, 0]], pt1[cWires[i, 1]], aad) then
  421. Exit;
  422. end;
  423. Result := false;
  424. end;
  425. function FastCheckCubeVsCube(obj1, obj2: TGLBaseSceneObject): Boolean;
  426. { var
  427. aad1,aad2 : TGLVector;
  428. D1,D2,D : Double;
  429. }
  430. begin
  431. // this bit of code isn't needed (since collision code does BoundingBox elimination)
  432. // also is incorrect when objects further up the "object tree" are scaled
  433. {
  434. aad1 := obj1.AxisAlignedDimensions;
  435. aad2 := obj2.AxisAlignedDimensions;
  436. D1 := VectorLength(aad1);
  437. D2 := VectorLength(aad2);
  438. D := Sqrt(obj1.SqrDistanceTo(obj2.AbsolutePosition));
  439. if D>(D1+D2) then result := false
  440. else begin
  441. D1 := MinAbsXYZComponent(aad1);
  442. D2 := MinAbsXYZComponent(aad2);
  443. if D<(D1+D2) then result := true
  444. else begin
  445. }
  446. //
  447. Result := DoCubesIntersectPrim(obj1, obj2) or
  448. DoCubesIntersectPrim(obj2, obj1);
  449. { end;
  450. end;
  451. }
  452. end;
  453. { Behaviour - Checks for collisions between Faces and cube by Checking
  454. whether triangles on the mesh have a point inside the cube,
  455. or a triangle intersects the side
  456. Issues - Checks whether triangles on the mesh have a point inside the cube
  457. 1) When the cube is completely inside a mesh, it will contain
  458. no triangles hence no collision detected
  459. 2) When the mesh is (almost) completely inside the cube
  460. Octree.GetTrianglesInCube returns no points, why? }
  461. function FastCheckCubeVsFace(obj1, obj2: TGLBaseSceneObject): Boolean;
  462. // var
  463. // triList : TGLAffineVectorList;
  464. // m1to2, m2to1 : TGLMatrix;
  465. // i:integer;
  466. begin
  467. if (obj2 is TGLFreeForm) then
  468. begin
  469. // check if we are initialized correctly
  470. if not Assigned(TGLFreeForm(obj2).Octree) then
  471. TGLFreeForm(obj2).BuildOctree;
  472. Result := TGLFreeForm(obj2).OctreeAABBIntersect
  473. (obj1.AxisAlignedBoundingBoxUnscaled, obj1.AbsoluteMatrix,
  474. obj1.InvAbsoluteMatrix)
  475. // could then analyse triangles and return contact points
  476. end
  477. else
  478. begin
  479. // CubeVsFace only works if one is FreeForm Object
  480. Result := IntersectCubes(obj1, obj2);
  481. end;
  482. end;
  483. function FastCheckFaceVsCube(obj1, obj2: TGLBaseSceneObject): Boolean;
  484. begin
  485. Result := FastCheckCubeVsFace(obj2, obj1);
  486. end;
  487. // this function does not check for rounds that results from Smoth rendering
  488. // if anybody needs this, you are welcome to show a solution, but usually this should be good enough
  489. function FastCheckFaceVsFace(obj1, obj2: TGLBaseSceneObject): Boolean;
  490. type
  491. TTriangle = array [0 .. 2] of TAffineVector;
  492. PTriangle = ^TTriangle;
  493. var
  494. i: Integer;
  495. triList: TGLAffineVectorList;
  496. tri: PTriangle;
  497. m1to2, m2to1: TGLMatrix;
  498. AABB2: TAABB;
  499. begin
  500. Result := false;
  501. if (obj1 is TGLFreeForm) and (obj2 is TGLFreeForm) then
  502. begin
  503. // check if we are initialized correctly
  504. if not Assigned(TGLFreeForm(obj1).Octree) then
  505. TGLFreeForm(obj1).BuildOctree;
  506. if not Assigned(TGLFreeForm(obj2).Octree) then
  507. TGLFreeForm(obj2).BuildOctree;
  508. // Check triangles against the other object
  509. // check only the one that are near the destination object (using octree of obj1)
  510. // get the 'hot' ones using the tree
  511. MatrixMultiply(obj2.AbsoluteMatrix, obj1.InvAbsoluteMatrix, m1to2);
  512. MatrixMultiply(obj1.AbsoluteMatrix, obj2.InvAbsoluteMatrix, m2to1);
  513. AABB2 := obj2.AxisAlignedBoundingBoxUnscaled;
  514. triList := TGLFreeForm(obj1).Octree.GetTrianglesFromNodesIntersectingCube
  515. (AABB2, m1to2, m2to1);
  516. // in the list originally are the local coords, TransformAsPoints-> now we have obj1 absolute coords
  517. triList.TransformAsPoints(obj1.AbsoluteMatrix);
  518. // Transform to Absolute Coords
  519. try
  520. i := 0;
  521. while i < triList.Count - 2 do
  522. begin
  523. // here we pass absolute coords, then these are transformed with Obj2's InvAbsoluteMatrix to match the local Obj2 System
  524. tri := @triList.List[i];
  525. // the next function will check the given Triangle against only these ones that are close (using the octree of obj2)
  526. if TGLFreeForm(obj2).OctreeTriangleIntersect(tri[0], tri[1], tri[2])
  527. then
  528. begin
  529. Result := true;
  530. { TODO : Optimize, exit was disabled for performance checks }
  531. Exit;
  532. end;
  533. Inc(i, 3);
  534. end;
  535. finally
  536. triList.Free;
  537. end;
  538. end
  539. else
  540. begin
  541. // FaceVsFace does work only for two FreeForm Objects
  542. Result := IntersectCubes(obj1, obj2);
  543. end;
  544. end;
  545. function IntersectCubes(obj1, obj2: TGLBaseSceneObject): Boolean;
  546. var
  547. aabb1, AABB2: TAABB;
  548. m1to2, m2to1: TGLMatrix;
  549. begin
  550. // Calc AABBs
  551. aabb1 := obj1.AxisAlignedBoundingBoxUnscaled;
  552. AABB2 := obj2.AxisAlignedBoundingBoxUnscaled;
  553. // Calc Conversion Matrixes
  554. MatrixMultiply(obj1.AbsoluteMatrix, obj2.InvAbsoluteMatrix, m1to2);
  555. MatrixMultiply(obj2.AbsoluteMatrix, obj1.InvAbsoluteMatrix, m2to1);
  556. Result := IntersectAABBs(aabb1, AABB2, m1to2, m2to1);
  557. end;
  558. // ------------------
  559. // ------------------ TCollisionManager ------------------
  560. // ------------------
  561. constructor TGLCollisionManager.Create(AOwner: TComponent);
  562. begin
  563. inherited Create(AOwner);
  564. FClients := TList.Create;
  565. RegisterManager(Self);
  566. end;
  567. destructor TGLCollisionManager.Destroy;
  568. begin
  569. DeRegisterAllClients;
  570. DeRegisterManager(Self);
  571. FClients.Free;
  572. inherited Destroy;
  573. end;
  574. procedure TGLCollisionManager.RegisterClient(aClient: TGLBCollision);
  575. begin
  576. if Assigned(aClient) then
  577. if FClients.IndexOf(aClient) < 0 then
  578. begin
  579. FClients.Add(aClient);
  580. aClient.FManager := Self;
  581. end;
  582. end;
  583. procedure TGLCollisionManager.DeRegisterClient(aClient: TGLBCollision);
  584. begin
  585. if Assigned(aClient) then
  586. begin
  587. aClient.FManager := nil;
  588. FClients.Remove(aClient);
  589. end;
  590. end;
  591. procedure TGLCollisionManager.DeRegisterAllClients;
  592. var
  593. i: Integer;
  594. begin
  595. // Fast deregistration
  596. for i := 0 to FClients.Count - 1 do
  597. TGLBCollision(FClients[i]).FManager := nil;
  598. FClients.Clear;
  599. end;
  600. // Reference code
  601. {
  602. procedure TCollisionManager.CheckCollisions;
  603. var
  604. obj1, obj2 : TGLBaseSceneObject;
  605. cli1, cli2 : TGLBCollision;
  606. grp1, grp2 : Integer; // GroupIndex of collisions
  607. i, j : Integer;
  608. begin
  609. if not Assigned(FOnCollision) then Exit;
  610. // if you know a code slower than current one, call me ;)
  611. // TODO : speed improvements & distance cacheing
  612. for i:=0 to FClients.Count-2 do begin
  613. cli1:=TGLBCollision(FClients[i]);
  614. obj1:=cli1.OwnerBaseSceneObject;
  615. grp1:=cli1.GroupIndex;
  616. for j:=i+1 to FClients.Count-1 do begin
  617. cli2:=TGLBCollision(FClients[j]);
  618. obj2:=cli2.OwnerBaseSceneObject;
  619. grp2:=cli2.GroupIndex;
  620. // if either one GroupIndex=0 or both are different, check for collision
  621. if ((grp1=0) or (grp2=0) or (grp1<>grp2)) then begin
  622. if cFastCollisionChecker[cli1.BoundingMode, cli2.BoundingMode](obj1, obj2) then
  623. FOnCollision(Self, obj1, obj2);
  624. end;
  625. end;
  626. end;
  627. end;
  628. }
  629. // [---- new CheckCollisions / Dan Bartlett
  630. // CheckCollisions (By Dan Bartlett) - sort according to Z axis
  631. //
  632. // Some comments: Much faster than original, especially when objects are spread out.
  633. // TODO:
  634. // Try to make faster when objects are close
  635. // Still more improvements can be made, better method (dynamic octree?)
  636. // Faster sorting? (If a faster way than Delphi's QuickSort is available)
  637. // Another Event called OnNoCollisionEvent could be added
  638. // Fit bounding box methods into GLScene "Grand Scheme Of Things"
  639. //
  640. // Behaviour:
  641. // If GroupIndex < 0 then it will not be checked for collisions against
  642. // any other object *** WARNING: THIS IS DIFFERENT FROM PREVIOUS VERSION ***
  643. //
  644. // If GroupIndex = 0 then object will be tested against all objects with GroupIndex >= 0
  645. // Collision Testing will only be performed on objects from different groups
  646. // Collision testing occurs even when an object is not visible, allowing low-triangle count
  647. // collision shapes to be used to model complex objects (Different to previous version)
  648. type
  649. // only add collision node to list if GroupIndex>=0
  650. TCollisionNode = class
  651. public
  652. Collision: TGLBCollision;
  653. AABB: TAABB;
  654. constructor Create(Collision: TGLBCollision; const AABB: TAABB);
  655. end;
  656. constructor TCollisionNode.Create(Collision: TGLBCollision; const AABB: TAABB);
  657. begin
  658. inherited Create();
  659. Self.Collision := Collision;
  660. Self.AABB := AABB;
  661. end;
  662. function CompareDistance(Item1, Item2: Pointer): Integer;
  663. var
  664. d: Extended;
  665. begin
  666. // Z-axis sort
  667. d := (TCollisionNode(Item2).AABB.min.Z - TCollisionNode(Item1).AABB.min.Z);
  668. if d > 0 then
  669. Result := -1
  670. else if d < 0 then
  671. Result := 1
  672. else
  673. Result := 0;
  674. end;
  675. procedure TGLCollisionManager.CheckCollisions;
  676. var
  677. NodeList: TList;
  678. CollisionNode1, CollisionNode2: TCollisionNode;
  679. obj1, obj2: TGLBaseSceneObject;
  680. cli1, cli2: TGLBCollision;
  681. grp1, grp2: Integer; // GroupIndex of collisions
  682. i, j: Integer;
  683. box1: TAABB;
  684. begin
  685. if not Assigned(FOnCollision) then
  686. Exit;
  687. // this next bit of code would be faster if bounding box was stored
  688. NodeList := TList.Create;
  689. try
  690. NodeList.Count := 0;
  691. for i := 0 to FClients.Count - 1 do
  692. begin
  693. cli1 := TGLBCollision(FClients[i]);
  694. grp1 := cli1.GroupIndex;
  695. if grp1 < 0 then // if groupindex is negative don't add to list
  696. Continue;
  697. obj1 := cli1.OwnerBaseSceneObject;
  698. // TODO: need to do different things for different objects, especially points (to improve speed)
  699. box1 := obj1.AxisAlignedBoundingBoxUnscaled;
  700. // get obj1 axis-aligned bounding box
  701. if box1.min.Z >= box1.max.Z then
  702. Continue; // check for case where no bb exists
  703. AABBTransform(box1, obj1.AbsoluteMatrix); // & transform it to world axis
  704. CollisionNode1 := TCollisionNode.Create(cli1, box1);
  705. NodeList.Add(CollisionNode1);
  706. end;
  707. if NodeList.Count < 2 then
  708. Exit;
  709. NodeList.Sort(@CompareDistance);
  710. // depth-sort bounding boxes (min bb.z values)
  711. for i := 0 to NodeList.Count - 2 do
  712. begin
  713. CollisionNode1 := TCollisionNode(NodeList[i]);
  714. cli1 := CollisionNode1.Collision;
  715. grp1 := cli1.GroupIndex;
  716. for j := i + 1 to NodeList.Count - 1 do
  717. begin
  718. CollisionNode2 := TCollisionNode(NodeList[j]);
  719. cli2 := CollisionNode2.Collision;
  720. // Check BBox1 and BBox2 overlap in the z-direction
  721. if (CollisionNode2.AABB.min.Z > CollisionNode1.AABB.max.Z) then
  722. Break;
  723. grp2 := cli2.GroupIndex;
  724. // if either one GroupIndex=0 or both are different, check for collision
  725. if ((grp1 = 0) or (grp2 = 0) or (grp1 <> grp2)) = false then
  726. Continue;
  727. // check whether box1 and box2 overlap in the XY Plane
  728. if IntersectAABBsAbsoluteXY(CollisionNode1.AABB, CollisionNode2.AABB)
  729. then
  730. begin
  731. obj1 := cli1.OwnerBaseSceneObject;
  732. obj2 := cli2.OwnerBaseSceneObject;
  733. if cFastCollisionChecker[cli1.BoundingMode, cli2.BoundingMode]
  734. (obj1, obj2) then
  735. FOnCollision(Self, obj1, obj2);
  736. end;
  737. end;
  738. end;
  739. finally
  740. for i := 0 to NodeList.Count - 1 do
  741. begin
  742. CollisionNode1 := NodeList.Items[i];
  743. CollisionNode1.Free;
  744. end;
  745. NodeList.Free;
  746. end;
  747. end;
  748. // new CheckCollisions / Dan Bartlett -----]
  749. // ------------------
  750. // ------------------ TGLBCollision ------------------
  751. // ------------------
  752. constructor TGLBCollision.Create(AOwner: TXCollection);
  753. begin
  754. inherited Create(AOwner);
  755. end;
  756. destructor TGLBCollision.Destroy;
  757. begin
  758. Manager := nil;
  759. inherited Destroy;
  760. end;
  761. class function TGLBCollision.FriendlyName: String;
  762. begin
  763. Result := 'Collision';
  764. end;
  765. class function TGLBCollision.FriendlyDescription: String;
  766. begin
  767. Result := 'Collision-detection registration';
  768. end;
  769. procedure TGLBCollision.WriteToFiler(writer: TWriter);
  770. begin
  771. with writer do
  772. begin
  773. // ArchiveVersion 1, added FGroupIndex
  774. // ArchiveVersion 2, added inherited call
  775. WriteInteger(2);
  776. inherited;
  777. if Assigned(FManager) then
  778. WriteString(FManager.GetNamePath)
  779. else
  780. WriteString('');
  781. WriteInteger(Integer(BoundingMode));
  782. WriteInteger(FGroupIndex);
  783. end;
  784. end;
  785. procedure TGLBCollision.ReadFromFiler(reader: TReader);
  786. var
  787. archiveVersion: Integer;
  788. begin
  789. with reader do
  790. begin
  791. archiveVersion := ReadInteger;
  792. Assert(archiveVersion in [0 .. 2]);
  793. if archiveVersion >= 2 then
  794. inherited;
  795. FManagerName := ReadString;
  796. BoundingMode := TCollisionBoundingMode(ReadInteger);
  797. Manager := nil;
  798. if archiveVersion >= 1 then
  799. FGroupIndex := ReadInteger
  800. else
  801. FGroupIndex := 0;
  802. end;
  803. end;
  804. procedure TGLBCollision.Loaded;
  805. var
  806. mng: TComponent;
  807. begin
  808. inherited;
  809. if FManagerName <> '' then
  810. begin
  811. mng := FindManager(TGLCollisionManager, FManagerName);
  812. if Assigned(mng) then
  813. Manager := TGLCollisionManager(mng);
  814. FManagerName := '';
  815. end;
  816. end;
  817. procedure TGLBCollision.Assign(Source: TPersistent);
  818. begin
  819. if Source is TGLBCollision then
  820. begin
  821. Manager := TGLBCollision(Source).Manager;
  822. BoundingMode := TGLBCollision(Source).BoundingMode;
  823. end;
  824. inherited Assign(Source);
  825. end;
  826. procedure TGLBCollision.SetManager(const val: TGLCollisionManager);
  827. begin
  828. if val <> FManager then
  829. begin
  830. if Assigned(FManager) then
  831. FManager.DeRegisterClient(Self);
  832. if Assigned(val) then
  833. val.RegisterClient(Self);
  834. end;
  835. end;
  836. procedure TGLBCollision.SetGroupIndex(const value: Integer);
  837. begin
  838. FGroupIndex := value;
  839. end;
  840. function GetOrCreateCollision(behaviours: TGLBehaviours): TGLBCollision;
  841. var
  842. i: Integer;
  843. begin
  844. i := behaviours.IndexOfClass(TGLBCollision);
  845. if i >= 0 then
  846. Result := TGLBCollision(behaviours[i])
  847. else
  848. Result := TGLBCollision.Create(behaviours);
  849. end;
  850. function GetOrCreateCollision(obj: TGLBaseSceneObject): TGLBCollision;
  851. begin
  852. Result := GetOrCreateCollision(obj.behaviours);
  853. end;
  854. // ------------------------------------------------------------------
  855. initialization
  856. // ------------------------------------------------------------------
  857. RegisterXCollectionItemClass(TGLBCollision);
  858. finalization
  859. UnregisterXCollectionItemClass(TGLBCollision);
  860. end.