<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom"><channel><title>Collisison Detection on My Blog</title><link>https://EMEEEEMMMM.github.io/tags/collisison-detection/</link><description>Recent content in Collisison Detection on My Blog</description><generator>Hugo -- gohugo.io</generator><language>en-us</language><lastBuildDate>Thu, 18 Jun 2026 14:05:02 +0800</lastBuildDate><atom:link href="https://EMEEEEMMMM.github.io/tags/collisison-detection/index.xml" rel="self" type="application/rss+xml"/><item><title>Structure Of The Engine</title><link>https://EMEEEEMMMM.github.io/posts/structureoftheengine/</link><pubDate>Thu, 18 Jun 2026 14:05:02 +0800</pubDate><guid>https://EMEEEEMMMM.github.io/posts/structureoftheengine/</guid><description>&lt;img src="https://EMEEEEMMMM.github.io/" alt="Featured image of post Structure Of The Engine" /&gt;&lt;p&gt;This article i want to share about the two version in my &lt;a class="link" href="https://github.com/EMEEEEMMMM/AtlasPhys" target="_blank" rel="noopener"
 &gt;repository&lt;/a&gt;, the first one is prototype written in python.&lt;/p&gt;
&lt;h2 id="1-overview"&gt;1. Overview
&lt;/h2&gt;&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Integration&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Broad Phase&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Narrow Phase&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Contact Generation&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Constraint Solving&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Positional Correction&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Others&lt;/strong&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;h2 id="2-integration"&gt;2. Integration:
&lt;/h2&gt;&lt;p&gt;A typical semi-implicit Euler method to update the objects&amp;rsquo; velocity and position. The position is updated right after the velocity. I manually set the $\tau$ to zero so that the $\omega$ won&amp;rsquo;t change by itself.
&lt;/p&gt;
$$
\begin{align}
&amp;a = g \\
&amp;{v_{t + \Delta t}} = {v_t} + a\Delta t \\
&amp;{x_{t + \Delta t}} = {x_t} + {v_{t + \Delta t}}\Delta t \\
&amp;\tau = I\alpha \\
&amp;{\omega _{t + \Delta t}} = {\omega _t} + I_{world}^{ - 1}\tau \Delta t \\
&amp;{\theta _{t + \Delta t}} = {\theta _t} + {\omega _{t + \Delta t}}\Delta t \\
&amp;I_{world}^{ - 1} = RI_{body}^{ - 1}{R^T} 
\end{align}$$&lt;h2 id="3-broad-phase"&gt;3. Broad Phase:
&lt;/h2&gt;&lt;p&gt;My implementation of AABB is different from the standards, the time complexity is stil $O(n^2)$ technically, its role is to filter the objects are probably not going to collide. And since there are only two shapes allowed in the scene (Cubes and Spheres), AABB will work perfectly as I expected. I generate the object&amp;rsquo;s X/Y/Z_MAX/MIN when the object was created (G_Object.py line 128&amp;amp;133).&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt;1
&lt;/span&gt;&lt;span class="lnt"&gt;2
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-python" data-lang="python"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;MaxXYZ&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;NDArray&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;np&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;uint8&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;np&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;argmax&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;XYZVertices&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;axis&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;MinXYZ&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;NDArray&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;np&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;uint8&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;np&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;argmin&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;XYZVertices&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;axis&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;Since the XYZ values here are all in the local space, so when detection i added the object&amp;rsquo;s position.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt; 1
&lt;/span&gt;&lt;span class="lnt"&gt; 2
&lt;/span&gt;&lt;span class="lnt"&gt; 3
&lt;/span&gt;&lt;span class="lnt"&gt; 4
&lt;/span&gt;&lt;span class="lnt"&gt; 5
&lt;/span&gt;&lt;span class="lnt"&gt; 6
&lt;/span&gt;&lt;span class="lnt"&gt; 7
&lt;/span&gt;&lt;span class="lnt"&gt; 8
&lt;/span&gt;&lt;span class="lnt"&gt; 9
&lt;/span&gt;&lt;span class="lnt"&gt;10
&lt;/span&gt;&lt;span class="lnt"&gt;11
&lt;/span&gt;&lt;span class="lnt"&gt;12
&lt;/span&gt;&lt;span class="lnt"&gt;13
&lt;/span&gt;&lt;span class="lnt"&gt;14
&lt;/span&gt;&lt;span class="lnt"&gt;15
&lt;/span&gt;&lt;span class="lnt"&gt;16
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-python" data-lang="python"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="n"&gt;check_aabb&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;	&lt;span class="n"&gt;A&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;X_MAX&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;A&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Position&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;	&lt;span class="n"&gt;A&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;X_MIN&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;A&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Position&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;	&lt;span class="n"&gt;A&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Y_MAX&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;A&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Position&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;	&lt;span class="n"&gt;A&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Y_MIN&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;A&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Position&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;	&lt;span class="n"&gt;A&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Z_MAX&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;A&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Position&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;	&lt;span class="n"&gt;A&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Z_MIN&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;A&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Position&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;	&lt;span class="n"&gt;B&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;X_MAX&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;B&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Position&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;	&lt;span class="n"&gt;B&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;X_MIN&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;B&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Position&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;	&lt;span class="n"&gt;B&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Y_MAX&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;B&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Position&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;	&lt;span class="n"&gt;B&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Y_MIN&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;B&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Position&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;	&lt;span class="n"&gt;B&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Z_MAX&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;B&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Position&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;	&lt;span class="n"&gt;B&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Z_MIN&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;B&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Position&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;):&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;continue&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;h2 id="4-narrow-phase"&gt;4. Narrow Phase:
&lt;/h2&gt;&lt;p&gt;Consists of two parts: GJK and EPA. All of the code about these two can be found in GJK.py. GJK and EPA happens only when Cube and Cube are going to collide reported by the AABB method to avoid performance issues of running GJK and EPA on spheres.&lt;/p&gt;
&lt;h3 id="41-gjk"&gt;4.1 GJK:
&lt;/h3&gt;&lt;h4 id="minkowski-sums--differences"&gt;Minkowski Sums / Differences
&lt;/h4&gt;&lt;h5 id="sum"&gt;Sum:
&lt;/h5&gt;$${\rm{A}} \oplus B = \left\{ {a + b|a \in A,b \in B} \right\}$$&lt;h5 id="difference"&gt;Difference:
&lt;/h5&gt;$${\rm{A}} \ominus B = \left\{ {a + \left( { - {\rm{b}}} \right)|a \in A,b \in B} \right\}$$&lt;h5 id="properties"&gt;Properties:
&lt;/h5&gt;&lt;ol&gt;
&lt;li&gt;A and B convex -&amp;gt; ${\rm{A}} \ominus B$ convex&lt;/li&gt;
&lt;li&gt;A and B intersect -&amp;gt; $\left( {0,0} \right) \in A \ominus&lt;/li&gt;
&lt;/ol&gt;
&lt;h4 id="support-function2-dimension"&gt;Support function(2 dimension):
&lt;/h4&gt;&lt;p&gt;C(Minkowski Sum):
&lt;/p&gt;
$$C = A \oplus B $$&lt;p&gt;
The furthest point on A in the direction of ${\vec d}$:
&lt;/p&gt;
$$ {s_A}\left( {\vec d} \right) \to \left( {{x_A},{y_A}} \right) $$&lt;p&gt;
The furthest point on B in the direction of ${\vec d}$:
&lt;/p&gt;
$${s_B}\left( {\vec d} \right) \to \left( {{x_B},{y_B}} \right) $$&lt;p&gt;
Therefore, the furthest point on C in the direction of ${\vec d}$:
&lt;/p&gt;
$$ {s_C}\left( {\vec d} \right) = {s_A}\left( {\vec d} \right) + {s_B}\left( {\vec d} \right) \to \left( {{x_C},{y_C}} \right) $$&lt;p&gt;
&lt;img alt="Notes taken" class="gallery-image" data-flex-basis="320px" data-flex-grow="133" height="6144" loading="lazy" sizes="(max-width: 767px) calc(100vw - 30px), (max-width: 1023px) 700px, (max-width: 1279px) 950px, 1232px" src="https://EMEEEEMMMM.github.io/posts/structureoftheengine/IMG_20260213_154637.jpg" srcset="https://EMEEEEMMMM.github.io/posts/structureoftheengine/IMG_20260213_154637_hu_29e16e6fe08f7158.jpg 800w, https://EMEEEEMMMM.github.io/posts/structureoftheengine/IMG_20260213_154637_hu_bd4682a8a25dfd09.jpg 1600w, https://EMEEEEMMMM.github.io/posts/structureoftheengine/IMG_20260213_154637_hu_bc94e72f21a59986.jpg 2400w, https://EMEEEEMMMM.github.io/posts/structureoftheengine/IMG_20260213_154637.jpg 8192w" width="8192"&gt;
To judge whether the simplex contains the origin, there is a classification for the linear, triangle, tetrahedral cases.&lt;/p&gt;
&lt;h4 id="linear-case"&gt;Linear case:
&lt;/h4&gt;&lt;p&gt;After the initialization, the simplex should contains two points. In 3D space the probability of the origin in a line segment is 0, so the method takes the role of updating the direction variable and returns &lt;em&gt;False&lt;/em&gt;.
Simplex = [B, A] a line segment where A is the latest point added in.
If the dot product $\vec{AB} \cdot \vec{AO} &amp;gt; 0$, the direction is updated as the triple cross $(\vec{AB} \cdot \vec{AO}) \cdot \vec{AB}$. Else, delete point A and update the direction as $\vec{AO}$ since in this case point B is not helpful to find a closer point to origin than A and that makes the result discrete. Finally, returns &lt;em&gt;False&lt;/em&gt;.&lt;/p&gt;
&lt;h4 id="triangle-case"&gt;Triangle case:
&lt;/h4&gt;&lt;p&gt;Simplex = [C, B, A] a triangle. The goal in at this stage is 1. Judge whether the triangle contains the origin. 2. If not, trim the simplex into a line segment and update the direction. Core logic here is 1. Determine whether the origin is on which side of the plane where the triangle is located. 2. Determine whether the origin is outside which side of the plane of the triangle. 3. Reduce the triangle to a line segment and continue the iteration.&lt;/p&gt;
&lt;h4 id="tetrahedral-case"&gt;Tetrahedral case:
&lt;/h4&gt;&lt;p&gt;Simplex = [D, C, B, A] a tetrahedral.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Calculate the normal vector of each face.&lt;/li&gt;
&lt;li&gt;Determine whether the origin is on the outside of each face.&lt;/li&gt;
&lt;li&gt;If the origin is on the outside of any face, trim the point which is not on that face and pass the simplex to the triangle case, returns &lt;em&gt;False&lt;/em&gt;. If the origin is on the inside of all the faces, the tetrahedral contains the origin, the function returns &lt;em&gt;True&lt;/em&gt;.&lt;/li&gt;
&lt;/ol&gt;
&lt;h3 id="42-epa"&gt;4.2 EPA:
&lt;/h3&gt;&lt;p&gt;Core logic here is to expand the simplex the GJK returns to approach to the actual Minkowski difference of the two objects.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Obtain the edge with the shortest distance to the origin, and denote the corresponding Minkowski difference as $M_{1},M_{2}$. Find the vector perpendicular to this edge and directed away from the origin.&lt;/li&gt;
&lt;li&gt;Then calculated Minkowski difference $M_{3}$.&lt;/li&gt;
&lt;li&gt;If $\left\vert M_{1}-M_{3} \right\vert+\left\vert M_{2}-M_{3} \right\vert &amp;lt; \epsilon$, exit the iteration.&lt;/li&gt;
&lt;li&gt;If $M_{3}$ not belonging to the point found in the direction, exit iteration.&lt;/li&gt;
&lt;li&gt;If $M_{1}=M_{3}$ or $M_{2}=M_{3}$ if there is a repetition in the existing simplex, exit the iteration.&lt;/li&gt;
&lt;/ol&gt;
&lt;h1 id="5-contact-generation"&gt;5. Contact Generation:
&lt;/h1&gt;&lt;p&gt;All manifolds of contacts are managed by the class &lt;em&gt;ManifoldManager&lt;/em&gt;, its &lt;em&gt;update()&lt;/em&gt; method returns a list contains the instance of another class &lt;em&gt;PersistentContactManifold&lt;/em&gt;.&lt;/p&gt;
&lt;h2 id="51-manifoldmanager"&gt;5.1 ManifoldManager:
&lt;/h2&gt;&lt;p&gt;It has only one method &lt;em&gt;update()&lt;/em&gt;, this method will be executed every physic step to update the contacts between objects:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;It walks through the list of objects ($O(n^2)$).&lt;/li&gt;
&lt;li&gt;AABB detects whether the objects are going to collide, if not continue to the next object.&lt;/li&gt;
&lt;li&gt;Generate &lt;em&gt;Keys&lt;/em&gt; to avoid having two identical contact manifold.&lt;/li&gt;
&lt;li&gt;&lt;em&gt;generate_contacts()&lt;/em&gt;&lt;/li&gt;
&lt;li&gt;&lt;em&gt;update_from_collision()&lt;/em&gt; method from &lt;em&gt;PersistentContactManifold&lt;/em&gt;&lt;/li&gt;
&lt;li&gt;Clean up the dead keys. (It seems that i had found a bug in the program which there is no method to remove keys from the &lt;em&gt;ActiveKeys&lt;/em&gt;.)&lt;/li&gt;
&lt;li&gt;Returns the Result list.&lt;/li&gt;
&lt;/ol&gt;
&lt;h2 id="52-generate_"&gt;5.2 &lt;em&gt;generate_contacts()&lt;/em&gt;:
&lt;/h2&gt;&lt;h3 id="521-generate_pc_contacts"&gt;5.2.1 generate_pc_contacts():
&lt;/h3&gt;&lt;ol&gt;
&lt;li&gt;Walk through the list of vertices of the cube.&lt;/li&gt;
&lt;li&gt;If $\begin{bmatrix}0.0 \ 1.0 \ 0.0 \end{bmatrix} \cdot \begin{bmatrix}x \ y \ z \end{bmatrix} - PlaneHeight &amp;lt; 0$, append a contact into the contacts list.&lt;/li&gt;
&lt;li&gt;Returns contacts list and the normal.&lt;/li&gt;
&lt;/ol&gt;
&lt;h3 id="522-generate_ps_contacts"&gt;5.2.2 generate_ps_contacts():
&lt;/h3&gt;&lt;ol&gt;
&lt;li&gt;If $\begin{bmatrix} 0.0 \ 1.0 \ 0.0 \end{bmatrix} \cdot \begin{bmatrix} x \ y \ z \end{bmatrix}-PlaneHeight \geq R$, returns an empty list and the normal since didn&amp;rsquo;t collide.&lt;/li&gt;
&lt;li&gt;Returns $\begin{bmatrix}x \ y-R \ z\end{bmatrix}$,$R-(\begin{bmatrix}0.0 \ 1.0 \ 0.0 \end{bmatrix} \cdot \begin{bmatrix}x \ y \ z \end{bmatrix} - PlaneHeight)$,normal.&lt;/li&gt;
&lt;/ol&gt;
&lt;h3 id="523-generate_ss_contacts"&gt;5.2.3 generate_ss_contacts():
&lt;/h3&gt;&lt;ol&gt;
&lt;li&gt;If $\sqrt{(x_{B}-x_{A})^2+(y_{B}-y_{A})^2+(z_{B}-z_{A})^2}\geq R_{A}+R_{B}$, returns two empty lists since didn&amp;rsquo;t collide.&lt;/li&gt;
&lt;li&gt;Returns $\begin{bmatrix}x_{A} \ y_{A} \ z_{A}\end{bmatrix}+\begin{bmatrix}x_{B}-x_{A} \ y_{B}-y_{A} \ z_{B}-z_{A}\end{bmatrix}*R_{A}$,$R_{A}+R_{B} - \sqrt{(x_{B}-x_{A})^2+(y_{B}-y_{A})^2+(z_{B}-z_{A})^2}$, normal.&lt;/li&gt;
&lt;/ol&gt;
&lt;h3 id="524-generate_cc_contacts"&gt;5.2.4 generate_cc_contacts():
&lt;/h3&gt;&lt;ol&gt;
&lt;li&gt;GJK+EPA returns whether collide, the normal, the Depth.&lt;/li&gt;
&lt;li&gt;Face clipping using Sutherland Hodgman algorithm.&lt;/li&gt;
&lt;/ol&gt;
&lt;h3 id="525-generate_cs_contacts"&gt;5.2.5 generate_cs_contacts():
&lt;/h3&gt;&lt;ol&gt;
&lt;li&gt;Turn the center of the sphere into the local space of the cube.&lt;/li&gt;
&lt;li&gt;Determine which plane the center of the ball is closest to.&lt;/li&gt;
&lt;li&gt;Choose the normal of that surface.&lt;/li&gt;
&lt;li&gt;Calculate penetration.&lt;/li&gt;
&lt;li&gt;Generate a contact point.&lt;/li&gt;
&lt;/ol&gt;
&lt;h2 id="53-persistentcontactmanifold"&gt;5.3 PersistentContactManifold:
&lt;/h2&gt;&lt;h3 id="warm-start"&gt;Warm start:
&lt;/h3&gt;&lt;p&gt;After the &lt;em&gt;update()&lt;/em&gt; method in ManifoldManager, &lt;em&gt;warm_start()&lt;/em&gt; method will be executed. It uses the impulse from last physic step and applies that to the object by using the method &lt;em&gt;_apply_impulse()&lt;/em&gt; since the contact of objects are not instantaneous but continuous. It makes the system approach the solution more quickly and helps improve the stabilization.&lt;/p&gt;
&lt;h3 id="apply-impulse"&gt;Apply Impulse:
&lt;/h3&gt;&lt;p&gt;All the impulse calculated will be applied by this function.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt;1
&lt;/span&gt;&lt;span class="lnt"&gt;2
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-python" data-lang="python"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;A&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Velocity&lt;/span&gt; &lt;span class="o"&gt;-=&lt;/span&gt; &lt;span class="n"&gt;J&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;A&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ReciprocalMass&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;B&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Velocity&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="n"&gt;J&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;B&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ReciprocalMass&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;$$
\begin{align}
F&amp;=m\frac{dv}{dt} \\
\int F dt&amp;=m \int \frac{dv}{dt} dt \\
J &amp;= m(v'-v) \\
\Delta v &amp;= \frac{J}{m}
\end{align}
$$&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt;1
&lt;/span&gt;&lt;span class="lnt"&gt;2
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-python" data-lang="python"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;A&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;AngularVelocity&lt;/span&gt; &lt;span class="o"&gt;-=&lt;/span&gt; &lt;span class="n"&gt;A&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;InvInertiaWorld&lt;/span&gt; &lt;span class="o"&gt;@&lt;/span&gt; &lt;span class="n"&gt;np&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;cross&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Ra&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;J&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;B&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;AngularVelocity&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="n"&gt;B&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;InvInertiaWorld&lt;/span&gt; &lt;span class="o"&gt;@&lt;/span&gt; &lt;span class="n"&gt;np&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;cross&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Rb&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;J&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;$$
\begin{align}
F &amp;= \frac{dp}{dt} \\
\int F dt &amp;= \int \frac{dp}{dt}dt \\
J &amp;= \Delta p \\ \\
 \\
\Delta L &amp;= r \times \Delta p = r \times J \\
I \Delta \omega &amp;= r \times J \\
\Delta \omega &amp;= I^{-1}(r \times p)
\end{align}
$$&lt;h1 id="6-constraint-solving"&gt;6. Constraint Solving:
&lt;/h1&gt;&lt;h2 id="61-normal-impulse"&gt;6.1 Normal Impulse:
&lt;/h2&gt;$$
\begin{align}
&amp;{r_A} = P - {x_A},{r_B} = P - {x_B}\\
&amp;{v_{P,A}} = {v_A} + {\omega _A} \times {r_A},{v_{P,B}} = {v_B} + {\omega _B} \times {r_B}\\
&amp;{v_{rel}} = {v_{P,B}} - {v_{P,A}}\\
&amp;{v_n} = {v_{rel}} \cdot n\\
&amp;J = jn\\
&amp;v_A' = {v_A} - \frac{j}{{{m_A}}}n,v_B' = {v_B} + \frac{j}{{{m_B}}}n\\
&amp;\omega _A' = {\omega _A} - I_A^{ - 1}\left( {{r_A} \times jn} \right),\omega _B' = {\omega _B} + I_B^{ - 1}\left( {{r_B} \times jn} \right)\\
&amp;v_{P,A}' = v_A' + \omega _A' \times {r_A},v_{P,B}' = v_B' + \omega _B' \times {r_B}\\
&amp;v_{rel}' = v_{P,B}' - v_{P,A}'\\
&amp;v_{re{l^\prime }} \cdot n = - e\left( {{v_{rel}} \cdot n} \right)\\
&amp;j = - \frac{{\left( {1 + e} \right){v_{rel}} \cdot n}}{{\frac{1}{{{m_A}}} + \frac{1}{{{m_B}}} + n \cdot \left[ {\left( {I_A^{ - 1}\left( {{r_A} \times n} \right)} \right) \times {r_A} + \left( {I_B^{ - 1}\left( {{r_B} \times n} \right)} \right) \times {r_B}} \right]}}
\end{align}
$$&lt;h2 id="62-friction"&gt;6.2 Friction:
&lt;/h2&gt;&lt;p&gt;I don&amp;rsquo;t know about this, too complicated.&lt;/p&gt;
&lt;h1 id="7-positional-correction"&gt;7. Positional Correction:
&lt;/h1&gt;&lt;p&gt;Used &lt;strong&gt;Baumgarte Stabilization Method&lt;/strong&gt; to avoid objects sink into each other, a correction directly on position with weighted mass distribution.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt;1
&lt;/span&gt;&lt;span class="lnt"&gt;2
&lt;/span&gt;&lt;span class="lnt"&gt;3
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-python" data-lang="python"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;Correction&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;NDArray&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;np&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;float32&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mf"&gt;0.2&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Penetration&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;SLOP&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;A&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ReciprocalMass&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;B&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ReciprocalMass&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Normal&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;A&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Position&lt;/span&gt; &lt;span class="o"&gt;-=&lt;/span&gt; &lt;span class="n"&gt;Correction&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;A&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ReciprocalMass&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;B&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Position&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="n"&gt;Correction&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;B&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ReciprocalMass&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;$$
\begin{align}
Correction_{n}=\beta \cdot (P-slop) \cdot \frac{\vec{n}}{m_{A}^{-1}+m_{B}^{-1}} \cdot w_{n} \\
\small where \; P \; is \; the \; max \; penetration \;,\; w_{n} =m_{n}^{-1}
\end{align}
$$&lt;hr&gt;
&lt;p&gt;The version in c++ is really similar to the version in python but have some major differences.&lt;/p&gt;
&lt;p&gt;In c++ version, I uses SAT + XPBD instead of GJK+EPA+PBD. The reason why is simple, SAT is much more easier to understand than either GJK or EPA. Just imagine a flashlight shining on two objects, if they didn&amp;rsquo;t collide, there must be at least one angle that when the flashlight is on them, the shadow they create are separate. From my personal experience of using GJK+EPA, they are too complicated to notice implementational errors in them. And huge cost of performance for them. As for XPBD, it is a better solution for the engine than PBD that&amp;rsquo;s for sure. But also much more complicated no matter the derivation of it or just implement it into the engine. The explanation for all these amazing algorithm can be found on almost every platform, I don&amp;rsquo;t think I will post blogs about any of these algorithms.&lt;/p&gt;
&lt;p&gt;Since this version is actually not even a half-baked product, no contact manifold in c++ version. This is probably the end of this project. I really like this project, I had been working on it for almost one year (during my school years of course), but it is time to move on.&lt;/p&gt;</description></item><item><title>Narrow Phase: GJK and EPA</title><link>https://EMEEEEMMMM.github.io/posts/gjkandepa/</link><pubDate>Sat, 02 May 2026 12:18:31 +0800</pubDate><guid>https://EMEEEEMMMM.github.io/posts/gjkandepa/</guid><description>&lt;img src="https://EMEEEEMMMM.github.io/" alt="Featured image of post Narrow Phase: GJK and EPA" /&gt;&lt;h1 id="narrow-phase-collision-detection-gjk-and-epa"&gt;Narrow Phase Collision Detection: GJK and EPA:
&lt;/h1&gt;&lt;p&gt;The narrow phase of the engine consists of two parts: Gilbert-Johnson-Keerthi algorithm (GJK) and Expanding Polytope algorithm (EPA). GJK tells you about whether the two object is colliding and the terminal simplex of the two objects&amp;rsquo; Minkowski difference. EPA tells you about the normal of the contact point and the depth of the collision.&lt;/p&gt;
&lt;h2 id="gjk"&gt;GJK:
&lt;/h2&gt;&lt;p&gt;The GJK does not operate directly on the polygons themselves. Instead, it operates on the mathematical abstraction know as the Minkowski Difference.&lt;/p&gt;
&lt;h3 id="minkowski-sums--differences"&gt;Minkowski Sums / Differences
&lt;/h3&gt;&lt;h4 id="sum"&gt;Sum:
&lt;/h4&gt;$${\rm{A}} \oplus B = \left\{ {a + b|a \in A,b \in B} \right\}$$&lt;h4 id="difference"&gt;Difference:
&lt;/h4&gt;$${\rm{A}} \ominus B = \left\{ {a + \left( { - {\rm{b}}} \right)|a \in A,b \in B} \right\}$$&lt;h4 id="properties"&gt;Properties:
&lt;/h4&gt;&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Preservation of Convexity:&lt;/strong&gt; If objects A and B are convex, their Minkowski difference $A \ominus B$ is strictly convex.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Intersection Equivalence:&lt;/strong&gt; Objects A and B intersect if and only if the origin vector is contained within their Minkowski difference, i.e., $(0,0) \in A \ominus B$.&lt;/li&gt;
&lt;/ol&gt;
&lt;h3 id="support-function2-dimension"&gt;Support function(2 dimension):
&lt;/h3&gt;&lt;p&gt;Instead of explicitly calculating the entire, complex Minkowski difference which is theoretically infinite, GJK relies on a &lt;em&gt;Support Function&lt;/em&gt; to sample it iteratively.&lt;/p&gt;
&lt;p&gt;C(Minkowski Sum):
&lt;/p&gt;
$$C = A \oplus B $$&lt;p&gt;
The furthest point on A in the direction of ${\vec d}$:
&lt;/p&gt;
$$ {s_A}\left( {\vec d} \right) \to \left( {{x_A},{y_A}} \right) $$&lt;p&gt;
The furthest point on B in the direction of ${-\vec d}$:
&lt;/p&gt;
$${s_B}\left( {-\vec d} \right) \to \left( {{x_B},{y_B}} \right) $$&lt;p&gt;
Therefore, a point $S_{A \ominus B} (d)$, we compute:
&lt;/p&gt;
$$ {s_C}\left( {\vec d} \right) = {s_A}\left( {\vec d} \right) - {s_B}\left( {- \vec d} \right) \to \left( {{x_C},{y_C}} \right) $$&lt;p&gt;
&lt;img alt="Notes" class="gallery-image" data-flex-basis="320px" data-flex-grow="133" height="6144" loading="lazy" sizes="(max-width: 767px) calc(100vw - 30px), (max-width: 1023px) 700px, (max-width: 1279px) 950px, 1232px" src="https://EMEEEEMMMM.github.io/posts/gjkandepa/IMG_20260213_154637.jpg" srcset="https://EMEEEEMMMM.github.io/posts/gjkandepa/IMG_20260213_154637_hu_29e16e6fe08f7158.jpg 800w, https://EMEEEEMMMM.github.io/posts/gjkandepa/IMG_20260213_154637_hu_bd4682a8a25dfd09.jpg 1600w, https://EMEEEEMMMM.github.io/posts/gjkandepa/IMG_20260213_154637_hu_bc94e72f21a59986.jpg 2400w, https://EMEEEEMMMM.github.io/posts/gjkandepa/IMG_20260213_154637.jpg 8192w" width="8192"&gt;
To judge whether the simplex contains the origin, there is a classification for the linear, triangle, tetrahedral cases.&lt;/p&gt;
&lt;p&gt;The logic of the implementation of the support function in my prototype of the engine follows the following steps:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;Get the object&amp;rsquo;s model matrix. The matrix contains the object&amp;rsquo;s current # ! Position, Rotation and Scale&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Convert the direction from the World Space -&amp;gt; Local Space. Create a 4D vector for the direction (w = 0 means that it is a direction instead of a point). Multiply the direction by the tranpose of the matrix. Local direction = Direction * Transpose of matrix&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Find the furthest point in local direction&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Convert the point from Local Space -&amp;gt; World Space. Create a 4D vector for the point(w = 1 means the it is a point instead of a direction). World Point = Matrix * Point&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Repeat the above operation on two object. The result point will be Point A - Point B.&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;h3 id="simplex-evolution-and-the-origin-check"&gt;Simplex Evolution and the Origin Check
&lt;/h3&gt;&lt;p&gt;GJK iteratively builds a simplex (a point, line segment, triangle, or tetrahedron) inside the Minkowski Difference to enclose the origin. To judge whether the simplex contains the origin, there is a distinct classification for the linear, triangle, and tetrahedral cases.&lt;/p&gt;
&lt;h4 id="linear-case"&gt;Linear case:
&lt;/h4&gt;&lt;p&gt;After the initialization, the simplex should contains two points. In 3D space the probability of the origin in a line segment is 0, so the method takes the role of updating the direction variable and returns &lt;em&gt;False&lt;/em&gt;.
Simplex = [B, A] a line segment where A is the latest point added in.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;If the dot product $\vec{AB} \cdot \vec{AO} &amp;gt; 0$, the origin lies in the region perpendicular to the segment. The search direction is updated as $\vec{AB} \times (\vec{AO} \times \vec{AB})$, which yields a vector perpendicular to $\vec{AB}$ pointing towards the origin.&lt;/li&gt;
&lt;li&gt;Else, delete point A and update the direction as $\vec{AO}$. In this case, point B is not helpful to find a closer point to the origin than A. This makes the geometric result discrete. Finally, the iteration returns &lt;em&gt;False&lt;/em&gt;, and a new point is searched. In 3D space, the probability of the origin lying exactly on a line segment is near 0.&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 id="triangle-case"&gt;Triangle case:
&lt;/h4&gt;&lt;p&gt;Simplex = $[C, B, A]$, forming a triangle. The goals at this stage are:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Judge whether the triangle contains the origin.&lt;/li&gt;
&lt;li&gt;If not, trim the simplex into a line segment and update the direction.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;The core logic requires determining the region the origin resides in:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Determine which side of the plane the triangle is located relative to the origin.&lt;/li&gt;
&lt;li&gt;Determine whether the origin is outside which specific edge of the triangle. This is achieved using edge normals (e.g., $\vec{AB}^\perp = \vec{AB} \times \vec{ABC}$ where $\vec{ABC}$ is the triangle&amp;rsquo;s surface normal).&lt;/li&gt;
&lt;li&gt;If the origin is outside an edge (e.g., $\vec{AC}$), reduce the triangle to a line segment (keep A and C), update the search direction orthogonal to that edge, and continue the iteration. If it is contained within all edge boundaries, we proceed to 3D analysis or return &lt;em&gt;True&lt;/em&gt; (for 2D).&lt;/li&gt;
&lt;/ol&gt;
&lt;h4 id="tetrahedral-case"&gt;Tetrahedral case:
&lt;/h4&gt;&lt;p&gt;Simplex = $[D, C, B, A]$, forming a tetrahedral structure.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Calculate the normal vector of each of the four faces. (Ensure normals point outward from the tetrahedron&amp;rsquo;s center).&lt;/li&gt;
&lt;li&gt;Determine whether the origin is on the outside of each face. (Evaluated via dot product: $\vec{Normal} \cdot \vec{AO} &amp;gt; 0$).&lt;/li&gt;
&lt;li&gt;If the origin is on the outside of any face, trim the point which is not on that face. Pass the newly reduced simplex (a triangle) back to the triangle case logic, and return &lt;em&gt;False&lt;/em&gt;.&lt;/li&gt;
&lt;li&gt;If the origin is on the inside of all the faces, the tetrahedral definitively contains the origin. The function returns &lt;em&gt;True&lt;/em&gt;, signaling an intersection.&lt;/li&gt;
&lt;/ol&gt;
&lt;hr&gt;
&lt;h2 id="epa"&gt;EPA:
&lt;/h2&gt;&lt;p&gt;Once GJK returns &lt;em&gt;True&lt;/em&gt;, we have an intersecting simplex. However, GJK cannot calculate penetration depth. The core logic of EPA here is to geometrically expand the simplex that GJK returns to approach the actual boundary of the Minkowski difference of the two objects. The minimum distance from the origin to this boundary represents the penetration depth and collision normal.&lt;/p&gt;
&lt;h3 id="iterative-expansion-logic"&gt;Iterative Expansion Logic:
&lt;/h3&gt;&lt;p&gt;The standard EPA pipeline proceeds as follows:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Find the Closest Feature:&lt;/strong&gt; Obtain the edge (in 2D) or face (in 3D) of the simplex with the shortest perpendicular distance to the origin. Denote the corresponding Minkowski difference vertices of this edge as $M_1, M_2$. Find the normal vector perpendicular to this edge and directed away from the origin.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Support Point Sampling:&lt;/strong&gt; Calculate a new Minkowski difference support point, $M_3$, by evaluating the support function along this normal direction.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Collinearity/Convergence Check:&lt;/strong&gt; If $\left| M_1 - M_3 \right| + \left| M_2 - M_3 \right| &amp;lt; \epsilon$, exit the iteration. &lt;em&gt;(Academic Note: This condition checks if $M_3$ lies exactly on the segment $M_1 M_2$. In practice, standard 3D EPA checks if the dot product of $M_3$ along the normal is minimally larger than the distance to the face).&lt;/em&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Directional Progress Check:&lt;/strong&gt; If $M_3$ does not belong to the furthest point found in the direction (i.e., no significant progress is made outward), exit the iteration. The current distance to the feature is the penetration depth.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Duplication Check:&lt;/strong&gt; If $M_1 = M_3$ or $M_2 = M_3$, indicating a repetition in the existing simplex, exit the iteration. The algorithm has found the true boundary.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Expansion:&lt;/strong&gt; If the checks pass, split the closest edge/face by inserting $M_3$, creating new edges/faces, and repeat the process from Step 1 until the exact boundary is met.&lt;/li&gt;
&lt;/ol&gt;
&lt;h2 id="implementation"&gt;Implementation
&lt;/h2&gt;&lt;p&gt;The full implementation of GJK and EPA can be find at &lt;a class="link" href="https://github.com/EMEEEEMMMM/AtlasPhys/blob/main/prototype_python/utils/GJK.py" target="_blank" rel="noopener"
 &gt;https://github.com/EMEEEEMMMM/AtlasPhys/blob/main/prototype_python/utils/GJK.py&lt;/a&gt; and &lt;a class="link" href="https://github.com/EMEEEEMMMM/AtlasPhys/blob/main/prototype_python/utils/EPA.py" target="_blank" rel="noopener"
 &gt;https://github.com/EMEEEEMMMM/AtlasPhys/blob/main/prototype_python/utils/EPA.py&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;The visualization of GJK made by AI.
&lt;div class="interactive-canvas-wrap" style="position:relative;width:100%;padding-top:62.5%;"&gt;
 &lt;canvas id="gjk3dCanvas" class="interactive-canvas" style="position:absolute;inset:0;width:100%;height:100%;display:block;"&gt;&lt;/canvas&gt;
&lt;/div&gt;&lt;script src="https://EMEEEEMMMM.github.io/js/gjk-demo.js" defer&gt;&lt;/script&gt;
&lt;script defer&gt;
window.addEventListener('load', function () {
 const id = "gjk3dCanvas";
 const c = document.getElementById(id);
 if (!c) return;
 const p = c.parentElement;
 function fit() {
 const w = p.clientWidth;
 const h = p.clientHeight || Math.max(window.innerHeight * 0.5, 300);
 c.style.width = '100%';
 c.style.height = '100%';
 try { c.width = w; c.height = h; } catch (e) {}
 window.dispatchEvent(new Event('resize'));
 }
 fit();
 if (typeof ResizeObserver !== 'undefined') {
 try { new ResizeObserver(fit).observe(p); } catch (e) {}
 }
 window.addEventListener('resize', fit);
});
&lt;/script&gt;&lt;/p&gt;
&lt;div style="text-align: center; margin-top: -1rem; margin-bottom: 2rem;"&gt;
 &lt;button id="nextStepBtn" style="padding: 0.6rem 1.5rem; background: #2563eb; color #fff; border: none; border-radius: 6px; cursor: pointer; font-weight: bold;"&gt;Next Step &lt;/button&gt;
&lt;/div&gt;</description></item></channel></rss>