Skip to content

Commit 7108daa

Browse files
committed
implemented face collision detection algorithm: ray-triangle + octree
1 parent fafc71e commit 7108daa

File tree

10 files changed

+361
-110
lines changed

10 files changed

+361
-110
lines changed

README.md

Lines changed: 5 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -12,10 +12,10 @@ The purpose of this application is to learn and share how to draw using OpenGL l
1212
* Collada format (DAE): https://en.wikipedia.org/wiki/COLLADA
1313

1414

15-
News (21/12/2017)
15+
News (22/12/2017)
1616
=================
1717

18-
* Improved collision detection algorithm (ray-aabb)
18+
* Implemented collision detection algorithm: ray-aabb + ray-triangle + octree
1919
* Support for collada files with skeletal animations :)
2020
* Fixed #28: Load texture feature is now available
2121

@@ -71,6 +71,7 @@ Features
7171
- rotate with 2 fingers to rotate camera
7272
- pinch & spread to zoom in/out the camera
7373
- skeletal animations
74+
- ray collision detection
7475

7576

7677
Try it
@@ -123,36 +124,30 @@ ChangeLog
123124

124125
(f) fixed, (i) improved, (n) new feature
125126

127+
- 2.0.4 (22/12/2017)
128+
- (n) Implemented face collision detection algorithm: ray-triangle + octree
126129
- 2.0.3 (21/12/2017)
127130
- (i) Improved collision detection algorithm (ray-aabb) for selecting objects
128131
- (i) BoundingBox code cleanup
129-
130132
- 2.0.2 (17/12/2017)
131133
- (f) Collada XML parser is now android's XmlPullParser
132134
- (f) Animation engine frame times improved
133135
- (n) Camera now moves smoothly
134-
135136
- 2.0.1 (08/12/2017)
136137
- (f) Multiple Collada parser fixes
137138
- (f) Camera now can look inside objects
138-
139139
- 2.0.0 (24/11/2017)
140140
- (n) Support for collada files with skeletal animations :)
141-
142141
- 1.4.1 (21/11/2017)
143142
- (f) #29: Crash loading obj with only vertex info
144-
145143
- 1.4.0 (19/11/2017)
146144
- (f) #28: Load texture available for any model having texture coordinates
147-
148145
- 1.3.1 (23/04/2017)
149146
- (f) #18: Removed asReadOnlyBuffer() because it is causing IndexOutOfBounds on Android 7
150-
151147
- 1.3.0 (17/04/2017)
152148
- (n) #17: Added support for STL files
153149
- (n) #17: Asynchronous building of model so the build rendering is previewed
154150
- (f) #17: Added Toasts to buttons to show current state
155-
156151
- 1.2.10 (16/04/2017)
157152
- (f) #16: Immersive mode is now configurable in the ModelActivity Intent: b.putString("immersiveMode", "false");
158153
- (f) #16: Background color configurable in the ModelActivity Intent: b.putString("backgroundColor", "0 0 0 1");
@@ -162,48 +157,35 @@ ChangeLog
162157
- (n) #16: Implemented Point Drawing, like wireframe mode but only the points are drawn
163158
- (f) #16: Removed trailing slash from parameter "assetDir"
164159
- (f) #16: Access to ByteBuffers made absolute so there are thread safe (future fixes need this)
165-
166160
- 1.2.9 (11/04/2017)
167161
- (f) #15: Toggle rotating light
168162
- (f) #15: Wireframe with textures and colors
169-
170163
- 1.2.8 (10/04/2017)
171164
- (f) Fixed #14: Camera movement improved. Only 1 rotation vector is used + space bounds set
172-
173165
- 1.2.8 (04/04/2017)
174166
- (f) Fixed #13: parsing of vertices with multiple spaces
175167
- (i) Improved error handling on loading task
176168
- (i) Vertices are defaulted to (0,0,0) if parsing fails
177-
178169
- 1.2.7 (03/04/2017)
179170
- (i) Removed commons-lang3 dependency
180-
181171
- 1.2.6 (02/04/2017)
182172
- (f) Fixed #12. Drawing the wireframe using GL_LINES and the index buffer (drawElements)
183-
184173
- 1.2.5 (01/04/2017)
185174
- (f) Fixed #10. Map faces to texture only when using the only loaded texture
186175
- (f) Fixed #11. Generation of missing vertex normals
187-
188176
- 1.2.4 (30/03/2017)
189177
- (f) Fixed #5. Memory performance optimization
190-
191178
- 1.2.3 (27/03/2017)
192179
- (f) Fixed #1. Cpu performance optimization
193-
194180
- 1.2.2 (25/03/2017)
195181
- (f) Fixed #9. IOOBE loading face normals when faces had no texture or normals
196-
197182
- 1.2.1 (27/02/2017)
198183
- (f) Fixed loading external files issue #6
199184
- (i) Project moved to gradle
200-
201185
- 1.2.0 (06/04/2016)
202186
- (n) Implemented selection of objects
203-
204187
- 1.1.0 (30/03/2016)
205188
- (n) Implemented lighting & toggle textures & lights
206189
- (i) Refactoring of 3DObjectImpl
207-
208190
- 1.0.0 (27/03/2016)
209191
- (n) First release in Google Play Android Market

