convexhull.go 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465
  1. // Copyright 2016 The G3N Authors. All rights reserved.
  2. // Use of this source code is governed by a BSD-style
  3. // license that can be found in the LICENSE file.
  4. package shape
  5. import (
  6. "github.com/g3n/engine/geometry"
  7. "github.com/g3n/engine/math32"
  8. "github.com/g3n/engine/experimental/physics/collision"
  9. )
  10. // ConvexHull is a convex triangle-based geometry used for collision detection and contact resolution.
  11. type ConvexHull struct {
  12. geometry.Geometry
  13. // Cached geometry properties
  14. faces [][3]math32.Vector3
  15. faceNormals []math32.Vector3
  16. worldFaceNormals []math32.Vector3
  17. uniqueEdges []math32.Vector3
  18. worldUniqueEdges []math32.Vector3
  19. }
  20. func NewConvexHull(geom *geometry.Geometry) *ConvexHull {
  21. ch := new(ConvexHull)
  22. // // TODO check if geometry is convex, panic if not
  23. //if !geom.IsConvex() {
  24. // panic("geometry needs to be convex")
  25. //}
  26. // // TODO future: create function to break up geometry into convex shapes and add all shapes to body
  27. ch.Geometry = *geom
  28. // Perform single-time computations
  29. ch.computeFaceNormalsAndUniqueEdges()
  30. return ch
  31. }
  32. // Compute and store face normals and unique edges
  33. func (ch *ConvexHull) computeFaceNormalsAndUniqueEdges() {
  34. ch.Geometry.ReadFaces(func(vA, vB, vC math32.Vector3) bool {
  35. // Store face vertices
  36. var face [3]math32.Vector3
  37. face[0] = vA
  38. face[1] = vB
  39. face[2] = vC
  40. ch.faces = append(ch.faces, face)
  41. // Compute edges
  42. edge1 := math32.NewVec3().SubVectors(&vB, &vA)
  43. edge2 := math32.NewVec3().SubVectors(&vC, &vB)
  44. edge3 := math32.NewVec3().SubVectors(&vA, &vC)
  45. // Compute and store face normal in b.faceNormals
  46. faceNormal := math32.NewVec3().CrossVectors(edge2, edge1)
  47. if faceNormal.Length() > 0 {
  48. faceNormal.Normalize().Negate()
  49. }
  50. ch.faceNormals = append(ch.faceNormals, *faceNormal)
  51. // Compare unique edges recorded so far with the three new face edges and store the unique ones
  52. tol := float32(1e-6)
  53. for p := 0; p < len(ch.uniqueEdges); p++ {
  54. ue := ch.uniqueEdges[p]
  55. if !ue.AlmostEquals(edge1, tol) {
  56. ch.uniqueEdges = append(ch.uniqueEdges, *edge1)
  57. }
  58. if !ue.AlmostEquals(edge2, tol) {
  59. ch.uniqueEdges = append(ch.uniqueEdges, *edge1)
  60. }
  61. if !ue.AlmostEquals(edge3, tol) {
  62. ch.uniqueEdges = append(ch.uniqueEdges, *edge1)
  63. }
  64. }
  65. return false
  66. })
  67. // Allocate space for worldFaceNormals and worldUniqueEdges
  68. ch.worldFaceNormals = make([]math32.Vector3, len(ch.faceNormals))
  69. ch.worldUniqueEdges = make([]math32.Vector3, len(ch.uniqueEdges))
  70. }
  71. // ComputeWorldFaceNormalsAndUniqueEdges
  72. func (ch *ConvexHull) ComputeWorldFaceNormalsAndUniqueEdges(quat *math32.Quaternion) {
  73. // Re-compute world face normals from local face normals
  74. for i := 0; i < len(ch.faceNormals); i++ {
  75. ch.worldFaceNormals[i] = ch.faceNormals[i]
  76. ch.worldFaceNormals[i].ApplyQuaternion(quat)
  77. }
  78. // Re-compute world unique edges from local unique edges
  79. for i := 0; i < len(ch.uniqueEdges); i++ {
  80. ch.worldUniqueEdges[i] = ch.uniqueEdges[i]
  81. ch.worldUniqueEdges[i].ApplyQuaternion(quat)
  82. }
  83. }
  84. func (ch *ConvexHull) Faces() [][3]math32.Vector3 {
  85. return ch.faces
  86. }
  87. func (ch *ConvexHull) FaceNormals() []math32.Vector3 {
  88. return ch.faceNormals
  89. }
  90. func (ch *ConvexHull) WorldFaceNormals() []math32.Vector3 {
  91. return ch.worldFaceNormals
  92. }
  93. func (ch *ConvexHull) UniqueEdges() []math32.Vector3 {
  94. return ch.uniqueEdges
  95. }
  96. func (ch *ConvexHull) WorldUniqueEdges() []math32.Vector3 {
  97. return ch.worldUniqueEdges
  98. }
  99. // FindPenetrationAxis finds the penetration axis between two convex bodies.
  100. // The normal points from bodyA to bodyB.
  101. // Returns false if there is no penetration. If there is a penetration - returns true and the penetration axis.
  102. func (ch *ConvexHull) FindPenetrationAxis(chB *ConvexHull, posA, posB *math32.Vector3, quatA, quatB *math32.Quaternion) (bool, math32.Vector3) {
  103. // Keep track of the smaller depth found so far
  104. // Note that the penetration axis is the one that causes
  105. // the smallest penetration depth when the two hulls are squished onto that axis!
  106. // (may seem a bit counter-intuitive)
  107. depthMin := math32.Inf(1)
  108. var penetrationAxis math32.Vector3
  109. var depth float32
  110. // Assume the geometries are penetrating.
  111. // As soon as (and if) we figure out that they are not, then return false.
  112. penetrating := true
  113. worldFaceNormalsA := ch.WorldFaceNormals()
  114. worldFaceNormalsB := ch.WorldFaceNormals()
  115. // Check world normals of body A
  116. for _, worldFaceNormal := range worldFaceNormalsA {
  117. // Check whether the face is colliding with geomB
  118. penetrating, depth = ch.TestPenetrationAxis(chB, &worldFaceNormal, posA, posB, quatA, quatB)
  119. if !penetrating {
  120. return false, penetrationAxis // penetrationAxis doesn't matter since not penetrating
  121. }
  122. if depth < depthMin {
  123. depthMin = depth
  124. penetrationAxis.Copy(&worldFaceNormal)
  125. }
  126. }
  127. // Check world normals of body B
  128. for _, worldFaceNormal := range worldFaceNormalsB {
  129. // Check whether the face is colliding with geomB
  130. penetrating, depth = ch.TestPenetrationAxis(chB, &worldFaceNormal, posA, posB, quatA, quatB)
  131. if !penetrating {
  132. return false, penetrationAxis // penetrationAxis doesn't matter since not penetrating
  133. }
  134. if depth < depthMin {
  135. depthMin = depth
  136. penetrationAxis.Copy(&worldFaceNormal)
  137. }
  138. }
  139. worldUniqueEdgesA := ch.WorldUniqueEdges()
  140. worldUniqueEdgesB := ch.WorldUniqueEdges()
  141. // Check all combinations of unique world edges
  142. for _, worldUniqueEdgeA := range worldUniqueEdgesA {
  143. for _, worldUniqueEdgeB := range worldUniqueEdgesB {
  144. // Cross edges
  145. edgeCross := math32.NewVec3().CrossVectors(&worldUniqueEdgeA, &worldUniqueEdgeB)
  146. // If the edges are not aligned
  147. tol := float32(1e-6)
  148. if edgeCross.Length() > tol { // Cross product is not close to zero
  149. edgeCross.Normalize()
  150. penetrating, depth = ch.TestPenetrationAxis(chB, edgeCross, posA, posB, quatA, quatB)
  151. if !penetrating {
  152. return false, penetrationAxis
  153. }
  154. if depth < depthMin {
  155. depthMin = depth
  156. penetrationAxis.Copy(edgeCross)
  157. }
  158. }
  159. }
  160. }
  161. deltaC := math32.NewVec3().SubVectors(posA, posB)
  162. if deltaC.Dot(&penetrationAxis) > 0.0 {
  163. penetrationAxis.Negate()
  164. }
  165. return true, penetrationAxis
  166. }
  167. // Both hulls are projected onto the axis and the overlap size (penetration depth) is returned if there is one.
  168. // return {number} The overlap depth, or FALSE if no penetration.
  169. func (ch *ConvexHull) TestPenetrationAxis(chB *ConvexHull, worldAxis, posA, posB *math32.Vector3, quatA, quatB *math32.Quaternion) (bool, float32) {
  170. maxA, minA := ch.ProjectOntoWorldAxis(worldAxis, posA, quatA)
  171. maxB, minB := chB.ProjectOntoWorldAxis(worldAxis, posB, quatB)
  172. if maxA < minB || maxB < minA {
  173. return false, 0 // Separated
  174. }
  175. d0 := maxA - minB
  176. d1 := maxB - minA
  177. if d0 < d1 {
  178. return true, d0
  179. } else {
  180. return true, d1
  181. }
  182. }
  183. // ProjectOntoWorldAxis projects the geometry onto the specified world axis.
  184. func (ch *ConvexHull) ProjectOntoWorldAxis(worldAxis, pos *math32.Vector3, quat *math32.Quaternion) (float32, float32) {
  185. // Transform the world axis to local
  186. quatConj := quat.Conjugate()
  187. localAxis := worldAxis.Clone().ApplyQuaternion(quatConj)
  188. // Project onto the local axis
  189. max, min := ch.Geometry.ProjectOntoAxis(localAxis)
  190. // Offset to obtain values relative to world origin
  191. localOrigin := math32.NewVec3().Sub(pos).ApplyQuaternion(quatConj)
  192. add := localOrigin.Dot(localAxis)
  193. min -= add
  194. max -= add
  195. return max, min
  196. }
  197. // =====================================================================
  198. //{array} result The an array of contact point objects, see clipFaceAgainstHull
  199. func (ch *ConvexHull) ClipAgainstHull(chB *ConvexHull, posA, posB *math32.Vector3, quatA, quatB *math32.Quaternion, penAxis *math32.Vector3, minDist, maxDist float32) []collision.Contact {
  200. var contacts []collision.Contact
  201. // Invert penetration axis so it points from b to a
  202. invPenAxis := penAxis.Clone().Negate()
  203. // Find face of B that is closest (i.e. that is most aligned with the penetration axis)
  204. closestFaceBidx := -1
  205. dmax := math32.Inf(-1)
  206. worldFaceNormalsB := chB.WorldFaceNormals()
  207. for i, worldFaceNormal := range worldFaceNormalsB {
  208. // Note - normals must be pointing out of the body so that they align with the penetration axis in the line below
  209. d := worldFaceNormal.Dot(invPenAxis)
  210. if d > dmax {
  211. dmax = d
  212. closestFaceBidx = i
  213. }
  214. }
  215. // If found a closest face (sometimes we don't find one)
  216. if closestFaceBidx >= 0 {
  217. // Copy and transform face vertices to world coordinates
  218. faces := chB.Faces()
  219. worldClosestFaceB := ch.WorldFace(faces[closestFaceBidx], posB, quatB)
  220. // Clip the closest world face of B to only the portion that is inside the hull of A
  221. contacts = ch.clipFaceAgainstHull(posA, penAxis, quatA, worldClosestFaceB, minDist, maxDist)
  222. }
  223. return contacts
  224. }
  225. func (ch *ConvexHull) WorldFace(face [3]math32.Vector3, pos *math32.Vector3, quat *math32.Quaternion) [3]math32.Vector3 {
  226. var result [3]math32.Vector3
  227. result[0] = face[0]
  228. result[1] = face[1]
  229. result[2] = face[2]
  230. result[0].ApplyQuaternion(quat).Add(pos)
  231. result[1].ApplyQuaternion(quat).Add(pos)
  232. result[2].ApplyQuaternion(quat).Add(pos)
  233. return result
  234. }
  235. // Clip a face against a hull.
  236. //@param {Array} worldVertsB1 An array of Vec3 with vertices in the world frame.
  237. //@param Array result Array to store resulting contact points in. Will be objects with properties: point, depth, normal. These are represented in world coordinates.
  238. func (ch *ConvexHull) clipFaceAgainstHull(posA, penAxis *math32.Vector3, quatA *math32.Quaternion, worldClosestFaceB [3]math32.Vector3, minDist, maxDist float32) []collision.Contact {
  239. contacts := make([]collision.Contact, 0)
  240. // Find the face of A with normal closest to the separating axis (i.e. that is most aligned with the penetration axis)
  241. closestFaceAidx := -1
  242. dmax := math32.Inf(-1)
  243. worldFaceNormalsA := ch.WorldFaceNormals()
  244. for i, worldFaceNormal := range worldFaceNormalsA {
  245. // Note - normals must be pointing out of the body so that they align with the penetration axis in the line below
  246. d := worldFaceNormal.Dot(penAxis)
  247. if d > dmax {
  248. dmax = d
  249. closestFaceAidx = i
  250. }
  251. }
  252. if closestFaceAidx < 0 {
  253. // Did not find any closest face...
  254. return contacts
  255. }
  256. //console.log("closest A: ",worldClosestFaceA);
  257. // Get the face and construct connected faces
  258. facesA := ch.Faces()
  259. //worldClosestFaceA := n.WorldFace(facesA[closestFaceAidx], bodyA)
  260. closestFaceA := facesA[closestFaceAidx]
  261. connectedFaces := make([]int, 0) // indexes of the connected faces
  262. for faceIdx := 0; faceIdx < len(facesA); faceIdx++ {
  263. // Skip worldClosestFaceA
  264. if faceIdx == closestFaceAidx {
  265. continue
  266. }
  267. // Test that face has not already been added
  268. for _, cfidx := range connectedFaces {
  269. if cfidx == faceIdx {
  270. continue
  271. }
  272. }
  273. face := facesA[faceIdx]
  274. // Loop through face vertices and see if any of them are also present in the closest face
  275. // If a vertex is shared and this connected face hasn't been recorded yet - record and break inner loop
  276. for pConnFaceVidx := 0; pConnFaceVidx < len(face); pConnFaceVidx++ {
  277. var goToNextFace bool
  278. // Test if face shares a vertex with closetFaceA - add it to connectedFaces if so and break out of both loops
  279. for closFaceVidx := 0; closFaceVidx < len(closestFaceA); closFaceVidx++ {
  280. if closestFaceA[closFaceVidx].Equals(&face[pConnFaceVidx]) {
  281. connectedFaces = append(connectedFaces, faceIdx)
  282. goToNextFace = true
  283. break
  284. }
  285. }
  286. if goToNextFace {
  287. break
  288. }
  289. }
  290. }
  291. worldClosestFaceA := ch.WorldFace(closestFaceA, posA, quatA)
  292. // DEBUGGING
  293. //if n.debugging {
  294. // //log.Error("CONN-FACES: %v", len(connectedFaces))
  295. // for _, fidx := range connectedFaces {
  296. // wFace := n.WorldFace(facesA[fidx], bodyA)
  297. // ShowWorldFace(n.simulation.Scene(), wFace[:], &math32.Color{0.8, 0.8, 0.8})
  298. // }
  299. // //log.Error("worldClosestFaceA: %v", worldClosestFaceA)
  300. // //log.Error("worldClosestFaceB: %v", worldClosestFaceB)
  301. // ShowWorldFace(n.simulation.Scene(), worldClosestFaceA[:], &math32.Color{2, 0, 0})
  302. // ShowWorldFace(n.simulation.Scene(), worldClosestFaceB[:], &math32.Color{0, 2, 0})
  303. //}
  304. clippedFace := make([]math32.Vector3, len(worldClosestFaceB))
  305. for i, v := range worldClosestFaceB {
  306. clippedFace[i] = v
  307. }
  308. // TODO port simplified loop to cannon.js once done and verified
  309. // https://github.com/schteppe/cannon.js/issues/378
  310. // https://github.com/TheRohans/cannon.js/commit/62a1ce47a851b7045e68f7b120b9e4ecb0d91aab#r29106924
  311. // Iterate over connected faces and clip the planes associated with their normals
  312. for _, cfidx := range connectedFaces {
  313. connFace := facesA[cfidx]
  314. connFaceNormal := worldFaceNormalsA[cfidx]
  315. // Choose a vertex in the connected face and use it to find the plane constant
  316. worldFirstVertex := connFace[0].Clone().ApplyQuaternion(quatA).Add(posA)
  317. planeDelta := - worldFirstVertex.Dot(&connFaceNormal)
  318. clippedFace = ch.clipFaceAgainstPlane(clippedFace, connFaceNormal.Clone(), planeDelta)
  319. }
  320. // Plot clipped face
  321. //if n.debugging {
  322. // log.Error("worldClosestFaceBClipped: %v", clippedFace)
  323. // ShowWorldFace(n.simulation.Scene(), clippedFace, &math32.Color{0, 0, 2})
  324. //}
  325. closestFaceAnormal := worldFaceNormalsA[closestFaceAidx]
  326. worldFirstVertex := worldClosestFaceA[0].Clone()//.ApplyQuaternion(quatA).Add(&posA)
  327. planeDelta := -worldFirstVertex.Dot(&closestFaceAnormal)
  328. // For each vertex in the clipped face resolve its depth (relative to the closestFaceA) and create a contact
  329. for _, vertex := range clippedFace {
  330. depth := closestFaceAnormal.Dot(&vertex) + planeDelta
  331. // Cap distance
  332. if depth <= minDist {
  333. depth = minDist
  334. }
  335. if depth <= maxDist {
  336. if depth <= 0 {
  337. contacts = append(contacts, collision.Contact{
  338. Point: vertex,
  339. Normal: closestFaceAnormal,
  340. Depth: depth,
  341. })
  342. }
  343. }
  344. }
  345. return contacts
  346. }
  347. // clipFaceAgainstPlane clips the specified face against the back of the specified plane.
  348. // This is used multiple times when finding the portion of a face of one convex hull that is inside another convex hull.
  349. // planeNormal and planeConstant satisfy the plane equation n*x = p where n is the planeNormal and p is the planeConstant (and x is a point on the plane).
  350. func (ch *ConvexHull) clipFaceAgainstPlane(face []math32.Vector3, planeNormal *math32.Vector3, planeConstant float32) []math32.Vector3 {
  351. // inVertices are the verts making up the face of hullB
  352. clippedFace := make([]math32.Vector3, 0)
  353. // If not a face (if an edge or a vertex) - don't clip it
  354. if len(face) < 2 {
  355. return face
  356. }
  357. firstVertex := face[len(face)-1]
  358. dotFirst := planeNormal.Dot(&firstVertex) + planeConstant
  359. for vi := 0; vi < len(face); vi++ {
  360. lastVertex := face[vi]
  361. dotLast := planeNormal.Dot(&lastVertex) + planeConstant
  362. if dotFirst < 0 { // Inside hull
  363. if dotLast < 0 { // Start < 0, end < 0, so output lastVertex
  364. clippedFace = append(clippedFace, lastVertex)
  365. } else { // Start < 0, end >= 0, so output intersection
  366. newv := firstVertex.Clone().Lerp(&lastVertex, dotFirst / (dotFirst - dotLast))
  367. clippedFace = append(clippedFace, *newv)
  368. }
  369. } else { // Outside hull
  370. if dotLast < 0 { // Start >= 0, end < 0 so output intersection and end
  371. newv := firstVertex.Clone().Lerp(&lastVertex, dotFirst / (dotFirst - dotLast))
  372. clippedFace = append(clippedFace, *newv)
  373. clippedFace = append(clippedFace, lastVertex)
  374. }
  375. }
  376. firstVertex = lastVertex
  377. dotFirst = dotLast
  378. }
  379. return clippedFace
  380. }