package framework.model3D; import javax.media.j3d.BoundingPolytope; import javax.media.j3d.BoundingSphere; import javax.media.j3d.Transform3D; import javax.vecmath.Matrix4d; import javax.vecmath.Point3d; import javax.vecmath.Vector3d; import javax.vecmath.Vector4d; import java.util.ArrayList; import java.util.Arrays; public class OBB implements Cloneable { private ArrayList<Vector3d> vertexList = new ArrayList<Vector3d>(); private Vector4d[] plane; private BoundingPolytope bp = null; private static int edges[][] = { { 0, 1 }, { 0, 2 }, { 1, 3 }, { 2, 3 }, { 0, 4 }, { 1, 5 }, { 3, 7 }, { 2, 6 }, { 4, 5 }, { 4, 6 }, { 5, 7 }, { 6, 7 } }; private static Integer planes[][] = { {0, 2, 3, 1}, {0, 1, 5, 4}, {1, 3, 7, 5}, {4, 5, 7, 6}, {0, 4, 6, 2}, {2, 6, 7, 3}}; private static boolean[][] inside = new boolean[8][6]; public OBB() { } public OBB(Vector3d v1, Vector3d v2, Vector3d v3, Vector3d v4, Vector3d v5, Vector3d v6, Vector3d v7, Vector3d v8) { vertexList.add(v1); vertexList.add(v2); vertexList.add(v3); vertexList.add(v4); vertexList.add(v5); vertexList.add(v6); vertexList.add(v7); vertexList.add(v8); createPlanes(); } public void addVertex(Vector3d v) { vertexList.add(v); } public Vector3d getVertex(int i) { return vertexList.get(i); } public BoundingPolytope getBoundingPolytope() { return bp; } public BoundingSphere getBoundingSphere() { double radius = 0.0; Point3d p = new Point3d(); Vector3d cv = new Vector3d(); for (int i = 0; i < vertexList.size() - 1; i++) { for (int j = i + 1; j < vertexList.size(); j++) { Vector3d v = new Vector3d(); v.sub(vertexList.get(i), vertexList.get(j)); if (radius < v.length()) { radius = v.length(); cv.add(vertexList.get(i), vertexList.get(j)); cv.scale(0.5); p.x = cv.x; p.y = cv.y; p.z = cv.z; } } } BoundingSphere s = new BoundingSphere(p, radius / 2); return s; } /** * 頂点情報を元に面および境界多面体を生成する */ public void createPlanes() { // 面の作成 Vector3d v1 = new Vector3d(); Vector3d v2 = new Vector3d(); Vector3d v3 = new Vector3d(); Vector4d[] plane = new Vector4d[6]; // 0231 v1 = getVertex(0); v2.sub(getVertex(2), getVertex(0)); v3.sub(getVertex(1), getVertex(0)); Vector3d n = new Vector3d(); n.cross(v2, v3); n.normalize(); plane[0] = new Vector4d(); plane[0].set(n.x, n.y, n.z, -n.dot(v1)); // 0154 v1 = getVertex(0); v2.sub(getVertex(1), getVertex(0)); v3.sub(getVertex(4), getVertex(0)); n = new Vector3d(); n.cross(v2, v3); n.normalize(); plane[1] = new Vector4d(); plane[1].set(n.x, n.y, n.z, -n.dot(v1)); // 1375 v1 = getVertex(1); v2.sub(getVertex(3), getVertex(1)); v3.sub(getVertex(5), getVertex(1)); n = new Vector3d(); n.cross(v2, v3); n.normalize(); plane[2] = new Vector4d(); plane[2].set(n.x, n.y, n.z, -n.dot(v1)); // 4576 v1 = getVertex(6); v2.sub(getVertex(4), getVertex(6)); v3.sub(getVertex(7), getVertex(6)); n = new Vector3d(); n.cross(v2, v3); n.normalize(); plane[3] = new Vector4d(); plane[3].set(n.x, n.y, n.z, -n.dot(v1)); // 0462 v1 = getVertex(6); v2.sub(getVertex(2), getVertex(6)); v3.sub(getVertex(4), getVertex(6)); n = new Vector3d(); n.cross(v2, v3); n.normalize(); plane[4] = new Vector4d(); plane[4].set(n.x, n.y, n.z, -n.dot(v1)); // 2673 v1 = getVertex(6); v2.sub(getVertex(7), getVertex(6)); v3.sub(getVertex(2), getVertex(6)); n = new Vector3d(); n.cross(v2, v3); n.normalize(); plane[5] = new Vector4d(); plane[5].set(n.x, n.y, n.z, -n.dot(v1)); this.plane = plane; bp = new BoundingPolytope(); bp.setPlanes(plane); } /** * 平面との衝突判定 * @param plane 衝突判定の対象となる平面 * @return 衝突判定の結果(衝突していない場合はnull) */ public CollisionResult intersect(Vector4d plane) { int i = 0; boolean inside = false; boolean outside = false; int count = 0; Vector3d center = new Vector3d(0, 0, 0); ArrayList<Vector3d> collisionPoints = new ArrayList<Vector3d>(); double l = Math.sqrt(plane.x * plane.x + plane.y * plane.y + plane.z * plane.z); double length; double deepest = 0.0; Vector3d v; for (i = 0; i < vertexList.size(); i++) { v = vertexList.get(i); if (GeometryUtility.inside(v, plane)) { inside = true; length = -(v.x * plane.x + v.y * plane.y + v.z * plane.z + plane.w) / l; if (length > deepest + GeometryUtility.TOLERANCE) { center.set(v); collisionPoints.clear(); collisionPoints.add(v); count = 1; deepest = length; } else if (length >= deepest - GeometryUtility.TOLERANCE) { center.add(v); collisionPoints.add(v); count++; } } else { outside = true; } } if (!inside || !outside) { // 全頂点が外側か全頂点が内側の場合 return null; } center.scale(1.0 / count); CollisionResult cr = new CollisionResult(); cr.length = deepest; cr.collisionPoint.setVector3d(center); cr.collisionPoints = collisionPoints; cr.normal.setX(plane.x / l); cr.normal.setY(plane.y / l); cr.normal.setZ(plane.z / l); return cr; } /** * OBBとの衝突判定 * @param another 衝突判定の対象となるOBB * @return 衝突判定の結果(衝突していない場合はnull) */ public CollisionResult intersect(OBB another) { // anotherの各頂点がthisの各面の内側にあるかを調べる // i:anotherの頂点, j:thisの面 OBB o1 = this; OBB o2 = another; for (int i = 0; i < o2.vertexList.size(); i++) { for (int j = 0; j < o1.plane.length; j++) { inside[i][j] = GeometryUtility.inside(o2.vertexList.get(i), o1.plane[j]); } } int v = 0; boolean bCollides = false; // anotherの各頂点がthisに包含されているか? Vector3d contactCenter = new Vector3d(); int count = 0; for (int i = 0; i < o2.vertexList.size(); i++) { boolean f1 = false; for (int p = 0; p < o1.plane.length; p++) { f1 = inside[i][p]; if (!f1) break; // 包含されていない } if (f1) { // thisのすべての面の内側 = thisに包含されている if (!bCollides) { bCollides = true; v = i; } contactCenter.add(o2.vertexList.get(i)); count++; } } if (!bCollides) { // thisの各頂点がanotherの各面の内側にあるかを調べる o1 = another; o2 = this; for (int i = 0; i < o2.vertexList.size(); i++) { for (int j = 0; j < o1.plane.length; j++) { inside[i][j] = GeometryUtility.inside(o2.vertexList.get(i), o1.plane[j]); } } // thisの各頂点がanotherに包含されているか? for (int i = 0; i < o2.vertexList.size(); i++) { boolean f1 = false; for (int p = 0; p < o1.plane.length; p++) { f1 = inside[i][p]; if (!f1) break; // 包含されていない } if (f1) { // anotherのすべての面の内側 = anotherに包含されている if (!bCollides) { bCollides = true; v = i; } contactCenter.add(o2.vertexList.get(i)); count++; } } } CollisionResult cr = new CollisionResult(); ArrayList<Vector3d> collisionPoints = new ArrayList<Vector3d>(); if (bCollides) { // いずれかのOBBの頂点vが他方のOBBに包含されていた場合(vを持つ側のOBBをo2、他方をo1とする) // o1のどの面と衝突したかを判定する double lMin = 0; int p1 = -1; Vector3d normal = null; contactCenter.scale(1.0 / (double)count); for (int p = 0; p < o1.plane.length; p++) { Vector3d n = new Vector3d(o1.plane[p].x, o1.plane[p].y, o1.plane[p].z); double l = -(contactCenter.x * o1.plane[p].x + contactCenter.y * o1.plane[p].y + contactCenter.z * o1.plane[p].z + o1.plane[p].w) / n.length(); if (lMin > l || normal == null) { lMin = l; p1 = p; normal = n; } } // o2上のvを含む辺のうち衝突面の法線と逆方向に向かっている辺を探す(そのような辺の数によって接触状況が変わる) Vector3d collisionPoint = o2.vertexList.get(v); Vector3d d1; ArrayList<Integer> contactEdges = new ArrayList<Integer>(); for (int e = 0; e < edges.length; e++) { if (v == edges[e][0]) { d1 = new Vector3d(o2.vertexList.get(edges[e][1])); d1.sub(collisionPoint); if (normal.dot(d1) < GeometryUtility.TOLERANCE) { // 衝突面に接している辺 contactEdges.add(edges[e][1]); // vの反対側の端点 } } else if (v == edges[e][1]) { d1 = new Vector3d(o2.vertexList.get(edges[e][0])); d1.sub(collisionPoint); if (normal.dot(d1) < GeometryUtility.TOLERANCE) { // 衝突面に接している辺 contactEdges.add(edges[e][0]); // vの反対側の端点 } } } // 接触状況に応じた処理 if (contactEdges.size() == 0) { // a) 頂点で接触 collisionPoints.add(collisionPoint); cr.collisionPoint.setVector3d(collisionPoint); } else if (contactEdges.size() == 1) { // b) 辺で接触 boolean f1 = true; Vector3d otherPoint = o2.vertexList.get(contactEdges.get(0)); Vector3d intersectionPoint = null; double nearest = 0.0; for (int p = 0; p < o1.plane.length; p++) { if (!inside[contactEdges.get(0)][p]) { // collisionPoint と otherPoint の間を通る平面 Vector3d ip = GeometryUtility.intersect(o1.plane[p], collisionPoint, otherPoint); if (ip != null) { f1 = false; // 包含されていない Vector3d ip2 = (Vector3d)ip.clone(); ip2.sub(collisionPoint); double d = ip2.length(); if (intersectionPoint == null || d < nearest) { intersectionPoint = ip; nearest = d; } } } } if (f1) { // b-1) 辺全体が接触している collisionPoints.add(collisionPoint); collisionPoints.add(otherPoint); Vector3d center = new Vector3d(collisionPoint); center.add(otherPoint); center.scale(0.5); cr.collisionPoint.setVector3d(center); } else { // b-2) 辺の一部が接触している collisionPoints.add(collisionPoint); collisionPoints.add(intersectionPoint); Vector3d center = new Vector3d(collisionPoint); center.add(intersectionPoint); center.scale(0.5); cr.collisionPoint.setVector3d(center); } } else { // c) 面で接触 Vector3d center = new Vector3d(); int v2 = contactEdges.get(0); int v3 = contactEdges.get(1); int p2; for (p2 = 0; p2 < o2.plane.length; p2++) { if (v == planes[p2][0] || v == planes[p2][1] || v == planes[p2][2] || v == planes[p2][3]) { if (v2 == planes[p2][0] || v2 == planes[p2][1] || v2 == planes[p2][2] || v2 == planes[p2][3]) { if (v3 == planes[p2][0] || v3 == planes[p2][1] || v3 == planes[p2][2] || v3 == planes[p2][3]) { break; } } } } // o1の面p1とo2の面p2が接している // 面と面の共通領域を求める for (v2 = 0; v2 < 4; v2++) { if (planes[p2][v2] == v) break; } Vector3d d2 = (Vector3d)o2.vertexList.get(planes[p2][(v2 + 1) % 4]).clone(); d2.sub(collisionPoint); d2.cross(normal, d2); int sign = -1; int v1; for (v1 = 4; v1 >= 0; v1--) { d1 = (Vector3d)o1.vertexList.get(planes[p1][v1 % 4]).clone(); d1.sub(o1.vertexList.get(planes[p1][(v1 + 1) % 4])); if (d2.dot(d1) > -GeometryUtility.TOLERANCE) { sign = 1; } else if (sign == 1) { // 内積の符号が正から負に変わったとき v1 = (v1 + 1) % 4; break; } } Vector3d vp1, vp2, dst1, dst2, src1, src2, d3; ArrayList<Vector3d[]> contours = new ArrayList<Vector3d[]>(); for (int i = 0; i < 4; i++) { dst1 = vp1 = (Vector3d)o1.vertexList.get(planes[p1][(v1 - i + 4) % 4]).clone(); src2 = vp2 = (Vector3d)o2.vertexList.get(planes[p2][(v2 + i) % 4]).clone(); src1 = (Vector3d)o1.vertexList.get(planes[p1][(v1 - i + 4 + 1) % 4]).clone(); dst2 = (Vector3d)o2.vertexList.get(planes[p2][(v2 + i + 1) % 4]).clone(); d1 = (Vector3d)dst1.clone(); d1.sub(src1); d2 = (Vector3d)dst2.clone(); d2.sub(src2); d3 = (Vector3d)vp2.clone(); d3.sub(vp1); d1.cross(d1, normal); if (d1.dot(d3) < GeometryUtility.TOLERANCE) { // o1の辺<src1,dst1>は、頂点vp2の内側にあるので、共通領域の輪郭を構成する contours.add(new Vector3d[]{src1, dst1}); } d3.negate(); d2.cross(d2, normal); if (d2.dot(d3) < GeometryUtility.TOLERANCE) { // o2の辺<src2,dst2>は、頂点vp1の内側にあるので、共通領域の輪郭を構成する contours.add(new Vector3d[]{src2, dst2}); } } Vector3d[] edge1, edge2; Vector3d d; Vector4d p; for (int i = 0; i < contours.size(); i++) { edge1 = contours.get(i); edge2 = contours.get((i + 1) % contours.size()); if (edge1[1].equals(edge2[0])) { // edge1とedge2は始点と終点を共有している if (!collisionPoints.contains(edge1[1])) { collisionPoints.add(edge1[1]); center.add(edge1[1]); } } else if (!edge1[0].equals(edge2[0]) && !edge1[1].equals(edge2[1])) { // edge1とedge2の交点を求める d = (Vector3d)edge2[1].clone(); d.add(normal); p = GeometryUtility.createPlane(edge2[0], edge2[1], d); if (p != null) { d = GeometryUtility.intersect(p, edge1[0], edge1[1]); if (d != null && !collisionPoints.contains(d)) { collisionPoints.add(d); center.add(d); } } } } if (collisionPoints.size() <= 2) return null; // 面で接触しているはずが2点以下である場合、衝突していない center.scale(1.0 / (double)collisionPoints.size()); cr.collisionPoint.setVector3d(center); } cr.length = lMin; cr.collisionPoints = collisionPoints; if (o2 == this) normal.negate(); normal.normalize(); cr.normal = normal; return cr; } else { // anotherの辺がthisの面と交わっているか? Vector3d center = new Vector3d(); Vector3d normal = new Vector3d(); ArrayList<Vector3d> intersections = new ArrayList<Vector3d>(); ArrayList<Integer> contactPlanes = new ArrayList<Integer>(); for (int e = 0; e < edges.length; e++) { // anotherの各辺eに対して処理 Vector3d intersection = null; intersections.clear(); contactPlanes.clear(); for (int p = 0; p < this.plane.length; p++) { if (inside[edges[e][0]][p] == !inside[edges[e][1]][p]) { // anotherの辺eが、thisの面pを貫いている Vector3d ip = GeometryUtility.intersect( this.plane[p], another.vertexList.get(edges[e][0]), another.vertexList.get(edges[e][1])); // eとpの交点がthisに包含されているか? if (ip != null) { intersection = ip; boolean bInside = true; for (int p2 = 0; p2 < 6; p2++) { if (p != p2 && !GeometryUtility.inside(intersection, this.plane[p2])) { bInside = false; break; } } if (bInside) { // eがpと交わっている contactPlanes.add(p); intersections.add(intersection); bCollides = true; } } } } if (contactPlanes.size() == 1) { // eと交わっているthisの面が1つ collisionPoints.add(intersection); center.add(intersection); } else if (contactPlanes.size() == 2) { // eと交わっているthisの面が2つ int p1 = contactPlanes.get(0); int p2 = contactPlanes.get(1); ArrayList<Integer> p1vs = new ArrayList<Integer>(Arrays.asList(planes[p1])); ArrayList<Integer> p2vs = new ArrayList<Integer>(Arrays.asList(planes[p2])); p1vs.retainAll(p2vs); if (p1vs.size() == 2) { // p1とp2は隣り合う面(anotherの辺eはthisのある辺と接触している) Vector3d v1 = this.vertexList.get(p1vs.get(0)); // p1とp2の間の辺の始点 Vector3d v2 = this.vertexList.get(p1vs.get(1)); // p1とp2の間の辺の終点 Vector3d d1 = (Vector3d)v1.clone(); d1.sub(v2); Vector3d d2 = (Vector3d)another.vertexList.get(edges[e][0]).clone(); d2.sub(another.vertexList.get(edges[e][1])); normal.cross(d1, d2); // 辺eと、p1とp2の間の辺の両方に垂直なベクトルを法線方向とする normal.normalize(); Vector3d n1 = new Vector3d(this.plane[p1].x, this.plane[p1].y, this.plane[p1].z); Vector3d n2 = new Vector3d(this.plane[p2].x, this.plane[p2].y, this.plane[p2].z); n1.normalize(); n2.normalize(); n1.add(n2); n1.scale(0.5); if (n1.dot(normal) < -GeometryUtility.TOLERANCE) { normal.negate(); } intersection = intersections.get(0); intersection.add(intersections.get(1)); intersection.scale(0.5); center.add(intersection); // 衝突点は1つ collisionPoints.add(intersection); Vector3d c = GeometryUtility.nearest(v1, v2, center); c.sub(center); cr.length = c.length(); cr.normal = normal; } else { // p1とp2は向かい合う面(anotherの辺eはthisのある面と接触している) center.add(intersections.get(0)); // 衝突点は2つ center.add(intersections.get(1)); collisionPoints.add(intersections.get(0)); collisionPoints.add(intersections.get(1)); // thisのどの面と衝突したかを判定する double lMin = 0; normal = null; Vector4d plane; for (int p = 0; p < this.plane.length; p++) { plane = this.plane[p]; Vector3d n = new Vector3d(plane.x, plane.y, plane.z); double l = -(center.x * plane.x + center.y * plane.y + center.z * plane.z + plane.w) / n.length(); if (lMin > l || normal == null) { lMin = l; normal = n; } } normal.normalize(); cr.length = lMin; cr.normal = normal; } } } if (!bCollides) return null; center.scale(1.0 / (double)collisionPoints.size()); cr.collisionPoint.setVector3d(center); cr.collisionPoints = collisionPoints; return cr; } } public Object clone() { OBB obb = new OBB(); obb.plane = new Vector4d[6]; for (int i = 0; i < plane.length; i++) { obb.plane[i] = (Vector4d) plane[i].clone(); } for (int i = 0; i < vertexList.size(); i++) { obb.vertexList.add((Vector3d) vertexList.get(i).clone()); } obb.bp = new BoundingPolytope(obb.plane); // System.out.println("veretexListSIZE:"+vertexList.size()); return obb; } public void transform(Transform3D t) { // TODO Auto-generated method stub bp.transform(t); bp.getPlanes(plane); // for(int i = 0; i<plane.length;i++){ // System.out.println("plane"); // System.out.print(plane[i].x + "," + plane[i].y + "," + plane[i].z + // "," + plane[i].w); // t.transform(plane[i]); // System.out.println("-->" + plane[i].x + "," + plane[i].y + "," + // plane[i].z + "," + plane[i].w); // } for (int i = 0; i < vertexList.size(); i++) { // System.out.println("vertex"); // System.out.print(vertexList.get(i).x + "," + vertexList.get(i).y // + "," + vertexList.get(i).z); Matrix4d mat4d = new Matrix4d(); t.get(mat4d); double x = mat4d.m00 * vertexList.get(i).x + mat4d.m01 * vertexList.get(i).y + mat4d.m02 * vertexList.get(i).z + mat4d.m03; double y = mat4d.m10 * vertexList.get(i).x + mat4d.m11 * vertexList.get(i).y + mat4d.m12 * vertexList.get(i).z + mat4d.m13; double z = mat4d.m20 * vertexList.get(i).x + mat4d.m21 * vertexList.get(i).y + mat4d.m22 * vertexList.get(i).z + mat4d.m23; vertexList.get(i).x = x; vertexList.get(i).y = y; vertexList.get(i).z = z; // t.transform(vertexList.get(i)); // System.out.println("-->" + vertexList.get(i).x + "," + // vertexList.get(i).y + "," + vertexList.get(i).z); } } }