app/src/main/java/org/andresoviedo/app/model3D/collision/CollisionDetection.java

Lines changed: 151 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,16 @@
11
package org.andresoviedo.app.model3D.collision;
22

33
import android.opengl.GLU;
4+
import android.opengl.Matrix;
45
import android.util.Log;
56

67
import org.andresoviedo.app.model3D.entities.BoundingBox;
78
import org.andresoviedo.app.model3D.model.Object3DData;
89
import org.andresoviedo.app.model3D.view.ModelRenderer;
910
import org.andresoviedo.app.util.math.Math3DUtils;
1011

12+
import java.nio.FloatBuffer;
13+
import java.util.Arrays;
1114
import java.util.List;
1215

1316
/**
@@ -26,30 +29,32 @@ public class CollisionDetection {
2629
* @param windowY the window y coordinate
2730
* @return the nearest object intersected by the specified coordinates or null
2831
*/
29-
public static Object3DData getIntersection(List<Object3DData> objects, ModelRenderer mRenderer, float windowX, float windowY) {
32+
public static Object3DData getBoxIntersection(List<Object3DData> objects, ModelRenderer mRenderer, float windowX, float windowY) {
3033
float[] nearHit = unproject(mRenderer, windowX, windowY, 0);
3134
float[] farHit = unproject(mRenderer, windowX, windowY, 1);
32-
return getIntersection(objects, nearHit, farHit);
35+
float[] direction = Math3DUtils.substract(farHit, nearHit);
36+
Math3DUtils.normalize(direction);
37+
return getBoxIntersection(objects, nearHit, direction);
3338
}
3439

3540
/**
3641
* Get the nearest object intersected by the specified ray or null if no object is intersected
3742
*
38-
* @param objects the list of objects to test
39-
* @param p1 the ray start point
40-
* @param p2 the ray end point
43+
* @param objects the list of objects to test
44+
* @param p1 the ray start point
45+
* @param direction the ray direction
4146
* @return the object intersected by the specified ray
4247
*/
43-
public static Object3DData getIntersection(List<Object3DData> objects, float[] p1, float[] p2) {
44-
float[] direction = Math3DUtils.substract(p2, p1);
45-
Math3DUtils.normalize(direction);
48+
public static Object3DData getBoxIntersection(List<Object3DData> objects, float[] p1, float[] direction) {
4649
float min = Float.MAX_VALUE;
4750
Object3DData ret = null;
4851
for (Object3DData obj : objects) {
49-
if (obj.getId().startsWith("Line")) continue;
52+
if (obj.getId().equals("Point") || obj.getId().equals("Line")) {
53+
continue;
54+
}
5055
BoundingBox box = obj.getBoundingBox();
5156
float[] intersection = getBoxIntersection(p1, direction, box);
52-
if (intersection[0] > 0 && intersection[0] < intersection[1] && intersection[0] < min) {
57+
if (intersection[0] > 0 && intersection[0] <= intersection[1] && intersection[0] < min) {
5358
min = intersection[0];
5459
ret = obj;
5560
}
@@ -68,17 +73,16 @@ public static Object3DData getIntersection(List<Object3DData> objects, float[] p
6873
* @param p2 ray end point
6974
* @return the entry and exit point of the ray intersecting the nearest object
7075
*/
71-
public static float[] getIntersectionPoint(List<Object3DData> objects, float[] p1, float[] p2) {
76+
public static float[] getBoxIntersectionPoint(List<Object3DData> objects, float[] p1, float[] p2) {
7277
float[] direction = Math3DUtils.substract(p2, p1);
7378
Math3DUtils.normalize(direction);
7479
float min = Float.MAX_VALUE;
7580
float[] intersection2 = null;
7681
Object3DData ret = null;
7782
for (Object3DData obj : objects) {
78-
if (obj.getId().startsWith("Line")) continue;
7983
BoundingBox box = obj.getBoundingBox();
8084
float[] intersection = getBoxIntersection(p1, direction, box);
81-
if (intersection[0] > 0 && intersection[0] < intersection[1] && intersection[0] < min) {
85+
if (intersection[0] > 0 && intersection[0] <= intersection[1] && intersection[0] < min) {
8286
min = intersection[0];
8387
ret = obj;
8488
intersection2 = intersection;
@@ -113,8 +117,8 @@ public static boolean isBoxIntersection(float[] origin, float[] dir, BoundingBox
113117
* @return the intersection points of the near and far plane
114118
*/
115119
public static float[] getBoxIntersection(float[] origin, float[] dir, BoundingBox b) {
116-
float[] tMin = Math3DUtils.divide(Math3DUtils.substract(b.getCurrentMin(), origin), dir);
117-
float[] tMax = Math3DUtils.divide(Math3DUtils.substract(b.getCurrentMax(), origin), dir);
120+
float[] tMin = Math3DUtils.divide(Math3DUtils.substract(b.getMin(), origin), dir);
121+
float[] tMax = Math3DUtils.divide(Math3DUtils.substract(b.getMax(), origin), dir);
118122
float[] t1 = Math3DUtils.min(tMin, tMax);
119123
float[] t2 = Math3DUtils.max(tMin, tMax);
120124
float tNear = Math.max(Math.max(t1[0], t1[1]), t1[2]);
@@ -143,4 +147,136 @@ public static float[] unproject(ModelRenderer mRenderer, float rx, float ry, flo
143147
xyzw[3] = 1;
144148
return xyzw;
145149
}
150+
151+
public static float[] getTriangleIntersection(List<Object3DData> objects, ModelRenderer mRenderer, float windowX, float windowY) {
152+
float[] nearHit = unproject(mRenderer, windowX, windowY, 0);
153+
float[] farHit = unproject(mRenderer, windowX, windowY, 1);
154+
float[] direction = Math3DUtils.substract(farHit, nearHit);
155+
Math3DUtils.normalize(direction);
156+
Object3DData intersected = getBoxIntersection(objects, nearHit, direction);
157+
if (intersected != null) {
158+
Log.d("CollisionDetection", "intersected:" + intersected.getId() + ", rayOrigin:" + Arrays.toString(nearHit) + ", rayVector:" + Arrays.toString(direction));
159+
FloatBuffer buffer = intersected.getVertexArrayBuffer().asReadOnlyBuffer();
160+
float[] modelMatrix = intersected.getModelMatrix();
161+
buffer.position(0);
162+
float[] selectedv1 = null;
163+
float[] selectedv2 = null;
164+
float[] selectedv3 = null;
165+
float min = Float.MAX_VALUE;
166+
for (int i = 0; i < buffer.capacity(); i += 9) {
167+
float[] v1 = new float[]{buffer.get(), buffer.get(), buffer.get(), 1};
168+
float[] v2 = new float[]{buffer.get(), buffer.get(), buffer.get(), 1};
169+
float[] v3 = new float[]{buffer.get(), buffer.get(), buffer.get(), 1};
170+
Matrix.multiplyMV(v1, 0, modelMatrix, 0, v1, 0);
171+
Matrix.multiplyMV(v2, 0, modelMatrix, 0, v2, 0);
172+
Matrix.multiplyMV(v3, 0, modelMatrix, 0, v3, 0);
173+
float t = getTriangleIntersection(nearHit, direction, v1, v2, v3);
174+
if (t != -1 && t < min) {
175+
min = t;
176+
selectedv1 = v1;
177+
selectedv2 = v2;
178+
selectedv3 = v3;
179+
}
180+
}
181+
if (selectedv1 != null) {
182+
float[] outIntersectionPoint = Math3DUtils.add(nearHit, Math3DUtils.multiply(direction, min));
183+
return outIntersectionPoint;
184+
}
185+
}
186+
return null;
187+
}
188+
189+
public static float[] getTriangleIntersection2(List<Object3DData> objects, ModelRenderer mRenderer, float windowX, float windowY) {
190+
float[] nearHit = unproject(mRenderer, windowX, windowY, 0);
191+
float[] farHit = unproject(mRenderer, windowX, windowY, 1);
192+
float[] direction = Math3DUtils.substract(farHit, nearHit);
193+
Math3DUtils.normalize(direction);
194+
Object3DData intersected = getBoxIntersection(objects, nearHit, direction);
195+
if (intersected != null) {
196+
Log.d("CollisionDetection", "intersected 2:"+intersected.getId());
197+
Octree octree = null;
198+
synchronized (intersected) {
199+
octree = intersected.getOctree();
200+
if (octree == null) {
201+
octree = Octree.build(intersected);
202+
intersected.setOctree(octree);
203+
}
204+
}
205+
float intersection = getTriangleIntersection2_Impl(octree, nearHit, direction);
206+
if (intersection != -1) {
207+
return Math3DUtils.add(nearHit, Math3DUtils.multiply(direction, intersection));
208+
}
209+
else {
210+
return null;
211+
}
212+
}
213+
return null;
214+
}
215+
216+
private static float getTriangleIntersection2_Impl(Octree octree, float[] rayOrigin, float[] rayDirection){
217+
//Log.v("CollisionDetection","Testing octree "+octree);
218+
if (!isBoxIntersection(rayOrigin, rayDirection, octree.boundingBox)) {
219+
return -1;
220+
}
221+
Octree selected = null;
222+
float min = Float.MAX_VALUE;
223+
for (Octree child : octree.children) {
224+
if (child == null) {
225+
continue;
226+
}
227+
float intersection = getTriangleIntersection2_Impl(child, rayOrigin, rayDirection);
228+
if (intersection != -1 && intersection < min){
229+
min = intersection;
230+
selected = child;
231+
}
232+
}
233+
float[] selectedTriangle = null;
234+
for (float[] triangle : octree.triangles){
235+
float[] vertex0 = new float[]{triangle[0], triangle[1], triangle[2]};
236+
float[] vertex1 = new float[]{triangle[4], triangle[5], triangle[6]};
237+
float[] vertex2 = new float[]{triangle[8], triangle[9], triangle[10]};
238+
float intersection = getTriangleIntersection(rayOrigin, rayDirection, vertex0, vertex1, vertex2);
239+
if (intersection != -1 && intersection < min){
240+
min = intersection;
241+
selectedTriangle = triangle;
242+
selected = octree;
243+
}
244+
}
245+
if (min != Float.MAX_VALUE){
246+
return min;
247+
}
248+
return -1;
249+
}
250+
251+
public static float getTriangleIntersection(float[] rayOrigin,
252+
float[] rayVector,
253+
float[] vertex0, float[] vertex1, float[] vertex2) {
254+
float EPSILON = 0.0000001f;
255+
float[] edge1, edge2, h, s, q;
256+
float a, f, u, v;
257+
edge1 = Math3DUtils.substract(vertex1, vertex0);
258+
edge2 = Math3DUtils.substract(vertex2, vertex0);
259+
h = Math3DUtils.crossProduct(rayVector, edge2);
260+
a = Math3DUtils.dotProduct(edge1, h);
261+
if (a > -EPSILON && a < EPSILON)
262+
return -1;
263+
f = 1 / a;
264+
s = Math3DUtils.substract(rayOrigin, vertex0);
265+
u = f * Math3DUtils.dotProduct(s, h);
266+
if (u < 0.0 || u > 1.0)
267+
return -1;
268+
q = Math3DUtils.crossProduct(s, edge1);
269+
v = f * Math3DUtils.dotProduct(rayVector, q);
270+
if (v < 0.0 || u + v > 1.0)
271+
return -1;
272+
// At this stage we can compute t to find out where the intersection point is on the line.
273+
float t = f * Math3DUtils.dotProduct(edge2, q);
274+
if (t > EPSILON) // ray intersection
275+
{
276+
Log.d("CollisionDetection", "Triangle intersection at: " + t);
277+
return t;
278+
} else // This means that there is a line intersection but not a ray intersection.
279+
return -1;
280+
}
146281
}
282+

0 commit comments

Comments
 (0)