GXS.Collision.pas 29 KB

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