/**********************************************************************
This file is part of Crack dot Com's free source code release of
Golgotha.
for
information about compiling & licensing issues visit this URL
If that doesn't help, contact Jonathan Clark at
golgotha_source@usa.net (Subject should have "GOLG" in it)
***********************************************************************/
#include "map.hh"
#include "map_man.hh"
#include "map_vert.hh"
#include "map_cell.hh"
#include "div_table.cc"
#include "camera.hh"
#include "r1_api.hh"
#include "tmanage.hh"
#include "r1_clip.hh"
#include "g1_render.hh"
#include "tile.hh"
#include "controller.hh"
#include "statistics.hh"
#include "lisp/li_class.hh"
#include "resources.hh"
#include "time/profile.hh"
#include "light.hh"
static i4_profile_class pf_map_fast("map_fast"),
pf_calc_map_lod("calc_map_lod"),
pf_gather_objects("gather_objects"),
pf_draw_tri("draw_tri");
i4_image_class *render_map_section(int x1, int y1, int x2, int y2, int im_w, int im_h);
inline float fmin(float a, float b) { return (ab)?a:b; }
class texture_context
{
public:
i4_float x1,y1; // offset in the map
i4_float sx, sy; // scale factors to get [0,1] texture coords.
r1_texture_handle texture; // texture to use
};
class lod_node
{
public:
w16 x1,y1,x2,y2; // bounding box for this LOD node
i4_float z1,z2;
i4_float metric; // metric for determining LOD "importance"
lod_node *child[2]; // orthogonal BSP children
w8 texture_context; // which texture context to use or 0 for 1x1
w8 flags;
enum
{
CLIPPED = 1<<0, // this node is completely out of the view
IN_VIEW = 1<<1, // in the view
CLIP_FLAGS = CLIPPED | IN_VIEW,
V_SPLIT = 1<<2, // node split vertically
};
lod_node(w16 x1,w16 y1,w16 x2,w16 y2, i4_float metric=0)
: x1(x1), y1(y1), x2(x2), y2(y2), metric(metric)
{
child[0] = 0;
child[1] = 0;
}
};
class g1_lod_context_class
{
public:
// texture contexts for the large subdivided texture used to cover the whole map
texture_context map_texture[5];
int num_context, last_context;
// LOD tree
lod_node *root;
// breadth first ordering of the lod_nodes
lod_node *queue[150*100*2];
int num_queued, front;
// lod_nodes submitted for drawing
lod_node *quad[150*100];
int num_quads;
// current controller
g1_object_controller_class *cont;
i4_3d_vector pos;
g1_lod_context_class() : root(0) {}
void init_lod()
// make LOD tree
{
if (root)
delete_lod_tree(root);
num_context=0;
make_lod_node(&root,0,0,0,g1_get_map()->width(),g1_get_map()->height());
for (int i=1; i<=num_context; i++)
{
int x1,y1,x2,y2;
x1 = i4_f_to_i(map_texture[i].x1);
y1 = i4_f_to_i(map_texture[i].y1);
x2 = x1 + i4_f_to_i(1.0/map_texture[i].sx + 0.5);
y2 = y1 + i4_f_to_i(1.0/map_texture[i].sy + 0.5);
i4_image_class *im = render_map_section(x1,y1,x2,y2, 256,256);
map_texture[i].texture = g1_render.r_api->get_tmanager()->register_image(im);
delete im;
}
}
void uninit_lod()
{
delete_lod_tree(root);
}
void use_controller(g1_object_controller_class *_cont) { cont = _cont; }
int test_clip(lod_node *p)
// test bounding region of lod_node against the view frustrum
{
const i4_float MAX_FLY_HEIGHT=3.0;
int clip = cont->test_clip(i4_3d_vector(p->x1,p->y1,p->z1),
i4_3d_vector(p->x2,p->y2,p->z2+MAX_FLY_HEIGHT));
w8 flags =
(clip<0)? lod_node::CLIPPED :
(clip>0)? lod_node::IN_VIEW :
0;
p->flags = (p->flags & ~lod_node::CLIP_FLAGS) | flags;
return clip>=0;
}
void make_lod_node(lod_node **pp, lod_node *parent, int x1, int y1, int x2, int y2, int level=0)
{
int dx = x2-x1, dy = y2-y1;
int mx = (x1+x2)/2, my = (y1+y2)/2;
if (dx==0 || dy==0)
return;
lod_node *p;
*pp = p = new lod_node(x1,y1,x2,y2);
// hack to get texture contexts
if (level<2)
p->texture_context = 0;
else if (level==2)
{
p->texture_context = ++num_context;
map_texture[num_context].x1 = x1;
map_texture[num_context].y1 = y1;
map_texture[num_context].sx = 1.0*div_table[x2-x1];
map_texture[num_context].sy = 1.0*div_table[y2-y1];
}
else
p->texture_context = parent->texture_context;
if (dx>1 || dy>1)
{
if (dx>=dy)
{
p->flags |= lod_node::V_SPLIT;
make_lod_node(&p->child[0], p, x1,y1,mx,y2, level+1);
make_lod_node(&p->child[1], p, mx,y1,x2,y2, level+1);
}
else
{
p->flags &= ~lod_node::V_SPLIT;
make_lod_node(&p->child[0], p, x1,y1,x2,my, level+1);
make_lod_node(&p->child[1], p, x1,my,x2,y2, level+1);
}
p->z1 = fmin(p->child[0]->z1, p->child[1]->z1);
p->z2 = fmax(p->child[0]->z2, p->child[1]->z2);
// area metric
p->metric = p->child[0]->metric + p->child[1]->metric;
}
else
{
i4_float
z11 = g1_get_map()->vertex(x1,y1)->get_height(),
z21 = g1_get_map()->vertex(x2,y1)->get_height(),
z12 = g1_get_map()->vertex(x1,y2)->get_height(),
z22 = g1_get_map()->vertex(x2,y2)->get_height();
p->z1 = p->z2 = z11;
p->z1 = fmin(p->z1, z21);
p->z2 = fmax(p->z2, z21);
p->z1 = fmin(p->z1, z12);
p->z2 = fmax(p->z2, z12);
p->z1 = fmin(p->z1, z22);
p->z2 = fmax(p->z2, z22);
// area metric
i4_float dz1,dz2;
dz1 = z22 - z21;
dz2 = z11 - z21;
p->metric = sqrt(dz1*dz1 + dz2*dz2 + 1);
dz1 = z22 - z12;
dz2 = z11 - z12;
p->metric += dz1*dz1 + dz2*dz2 + 1;
}
}
void delete_lod_tree(lod_node *p)
{
if (!p)
return;
delete_lod_tree(p->child[0]);
delete_lod_tree(p->child[1]);
delete p;
}
void calculate_lod(lod_node *p)
{
// cont->view.get_camera_pos(pos);
cont->get_pos(pos);
num_quads=0;
front = num_queued = 0;
queue[num_queued++] = p;
root->flags = 0;
while (frontx1,y1=p->y1,x2=p->x2,y2=p->y2;
w8 sub_flags=p->flags & lod_node::IN_VIEW;
// do some kind of clip test
if (!sub_flags)
if (test_clip(p))
sub_flags=p->flags & lod_node::IN_VIEW;
else
continue;
// int dx = x2-x1, dy = y2-y1;
int mx = (x1+x2)/2, my = (y1+y2)/2;
i4_float fx = pos.x - mx, fy = pos.y - my, fz = pos.z - g1_get_vertex(mx,my)->get_height();
i4_float d1 = fx*fx+fy*fy+fz*fz;
i4_float d2 = p->metric*20;
if (p->child[0] && d1child[0]->flags = (p->child[0]->flags & ~lod_node::CLIP_FLAGS) | sub_flags;
p->child[1]->flags = (p->child[1]->flags & ~lod_node::CLIP_FLAGS) | sub_flags;
queue[num_queued++] = p->child[0];
queue[num_queued++] = p->child[1];
// count & mark t splits
lod_node *q = p->child[0];
if (p->flags & lod_node::V_SPLIT)
{
g1_get_vertex(q->x2,q->y1)->flags ^= g1_map_vertex_class::T_INTERSECTION;
g1_get_vertex(q->x2,q->y2)->flags ^= g1_map_vertex_class::T_INTERSECTION;
}
else
{
g1_get_vertex(q->x1,q->y2)->flags ^= g1_map_vertex_class::T_INTERSECTION;
g1_get_vertex(q->x2,q->y2)->flags ^= g1_map_vertex_class::T_INTERSECTION;
}
}
else
{
g1_map_vertex_class *v11, *v12, *v21, *v22;
v11 = g1_get_vertex(x1,y1);
v21 = g1_get_vertex(x2,y1);
v12 = g1_get_vertex(x1,y2);
v22 = g1_get_vertex(x2,y2);
v11->t_height = v11->get_height();
v21->t_height = v21->get_height();
v12->t_height = v12->get_height();
v22->t_height = v22->get_height();
quad[num_quads++] = p;
}
}
// T intersection fixing
// Map of directions
// dzdx2
// z22* ----> *
// ^ ^
// dzdy1 | | dzdy2
// | |
// z12* ----> *
// z11 dzdx1 z21
for (int test=0; test<2; test++)
for (front = 0; frontx1,y1=quad[front]->y1,
x2=quad[front]->x2,y2=quad[front]->y2;
int i,j;
i4_float z11,z12,z21,z22;
i4_float dzdx1,dzdx2,dzdy1,dzdy2;
z11 = g1_get_vertex(x1,y1)->t_height;
z21 = g1_get_vertex(x2,y1)->t_height;
z12 = g1_get_vertex(x1,y2)->t_height;
z22 = g1_get_vertex(x2,y2)->t_height;
dzdx1 = (z21 - z11)*div_table[x2 - x1];
dzdx2 = (z22 - z12)*div_table[x2 - x1];
dzdy1 = (z12 - z11)*div_table[y2 - y1];
dzdy2 = (z22 - z21)*div_table[y2 - y1];
z22 = z12;
z12 = z11;
i4_float adj1,adj2;
#define DEPIXEL_ADJ 0.1
adj1 = (pos.xx2)?DEPIXEL_ADJ:-DEPIXEL_ADJ;
for (j=y1+1; jflags & g1_map_vertex_class::T_INTERSECTION)
g1_get_vertex(x1,j)->t_height = z11 + adj1;
// if (g1_get_vertex(x2,j)->flags & g1_map_vertex_class::T_INTERSECTION)
g1_get_vertex(x2,j)->t_height = z21 + adj2;
}
adj1 = (pos.yy2)?DEPIXEL_ADJ:-DEPIXEL_ADJ;
for (i=x1+1; iflags & g1_map_vertex_class::T_INTERSECTION)
g1_get_vertex(i,y1)->t_height = z12 + adj1;
// if (g1_get_vertex(i,y2)->flags & g1_map_vertex_class::T_INTERSECTION)
g1_get_vertex(i,y2)->t_height = z22 + adj2;
}
}
}
} g1_lod;
void g1_map_class::init_lod()
{
g1_lod.init_lod();
}
void g1_map_class::calc_map_lod(g1_object_controller_class *cont)
{
pf_calc_map_lod.start();
g1_lod.use_controller(cont);
g1_lod.calculate_lod(g1_lod.root);
pf_calc_map_lod.stop();
}
static r1_texture_ref g1_default_texture("tron_grid");
const w32 g1_max_objs_in_view = 256;
w32 g1_num_objs_in_view = 0;
w32 g1_objs_in_view[g1_max_objs_in_view];
r1_vert temp_buf_1[9];
r1_vert temp_buf_2[9];
i4_array g1_obj_transforms_in_view(0, g1_max_objs_in_view);
class transform_killer_class : public i4_init_class
{
//fix this trey! can't have global i4_array's without something to clean it up.
void uninit() { g1_obj_transforms_in_view.uninit(); }
} transform_killer;
i4_bool g1_draw_tri(r1_vert *points,
w16 a, w16 b, w16 c,
r1_texture_handle texture, w16 clip=1)
//{{{
{
pf_draw_tri.start();
g1_stat_counter.increment(g1_statistics_counter_class::TERRAIN_POLYS);
g1_stat_counter.increment(g1_statistics_counter_class::TOTAL_POLYS);
i4_bool ret = i4_F;
r1_render_api_class *api = g1_render.r_api;
i4_float size=0;
sw32 num_poly_verts = 3;
r1_vert *clipped_poly, *v;
w16 *clipped_refs;
static w16 clipping_refs[16] = { 0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15 };
w16 refs[3];
i4_float near_w=0.0001;
int i,j;
refs[0] = a;
refs[1] = b;
refs[2] = c;
clipped_poly = points;
clipped_refs = &refs[0];
// hacked backface culling (should do this in worldspace before projection...)
i4_3d_vector vt1(clipped_poly[a].v),vt2(clipped_poly[c].v),normal;
vt1 -= clipped_poly[b].v;
vt2 -= clipped_poly[b].v;
normal.cross(vt2,vt1);
if (normal.dot(clipped_poly[b].v)>0)
{
pf_draw_tri.stop();
return i4_F;
}
if ((clipped_poly[a].outcode | clipped_poly[b].outcode | clipped_poly[c].outcode)==0)
{
for (j=0; jw > near_w)
near_w=v->w;
}
}
else
{
clipped_poly = api->clip_poly(&num_poly_verts,
clipped_poly,
clipped_refs,
temp_buf_1,
temp_buf_2,
R1_CLIP_NO_CALC_OUTCODE);
if (!clipped_poly || num_poly_verts<3)
num_poly_verts=0;
if (num_poly_verts)
{
clipped_refs = clipping_refs;
for (j=0; jv.z);
v->px = v->v.x*ooz*g1_render.center_x + g1_render.center_x;
v->py = v->v.y*ooz*g1_render.center_y + g1_render.center_y;
v->w = ooz;
if (v->w > near_w)
near_w=v->w;
}
}
}
if (num_poly_verts)
{
if (texture)
{
sw32 texture_size=i4_f_to_i(g1_render.center_x * near_w * 0.5 * 0.5);
api->use_texture(texture, texture_size, 0);
}
if (g1_render.draw_mode==g1_render_class::WIREFRAME)
{
api->set_constant_color(0x7f7f7f);
api->disable_texture();
api->set_shading_mode(R1_SHADE_DISABLED);
r1_vert v[4];
v[0]=clipped_poly[0];
v[1]=clipped_poly[1];
v[2]=clipped_poly[2];
v[3]=clipped_poly[0];
r1_clip_render_lines(3, v, g1_render.center_x, g1_render.center_y, api);
api->set_shading_mode(R1_WHITE_SHADING);
}
else
api->render_poly(num_poly_verts, clipped_poly, clipped_refs);
ret = i4_T;
}
pf_draw_tri.stop();
return ret;
}
static void gather_objects(int x1, int y1, int x2, int y2)
{
pf_gather_objects.start();
for (int y=y1; yget_obj_list(); o; o=o->next)
{
g1_object_class *obj=o->object;
if (!obj->get_flag(g1_object_class::SCRATCH_BIT))
{
if (g1_num_objs_in_viewglobal_id;
obj->world_transform = g1_obj_transforms_in_view.add();
g1_num_objs_in_view++;
//have the object update his transform
obj->calc_world_transform(g1_render.frame_ratio);
obj->set_flag(g1_object_class::SCRATCH_BIT, 1);
}
}
}
}
}
pf_gather_objects.stop();
}
static i4_transform_class comp_t;
int object_compare(const void *a, const void *b)
{
g1_object_class
*oa = g1_global_id.get(*((w32*)a)),
*ob = g1_global_id.get(*((w32*)b));
// sort objects with alpha last
if (oa->get_type()->get_flag(g1_object_definition_class::HAS_ALPHA))
{
if (!ob->get_type()->get_flag(g1_object_definition_class::HAS_ALPHA))
return -1;
}
else if (ob->get_type()->get_flag(g1_object_definition_class::HAS_ALPHA))
return 1;
i4_float
za = comp_t.x.z*oa->x + comp_t.y.z*oa->y + comp_t.z.z*oa->h + comp_t.t.z,
zb = comp_t.x.z*ob->x + comp_t.y.z*ob->y + comp_t.z.z*ob->h + comp_t.t.z;
if (za>zb)
return 1;
else
return -1;
}
void g1_map_class::fast_draw_cells(g1_draw_context_class *context)
{
pf_map_fast.start();
r1_render_api_class *api = g1_render.r_api;
i4_transform_class t(*context->transform);
i4_3d_vector pos;
// g1_lod.cont->view.get_camera_pos(pos);
pos = g1_lod.pos;
g1_num_objs_in_view = 0;
g1_obj_transforms_in_view.clear();
if (g1_render.draw_mode==g1_render_class::SOLID)
api->use_texture(g1_default_texture.get(), 1, 0);
int i=0,j;
g1_map_vertex_class *vt[4];
g1_map_vertex_class *v=verts;
g1_lod.last_context=0;
for (j=0; jx1, y1 = p->y1,
x2 = p->x2, y2 = p->y2,
clip = !(p->flags & lod_node::IN_VIEW);
sw32 mw_p1=width()+1;
vt[0]=v + x1 + y1 * mw_p1;
vt[1]=v + x2 + y1 * mw_p1;
vt[2]=v + x2 + y2 * mw_p1;
vt[3]=v + x1 + y2 * mw_p1;
vt[0]->transform(t, x1, y1, g1_render.scale_x,g1_render.scale_y);
vt[1]->transform(t, x2, y1, g1_render.scale_x,g1_render.scale_y);
vt[2]->transform(t, x2, y2, g1_render.scale_x,g1_render.scale_y);
vt[3]->transform(t, x1, y2, g1_render.scale_x,g1_render.scale_y);
if (vt[0]->calc_clip_code()==0)
vt[0]->project(g1_render.center_x, g1_render.center_y);
if (vt[1]->calc_clip_code()==0)
vt[1]->project(g1_render.center_x, g1_render.center_y);
if (vt[2]->calc_clip_code()==0)
vt[2]->project(g1_render.center_x, g1_render.center_y);
if (vt[3]->calc_clip_code()==0)
vt[3]->project(g1_render.center_x, g1_render.center_y);
// collect objects
i4_3d_vector mid(i4_float(x2+x1)/2.0,i4_float(y2+y1)/2.0,i4_float(p->z2+p->z1)/2.0);
mid -= pos;
if (mid.dot(mid) < g1_resources.lod_disappear_dist)
gather_objects(x1,y1,x2,y2);
else
i++;
if (x1+1==x2 && y1+1==y2)
{
// single cell
g1_map_cell_class *map_cell=cells + x1 + y1*width();
g1_lod.last_context=0;
r1_texture_handle han = g1_tile_man.get_texture(map_cell->type);
if (han && han!=g1_tile_man.get_pink())
{
static float u[4]={0,1,1,0},v[4]={1,1,0,0};
int uv_on=map_cell->get_rotation();
int uv_dir=map_cell->mirrored() ? 3 : 1;
r1_vert poly[4];
for (int i=0; i<4; i++)
{
vt[i]->set_r1_vert(&poly[i]);
poly[i].s=u[uv_on];
poly[i].t=v[uv_on];
poly[i].a=1.0;
uv_on=(uv_on+uv_dir)&3;
}
vt[0]->get_rgb(poly[0].r, poly[0].g, poly[0].b, x1, y1);
vt[1]->get_rgb(poly[1].r, poly[1].g, poly[1].b, x2, y1);
vt[2]->get_rgb(poly[2].r, poly[2].g, poly[2].b, x2, y2);
vt[3]->get_rgb(poly[3].r, poly[3].g, poly[3].b, x1, y2);
if (post_cell_draw)
post_cell_draw(x1,y1, post_cell_draw_context);
if (g1_draw_tri(poly, 0,1,2, han))
han = 0;
g1_draw_tri(poly, 0,2,3, han);
}
}
else
{
// larger LOD cell
texture_context *tc = &g1_lod.map_texture[p->texture_context];
r1_texture_handle han;
if (g1_lod.last_context==p->texture_context)
han = 0;
else
han = tc->texture;
r1_vert poly[4];
for (int i=0; i<4; i++)
{
vt[i]->set_r1_vert(&poly[i]);
poly[i].a=1.0;
}
vt[0]->get_rgb(poly[0].r, poly[0].g, poly[0].b, x1, y1);
vt[1]->get_rgb(poly[1].r, poly[1].g, poly[1].b, x2, y1);
vt[2]->get_rgb(poly[2].r, poly[2].g, poly[2].b, x2, y2);
vt[3]->get_rgb(poly[3].r, poly[3].g, poly[3].b, x1, y2);
poly[0].s=(x1 - tc->x1)*tc->sx;
poly[0].t=(y1 - tc->y1)*tc->sy;
poly[1].s=(x2 - tc->x1)*tc->sx;
poly[1].t=(y1 - tc->y1)*tc->sy;
poly[2].s=(x2 - tc->x1)*tc->sx;
poly[2].t=(y2 - tc->y1)*tc->sy;
poly[3].s=(x1 - tc->x1)*tc->sx;
poly[3].t=(y2 - tc->y1)*tc->sy;
if (g1_draw_tri(poly, 0,1,2, han))
han = 0;
if (g1_draw_tri(poly, 0,2,3, han))
han = 0;
if (han==0)
g1_lod.last_context = p->texture_context;
}
}
for (i=0; iset_flag(g1_object_class::SCRATCH_BIT, 0);
}
li_class *old_this=li_this;
//draw the objects BACK TO FRONT
comp_t = *context->transform;
qsort(g1_objs_in_view, g1_num_objs_in_view, sizeof(g1_objs_in_view[0]), object_compare);
for (i=g1_num_objs_in_view-1;i>=0;i--)
{
g1_object_class *o=g1_global_id.checked_get(g1_objs_in_view[i]);
if (o)
{
li_this=o->vars;
o->set_flag(g1_object_class::SCRATCH_BIT, 0);
if (o->world_transform!=0)
o->draw(context);
else
i4_warning("null transform");
}
}
if (context->draw_editor_stuff)
{
for (i=g1_num_objs_in_view-1;i>=0;i--)
{
g1_object_class *o=g1_global_id.checked_get(g1_objs_in_view[i]);
if (o)
{
li_this=o->vars;
o->editor_draw(context);
}
}
}
li_this=old_this;
for (j=0; jx1, y1 = p->y1,
x2 = p->x2, y2 = p->y2;
sw32 mw_p1=width()+1;
vt[0]=v + x1 + y1 * mw_p1;
vt[1]=v + x2 + y1 * mw_p1;
vt[2]=v + x2 + y2 * mw_p1;
vt[3]=v + x1 + y2 * mw_p1;
vt[0]->clear_calculations();
vt[1]->clear_calculations();
vt[2]->clear_calculations();
vt[3]->clear_calculations();
}
g1_stat_counter.increment(g1_statistics_counter_class::FRAMES);
pf_map_fast.stop();
}
class num_in_view_reset_class : public g1_global_id_reset_notifier
{
public:
virtual void reset()
{
g1_num_objs_in_view=0;
}
} num_in_view_reset_inst;