convert line ends
[canaan.git] / prj / cam / src / portal / wrcast.c
blob5e4ccc5f67e1a3ddd35af4fde34f5629bf02832f
1 /*
2 @Copyright Looking Glass Studios, Inc.
3 1996,1997,1998,1999,2000 Unpublished Work.
4 */
6 // $Header: r:/t2repos/thief2/src/portal/wrcast.c,v 1.42 2000/02/19 13:18:52 toml Exp $
8 /* ----- /-/-/-/-/-/-/-/-/ <<< (((((( /\ )))))) >>> \-\-\-\-\-\-\-\-\ ----- *\
10 The raycaster returns TRUE if you can get from the start to the
11 end, and FALSE otherwise.
13 The comments on the earlier raycaster, which is still present as
14 OldPortalRaycast, follow these. The two routines have enough in
15 common that I've skimped in places where the earlier description is
16 still valid (esp.: epsilon issues, coplanar surfaces).
18 We're working parametrically, with the ray going from 0 to 1.0.
19 For each plane we might exit through, we compute an exit time T.
20 The math is described the the earlier comments.
22 We eliminate some planes through backface culling, without finding
23 Ts. The first step in finding a T is calculating the distance from
24 the plane to the starting point of the ray, so the culling is free.
26 Of those planes which face the right direction, our exit plane is
27 the one with the smallest T. If it has no portals we stop, and if
28 it's all one portal we go through it. For more complex cases we
29 use the plane's normal to identify its larger axes and check which
30 portal we've hit using a 2d convex-hull test. There are six cases
31 for this: three axes x two winding directions. There's a separate
32 goofy little tester function for each case. If we haven't hit any
33 of the portals, we've hit a solid polygon, and we stop.
35 Optimizations...
37 Naturally, once things are optimized a bit, all explanation of
38 theory is turned into half-truths. We do not, in fact, find our
39 times using our start and end points, because we wouldn't be able
40 to backface-cull in the first cell. Instead we back up to T = -2
41 and perform all calculations from that point (for large cells with
42 short raycasts, this won't help; but doing it the right way, using
43 the diameter of the bounding sphere of our starting cell, requires
44 a divide, and turns out to be slower).
46 If a plane has more than one portal to test, but no nonportals, we
47 don't have to test the last portal. We just enter it.
49 (Not done:) We're deriving a lot of static data on-the-fly,
50 including the major axes and directions of our plane normals and
51 which polygons are in which planes. We could add fields for these
52 to the world rep, though if it were only used here the change
53 wouldn't gain us much.
55 (Not done:) In the exit time loop, we could accumulate distances to
56 divide in an array and run them two at a time so they overlapped.
57 This might be more of an ASM approach. Unfortunately we don't have
58 any integer operations to stick in between.
60 \* ----- \-\-\-\-\-\-\-\-\ <<< (((((( \/ )))))) >>> /-/-/-/-/-/-/-/-/ ----- */
63 // So here's the comments on the earlier version. Still good reading!
65 // World-rep raycast
67 // This routine casts an infinitely thin ray from one location to
68 // another, and checks for intersecting terrain. If the ray is
69 // coplanar with a surface, that surface doesn't affect it. If a ray
70 // hits the edge of a surface, it is stopped. (This prevents the ray
71 // from passing through the crack between polygons, but lets us get
72 // useful data comparing points on the surfaces of polygons.)
74 // The function returns true if you can get from one point to the
75 // other. If it returns false, then you can call a query function to
76 // determine what cell, what plane, and what polygon the ray was
77 // stopped by. (Determining what polygon is expensive; determining
78 // the others is free once the ray has been cast.)
80 // If the start point isn't in the database, no cell or plane or
81 // polygon will be available.
83 // If the function returns true, the cell cache for the endpoint will
84 // be set. Thus, for example, if you test for moving a thing from x
85 // to y with a raycast, and it's valid, you should copy the entire
86 // location y into the object's storage, so that its cell cache is
87 // updated correctly
89 // There's an extra parameter to raycast. This is the flag value.
90 // Any portal which has any bits in common with the flag value will
91 // block the raycast. (I suppose people might also want to check for
92 // particular clutids to do things like "this raycast doesn't go
93 // through water", but there are a few flag bits free still!)
95 // IMPLEMENTATION
97 // The basic approach is to find the cell that the start location is
98 // in. Then we explore through the series of cells the ray encounters
99 // in order.
101 // We begin by considering the ray as parameterized
102 // start + t*(end-start)
104 // We will explore the range of t from 0..1.
106 // Suppose that at time r we enter a particular cell. (We enter the
107 // first cell at some negative time t, but we don't actually need to
108 // know it).
110 // Then we can iterate over all the planes of the cell. We compute
111 // the point-to-plane distance for both the start and end point. If
112 // they're on opposite sides, we compute the time s at which they
113 // intersect.
115 // Then we know that we exit the cell through the plane which has the
116 // least s greater than r.
118 // Now we must deal with the fact that there may be multiple coplanar
119 // surfaces along the exit plane. We iterate over all of those
120 // surfaces. If none are portals, then we collided with a wall, and
121 // are done. If one of them is a portal, then we need to check
122 // whether we enter that cell at the same time we exited this cell.
123 // If we did, then we enter that cell, and if not, we're done.
125 // If there's more than one coplanar portal, then we test whether the
126 // entry time into each cell is identical to the exit time for this
127 // cell.
129 // COMPLICATIONS
131 // First of all, we need to be able to have a "tolerance" for deciding
132 // if two "times" are equal. We'd like this tolerance to be based on
133 // space, not time, so that two things are considered coplanar if
134 // they're within e.g. 0.001 units; otherwise we get different
135 // results for short raycasts and long raycasts.
137 // Therefore we will actually do the following:
138 // delta = end - start
139 // len = || delta ||
140 // delta = delta / len
142 // Now t ranges from 0..len instead of 0..1
144 // Second, when we try to decide if we make it through a portal, we
145 // have to "look ahead" to the next cell. Rather than looking ahead,
146 // doing the math, and then coming back, we want to do it all in one
147 // step. So if there's only one portal, then we will "tentatively"
148 // advance into the next cell. When we check for a time s s.t. r < s,
149 // we also check for an s s.t. s ~= r. We can just do this in general
150 // all the time, except in the first cell; but if we have to have a
151 // boolean which is set in the first cell, we might as well do it all
152 // the time.
154 // In the case of multiple coplanar portals, this is more complicated;
155 // we need to tentatively advance, but be able to back up and try
156 // another. Rather than write this potentially complicated code, we
157 // note that this case probably occurs infrequently enough that we can
158 // afford to test the cells redundantly. Consider the costs:
160 // Suppose we have n coplanar portals, and an equal chance of going
161 // through any of the portals (ignore the possibility of hitting a
162 // wall, which requires exploring all portals). There are n cases.
163 // For the ith case, we explore i portals before determining which one
164 // we go through.
166 // If we assume naive lookahead, then we will explore all of the cells
167 // up to the ith, and then we will immediately reexplore that cell.
168 // If we assume smart lookahead, we explore all of the cells up to the
169 // ith, and then we're done and already have the information to
170 // continue the next cell. Thus, in every case, smart lookahead saves
171 // one cell exploration every time there are coplanar portals.
173 // However, if there are n coplanar portals, then on average we will
174 // explore (n+1)/2 of them with smart, and (n+3)/2 of them with naive.
175 // If there's only 1 coplanar portal, this is savings (1 vs. 2), and
176 // the code is easy to do. With 3 coplanar portals, the savings is
177 // smaller (2 vs. 3), and it gets progressively smaller as n gets
178 // larger.
180 // Thirdly, we can optimize the last cell. If we know what cell the
181 // endpoint is in, then the moment we enter that cell, we can stop, as
182 // long as we've truly entered it, not tentatively entered it. I
183 // think the old portal raycaster had bugs in this area because I
184 // never worked the logic out formally.
186 // Fourthly, we optimize for the cast where both end points are in the
187 // same cell, but we don't know that the end point is in the cell when
188 // we start. The way we do this that when we check the distance of
189 // points from each plane, we check the outside point first. If it's
190 // inside the plane, then we skip testing the inside point. If we're
191 // checking entry time, we can't skip it, but we never are in the
192 // first cell.
194 // Fifthly, if our raycast succeeds, we stuff the cell cache for the
195 // destination, in case it actually gets reused.
197 #include <math.h> // fabs()
198 #include <stdlib.h> // exit()
200 #include <lg.h>
201 #include <dbg.h>
202 #include <mprintf.h>
203 #include <r3d.h>
204 #include <matrix.h>
206 #include <_portal.h>
208 #include <port.h>
209 #include <wrtype.h>
210 #include <wrdb.h>
211 #include <wrfunc.h>
212 #include <memall.h>
213 #include <dbmem.h> // must be last header!
216 // These should really be in dbg.h. If I need to use them more than
217 // once, I'll put them there.
219 #ifdef DBG_ON
220 # define DbgSetDbgSlot(b,s) dbgBank[b].gate[DG_DBG] |= (s)
221 # define DbgClearDbgSlot(b,s) dbgBank[b].gate[DG_DBG] &= ~(s)
222 # define DbgSetMonoSlot(b,s) dbgBank[b].gate[DG_MONO] |= (s)
223 # define DbgClearMonoSlot(b,s) dbgBank[b].gate[DG_MONO] &= ~(s)
224 #else
225 # define DbgSetDbgSlot(b,s)
226 # define DbgClearDbgSlot(b,s)
227 # define DbgSetMonoSlot(b,s)
228 # define DbgClearMonoSlot(b,s)
229 #endif
232 #ifndef _WIN32
233 #define RAYCAST_ERROR
234 #endif
237 #define DBG_Raycast(x) DBG(DSRC_PORTAL_Raycast, x)
238 #define SpewRaycast(x) Spew(DSRC_PORTAL_Raycast, x)
241 bool debug_raycast;
243 #ifdef WARN_ON
244 # define Complain(x) Warning((x));
245 #else
246 # define Complain(x) mprintf(x);
247 #endif
249 #define RAYCAST_EPSILON 0.001
250 #define EXTENDED_TIME_RAYCAST_EPSILON (RAYCAST_EPSILON / 3.0)
251 #define HULL_TEST_EPSILON 0.00025
254 // OPTIMIZE: use the dot product cache
255 #define PlaneDist(vec,pln) (mx_dot_vec(vec, &(pln)->normal) + (pln)->plane_constant)
257 //////////////////////////////
259 // test if a given point lies along the surface of a cell
261 bool PortalPointOnCell(int cell, mxs_vector *pt)
263 PortalCell *r = WR_CELL(cell);
264 mxs_real dist;
265 PortalPlane *p = r->plane_list;
266 int n = r->num_planes, i;
267 bool found_surface = FALSE;
269 SpewRaycast (("PortalPointOnCell: "));
271 for (i=0; i < n; ++i,++p) {
272 dist = PlaneDist(pt, p);
273 SpewRaycast (("!%g ", dist));
275 // if the point is behind the plane, we're not in this cell
276 if (dist < -RAYCAST_EPSILON * 2.0)
277 return FALSE;
279 if (fabs(dist) <= RAYCAST_EPSILON * 2.0) // OPTIMIZE: I don't think we need this fabs [DS]
280 found_surface = TRUE;
283 // this means we're inside the cell, which is also FALSE
284 return found_surface;
287 //////////////////////////////
289 // Raycast along a vector, instead of from start point to end point
291 void PortalRaycastVector (Location *start_loc, mxs_vector *vec,
292 Location *hit_loc, int use_zero_epsilon)
294 // The way we do this is to do a bunch of finite raycasts, and keep
295 // projecting forward if we don't hit.
297 Location loc1, loc2; // start and end of this raycast
298 mxs_vector next; // used to construct loc2;
300 loc1 = *start_loc;
302 // We could make this faster with pointers switching back and forth
303 // so that we're not copying Locations around. I'm sure it's not
304 // worth it; I'm just mentioning it here so Sean doesn't think I'm
305 // an idiot. (DS)
307 // I think we can afford one copy every 100 units, so long as
308 // people don't decide a unit is a millimeter. (SB)
310 // Five bucks to the first person to make this code go into an
311 // infinite loop
312 for (;;)
314 mx_scale_add_vec (&next, &loc1.vec, vec, 100.0);
315 MakeLocationFromVector (&loc2, &next);
316 if (!PortalRaycast (&loc1, &loc2, hit_loc, use_zero_epsilon))
317 return;
318 loc1 = loc2; // oh, the inefficiency
323 // These globals are used for passing values into the six routines
324 // which follow, and are not guaranteed to have any particular value
325 // at any time outside the raycaster since they're used by other
326 // functions which borrow the raycaster's internals. Say, do you
327 // think these names are long enough?
328 static PortalPolygonCore *_portal_raycast_cur_poly;
329 static mxs_vector *_portal_raycast_cur_vectors;
330 static uchar *_portal_raycast_point_indirection;
331 static mxs_vector _portal_raycast_perp_edge;
332 static mxs_vector _portal_raycast_contact;
335 // These are set by PortalRaycast().
336 int PortalRaycastPlane; // PLANE_INVALID if we hit nothing
337 int PortalRaycastCell; // end cell of cast if we didn't hit
338 float PortalRaycastTime; // 0-1, if we hit anything
339 mxs_vector PortalRaycastHit; // undefined if we hit nothing
341 bool PortalRaycastResult = TRUE;
343 // These are set by PortalRaycastRefs().
344 int PortalRaycastRefCount;
345 int PortalRaycastRef[RAYCAST_MAX_REFS];
348 /* ----- /-/-/-/-/-/-/-/-/ <<< (((((( /\ )))))) >>> \-\-\-\-\-\-\-\-\ ----- *\
350 The next six routines are used for convex hull tests on the larger
351 axes of a polygon. We have a total of six since the standard for
352 right-sidedness depends on which direction the ray is coming from
353 (that is, it's a winding thing). TRUE means our ray passes through
354 this polygon.
356 We check whether a point is within a polygon using a dot product.
357 But we're cheap. We don't want to have to normalize the components.
358 So we use a quick and dirty Manhattan distance.
360 \* ----- \-\-\-\-\-\-\-\-\ <<< (((((( \/ )))))) >>> /-/-/-/-/-/-/-/-/ ----- */
361 bool _PortalConvexHullXYPosZ(void)
363 mxs_vector *end;
364 uchar i;
365 float size1, size2;
366 uchar sides = _portal_raycast_cur_poly->num_vertices;
367 mxs_vector *start = _portal_raycast_cur_vectors
368 + _portal_raycast_point_indirection[sides - 1];
370 for (i = 0; i < sides; i++) {
371 end = _portal_raycast_cur_vectors
372 + _portal_raycast_point_indirection[i];
373 _portal_raycast_perp_edge.x = end->y - start->y;
374 _portal_raycast_perp_edge.y = start->x - end->x;
376 if (_portal_raycast_perp_edge.x >= 0.0)
377 size1 = _portal_raycast_perp_edge.x;
378 else
379 size1 = -_portal_raycast_perp_edge.x;
381 if (_portal_raycast_perp_edge.y >= 0.0)
382 size2 = _portal_raycast_perp_edge.y;
383 else
384 size2 = -_portal_raycast_perp_edge.y;
386 // If we're to the left of any side, we're outside our polygon.
387 if ((_portal_raycast_perp_edge.x
388 * (_portal_raycast_contact.x - start->x)
389 + _portal_raycast_perp_edge.y
390 * (_portal_raycast_contact.y - start->y))
391 <= (-HULL_TEST_EPSILON * (size1 + size2)))
392 return FALSE;
394 start = end;
397 return TRUE;
401 // This is just like _PortalConvexHullXYPosZ, above.
402 bool _PortalConvexHullXYNegZ(void)
404 mxs_vector *end;
405 uchar i;
406 float size1, size2;
407 uchar sides = _portal_raycast_cur_poly->num_vertices;
408 mxs_vector *start = _portal_raycast_cur_vectors
409 + _portal_raycast_point_indirection[sides - 1];
411 for (i = 0; i < sides; i++) {
412 end = _portal_raycast_cur_vectors
413 + _portal_raycast_point_indirection[i];
414 _portal_raycast_perp_edge.x = end->y - start->y;
415 _portal_raycast_perp_edge.y = start->x - end->x;
417 if (_portal_raycast_perp_edge.x >= 0.0)
418 size1 = _portal_raycast_perp_edge.x;
419 else
420 size1 = -_portal_raycast_perp_edge.x;
422 if (_portal_raycast_perp_edge.y >= 0.0)
423 size2 = _portal_raycast_perp_edge.y;
424 else
425 size2 = -_portal_raycast_perp_edge.y;
427 // If we're to the left of any side, we're outside our polygon.
428 if ((_portal_raycast_perp_edge.x
429 * (_portal_raycast_contact.x - start->x)
430 + _portal_raycast_perp_edge.y
431 * (_portal_raycast_contact.y - start->y))
432 >= (HULL_TEST_EPSILON * (size1 + size2)))
433 return FALSE;
435 start = end;
438 return TRUE;
442 // This is just like _PortalConvexHullXYPosZ, above.
443 bool _PortalConvexHullYZPosX(void)
445 mxs_vector *end;
446 uchar i;
447 float size1, size2;
448 uchar sides = _portal_raycast_cur_poly->num_vertices;
449 mxs_vector *start = _portal_raycast_cur_vectors
450 + _portal_raycast_point_indirection[sides - 1];
452 for (i = 0; i < sides; i++) {
453 end = _portal_raycast_cur_vectors
454 + _portal_raycast_point_indirection[i];
455 _portal_raycast_perp_edge.y = end->z - start->z;
456 _portal_raycast_perp_edge.z = start->y - end->y;
458 if (_portal_raycast_perp_edge.y >= 0.0)
459 size1 = _portal_raycast_perp_edge.y;
460 else
461 size1 = -_portal_raycast_perp_edge.y;
463 if (_portal_raycast_perp_edge.z >= 0.0)
464 size2 = _portal_raycast_perp_edge.z;
465 else
466 size2 = -_portal_raycast_perp_edge.z;
468 if ((_portal_raycast_perp_edge.y
469 * (_portal_raycast_contact.y - start->y)
470 + _portal_raycast_perp_edge.z
471 * (_portal_raycast_contact.z - start->z))
472 <= (-HULL_TEST_EPSILON * (size1 + size2)))
473 return FALSE;
475 start = end;
478 return TRUE;
482 // This is just like _PortalConvexHullXYPosZ, above.
483 bool _PortalConvexHullYZNegX(void)
485 mxs_vector *end;
486 uchar i;
487 float size1, size2;
488 uchar sides = _portal_raycast_cur_poly->num_vertices;
489 mxs_vector *start = _portal_raycast_cur_vectors
490 + _portal_raycast_point_indirection[sides - 1];
492 for (i = 0; i < sides; i++) {
493 end = _portal_raycast_cur_vectors
494 + _portal_raycast_point_indirection[i];
495 _portal_raycast_perp_edge.y = end->z - start->z;
496 _portal_raycast_perp_edge.z = start->y - end->y;
498 if (_portal_raycast_perp_edge.y >= 0.0)
499 size1 = _portal_raycast_perp_edge.y;
500 else
501 size1 = -_portal_raycast_perp_edge.y;
503 if (_portal_raycast_perp_edge.z >= 0.0)
504 size2 = _portal_raycast_perp_edge.z;
505 else
506 size2 = -_portal_raycast_perp_edge.z;
508 if ((_portal_raycast_perp_edge.y
509 * (_portal_raycast_contact.y - start->y)
510 + _portal_raycast_perp_edge.z
511 * (_portal_raycast_contact.z - start->z))
512 >= (HULL_TEST_EPSILON * (size1 + size2)))
513 return FALSE;
515 start = end;
518 return TRUE;
522 // This is just like _PortalConvexHullXYPosZ, above.
523 bool _PortalConvexHullZXPosY(void)
525 mxs_vector *end;
526 uchar i;
527 float size1, size2;
528 uchar sides = _portal_raycast_cur_poly->num_vertices;
529 mxs_vector *start = _portal_raycast_cur_vectors
530 + _portal_raycast_point_indirection[sides - 1];
532 for (i = 0; i < sides; i++) {
533 end = _portal_raycast_cur_vectors
534 + _portal_raycast_point_indirection[i];
535 _portal_raycast_perp_edge.z = end->x - start->x;
536 _portal_raycast_perp_edge.x = start->z - end->z;
538 if (_portal_raycast_perp_edge.z >= 0.0)
539 size1 = _portal_raycast_perp_edge.z;
540 else
541 size1 = -_portal_raycast_perp_edge.z;
543 if (_portal_raycast_perp_edge.x >= 0.0)
544 size2 = _portal_raycast_perp_edge.x;
545 else
546 size2 = -_portal_raycast_perp_edge.x;
548 if ((_portal_raycast_perp_edge.z
549 * (_portal_raycast_contact.z - start->z)
550 + _portal_raycast_perp_edge.x
551 * (_portal_raycast_contact.x - start->x))
552 <= (-HULL_TEST_EPSILON * (size1 + size2)))
553 return FALSE;
555 start = end;
558 return TRUE;
562 // This is just like _PortalConvexHullXYPosZ, above.
563 bool _PortalConvexHullZXNegY(void)
565 mxs_vector *end;
566 uchar i;
567 float size1, size2;
568 uchar sides = _portal_raycast_cur_poly->num_vertices;
569 mxs_vector *start = _portal_raycast_cur_vectors
570 + _portal_raycast_point_indirection[sides - 1];
572 for (i = 0; i < sides; i++) {
573 end = _portal_raycast_cur_vectors
574 + _portal_raycast_point_indirection[i];
575 _portal_raycast_perp_edge.z = end->x - start->x;
576 _portal_raycast_perp_edge.x = start->z - end->z;
578 if (_portal_raycast_perp_edge.z >= 0.0)
579 size1 = _portal_raycast_perp_edge.z;
580 else
581 size1 = -_portal_raycast_perp_edge.z;
583 if (_portal_raycast_perp_edge.x >= 0.0)
584 size2 = _portal_raycast_perp_edge.x;
585 else
586 size2 = -_portal_raycast_perp_edge.x;
588 if ((_portal_raycast_perp_edge.z
589 * (_portal_raycast_contact.z - start->z)
590 + _portal_raycast_perp_edge.x
591 * (_portal_raycast_contact.x - start->x))
592 >= (HULL_TEST_EPSILON * (size1 + size2)))
593 return FALSE;
595 start = end;
598 return TRUE;
602 bool OldPortalRaycast(Location *start_loc, Location *end_loc,
603 Location *hit_loc, int portal_block_flags);
604 bool use_old_raycast = FALSE;
605 bool (*_portal_raycast_hull_test)() = _PortalConvexHullXYPosZ;
608 /* ----- /-/-/-/-/-/-/-/-/ <<< (((((( /\ )))))) >>> \-\-\-\-\-\-\-\-\ ----- *\
610 We have six routines for convex hull tests. Each projects the
611 polygon onto a major axis plane. So we choose our test based on
612 the normal of the polygon's plane.
614 \* ----- \-\-\-\-\-\-\-\-\ <<< (((((( \/ )))))) >>> /-/-/-/-/-/-/-/-/ ----- */
615 void _PortalRaycastSetHullTest(mxs_vector *norm)
617 float x_size = fabs(norm->x);
618 float y_size = fabs(norm->y);
619 float z_size = fabs(norm->z);
621 if (x_size > y_size)
622 if (x_size > z_size)
623 if (norm->x < 0)
624 _portal_raycast_hull_test = _PortalConvexHullYZNegX;
625 else
626 _portal_raycast_hull_test = _PortalConvexHullYZPosX;
627 else
628 if (norm->z < 0)
629 _portal_raycast_hull_test = _PortalConvexHullXYNegZ;
630 else
631 _portal_raycast_hull_test = _PortalConvexHullXYPosZ;
632 else
633 if (y_size > z_size)
634 if (norm->y < 0)
635 _portal_raycast_hull_test = _PortalConvexHullZXNegY;
636 else
637 _portal_raycast_hull_test = _PortalConvexHullZXPosY;
638 else
639 if (norm->z < 0)
640 _portal_raycast_hull_test = _PortalConvexHullXYNegZ;
641 else
642 _portal_raycast_hull_test = _PortalConvexHullXYPosZ;
646 typedef struct {
647 PortalPolygonCore *poly;
648 uint vertex_offset;
649 } _PortalRaycastExit;
652 /* ----- /-/-/-/-/-/-/-/-/ <<< (((((( /\ )))))) >>> \-\-\-\-\-\-\-\-\ ----- *\
654 Here's the latest raycaster. It differs from the earlier one
655 mainly in that it does not calculate entry times, and decides where
656 to go next using convex hull tests.
658 \* ----- \-\-\-\-\-\-\-\-\ <<< (((((( \/ )))))) >>> /-/-/-/-/-/-/-/-/ ----- */
659 bool PortalRaycast(Location *start_loc, Location *end_loc,
660 Location *hit_loc, int use_zero_epsilon)
662 int cur_cell;
663 int i;
665 // ray and associated error terms
666 mxs_vector *plane_norm, start_point, end_point;
667 mxs_vector ray_vector, early_point;
668 double time_epsilon, space_epsilon, ray_length, end_time;
670 // used in deciding what surface we've reached in a cell
671 int num_nonportals_in_cell;
672 PortalPolygonCore *cur_poly;
673 PortalPlane *cur_plane;
674 int plane_is_all_portals;
675 int num_portals_in_plane;
676 int num_portals_to_test;
677 _PortalRaycastExit possible_exit[256];
679 #ifndef SHIP
680 if (start_loc->cell == CELL_INVALID && start_loc->hint == CELL_INVALID)
681 Warning(("raycast loc w/ no cell, no hint: (%g %g %g)\n",
682 start_loc->vec.x, start_loc->vec.y, start_loc->vec.z));
683 #endif
685 cur_cell = CellFromLoc(start_loc);
687 // set our return value & contact info
688 PortalRaycastResult = TRUE;
689 PortalRaycastPlane = PLANE_INVALID;
691 #ifndef SHIP
692 if (use_old_raycast)
693 return OldPortalRaycast(start_loc, end_loc,
694 hit_loc, 0);
697 int start_cell = start_loc->cell;
698 int start_hint = start_loc->hint;
700 if (start_cell == CELL_INVALID)
701 if (start_hint != cur_cell)
702 mprintf("PortalRaycast: bad hint at (%g %g %g)\n",
703 start_loc->vec.x, start_loc->vec.y, start_loc->vec.z);
705 #endif
707 // check to make sure we're in the database
708 if (cur_cell == CELL_INVALID) {
710 // stuff our globals used for query functions and return FALSE
711 PortalRaycastCell = CELL_INVALID;
712 SpewRaycast (("Started outside.\n"));
713 *hit_loc = *start_loc;
714 PortalRaycastResult = FALSE;
716 goto finished;
720 start_point = start_loc->vec;
721 end_point = end_loc->vec;
723 // We want an error term proportional to the length of the segment
724 // we're casting along. Along the way we find the length of our
725 // ray. We have to account for the fact that we are really casting
726 // along a ray three times the length of the one passed in.
727 mx_sub_vec(&ray_vector, &end_point, &start_point);
728 ray_length = mx_mag_vec(&ray_vector);
730 if (use_zero_epsilon) {
731 time_epsilon = space_epsilon = 0.0;
732 end_time = 1.0;
733 } else {
734 space_epsilon = EXTENDED_TIME_RAYCAST_EPSILON;
735 time_epsilon = space_epsilon / ray_length;
736 end_time = 1.0 - time_epsilon;
739 // Instead of finding our times from the starting point, we'll find
740 // them from T = -2 to try and backface-cull some planes in our
741 // first cell.
742 mx_scale_add_vec(&early_point, &start_point, &ray_vector, -2.0);
744 // So now our ray runs from early_point to end_point.
745 mx_scaleeq_vec(&ray_vector, 3.0);
747 // We advance through the cells until the next plane ahead is at
748 // least as far along the ray as our endpoint.
749 while (1) {
750 double start_dist, end_dist, time;
751 int plane_index, vertex_offset;
752 int cur_plane_index;
753 PortalCell *cell = wr_cell[cur_cell];
755 // We want the plane which is in front of us, but closer to our
756 // starting point than the other planes in front of us. We can
757 // backface cull the ones facing away from our ray.
759 PortalRaycastTime = 1000000.0; // big enough for ya?
760 for (plane_index = 0, cur_plane = cell->plane_list;
761 plane_index < cell->num_planes;
762 plane_index++, cur_plane++) {
763 plane_norm = &(cur_plane->normal);
765 start_dist = plane_norm->x * early_point.x
766 + plane_norm->y * early_point.y
767 + plane_norm->z * early_point.z
768 + cur_plane->plane_constant;
770 if (start_dist > space_epsilon) {
771 end_dist = plane_norm->x * end_point.x
772 + plane_norm->y * end_point.y
773 + plane_norm->z * end_point.z
774 + cur_plane->plane_constant;
776 if (end_dist < 0) {
777 time = start_dist / (start_dist - end_dist);
778 if (time < PortalRaycastTime) {
779 PortalRaycastTime = time;
780 cur_plane_index = plane_index;
786 // If every plane we didn't backface cull is beyond our
787 // endpoint, we must have reached the end of our ray without
788 // colliding with anything. We return TRUE.
789 if (PortalRaycastTime > end_time) {
790 PortalRaycastCell = cur_cell;
791 goto finished;
794 PortalRaycastPlane = cur_plane_index;
796 // If we have nonportals in our plane, we need to test our ray
797 // against all the portals; if we've hit none of them, we've hit
798 // a nonportal. So this is the bookkeeping to find out what
799 // we've got in this plane. Pity it's all deriving static info.
800 cur_poly = cell->poly_list;
801 num_nonportals_in_cell = cell->num_polys - cell->num_portal_polys;
802 plane_is_all_portals = 1;
803 vertex_offset = 0;
804 for (i = 0; i < num_nonportals_in_cell; i++) {
805 if (cur_poly->planeid == cur_plane_index)
806 plane_is_all_portals = 0;
807 vertex_offset += cur_poly->num_vertices;
808 cur_poly++;
811 // We may have more than one portal in the plane we're exiting.
812 // If so, we'll record which ones they are in an array.
813 num_portals_in_plane = 0;
814 for (; i < cell->num_polys; i++) {
815 if (cur_poly->planeid == cur_plane_index) {
816 possible_exit[num_portals_in_plane].poly = cur_poly;
817 possible_exit[num_portals_in_plane].vertex_offset = vertex_offset;
818 num_portals_in_plane++;
820 vertex_offset += cur_poly->num_vertices;
821 cur_poly++;
825 // If there's no cells we can reach through this plane, here we
826 // are, and we stop.
827 if (num_portals_in_plane == 0)
828 goto finished_with_collision;
830 // If this plane is nothing but one big portal, we go through it.
831 if (num_portals_in_plane == 1
832 && plane_is_all_portals) {
833 cur_cell = possible_exit[0].poly->destination;
834 } else {
835 // Here's the nasty case. A surface with more than one
836 // portal or a mixture of portals and nonportals. We'll test
837 // our intersection point against the portals, in 2d, using
838 // the larger axes of the plane.
839 _PortalRaycastSetHullTest(&(cell->plane_list + cur_plane_index)
840 ->normal);
842 // Here's the point we've hit on the plane.
843 mx_scale_add_vec(&_portal_raycast_contact,
844 &early_point, &ray_vector, PortalRaycastTime);
846 // If one of these portals has our intersection point then
847 // we head into its cell. If not, we've hit a regular
848 // poly and we're done. We can skip the last portal if
849 // there's no nonportals to hit.
850 num_portals_to_test = num_portals_in_plane
851 - plane_is_all_portals;
853 _portal_raycast_cur_vectors = cell->vpool;
855 for (i = 0; i < num_portals_to_test; i++) {
856 _portal_raycast_cur_poly = possible_exit[i].poly;
857 _portal_raycast_point_indirection
858 = cell->vertex_list + possible_exit[i].vertex_offset;
860 if ((*_portal_raycast_hull_test)())
861 break;
864 if (i == num_portals_to_test && !plane_is_all_portals)
865 goto finished_with_collision;
867 cur_cell = possible_exit[i].poly->destination;
872 // We set a few globals before we leave, for anyone who wants to
873 // know more about what we hit and where.
874 finished_with_collision:
875 PortalRaycastResult = FALSE;
876 PortalRaycastCell = cur_cell;
877 mx_scale_add_vec(&PortalRaycastHit, &early_point, &ray_vector,
878 PortalRaycastTime);
880 hit_loc->vec = PortalRaycastHit;
882 finished:
883 return PortalRaycastResult;
887 /* ----- /-/-/-/-/-/-/-/-/ <<< (((((( /\ )))))) >>> \-\-\-\-\-\-\-\-\ ----- *\
889 This is just like the above--literally--except that it leaves a
890 list of the cells it encounters in PortalRaycastRefCount and
891 PortalRaycastRef[], and stops and spews if it reaches
892 RAYCAST_MAX_REFS.
894 \* ----- \-\-\-\-\-\-\-\-\ <<< (((((( \/ )))))) >>> /-/-/-/-/-/-/-/-/ ----- */
895 bool PortalRaycastRefs(Location *start_loc, Location *end_loc,
896 Location *hit_loc, int use_zero_epsilon)
898 int cur_cell;
899 int i;
901 // ray and associated error terms
902 mxs_vector *plane_norm, start_point, end_point;
903 mxs_vector ray_vector, early_point;
904 double time_epsilon, space_epsilon, ray_length, end_time;
906 // used in deciding what surface we've reached in a cell
907 int num_nonportals_in_cell;
908 PortalPolygonCore *cur_poly;
909 PortalPlane *cur_plane;
910 int plane_is_all_portals;
911 int num_portals_in_plane;
912 int num_portals_to_test;
913 _PortalRaycastExit possible_exit[256];
914 PortalRaycastRefCount = 0;
916 #ifndef SHIP
917 if (start_loc->cell == CELL_INVALID && start_loc->hint == CELL_INVALID)
918 Warning(("ref raycast loc w/ no cell, no hint: (%g %g %g)\n",
919 start_loc->vec.x, start_loc->vec.y, start_loc->vec.z));
920 #endif
922 cur_cell = CellFromLoc(start_loc);
924 // set our return value & contact info
925 PortalRaycastResult = TRUE;
926 PortalRaycastPlane = PLANE_INVALID;
928 // check to make sure we're in the database
929 if (cur_cell == CELL_INVALID) {
931 // stuff our globals used for query functions and return FALSE
932 PortalRaycastCell = CELL_INVALID;
933 SpewRaycast (("Started outside.\n"));
934 *hit_loc = *start_loc;
935 PortalRaycastResult = FALSE;
937 goto finished;
941 start_point = start_loc->vec;
942 end_point = end_loc->vec;
944 // We want an error term proportional to the length of the segment
945 // we're casting along. Along the way we find the length of our
946 // ray. We have to account for the fact that we are really casting
947 // along a ray three times the length of the one passed in.
948 mx_sub_vec(&ray_vector, &end_point, &start_point);
949 ray_length = mx_mag_vec(&ray_vector);
951 if (use_zero_epsilon) {
952 time_epsilon = space_epsilon = 0.0;
953 end_time = 1.0;
954 } else {
955 space_epsilon = EXTENDED_TIME_RAYCAST_EPSILON;
956 time_epsilon = space_epsilon / ray_length;
957 end_time = 1.0 - time_epsilon;
960 // Instead of finding our times from the starting point, we'll find
961 // them from T = -2 to try and backface-cull some planes in our
962 // first cell.
963 mx_scale_add_vec(&early_point, &start_point, &ray_vector, -2.0);
965 // So now our ray runs from early_point to end_point.
966 mx_scaleeq_vec(&ray_vector, 3.0);
968 // We advance through the cells until the next plane ahead is at
969 // least as far along the ray as our endpoint.
970 while (1) {
971 double start_dist, end_dist, time;
972 int plane_index, vertex_offset;
973 int cur_plane_index;
974 PortalCell *cell = wr_cell[cur_cell];
976 PortalRaycastRef[PortalRaycastRefCount++] = cur_cell;
978 #ifndef SHIP
979 if (PortalRaycastRefCount == RAYCAST_MAX_REFS) {
980 mprintf("PortalRaycastRefs: ran out of refs!\n");
981 goto finished;
983 #endif // ~SHIP
985 // We want the plane which is in front of us, but closer to our
986 // starting point than the other planes in front of us. We can
987 // backface cull the ones facing away from our ray.
989 PortalRaycastTime = 1000000.0; // big enough for ya?
990 for (plane_index = 0, cur_plane = cell->plane_list;
991 plane_index < cell->num_planes;
992 plane_index++, cur_plane++) {
993 plane_norm = &(cur_plane->normal);
995 start_dist = plane_norm->x * early_point.x
996 + plane_norm->y * early_point.y
997 + plane_norm->z * early_point.z
998 + cur_plane->plane_constant;
1000 if (start_dist > space_epsilon) {
1001 end_dist = plane_norm->x * end_point.x
1002 + plane_norm->y * end_point.y
1003 + plane_norm->z * end_point.z
1004 + cur_plane->plane_constant;
1006 if (end_dist < 0) {
1007 time = start_dist / (start_dist - end_dist);
1008 if (time < PortalRaycastTime) {
1009 PortalRaycastTime = time;
1010 cur_plane_index = plane_index;
1016 // If every plane we didn't backface cull is beyond our
1017 // endpoint, we must have reached the end of our ray without
1018 // colliding with anything. We return TRUE.
1019 if (PortalRaycastTime > end_time) {
1020 PortalRaycastCell = cur_cell;
1021 goto finished;
1024 PortalRaycastPlane = cur_plane_index;
1026 // If we have nonportals in our plane, we need to test our ray
1027 // against all the portals; if we've hit none of them, we've hit
1028 // a nonportal. So this is the bookkeeping to find out what
1029 // we've got in this plane. Pity it's all deriving static info.
1030 cur_poly = cell->poly_list;
1031 num_nonportals_in_cell = cell->num_polys - cell->num_portal_polys;
1032 plane_is_all_portals = 1;
1033 vertex_offset = 0;
1034 for (i = 0; i < num_nonportals_in_cell; i++) {
1035 if (cur_poly->planeid == cur_plane_index)
1036 plane_is_all_portals = 0;
1037 vertex_offset += cur_poly->num_vertices;
1038 cur_poly++;
1041 // We may have more than one portal in the plane we're exiting.
1042 // If so, we'll record which ones they are in an array.
1043 num_portals_in_plane = 0;
1044 for (; i < cell->num_polys; i++) {
1045 if (cur_poly->planeid == cur_plane_index) {
1046 possible_exit[num_portals_in_plane].poly = cur_poly;
1047 possible_exit[num_portals_in_plane].vertex_offset = vertex_offset;
1048 num_portals_in_plane++;
1050 vertex_offset += cur_poly->num_vertices;
1051 cur_poly++;
1055 // If there's no cells we can reach through this plane, here we
1056 // are, and we stop.
1057 if (num_portals_in_plane == 0)
1058 goto finished_with_collision;
1060 // If this plane is nothing but one big portal, we go through it.
1061 if (num_portals_in_plane == 1
1062 && plane_is_all_portals) {
1063 cur_cell = possible_exit[0].poly->destination;
1064 } else {
1065 // Here's the nasty case. A surface with more than one
1066 // portal or a mixture of portals and nonportals. We'll test
1067 // our intersection point against the portals, in 2d, using
1068 // the larger axes of the plane.
1069 _PortalRaycastSetHullTest(&(cell->plane_list + cur_plane_index)
1070 ->normal);
1072 // Here's the point we've hit on the plane.
1073 mx_scale_add_vec(&_portal_raycast_contact,
1074 &early_point, &ray_vector, PortalRaycastTime);
1076 // If one of these portals has our intersection point then
1077 // we head into its cell. If not, we've hit a regular
1078 // poly and we're done. We can skip the last portal if
1079 // there's no nonportals to hit.
1080 num_portals_to_test = num_portals_in_plane
1081 - plane_is_all_portals;
1083 _portal_raycast_cur_vectors = cell->vpool;
1085 for (i = 0; i < num_portals_to_test; i++) {
1086 _portal_raycast_cur_poly = possible_exit[i].poly;
1087 _portal_raycast_point_indirection
1088 = cell->vertex_list + possible_exit[i].vertex_offset;
1090 if ((*_portal_raycast_hull_test)())
1091 break;
1094 if (i == num_portals_to_test && !plane_is_all_portals)
1095 goto finished_with_collision;
1097 cur_cell = possible_exit[i].poly->destination;
1102 // We set a few globals before we leave, for anyone who wants to
1103 // know more about what we hit and where.
1104 finished_with_collision:
1105 PortalRaycastResult = FALSE;
1106 PortalRaycastCell = cur_cell;
1107 mx_scale_add_vec(&PortalRaycastHit, &early_point, &ray_vector, PortalRaycastTime);
1109 hit_loc->vec = PortalRaycastHit;
1111 finished:
1112 return PortalRaycastResult;
1116 /* ----- /-/-/-/-/-/-/-/-/ <<< (((((( /\ )))))) >>> \-\-\-\-\-\-\-\-\ ----- *\
1118 The raycaster only deals with planes and portals. If we hit
1119 something, and we want to know which plane, we call this. A return
1120 value of -1 means that either the most recent raycast had a clear path
1121 or it started outside the world rep. Either way, there's no result we
1122 can sensibly return.
1124 \* ----- \-\-\-\-\-\-\-\-\ <<< (((((( \/ )))))) >>> /-/-/-/-/-/-/-/-/ ----- */
1125 int PortalRaycastFindPolygon(void)
1127 int i;
1128 PortalCell *cell;
1130 if (PortalRaycastResult || PortalRaycastCell == CELL_INVALID)
1131 return -1;
1133 cell = WR_CELL(PortalRaycastCell);
1135 _portal_raycast_point_indirection = cell->vertex_list;
1136 _portal_raycast_cur_vectors = cell->vpool;
1137 _PortalRaycastSetHullTest(&(cell->plane_list + PortalRaycastPlane)
1138 ->normal);
1139 _portal_raycast_contact = PortalRaycastHit;
1141 // We don't test the last poly since we know we hit _something_.
1142 for (i = 0; i < cell->num_render_polys - 1; i++) {
1143 _portal_raycast_cur_poly = &cell->poly_list[i];
1144 if (_portal_raycast_cur_poly->planeid == PortalRaycastPlane
1145 && (*_portal_raycast_hull_test)())
1146 break;
1148 _portal_raycast_point_indirection
1149 += _portal_raycast_cur_poly->num_vertices;
1151 return i;
1155 /* ----- /-/-/-/-/-/-/-/-/ <<< (((((( /\ )))))) >>> \-\-\-\-\-\-\-\-\ ----- *\
1157 This wrapper for testing a point against a polygon insulates the
1158 outside world from the gawdawfulness of it all. It trashes some of
1159 the raycaster's internal globals.
1161 We allow the client to specify not setting the hull test because
1162 it's cheaper if we have many coplanar polygons.
1164 This does not check whether the point is actually in the plane of
1165 the polygon. The test is basically 2D.
1167 \* ----- \-\-\-\-\-\-\-\-\ <<< (((((( \/ )))))) >>> /-/-/-/-/-/-/-/-/ ----- */
1168 bool PortalPointInPolygon(mxs_vector *point, PortalCell *cell,
1169 PortalPolygonCore *polygon,
1170 int vertex_offset, bool set_hull_test)
1172 _portal_raycast_point_indirection = cell->vertex_list + vertex_offset;
1173 _portal_raycast_cur_vectors = cell->vpool;
1174 _portal_raycast_cur_poly = polygon;
1175 _portal_raycast_contact = *point;
1177 if (set_hull_test)
1178 _PortalRaycastSetHullTest(&(cell->plane_list + polygon->planeid)
1179 ->normal);
1181 return (*_portal_raycast_hull_test)();
1186 /* ##################################################################
1188 Here's the original slow, temporary raycaster. We'll retire it
1189 soon.
1193 void compute_raycast_times(int cell, mxs_vector *start, mxs_vector *end,
1194 double *enter, double *exit)
1196 PortalCell *r = WR_CELL(cell);
1197 double start_dist, end_dist;
1198 double enter_time, exit_time;
1199 int i, n;
1201 n = r->num_planes;
1203 // time is on scale of 0..1
1204 enter_time = -1;
1205 exit_time = 2;
1207 for (i=0; i < n; ++i) {
1208 double time;
1210 #define PlaneDist(vec,pln) (mx_dot_vec(vec, &(pln)->normal) + (pln)->plane_constant)
1211 #if 0
1212 start_dist = PlaneDist(start, &r->plane_list[i]);
1213 end_dist = PlaneDist(end , &r->plane_list[i]);
1214 #else
1216 mxs_vector *norm = &r->plane_list[i].normal;
1217 start_dist = norm->x * start->x + norm->y * start->y
1218 + norm->z * start->z + r->plane_list[i].plane_constant;
1219 end_dist = norm->x * end->x + norm->y * end->y
1220 + norm->z * end->z + r->plane_list[i].plane_constant;
1222 #endif
1224 // now, there are icky cases if start_dist ~= 0 or if end_dist ~= 0.
1225 // we assume we fixup start_dist cases by displacing start location
1226 // away from cell boundary.
1228 if (start_dist < -RAYCAST_EPSILON) {
1229 // we're currently outside. if end_dist > RAYCAST_EPSILON, then we end
1230 // inside. otherwise, we never make it inside
1232 if (end_dist > 0) {
1233 time = - start_dist / ((double) end_dist - start_dist);
1234 if (time > enter_time)
1235 enter_time = time;
1236 } else {
1237 *enter = 3;
1238 *exit = 2;
1239 return;
1241 } else if (start_dist > RAYCAST_EPSILON) {
1242 // we're currently inside. end_dist < 0, we make it out
1243 // otherwise, we don't get out, so we ignore it
1245 if (end_dist < 0) {
1246 time = - start_dist / ((double) end_dist - start_dist);
1247 if (time < exit_time)
1248 exit_time = time;
1253 *enter = enter_time;
1254 *exit = exit_time;
1258 #define RAYCAST_SLOW_EPSILON 0.0001
1260 // We mark those cells which we have determined do not intersect our
1261 // ray, so we can avoid revisiting them.
1262 PortalCell *_portal_outside_cell[2000];
1263 int _portal_outside_cell_count = 0;
1266 //////////////////////////////
1268 // Do a raycast from start_loc to end_loc.
1269 // Returns whether the raycast was clear (not blocked).
1270 // If it returns FALSE, the point where it was blocked is stuffed into
1271 // hit_loc.
1273 // portal_block_flags is a bitfield of what kinds of portals block
1274 // this raycast. See wr.h.
1276 bool OldPortalRaycast(Location *start_loc, Location *end_loc,
1277 Location *hit_loc, int portal_block_flags)
1279 #ifndef SHIP
1280 int old_cell = start_loc->cell, old_hint = start_loc->hint;
1281 #endif
1282 int cur_cell = CellFromLoc(start_loc);
1283 int adjacent_cell;
1284 double current_enter, current_exit, epsilon;
1285 int i;
1286 bool clear_path = FALSE;
1288 #ifndef SHIP
1289 if (old_cell==CELL_INVALID)
1290 if (old_hint!=cur_cell)
1291 mprintf("Warning: PortalRaycast called with bad hint\n");
1292 #endif
1294 // check to make sure we're in the database
1295 if (cur_cell == CELL_INVALID)
1297 // stuff our globals used for query functions
1298 PortalRaycastCell = CELL_INVALID;
1299 SpewRaycast (("Started outside.\n"));
1300 *hit_loc = *start_loc;
1301 goto finished;
1305 mxs_vector delta;
1306 mx_sub_vec(&delta, &start_loc->vec, &end_loc->vec);
1307 epsilon = RAYCAST_SLOW_EPSILON / mx_mag_vec(&delta);
1310 compute_raycast_times(cur_cell, &start_loc->vec, &end_loc->vec,
1311 &current_enter, &current_exit);
1313 if (current_enter >= 1.0 - epsilon) {
1314 Warning(("PortalRaycast: Raycast starts outside cell or something.\n"));
1315 *hit_loc = *start_loc;
1316 goto finished;
1319 _portal_outside_cell_count = 0;
1320 for (;;) {
1321 double this_enter, this_exit;
1322 double best_enter, best_exit;
1323 int best_cell = -1, n;
1324 PortalCell *r = WR_CELL(cur_cell);
1326 if (current_exit >= 1.0 - epsilon) {
1327 clear_path = TRUE;
1328 goto finished;
1331 // iterate over all portals out of this cell
1333 best_enter = 2.0;
1335 n = r->num_portal_polys;
1336 for (i=0; i < n; ++i) {
1337 adjacent_cell = r->portal_poly_list[i].destination;
1339 // If we've already been to the chosen cell and decided it
1340 // doesn't touch our ray, we can decide it again much faster.
1341 if (wr_cell[adjacent_cell]->flags & CELL_TRAVERSED)
1342 continue;
1344 compute_raycast_times(adjacent_cell,
1345 &start_loc->vec, &end_loc->vec,
1346 &this_enter, &this_exit);
1348 if (this_enter < this_exit) { // non-empty intersection
1349 if (this_enter >= current_exit - epsilon // next in raycast chain?
1350 && this_exit > current_exit) {
1351 if (this_enter < best_enter) { // first intersection?
1352 best_enter = this_enter;
1353 best_exit = this_exit;
1354 best_cell = r->portal_poly_list[i].destination;
1357 } else {
1358 wr_cell[adjacent_cell]->flags |= CELL_TRAVERSED;
1359 _portal_outside_cell[_portal_outside_cell_count++]
1360 = wr_cell[adjacent_cell];
1364 if (best_enter > current_exit + epsilon) {
1365 if (hit_loc)
1366 mx_interpolate_vec(&hit_loc->vec, &start_loc->vec,
1367 &end_loc->vec, current_exit);
1368 goto finished;
1371 if (best_enter < current_exit - epsilon) {
1372 if (best_exit < current_exit) {
1373 #ifdef RAYCAST_ERROR
1374 Error(1, "PortalRaycast: raycast went backwards; failing.\n");
1375 #else
1376 Warning(("PortalRaycast: raycast went backwards; failing.\n"));
1377 if (hit_loc)
1378 mx_interpolate_vec(&hit_loc->vec, &start_loc->vec,
1379 &end_loc->vec, current_exit);
1380 goto finished;
1381 #endif
1384 Warning(("PortalRaycast: raycast went backwards.\n"));
1387 cur_cell = best_cell;
1388 current_enter = best_enter;
1389 current_exit = best_exit;
1392 finished:
1393 // clear our visitation flags
1394 for (i = 0; i < _portal_outside_cell_count; i++)
1395 _portal_outside_cell[i]->flags &= ~CELL_TRAVERSED;
1396 return clear_path;
1400 /* ##################################################################
1402 The old fast raycaster is still here for reference, but
1403 preprocessed out. Apparently it still has a bug somewhere.
1404 For that matter, later changes have overtaken it. */
1406 //#define INCLUDE_OLD_FAST
1407 #ifdef INCLUDE_OLD_FAST
1409 bool PortalRaycastFast(Location *start_loc, Location *end_loc, Location *hit_loc, int portal_block_flags)
1411 int cur_cell = CellFromLoc(start_loc);
1412 int end_cell = IS_CELLFROMLOC_FAST(end_loc) ? CellFromLoc(end_loc) : -1;
1414 #ifdef DBG_ON
1415 int prev_cell = -1;
1416 #endif
1418 mxs_vector *start; // beginning pos
1419 mxs_vector *end; // ending pos
1420 mxs_vector delta; // unit vector from start to end
1422 mxs_real max_time; // number of delta's to get from start to end
1423 mxs_real time; // time of collision
1425 bool check_entry_time; // need to make sure this is the right cell?
1426 mxs_vector entry_point; // where we enter the new cell
1428 // Set debug/spew flags by hand
1429 if (debug_raycast)
1431 DbgSetDbgSlot (DB_PORTAL, DS_PORTAL_Raycast);
1432 DbgSetMonoSlot (DB_PORTAL, DS_PORTAL_Raycast);
1434 // else
1435 // {
1436 // DbgClearDbgSlot (DB_PORTAL, DS_PORTAL_Raycast);
1437 // DbgClearMonoSlot (DB_PORTAL, DS_PORTAL_Raycast);
1438 // }
1440 // If check_entry_time is true, then we will check every
1441 // plane to see if we entered on that plane. If we did,
1442 // then we will clear check_entry_time to false.
1443 // This is really combining two different booleans (whether
1444 // we should be checking, and the results of that check),
1445 // but it allows us to short circuit the checking, and actually
1446 // makes the code a little shorter.
1447 #define FP(x) ((int) ((x) * 65536.0))
1449 SpewRaycast (("From %g %g %g (%d) to %g %g %g (%d).\n",
1450 start_loc->vec.x, start_loc->vec.y, start_loc->vec.z, cur_cell,
1451 end_loc->vec.x, end_loc->vec.y, end_loc->vec.z, end_cell));
1453 // check to make sure we're in the database
1454 if (cur_cell == CELL_INVALID) {
1455 // stuff our globals used for query functions
1456 PoeralRaycastCell = CELL_INVALID;
1457 SpewRaycast (("Started outside.\n"));
1458 *hit_loc = *start_loc;
1459 return FALSE;
1462 // quick exit if we didn't leave this cell
1464 if (cur_cell == end_cell) {
1465 SpewRaycast (("Started together.\n"));
1466 return TRUE;
1469 start = &start_loc->vec;
1470 end = &end_loc->vec;
1472 // compute length of line
1473 mx_sub_vec(&delta, end, start);
1474 max_time = mx_normeq_vec(&delta); // delta now a unit vector
1476 check_entry_time = FALSE;
1478 end_cell = -1; // disable optimization
1480 for(;;) {
1481 PortalCell *r; // current cell
1482 PortalPlane *p; // plane we're checking
1483 mxs_real inside_dist; // distance from start point to this plane
1484 mxs_real outside_dist; // distance from end point to this plane,
1485 // negative if point is "behind" plane (not in cell)
1487 // if the end point is on a polygon, allow it:
1488 mxs_real best_time = max_time - RAYCAST_EPSILON; // nearest collision
1489 int best_plane = -1; // plane of nearest collision
1490 int i, n;
1492 DBG_Raycast ({
1493 if (check_entry_time)
1494 SpewRaycast (("Tentatively enter cell %d\n", cur_cell));
1495 else
1496 SpewRaycast (("Enter cell %d\n", cur_cell));
1499 r = WR_CELL(cur_cell);
1500 p = r->plane_list;
1501 n = r->num_planes;
1503 // iterate through all the planes
1504 for (i=0; i < n; ++i,++p) {
1505 outside_dist = PlaneDist(end, p);
1507 DBG_Raycast ({
1508 inside_dist = -PlaneDist(start, p) / (outside_dist - PlaneDist(start,p));
1509 SpewRaycast (("%g:%g ", PlaneDist(start,p),
1510 inside_dist));
1513 if (outside_dist < -RAYCAST_EPSILON) { // < 0
1514 // the end point is behind this plane
1516 inside_dist = PlaneDist(start, p);
1518 if (inside_dist > -RAYCAST_EPSILON) { // >= 0
1520 if (inside_dist < RAYCAST_EPSILON)
1521 inside_dist = RAYCAST_EPSILON; // math depends on positive
1523 // the start point is in front of the plane
1524 // compute inside + (outside-inside)*time = 0
1525 time = inside_dist / (inside_dist - outside_dist);
1527 // rescale it into true distance
1529 // OPTIMIZE: Maybe we can do the rescale just once, after we've
1530 // found the smallest value of time? [DS]
1531 time *= max_time;
1533 // if (time > RAYCAST_EPSILON && time < best_time)
1534 if (time < best_time) {
1535 best_time = time;
1536 best_plane = i;
1539 // we don't bother with check entry time,
1540 // since this plane faces the start point
1543 } else { // this plane faces the end point
1544 // BUG: To check entry time, we have to check that the
1545 // _MAXIMAL_ entry time is equal to our expected time...
1547 // the end point is inside the plane, so we can't
1548 // hit it on the way out... but we may need to check
1549 // the entry time
1551 if (check_entry_time) {
1552 time = PlaneDist(&entry_point, p);
1553 if (time < -RAYCAST_EPSILON) // < 0
1555 // entry point is behind all planes in cell, so our
1556 // assumption that we got into this cell was
1557 // wrong... fortunately, we'll have already stuffed
1558 // the collision globals before we got here, since
1559 // we don't know the values any more
1561 SpewRaycast (("Failed to pass through portal coplanar with wall.\n"));
1563 collide_loc = entry_point;
1564 MakeLocationFromVector (hit_loc, &collide_loc);
1565 return FALSE;
1571 // If best_plane == -1, then we didn't find any exit times
1572 // that were sooner than max_time. That means that all of
1573 // the outside_dists were in front of the plane (proof?),
1574 // and thus the end point was inside this cell.
1576 if (best_plane == -1) {
1577 // update the cell cache for the end point
1578 // this will speed up successive raycasts to the same point
1579 SpewRaycast (("We made it!\n"));
1580 end_loc->cell = cur_cell; // HACK: reaching inside a data structure!
1581 return TRUE;
1584 SpewRaycast (("(plane %d) ", best_plane));
1586 // Check that we're really exiting this cell at a cell boundary.
1587 // Call the debug version of ourself if we failed.
1589 mxs_vector pt;
1590 mx_scale_add_vec(&pt, start, &delta, best_time);
1591 if (!PortalPointOnCell(cur_cell, &pt)) {
1592 #ifdef RAYCAST_ERROR
1593 if (!debug_raycast) {
1594 debug_raycast = TRUE;
1595 PortalRaycast(start_loc, end_loc, hit_loc, portal_block_flags);
1597 SpewRaycast(("\ntime was %g; max_time %g; unscaled %g\n", best_time, max_time, best_time/max_time));
1598 Error (1, "PortalRaycast: PortalPointOnCell failed.\n");
1599 #else
1600 Complain ("PortalRaycast: PortalPointOnCell failed!\n");
1601 goto hit_wall;
1602 #endif
1606 // update our collision values in case we collide
1607 PortalRaycastCell = cur_cell;
1608 PortalRaycastPlane = best_plane;
1610 // Now that we know what plane we exit through, we have to look
1611 // at all of the polygons for this plane.
1613 // OPTIMIZE: add a next-with-same-plane link field to PortalPolygonCore,
1614 // and a first-poly-in-this-plane field to PortalPlane (or a parallel array)
1616 // OPTIMIZE: special case if there's only one polygon which lies in this plane
1619 PortalPolygonCore *p = r->poly_list;
1620 // put the plane # back into an unsigned char for fast compare
1621 unsigned char plane = best_plane; // plane we hit
1622 int num_walls; // number of walls in this cell
1623 int new_cell; // cell we're moving into
1624 unsigned char seen_wall = FALSE; // have we examined any walls?
1625 unsigned char seen_portal = FALSE; // have we encountered any portals?
1627 n = r->num_polys;
1628 num_walls = n - r->num_portal_polys;
1630 // now, we need a flag to indicate whether or not
1631 // we've seen a wall or not, and another to indicate
1632 // whether or not we've seen a valid poly.
1634 for (i=0; i < n; ++i,++p) {
1635 if (p->planeid == plane) { // on the plane we hit?
1636 if (i < num_walls)
1637 seen_wall = TRUE;
1638 else if (p->flags & portal_block_flags)
1639 seen_wall = TRUE;
1640 else {
1641 #if 0 // not working yet
1642 if (!seen_portal) {
1643 seen_portal = TRUE;
1644 new_cell = p->destination;
1645 } else {
1646 // we've seen more than one portal, so
1647 // check the last one (new_cell) to see if it was THE one
1649 mxs_vector pt;
1650 mx_scale_add_vec(&pt, start, &delta, best_time);
1652 if (PortalPointOnCell(new_cell, &pt)) {
1653 SpewRaycast (("Went through parallel portal.\n"));
1654 // ok, this was the one!
1655 #ifdef DBG_ON
1656 if (prev_cell == new_cell)
1657 goto infinite_raycast;
1658 prev_cell = cur_cell;
1659 #endif
1660 seen_wall = FALSE; // we don't need to check entry time
1661 seen_portal = TRUE; // a redundant assignment, but slightly clearer
1662 break;
1664 else
1665 SpewRaycast(("Didn't go through parallel portal.\n"));
1666 new_cell = p->destination;
1668 #else
1669 // it's a portal
1670 mxs_vector pt; // OPTIMIZE: Haven't we computed this
1671 // point already? Maybe [DS]
1672 mx_scale_add_vec(&pt, start, &delta, best_time);
1673 if (PortalPointOnCell(p->destination, &pt)) {
1674 new_cell = p->destination;
1675 seen_wall = FALSE; // we don't need to check entry time
1676 seen_portal = TRUE; // a redundant assignment, but slightly clearer
1677 break;
1679 else
1680 SpewRaycast (("(not %d) ", p->destination));
1681 #endif
1686 if (!seen_portal) {
1687 if (!seen_wall) {
1688 #ifdef RAYCAST_ERROR
1689 if (!debug_raycast) {
1690 debug_raycast = TRUE;
1691 PortalRaycast(start_loc, end_loc, hit_loc, portal_block_flags);
1693 Error (1,"PortalRaycast: Didn't hit either a wall or a portal.\n");
1694 #else
1695 Complain (("PortalRaycast: Didn't hit either a wall or a portal!\n"));
1696 #endif
1699 SpewRaycast (("Hit a wall.\n"));
1700 time = best_time;
1701 goto hit_wall;
1704 #ifdef DBG_ON
1705 if (prev_cell == new_cell)
1706 goto infinite_raycast;
1708 prev_cell = cur_cell;
1709 #endif
1711 // update our current cell and go around again
1712 cur_cell = new_cell;
1714 // if there was a wall, we need to check our entry time
1715 check_entry_time = seen_wall;
1716 if (check_entry_time)
1717 mx_scale_add_vec(&entry_point, start, &delta, best_time);
1719 // (the following comment only applies to the #if 0 code)
1721 // note that if there are several coplanar portals and
1722 // no walls, we don't call PortalPointOnCell() for the
1723 // very last portal. If we didn't enter any of the others
1724 // we assume we went through the last one. But we don't
1725 // bother setting check_entry_time (which is only set if
1726 // we saw a wall). So that means we never bother verifying
1727 // that we went through that portal. But that's ok; we must
1728 // have gone through it; there is no other option, except
1729 // for EPSILON failures. But EPSILON failures always favor
1730 // us going through portals, not missing them, so if we didn't
1731 // make it through any of the others, regardless of tolerance,
1732 // then we must have made it through this one.
1735 // if we've truly reached the exit cell, we're done
1736 if (cur_cell == end_cell && !check_entry_time) {
1737 SpewRaycast (("Made it to exit cell %d.\n", end_cell));
1738 return TRUE;
1741 // above is an infinite loop so we don't fall off it
1743 hit_wall: // jump here with time==dist along the line we hit
1745 // collide_loc = start + time*delta
1746 mx_scale_add_vec(&collide_loc, start, &delta, time);
1747 MakeLocationFromVector (hit_loc, &collide_loc);
1748 return FALSE;
1750 #ifdef DBG_ON
1751 infinite_raycast:
1752 // There's either a bug in the code or in the database
1753 // (e.g. a concave cell, or an incorrect connectivity)
1754 Warning(("PortalRaycast: infinite loop: %d-%d\n", prev_cell, cur_cell));
1755 return FALSE;
1756 #endif
1759 #endif
1762 Local Variables:
1763 typedefs:("Location" "PortalCell" "PortalPlane" "PortalPolygonCore" "mxs_real" "mxs_vector")
1764 End: