1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17 package cz.cuni.amis.pogamut.ut2004.agent.navigation.navmesh;
18
19 import java.io.BufferedReader;
20 import java.io.File;
21 import java.io.FileNotFoundException;
22 import java.io.FileReader;
23 import java.io.IOException;
24 import java.io.ObjectInputStream;
25 import java.io.Serializable;
26 import java.util.ArrayList;
27 import java.util.List;
28 import java.util.Random;
29 import java.util.logging.Logger;
30
31 import javax.vecmath.Vector2d;
32 import javax.vecmath.Vector3d;
33
34 import math.geom2d.Point2D;
35 import math.geom2d.line.StraightLine2D;
36 import math.geom3d.Point3D;
37 import math.geom3d.Vector3D;
38 import math.geom3d.line.StraightLine3D;
39 import math.geom3d.plane.Plane3D;
40 import cz.cuni.amis.pogamut.base.utils.logging.LogCategory;
41 import cz.cuni.amis.pogamut.base3d.worldview.object.Location;
42 import cz.cuni.amis.pogamut.ut2004.agent.module.sensomotoric.BSPRayInfoContainer;
43 import cz.cuni.amis.pogamut.ut2004.communication.messages.gbinfomessages.AutoTraceRay;
44 import cz.cuni.amis.pogamut.ut2004.communication.messages.gbinfomessages.AutoTraceRayMessage;
45 import cz.cuni.amis.pogamut.ut2004.communication.messages.gbinfomessages.Self;
46 import cz.cuni.amis.utils.ExceptionToString;
47
48
49
50
51
52
53
54
55 public class LevelGeometry implements Serializable {
56
57
58
59
60 private static final long serialVersionUID = -8146957816661176041L;
61
62 public static class RaycastResult {
63 public Location from;
64 public Location to;
65 public Location rayVector;
66 public Location hitLocation;
67 public Vector3d hitNormal;
68 public boolean hit;
69 public double hitDistance;
70 public List<LevelGeometryBSPNode> travelledNodes = new ArrayList<LevelGeometryBSPNode>();
71 public List<Location> rayPoints = new ArrayList<Location>();
72
73
74
75 public int hitTriangle;
76
77 public RaycastResult(Location from, Location to) {
78 super();
79 this.from = from;
80 this.to = to;
81 this.hit = false;
82 rayVector = to.sub(from);
83 }
84 }
85
86 private transient Logger log;
87
88 private transient boolean loaded = false;
89
90 public static Random random = new Random();
91
92 public ArrayList<double[]> verts = new ArrayList<double[]>();
93 public ArrayList<int[]> triangles = new ArrayList<int[]>();
94 private LevelGeometryBSPNode bspTree;
95 private LevelGeometryBSPNode biggestLeafInTree;
96 public int leafCount = 0;
97
98
99 public double minX = Double.POSITIVE_INFINITY;
100 public double maxX = Double.NEGATIVE_INFINITY;
101 public double minY = Double.POSITIVE_INFINITY;
102 public double maxY = Double.NEGATIVE_INFINITY;
103 public double minZ = Double.POSITIVE_INFINITY;
104 public double maxZ = Double.NEGATIVE_INFINITY;
105
106
107
108
109
110 public LevelGeometry(Logger log) {
111 this.log = log;
112 if (this.log == null) {
113 this.log = new LogCategory("LevelGeometry");
114 }
115 }
116
117 public boolean isLoaded() {
118 return loaded;
119 }
120
121 public LevelGeometryBSPNode getBSPRoot() {
122 return bspTree;
123 }
124
125 public void clear() {
126 loaded = false;
127
128 verts = new ArrayList<double[]>();
129 triangles = new ArrayList<int[]>();
130 bspTree = null;
131 biggestLeafInTree = null;
132 leafCount = 0;
133
134 minX = Double.POSITIVE_INFINITY;
135 maxX = Double.NEGATIVE_INFINITY;
136 minY = Double.POSITIVE_INFINITY;
137 maxY = Double.NEGATIVE_INFINITY;
138 minZ = Double.POSITIVE_INFINITY;
139 maxZ = Double.NEGATIVE_INFINITY;
140 }
141
142 public boolean load(String mapName) throws FileNotFoundException, IOException, Exception {
143 if (loaded) {
144 clear();
145 }
146
147
148 verts.add(new double[0]);
149
150 double scale;
151 double[] centre;
152 String fileName, line;
153 File file;
154 BufferedReader br;
155
156
157 fileName = NavMeshConstants.pureLevelGeometryReadDir + "\\" + mapName + ".scale";
158 file = new File(fileName);
159 if (!file.exists()) {
160 log.warning("LevelGeometry .scale file does not exist at: " + file.getAbsolutePath());
161 return false;
162 }
163 br = new BufferedReader(new FileReader(file));
164 line = br.readLine();
165 scale = Double.parseDouble(line);
166
167
168 fileName = NavMeshConstants.pureLevelGeometryReadDir + "\\" + mapName + ".centre";
169 file = new File(fileName);
170 if (!file.exists()) {
171 log.warning("LevelGeometry .centre file does not exist at: " + file.getAbsolutePath());
172 return false;
173 }
174 br = new BufferedReader(new FileReader(file));
175 line = br.readLine();
176 String[] sc = line.split("[ \\t]");
177 centre = new double[3];
178 for(int i=0; i<3; i++) {
179 centre[i] = Double.parseDouble(sc[i]);
180 }
181
182
183 fileName = NavMeshConstants.pureLevelGeometryReadDir + "\\" + mapName + ".obj";
184 file = new File(fileName);
185 if (!file.exists()) {
186 log.warning("LevelGeometry .obj file does not exist at: " + file.getAbsolutePath());
187 return false;
188 }
189 br = new BufferedReader(new FileReader(file));
190 while((line = br.readLine()) != null) {
191 String[] words = line.split("[ \\t]");
192 if(words[0].equals("v")) {
193 double[] v = new double[3];
194 v[0] = Double.parseDouble(words[1])*scale + centre[0];
195 v[1] = Double.parseDouble(words[3])*scale + centre[1];
196 v[2] = Double.parseDouble(words[2])*scale + centre[2];
197 verts.add(v);
198
199
200 if(v[0] < minX) minX = v[0];
201 if(v[0] > maxX) maxX = v[0];
202 if(v[1] < minY) minY = v[1];
203 if(v[1] > maxY) maxY = v[1];
204 if(v[2] < minZ) minZ = v[2];
205 if(v[2] > maxZ) maxZ = v[2];
206
207 }
208 if(words[0].equals("f")) {
209 int[] t = new int[3];
210 t[0] = Integer.parseInt(words[1]);
211 t[1] = Integer.parseInt(words[2]);
212 t[2] = Integer.parseInt(words[3]);
213 triangles.add(t);
214 }
215 }
216
217
218 resetBSPTree();
219
220 loaded = true;
221
222 return true;
223 }
224
225 private void readObject(ObjectInputStream input) throws ClassNotFoundException, IOException {
226 input.defaultReadObject();
227 log = new LogCategory("LevelGeometry");
228 loaded = true;
229 }
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271 private void resetBSPTree() throws Exception {
272 log.info("Creating BSP tree...");
273 bspTree = new LevelGeometryBSPNode(this, null, minX, maxX, minY, maxY, minZ, maxZ);
274 for(int i = 0; i<triangles.size(); i++) bspTree.triangles.add(i);
275 biggestLeafInTree = null;
276 bspTree.build();
277 log.info("BSP tree is done building.");
278 log.info("Biggest leaf has " + biggestLeafInTree.triangles.size() + " triangles.");
279 log.info("BSP tree has " +leafCount+ " leaves");
280 bspTree.cleanInnerNodes();
281 }
282
283
284
285
286
287 void setBiggestLeafInTree(LevelGeometryBSPNode node) {
288 biggestLeafInTree = node;
289 }
290
291 int getNumberOfTrianglesInBiggestLeaf() {
292 if(biggestLeafInTree==null) return 0;
293 else return biggestLeafInTree.triangles.size();
294 }
295
296
297
298
299
300
301
302 private LevelGeometryBSPNode getBSPLeaf(Location location) {
303 double x = location.x;
304 double y = location.y;
305 double z = location.z;
306
307 LevelGeometryBSPNode node = this.bspTree;
308
309 if(x>node.maxX || x<node.minX) return null;
310 if(y>node.maxY || y<node.minY) return null;
311 if(z>node.maxZ || z<node.minZ) return null;
312
313
314 while(node.left!=null) {
315
316 switch(node.sepDim) {
317 case 0:
318 if(x<node.sepVal) node = node.left;
319 else node = node.right;
320 break;
321 case 1:
322 if(y<node.sepVal) node = node.left;
323 else node = node.right;
324 break;
325 case 2:
326 if(z<node.sepVal) node = node.left;
327 else node = node.right;
328 break;
329 default: throw new UnsupportedOperationException("This should not happen!");
330 }
331 }
332 return node;
333 }
334
335
336
337
338
339
340
341
342 public AutoTraceRay getAutoTraceRayMessage(Self self, BSPRayInfoContainer rayInfo) {
343
344
345 Vector3d direction = rayInfo.direction;
346 direction.normalize();
347
348
349 double upDownAngle = Math.asin(direction.getZ()/1);
350
351
352
353 double UTFullAngle = 65536;
354 double UTHalfAngle = UTFullAngle/2;
355 double UTQuarterAngle = UTHalfAngle/2;
356
357 double rayYaw;
358 double x = rayInfo.direction.x;
359 double y = rayInfo.direction.y;
360
361 if(x==0 && y==0) {
362 direction = new Vector3d(0,0,1);
363 } else {
364 rayYaw = NavMeshConstants.transform2DVectorToRotation(new Vector2d(x,y));
365
366
367 double Yaw = rayYaw + self.getRotation().getYaw();
368 Vector2d vector2d = NavMeshConstants.transformRotationTo2DVector(Yaw);
369 direction.x = vector2d.x;
370 direction.y = vector2d.y;
371
372
373 direction.normalize();
374 direction.z = Math.tan(upDownAngle);
375 }
376
377
378 direction.normalize();
379
380 direction.x *= rayInfo.length;
381 direction.y *= rayInfo.length;
382 direction.z *= rayInfo.length;
383
384
385
386
387
388
389 Location from = self.getLocation();
390 Location to = new Location(from.x + direction.x, from.y + direction.y, from.z + direction.z);
391
392
393 return getAutoTraceRayMessage(from, to, rayInfo);
394 }
395
396
397
398
399
400
401
402
403 private AutoTraceRay getAutoTraceRayMessage(Location from, Location to, BSPRayInfoContainer rayInfo) {
404
405 StraightLine3D ray = new StraightLine3D(from.asPoint3D(), to.asPoint3D());
406
407 RaycastResult raycast = raycast(from, to);
408
409 if (raycast.hit) {
410 return
411 new AutoTraceRayMessage(
412 rayInfo.unrealId,
413 from,
414 to,
415 false,
416 rayInfo.floorCorrection,
417 raycast.hit,
418 raycast.hitNormal,
419 raycast.hitLocation,
420 false,
421 null
422 );
423
424 } else {
425
426 return
427 new AutoTraceRayMessage(
428 rayInfo.unrealId,
429 from,
430 to,
431 false,
432 rayInfo.floorCorrection,
433 false,
434 null,
435 null,
436 false,
437 null
438 );
439 }
440 }
441
442
443
444
445
446
447
448
449 public RaycastResult raycast(Location from, Location to) {
450 RaycastResult result = new RaycastResult(from, to);
451 try {
452 raycastInner(from, to, result);
453 } catch (Exception e) {
454 log.severe(ExceptionToString.process("Failed to raycast: " + from + " -> " + to, e));
455 }
456 if (result.hit) {
457 result.hitDistance = result.hitLocation.getDistance(from.getLocation());
458 }
459 return result;
460 }
461
462 private boolean isValidNumber(Double number) {
463 if (number == null) return false;
464 if (number == Double.POSITIVE_INFINITY) return false;
465 if (number == Double.NEGATIVE_INFINITY) return false;
466 if (number == Double.NaN) return false;
467 return true;
468 }
469
470 private boolean isValidPoint(Point3D point) {
471 return isValidNumber(point.getX()) && isValidNumber(point.getY()) && isValidNumber(point.getZ());
472 }
473
474 private void raycastInner(Location from, Location to, RaycastResult result) {
475
476 StraightLine3D ray = new StraightLine3D(from.asPoint3D(), to.asPoint3D());
477
478
479 boolean collisionFound = false;
480 double collisionDistance = Double.POSITIVE_INFINITY;
481 Location hitLocation = null;
482 Vector3d normalVector = null;
483
484
485
486 LevelGeometryBSPNode leaf = this.getBSPLeaf(from);
487
488 if (leaf == null) {
489
490
491 return;
492 }
493
494 result.travelledNodes.add(leaf);
495
496 int triangleHit = -1;
497
498
499 for(Integer tId : leaf.triangles) {
500 int[] triangle = this.triangles.get(tId);
501 double[] v1 = this.verts.get(triangle[0]);
502 double[] v2 = this.verts.get(triangle[1]);
503 double[] v3 = this.verts.get(triangle[2]);
504 Point3D p1 = new Point3D(v1[0],v1[1],v1[2]);
505 Vector3D vector1 = new Vector3D(v2[0]-v1[0], v2[1]-v1[1], v2[2]-v1[2]);
506 Vector3D vector2 = new Vector3D(v3[0]-v1[0], v3[1]-v1[1], v3[2]-v1[2]);
507 Plane3D plane = new Plane3D(p1,vector1,vector2);
508 Point3D intersection = plane.getLineIntersection(ray);
509
510
511 boolean collision = isValidPoint(intersection) && this.isPointInTriangle(intersection, tId);
512 if(!collision) continue;
513
514
515 double zero = intersection.getDistance(from.asPoint3D()) + intersection.getDistance(to.asPoint3D()) - from.getDistance(to);
516 collision = Math.abs(zero) < 0.001;
517 if(!collision) continue;
518
519
520 double distance = intersection.getDistance(from.asPoint3D());
521 if(distance < collisionDistance) {
522 collisionFound = true;
523 collisionDistance = distance;
524 hitLocation = new Location(intersection.getX(),intersection.getY(),intersection.getZ());
525 Vector3D normalVector3D = plane.getNormalVector();
526 normalVector = new Vector3d(normalVector3D.getX(),normalVector3D.getY(),normalVector3D.getZ());
527 triangleHit = tId;
528 }
529 }
530
531
532 if(collisionFound) {
533 result.hitLocation = hitLocation;
534 result.hitNormal = normalVector;
535 result.hit = true;
536 result.hitTriangle = triangleHit;
537 return;
538 }
539
540
541 boolean out = false;
542
543 if(to.x > leaf.maxX) {
544 out = true;
545 }
546 if(to.x < leaf.minX) {
547 out = true;
548 }
549 if(to.y > leaf.maxY) {
550 out = true;
551 }
552 if(to.y < leaf.minY) {
553 out = true;
554 }
555 if(to.z > leaf.maxZ) {
556 out = true;
557 }
558 if(to.z < leaf.minZ) {
559 out = true;
560 }
561
562 if (!out) {
563
564 return;
565 }
566
567
568
569
570
571 collisionDistance = Double.MAX_VALUE;
572 Point3D collisionPoint = null;
573 double distance;
574
575
576 if(to.x > from.x) {
577 Point3D p1 = new Point3D(leaf.maxX,0,0);
578 Vector3D vector1 = new Vector3D(0, 1, 0);
579 Vector3D vector2 = new Vector3D(0, 0, 1);
580 Plane3D plane = new Plane3D(p1,vector1,vector2);
581 Point3D intersection = plane.getLineIntersection(ray);
582 if (isValidPoint(intersection)) {
583 distance = intersection.getDistance(from.asPoint3D());
584
585 if(distance < collisionDistance) {
586 collisionPoint = intersection;
587 collisionDistance = distance;
588 }
589 }
590 } else {
591 Point3D p1 = new Point3D(leaf.minX,0,0);
592 Vector3D vector1 = new Vector3D(0, 1, 0);
593 Vector3D vector2 = new Vector3D(0, 0, 1);
594 Plane3D plane = new Plane3D(p1,vector1,vector2);
595 Point3D intersection = plane.getLineIntersection(ray);
596 if (isValidPoint(intersection)) {
597 distance = intersection.getDistance(from.asPoint3D());
598
599 if(distance < collisionDistance) {
600 collisionPoint = intersection;
601 collisionDistance = distance;
602 }
603 }
604 }
605
606
607 if(to.y > from.y) {
608 Point3D p1 = new Point3D(0, leaf.maxY,0);
609 Vector3D vector1 = new Vector3D(1, 0, 0);
610 Vector3D vector2 = new Vector3D(0, 0, 1);
611 Plane3D plane = new Plane3D(p1,vector1,vector2);
612 Point3D intersection = plane.getLineIntersection(ray);
613 if (isValidPoint(intersection)) {
614 distance = intersection.getDistance(from.asPoint3D());
615
616 if(distance < collisionDistance) {
617 collisionPoint = intersection;
618 collisionDistance = distance;
619 }
620 }
621 } else {
622 Point3D p1 = new Point3D(0,leaf.minY,0);
623 Vector3D vector1 = new Vector3D(1, 0, 0);
624 Vector3D vector2 = new Vector3D(0, 0, 1);
625 Plane3D plane = new Plane3D(p1,vector1,vector2);
626 Point3D intersection = plane.getLineIntersection(ray);
627 distance = intersection.getDistance(from.asPoint3D());
628
629 if(distance < collisionDistance) {
630 collisionPoint = intersection;
631 collisionDistance = distance;
632 }
633 }
634
635
636 if(to.z > from.z) {
637 Point3D p1 = new Point3D(0,0,leaf.maxZ);
638 Vector3D vector1 = new Vector3D(1, 0, 0);
639 Vector3D vector2 = new Vector3D(0, 1, 0);
640 Plane3D plane = new Plane3D(p1,vector1,vector2);
641 Point3D intersection = plane.getLineIntersection(ray);
642 if (isValidPoint(intersection)) {
643 distance = intersection.getDistance(from.asPoint3D());
644
645 if(distance < collisionDistance) {
646 collisionPoint = intersection;
647 collisionDistance = distance;
648 }
649 }
650 } else {
651 Point3D p1 = new Point3D(0,0,leaf.minZ);
652 Vector3D vector1 = new Vector3D(1, 0, 0);
653 Vector3D vector2 = new Vector3D(0, 1, 0);
654 Plane3D plane = new Plane3D(p1,vector1,vector2);
655 Point3D intersection = plane.getLineIntersection(ray);
656 if (isValidPoint(intersection)) {
657 distance = intersection.getDistance(from.asPoint3D());
658
659 if(distance < collisionDistance) {
660 collisionPoint = intersection;
661 collisionDistance = distance;
662 }
663 }
664 }
665
666 if (collisionPoint == null) {
667
668 return;
669 }
670
671
672
673 Vector3d direction = to.sub(from).getNormalized().asVector3d();
674 Location newPoint = new Location(collisionPoint.getX()+direction.x,collisionPoint.getY()+direction.y,collisionPoint.getZ()+direction.z);
675
676 if (from.getDistance(to) < from.getDistance(newPoint)) {
677
678 return;
679 }
680
681
682 result.rayPoints.add(newPoint);
683 raycastInner(newPoint, to, result);
684 }
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722 public boolean isPointInTriangle(Point3D point3D, Integer tId) {
723 int[] triangle = triangles.get(tId);
724
725 Location p = new Location(point3D);
726 Location a = new Location(verts.get(triangle[0]));
727 Location b = new Location(verts.get(triangle[1]));
728 Location c = new Location(verts.get(triangle[2]));
729
730 Location v0 = b.sub(a);
731 Location v1 = c.sub(a);
732 Location v2 = p.sub(a);
733
734 double d00 = v0.dot(v0);
735 double d01 = v0.dot(v1);
736 double d11 = v1.dot(v1);
737 double d20 = v2.dot(v0);
738 double d21 = v2.dot(v1);
739 double denom = d00 * d11 - d01 *d01;
740
741 if (Math.abs(denom) < 0.0000001) return false;
742
743 double v = (d11 * d20 - d01 * d21) / denom;
744 double w = (d00 * d21 - d01 * d20) / denom;
745 double u = 1 - v - w;
746
747 boolean inside = 0 < u && u < 1 && 0 < v && v < 1 && 0 < w && w < 1;
748
749 if (inside) {
750 return true;
751 } else {
752 return false;
753 }
754 }
755
756 public double[][] getTriangle(int tId) {
757 int[] triangle = triangles.get(tId);
758 return new double[][]{ verts.get(triangle[0]), verts.get(triangle[1]), verts.get(triangle[2]) };
759 }
760
761 }
762