#include #include using namespace std; #include "opengl.h" #include "oct.h" #include "view.h" #include "entity.h" enum visibility_state { FULL, PARTIAL, OCCLUDED }; struct evis { entity *e; visibility_state state; double last; // time of last check void clear () { last = 0.; state = FULL; } evis () { clear (); } }; struct oct_visibility : visibility_base { vector vismap; visibility_state state; evis &get_visibility (int i, entity *e) { evis &evs = vismap [i]; if (evs.e != e) { evs.clear (); evs.e = e; } return evs; } oct_visibility (octant &oct) : state(FULL) { } }; octant world(0, sector (0, 0, 0), MAXEXTENT); octant::octant (octant *parent, const sector &orig, uoffs extent) : parent(parent) , orig(orig) , extent(extent) { for (fill = 8; fill--; ) sub[fill] = 0; } visibility_base *octant::new_visibility () { return new oct_visibility (*this); } void octant::clear_visibility (visibility_base *vs) { ((oct_visibility *)vs)->vismap.clear (); ((oct_visibility *)vs)->state = FULL; } octant::~octant () { for (fill = 8; fill--; ) delete sub[fill]; } static bool overlap (const sector &orig, uoffs extent, const sector &a, const sector &b) { sector size = (b - a + 1) >> 1; sector center = a + size; return abs (orig - center) <= extent + size; } static sector offset (const sector &s, int subindex, uoffs extent) { return sector ( s.x + (subindex & 1 ? extent : -extent), s.y + (subindex & 2 ? extent : -extent), s.z + (subindex & 4 ? extent : -extent) ); } void octant::add (entity *e) { const sector &a = e->a; const sector &b = e->b; uoffs size = max (abs (b - a)); if (size >= extent >> 4) { if (overlap (orig, extent, a, b)) { push_back (e); e->o.push_back (this); } } else { uoffs extent2 = extent >> 1; for (int i = 8; i--; ) { sector s = offset (orig, i, extent2); if (overlap (s, extent2, a, b)) { if (!sub[i]) { sub[i] = new octant (this, s, extent2); fill++; } sub[i]->add (e); } } } } void octant::remove (entity *e) { } bool octant::detect_visibility (view &ctx) //, bool fully_visible) { ctx.stat1++;//D sector centeri = orig - ctx.orig; point centerf = point (centeri); GLfloat rad = ctx.diagfact * extent; if (!overlap (ctx.frustum.c, sphere (centerf, rad))) return false; oct_visibility &vs = *(oct_visibility *)get_visibility (ctx); vs.state = FULL;//D #if 0 if (max (abs (centeri)) <= extent) ; else { #endif //if (distance (ctx.frustum.t, centerf) < -rad) return false; //if (distance (ctx.frustum.b, centerf) < -rad) return false; //if (distance (ctx.frustum.l, centerf) < -rad) return false; //if (distance (ctx.frustum.r, centerf) < -rad) return false; ////if (distance (ctx.frustum.n, centerf) < -rad) return false; #if 0 GLfloat fd = distance (ctx.frustum.f, centerf); if (fd < -(ctx.c_far - ctx.z_far) -rad * 3.F) return false; #endif // very important, optimize? GLfloat z = ctx.z_near + distance (ctx.frustum.n, centerf) + rad; if (ctx.perspfact * extent / z < 10.) // very crude "too small to see" check return false; #if 0 if (vs.state == PARTIAL || vs.state == FULL) ctx.nc_far = max (ctx.nc_far, z); #endif if (vs.state == OCCLUDED) { if (extent < ctx.z_far) { ctx.postdepthlist.push_back (this); return false; } else vs.state == PARTIAL; } bool visible = size () && vs.state == FULL; // node to start with unsigned char si = centeri.x > 0 ? 1 : 0 | centeri.y > 0 ? 2 : 0 | centeri.z > 0 ? 4 : 0; // bit-toggle to find next child for front-to-back order static unsigned char toggle[8+1] = { 0, 0^1, 1^2, 2^4, 4^3, 3^5, 5^6, 6^7, 0 }; unsigned char *next = toggle; do { si ^= *next; if (sub[si]) visible = visible | sub[si]->detect_visibility (ctx); } while (*++next); if (visible) { if (size ()) ctx.vislist.push_back (this); ctx.postdepthlist.push_back (this); } else { vs.state = OCCLUDED; ctx.postdepthlist.push_back (this); } return visible; } void octant::draw_depth (view &ctx) { oct_visibility &vs = *(oct_visibility *)get_visibility (ctx); vs.vismap.resize (size ()); if (vs.state == PARTIAL || vs.state == OCCLUDED) return; for (int i = 0; i < size (); ++i) { entity *e = (*this)[i]; const evis &evs = vs.get_visibility (i, e); if (evs.state != OCCLUDED) { if (!ctx.may_draw (e)) continue; sector center = ((e->a + e->b) >> 1) - ctx.orig; GLfloat z = length (vec3 (center)); ctx.pixfact = ctx.perspfact / z; ctx.nz_far = max (ctx.nz_far, z + extent); ctx.nz_near = min (ctx.nz_near, z - extent); e->draw (ctx); } } } void octant::draw_postdepth (view &ctx) { oct_visibility &vs = *(oct_visibility *)get_visibility (ctx); if (vs.state == PARTIAL || vs.state == OCCLUDED) { ctx.begin_occ_query (*this, 0); sector s = orig - ctx.orig; gl::draw_bbox (s - extent, s + extent); ctx.end_occ_query (); } else { int nvis = 0; for (int i = 0; i < size (); ++i) { entity *e = (*this)[i]; const evis &evs = vs.get_visibility (i, e); if (evs.state == OCCLUDED) { if (!ctx.may_draw (e)) continue; ctx.begin_occ_query (*this, e); gl::draw_bbox (e->a - ctx.orig, e->b - ctx.orig); ctx.end_occ_query (); } else nvis++; } if (nvis == 0 && size ()) vs.state = PARTIAL; } } void octant::draw_lighted (view &ctx) { oct_visibility &vs = *(oct_visibility *)get_visibility (ctx); if (vs.state == PARTIAL || vs.state == OCCLUDED) return; #if 0 { static vertex_buffer vb; static index_buffer ib; sector s = orig - ctx.orig; sector a = s - extent, b = s + extent; vertex_v3f vd[] = { point (a.x, a.y, a.z), point (b.x, a.y, a.z), point (a.x, b.y, a.z), point (b.x, b.y, a.z), point (a.x, a.y, b.z), point (b.x, a.y, b.z), point (a.x, b.y, b.z), point (b.x, b.y, b.z), }; if (!ib) { static GLushort verts[4*6] = { 0, 4, 6, 2, // -x 1, 3, 7, 5, // +x 0, 1, 5, 4, // -y 7, 3, 2, 6, // +y 0, 2, 3, 1, // -z 4, 5, 7, 6, // +z }; ib.set (verts, 4*6, GL_STATIC_DRAW_ARB); } vb.set (vd, 8, GL_STREAM_DRAW_ARB); vb.bind (); for (int i = 0; i < 6; i++) ib.draw (GL_LINE_LOOP, i*4, 4); } #endif for (int i = 0; i < size (); ++i) { entity *e = (*this)[i]; evis &evs = vs.get_visibility (i, e); if (evs.state != OCCLUDED) { if (!ctx.may_draw (e)) continue; sector center = ((e->a + e->b) >> 1) - ctx.orig; GLfloat z = length (vec3 (center)); ctx.pixfact = ctx.perspfact / z; if (ctx.pass->type != LIGHTED || !ctx.first_lighted || evs.last + 0.1 > timer.now) e->draw (ctx); else { evs.last = timer.now; ctx.begin_occ_query (*this, e); e->draw (ctx); ctx.end_occ_query (); } } } } void octant::event (occ_query &ev) { oct_visibility &vs = *(oct_visibility *)get_visibility (ev.ctx); entity *e = (entity *)ev.id; if (e) { for (vector::iterator i = vs.vismap.begin (); i != vs.vismap.end (); ++i) if (i->e == e) { i->state = ev.count ? FULL : OCCLUDED; return; } } else vs.state = ev.count ? (vs.state == PARTIAL ? PARTIAL : FULL) : OCCLUDED; }