mirror of
https://github.com/nzp-team/fteqw.git
synced 2024-11-26 13:50:53 +00:00
a97007ae5a
package manager: reworked to enable/disable plugins when downloaded, which can also be present-but-disabled. package manager: display a confirmation prompt before applying changes. do not allow other changes to be made while applying. prompt may be skipped with 'pkg apply' in dedicated servers. sv: downloads are no longer forced to lower case. sv: added sv_demoAutoCompress cvar. set to 1 to directly record to *.mvd.gz cl: properly support directly playing .mvd.gz files menus: reworked to separate mouse and keyboard focus. mouse focus becomes keyboard focus only on mouse clicks. tooltips follow mouse cursors. menus: cleaned up menu heirachy a little. now simpler. server browser: changed 'hide *' filters to 'show *' instead. I felt it was more logical. deluxmapping: changed to disabled, load, generate, like r_loadlit is. render targets api now supports negative formats to mean nearest filtering, where filtering is part of texture state. drawrotpic fixed, now batches and interacts with drawpic correctly. drawline fixed, no interacts with draw* correctly, but still does not batch. fixed saving games. provide proper userinfo to nq clients, where supported. qcc: catch string table overflows safely, giving errors instead of crashes. switch to 32bit statements if some over-sized function requires it. qtv: some bigcoords support tweaks git-svn-id: https://svn.code.sf.net/p/fteqw/code/trunk@5073 fc73d0e0-1445-4013-8a0c-d673dee63da5
7718 lines
214 KiB
C
7718 lines
214 KiB
C
/*
|
|
See gl_terrain.h for terminology, networking notes, etc.
|
|
*/
|
|
|
|
#include "quakedef.h"
|
|
|
|
#ifdef TERRAIN
|
|
#include "glquake.h"
|
|
#include "shader.h"
|
|
|
|
#include "pr_common.h"
|
|
|
|
#include "gl_terrain.h"
|
|
|
|
static terrainfuncs_t terrainfuncs;
|
|
|
|
cvar_t mod_terrain_networked = CVARD("mod_terrain_networked", "0", "Terrain edits are networked. Clients will download sections on demand, and servers will notify clients of changes.");
|
|
cvar_t mod_terrain_defaulttexture = CVARD("mod_terrain_defaulttexture", "", "Newly created terrain tiles will use this texture. This should generally be updated by the terrain editor.");
|
|
cvar_t mod_terrain_savever = CVARD("mod_terrain_savever", "", "Which terrain section version to write if terrain was edited.");
|
|
|
|
enum
|
|
{
|
|
hmcmd_brush_delete,
|
|
hmcmd_brush_insert,
|
|
hmcmd_prespawning, //sent before initial inserts
|
|
hmcmd_prespawned, //sent just after initial inserts
|
|
|
|
hmcmd_ent_edit = 0x40,
|
|
hmcmd_ent_remove
|
|
};
|
|
|
|
|
|
void validatelinks(link_t *firstnode)
|
|
{
|
|
/* link_t *node;
|
|
|
|
COM_AssertMainThread("foo");
|
|
|
|
for (node = firstnode->next; node; node = node->next)
|
|
if (firstnode == node)
|
|
break;
|
|
|
|
for (node = firstnode->prev; node; node = node->prev)
|
|
if (firstnode == node)
|
|
break;
|
|
|
|
return;*/
|
|
}
|
|
|
|
void validatelinks2(link_t *firstnode, link_t *panic)
|
|
{
|
|
/* link_t *node;
|
|
|
|
COM_AssertMainThread("foo");
|
|
|
|
for (node = firstnode->next; node; node = node->next)
|
|
{
|
|
if (node == panic)
|
|
Sys_Error("Panic\n");
|
|
if (firstnode == node)
|
|
break;
|
|
}
|
|
|
|
for (node = firstnode->prev; node; node = node->prev)
|
|
{
|
|
if (node == panic)
|
|
Sys_Error("Panic\n");
|
|
if (firstnode == node)
|
|
break;
|
|
}
|
|
|
|
return;*/
|
|
}
|
|
|
|
//FIXME
|
|
int Surf_NewLightmaps(int count, int width, int height, qboolean deluxe);
|
|
|
|
#ifndef SERVERONLY
|
|
static void ted_dorelight(heightmap_t *hm);
|
|
static void Terr_WorkerLoadedSectionLightmap(void *ctx, void *data, size_t a, size_t b);
|
|
static qboolean Terr_Collect(heightmap_t *hm);
|
|
#endif
|
|
static hmsection_t *QDECL Terr_GetSection(heightmap_t *hm, int x, int y, unsigned int flags);
|
|
static void Terr_LoadSectionWorker(void *ctx, void *data, size_t a, size_t b);
|
|
static void Terr_WorkerLoadedSection(void *ctx, void *data, size_t a, size_t b);
|
|
static void Terr_WorkerFailedSection(void *ctx, void *data, size_t a, size_t b);
|
|
|
|
static void Terr_Brush_DeleteIdx(heightmap_t *hm, size_t idx);
|
|
#ifndef SERVERONLY
|
|
static void Terr_Brush_Draw(heightmap_t *hm, batch_t **batches, entity_t *e);
|
|
#endif
|
|
|
|
#ifndef SERVERONLY
|
|
static texid_t Terr_LoadTexture(char *name)
|
|
{
|
|
extern texid_t missing_texture;
|
|
texid_t id;
|
|
if (*name)
|
|
{
|
|
id = R_LoadHiResTexture(name, NULL, 0);
|
|
if (!TEXVALID(id))
|
|
{
|
|
id = missing_texture;
|
|
Con_Printf("Unable to load texture %s\n", name);
|
|
}
|
|
}
|
|
else
|
|
id = missing_texture;
|
|
return id;
|
|
}
|
|
#endif
|
|
|
|
static void QDECL Terr_LoadSectionTextures(hmsection_t *s)
|
|
{
|
|
#ifndef SERVERONLY
|
|
extern texid_t missing_texture;
|
|
struct hmwater_s *w;
|
|
if (isDedicated)
|
|
return;
|
|
//CL_CheckOrEnqueDownloadFile(s->texname[0], NULL, 0);
|
|
//CL_CheckOrEnqueDownloadFile(s->texname[1], NULL, 0);
|
|
//CL_CheckOrEnqueDownloadFile(s->texname[2], NULL, 0);
|
|
//CL_CheckOrEnqueDownloadFile(s->texname[3], NULL, 0);
|
|
switch(s->hmmod->mode)
|
|
{
|
|
case HMM_BLOCKS:
|
|
s->textures.base = Terr_LoadTexture(va("maps/%s/atlas.tga", s->hmmod->path));
|
|
s->textures.fullbright = Terr_LoadTexture(va("maps/%s/atlas_luma.tga", s->hmmod->path));
|
|
s->textures.bump = Terr_LoadTexture(va("maps/%s/atlas_norm.tga", s->hmmod->path));
|
|
s->textures.specular = Terr_LoadTexture(va("maps/%s/atlas_spec.tga", s->hmmod->path));
|
|
s->textures.upperoverlay = missing_texture;
|
|
s->textures.loweroverlay = missing_texture;
|
|
break;
|
|
case HMM_TERRAIN:
|
|
s->textures.base = Terr_LoadTexture(s->texname[0]);
|
|
s->textures.upperoverlay = Terr_LoadTexture(s->texname[1]);
|
|
s->textures.loweroverlay = Terr_LoadTexture(s->texname[2]);
|
|
s->textures.fullbright = Terr_LoadTexture(s->texname[3]);
|
|
s->textures.bump = *s->texname[0]?R_LoadHiResTexture(va("%s_norm", s->texname[0]), NULL, 0):r_nulltex;
|
|
s->textures.specular = *s->texname[0]?R_LoadHiResTexture(va("%s_spec", s->texname[0]), NULL, 0):r_nulltex;
|
|
break;
|
|
}
|
|
for (w = s->water; w; w = w->next)
|
|
{
|
|
w->shader = R_RegisterCustom (w->shadername, SUF_NONE, Shader_DefaultWaterShader, NULL);
|
|
R_BuildDefaultTexnums(NULL, w->shader); //this might get expensive. hideously so.
|
|
}
|
|
#endif
|
|
}
|
|
|
|
static qboolean QDECL Terr_InitLightmap(hmsection_t *s, qboolean initialise)
|
|
{
|
|
#ifdef SERVERONLY
|
|
return false;
|
|
#else
|
|
heightmap_t *hm = s->hmmod;
|
|
|
|
if (s->lightmap < 0)
|
|
{
|
|
struct lmsect_s *lms;
|
|
if (!hm->unusedlmsects)
|
|
{
|
|
int lm;
|
|
int i;
|
|
lm = Surf_NewLightmaps(1, SECTTEXSIZE*LMCHUNKS, SECTTEXSIZE*LMCHUNKS, false);
|
|
for (i = 0; i < LMCHUNKS*LMCHUNKS; i++)
|
|
{
|
|
lms = BZ_Malloc(sizeof(*lms));
|
|
lms->lm = lm;
|
|
lms->x = (i & (LMCHUNKS-1))*SECTTEXSIZE;
|
|
lms->y = (i / LMCHUNKS)*SECTTEXSIZE;
|
|
lms->next = hm->unusedlmsects;
|
|
hm->unusedlmsects = lms;
|
|
hm->numunusedlmsects++;
|
|
}
|
|
}
|
|
|
|
lms = hm->unusedlmsects;
|
|
hm->unusedlmsects = lms->next;
|
|
|
|
s->lightmap = lms->lm;
|
|
s->lmx = lms->x;
|
|
s->lmy = lms->y;
|
|
|
|
hm->numunusedlmsects--;
|
|
hm->numusedlmsects++;
|
|
|
|
Z_Free(lms);
|
|
initialise = true;
|
|
}
|
|
|
|
if (initialise && s->lightmap >= 0)
|
|
{
|
|
int x, y;
|
|
unsigned char *lm;
|
|
lm = lightmap[s->lightmap]->lightmaps;
|
|
lm += (s->lmy * HMLMSTRIDE + s->lmx) * lightmap_bytes;
|
|
for (y = 0; y < SECTTEXSIZE; y++)
|
|
{
|
|
for (x = 0; x < SECTTEXSIZE; x++)
|
|
{
|
|
lm[x*4+0] = 0;
|
|
lm[x*4+1] = 0;
|
|
lm[x*4+2] = 0;
|
|
lm[x*4+3] = 255;
|
|
}
|
|
lm += (HMLMSTRIDE)*lightmap_bytes;
|
|
}
|
|
}
|
|
|
|
if (s->lightmap >= 0)
|
|
{
|
|
lightmap[s->lightmap]->modified = true;
|
|
lightmap[s->lightmap]->rectchange.l = 0;
|
|
lightmap[s->lightmap]->rectchange.t = 0;
|
|
lightmap[s->lightmap]->rectchange.r = HMLMSTRIDE;
|
|
lightmap[s->lightmap]->rectchange.b = HMLMSTRIDE;
|
|
}
|
|
|
|
return s->lightmap>=0;
|
|
#endif
|
|
}
|
|
|
|
static char *genextendedhex(int n, char *buf)
|
|
{
|
|
char *ret;
|
|
static char nibble[16] = "0123456789abcdef";
|
|
unsigned int m;
|
|
int i;
|
|
for (i = 7; i >= 1; i--) //>=1 ensures at least two nibbles appear.
|
|
{
|
|
m = 0xfffffff8<<(i*4);
|
|
if ((n & m) != m && (n & m) != 0)
|
|
break;
|
|
}
|
|
ret = buf;
|
|
for(i++; i >= 0; i--)
|
|
*buf++ = nibble[(n>>i*4) & 0xf];
|
|
*buf++ = 0;
|
|
return ret;
|
|
}
|
|
static char *Terr_DiskBlockName(heightmap_t *hm, int sx, int sy, char *out, size_t outsize)
|
|
{
|
|
char xpart[9];
|
|
char ypart[9];
|
|
//using a naming scheme centered around 0 means we can gracefully expand the map away from 0,0
|
|
sx -= CHUNKBIAS;
|
|
sy -= CHUNKBIAS;
|
|
//wrap cleanly
|
|
sx &= CHUNKLIMIT-1;
|
|
sy &= CHUNKLIMIT-1;
|
|
sx /= SECTIONSPERBLOCK;
|
|
sy /= SECTIONSPERBLOCK;
|
|
if (sx >= CHUNKBIAS/SECTIONSPERBLOCK)
|
|
sx |= 0xffffff00;
|
|
if (sy >= CHUNKBIAS/SECTIONSPERBLOCK)
|
|
sy |= 0xffffff00;
|
|
Q_snprintfz(out, outsize, "maps/%s/block_%s_%s.hms", hm->path, genextendedhex(sx, xpart), genextendedhex(sy, ypart));
|
|
return out;
|
|
}
|
|
static char *Terr_DiskSectionName(heightmap_t *hm, int sx, int sy, char *out, size_t outsize)
|
|
{
|
|
sx -= CHUNKBIAS;
|
|
sy -= CHUNKBIAS;
|
|
//wrap cleanly
|
|
sx &= CHUNKLIMIT-1;
|
|
sy &= CHUNKLIMIT-1;
|
|
Q_snprintfz(out, outsize, "maps/%s/sect_%03x_%03x.hms", hm->path, sx, sy);
|
|
return out;
|
|
}
|
|
#ifndef SERVERONLY
|
|
static char *Terr_TempDiskSectionName(heightmap_t *hm, int sx, int sy)
|
|
{
|
|
sx -= CHUNKBIAS;
|
|
sy -= CHUNKBIAS;
|
|
//wrap cleanly
|
|
sx &= CHUNKLIMIT-1;
|
|
sy &= CHUNKLIMIT-1;
|
|
return va("temp/%s/sect_%03x_%03x.hms", hm->path, sx, sy);
|
|
}
|
|
#endif
|
|
|
|
static int dehex_e(int i, qboolean *error)
|
|
{
|
|
if (i >= '0' && i <= '9')
|
|
return (i-'0');
|
|
else if (i >= 'A' && i <= 'F')
|
|
return (i-'A'+10);
|
|
else if (i >= 'a' && i <= 'f')
|
|
return (i-'a'+10);
|
|
else
|
|
*error = true;
|
|
return 0;
|
|
}
|
|
static qboolean Terr_IsSectionFName(heightmap_t *hm, const char *fname, int *sx, int *sy)
|
|
{
|
|
int l;
|
|
qboolean error = false;
|
|
*sx = 0xdeafbeef; //something clearly invalid
|
|
*sy = 0xdeafbeef;
|
|
|
|
//not this model...
|
|
if (!hm)
|
|
return false;
|
|
|
|
//expect the first 5 chars to be maps/ or temp/
|
|
fname += 5;
|
|
|
|
l = strlen(hm->path);
|
|
if (strncmp(fname, hm->path, l) || fname[l] != '/')
|
|
return false;
|
|
fname += l+1;
|
|
|
|
//fname now has a fixed length.
|
|
if (strlen(fname) != 16)
|
|
return false;
|
|
if (strncmp(fname, "sect_", 5) || fname[8] != '_' || (strcmp(fname+12, ".hms") && strcmp(fname+12, ".tmp")))
|
|
return false;
|
|
|
|
*sx = 0;
|
|
*sx += dehex_e(fname[5], &error)<<8;
|
|
*sx += dehex_e(fname[6], &error)<<4;
|
|
*sx += dehex_e(fname[7], &error)<<0;
|
|
|
|
*sy = 0;
|
|
*sy += dehex_e(fname[9], &error)<<8;
|
|
*sy += dehex_e(fname[10], &error)<<4;
|
|
*sy += dehex_e(fname[11], &error)<<0;
|
|
|
|
*sx += CHUNKBIAS;
|
|
*sy += CHUNKBIAS;
|
|
|
|
if ((unsigned)*sx >= CHUNKLIMIT)
|
|
*sx -= CHUNKLIMIT;
|
|
if ((unsigned)*sy >= CHUNKLIMIT)
|
|
*sy -= CHUNKLIMIT;
|
|
|
|
//make sure its a valid section index.
|
|
if ((unsigned)*sx >= CHUNKLIMIT)
|
|
return false;
|
|
if ((unsigned)*sy >= CHUNKLIMIT)
|
|
return false;
|
|
return true;
|
|
}
|
|
|
|
static int QDECL Terr_GenerateSections(heightmap_t *hm, int sx, int sy, int count, hmsection_t **sects)
|
|
{
|
|
//a worker is trying to load multiple sections at once.
|
|
//lock ALL of them atomically, so that we don't end up with too many workers all doing stuff at once.
|
|
int x, y;
|
|
|
|
hmsection_t *s;
|
|
hmcluster_t *cluster;
|
|
int numgen = 0;
|
|
|
|
Sys_LockMutex(com_resourcemutex);
|
|
for (y = 0; y < count; y++)
|
|
for (x = 0; x < count; x++)
|
|
{
|
|
int clusternum = ((sx+x)/MAXSECTIONS) + ((sy+y)/MAXSECTIONS)*MAXCLUSTERS;
|
|
cluster = hm->cluster[clusternum];
|
|
if (!cluster)
|
|
cluster = hm->cluster[clusternum] = Z_Malloc(sizeof(*cluster));
|
|
s = cluster->section[((sx+x)%MAXSECTIONS) + ((sy+y)%MAXSECTIONS)*MAXSECTIONS];
|
|
if (!s)
|
|
{
|
|
s = Z_Malloc(sizeof(*s));
|
|
s->loadstate = TSLS_LOADING0;
|
|
#ifndef SERVERONLY
|
|
s->lightmap = -1;
|
|
#endif
|
|
s->numents = 0;
|
|
s->sx = sx + x;
|
|
s->sy = sy + y;
|
|
cluster->section[(s->sx%MAXSECTIONS) + (s->sy%MAXSECTIONS)*MAXSECTIONS] = s;
|
|
hm->activesections++;
|
|
s->hmmod = hm;
|
|
|
|
s->flags = TSF_DIRTY;
|
|
|
|
hm->loadingsections+=1;
|
|
}
|
|
#ifndef SERVERONLY
|
|
else if (s->loadstate == TSLS_LOADED && s->lightmap < 0)
|
|
; //it lost its lightmap. the main thread won't be drawing with it, nor do any loaders.
|
|
//FIXME: might be used by tracelines on a worker (eg lightmap generation)
|
|
#endif
|
|
else if (s->loadstate != TSLS_LOADING0)
|
|
{
|
|
//this one is already active.
|
|
sects[x + y*count] = NULL;
|
|
continue;
|
|
}
|
|
|
|
s->loadstate = TSLS_LOADING1;
|
|
|
|
sects[x + y*count] = s;
|
|
numgen++;
|
|
}
|
|
Sys_UnlockMutex(com_resourcemutex);
|
|
return numgen;
|
|
}
|
|
static hmsection_t *QDECL Terr_GenerateSection(heightmap_t *hm, int sx, int sy, qboolean scheduleload)
|
|
{
|
|
hmsection_t *s;
|
|
hmcluster_t *cluster;
|
|
int clusternum = (sx/MAXSECTIONS) + (sy/MAXSECTIONS)*MAXCLUSTERS;
|
|
|
|
#ifdef LOADERTHREAD
|
|
Sys_LockMutex(com_resourcemutex);
|
|
#endif
|
|
cluster = hm->cluster[clusternum];
|
|
if (!cluster)
|
|
cluster = hm->cluster[clusternum] = Z_Malloc(sizeof(*cluster));
|
|
s = cluster->section[(sx%MAXSECTIONS) + (sy%MAXSECTIONS)*MAXSECTIONS];
|
|
if (!s)
|
|
{
|
|
s = Z_Malloc(sizeof(*s));
|
|
if (!s)
|
|
{
|
|
#ifdef LOADERTHREAD
|
|
Sys_UnlockMutex(com_resourcemutex);
|
|
#endif
|
|
return NULL;
|
|
}
|
|
#ifndef SERVERONLY
|
|
s->lightmap = -1;
|
|
#endif
|
|
|
|
#ifndef SERVERONLY
|
|
s->numents = 0;
|
|
#endif
|
|
|
|
s->sx = sx;
|
|
s->sy = sy;
|
|
cluster->section[(sx%MAXSECTIONS) + (sy%MAXSECTIONS)*MAXSECTIONS] = s;
|
|
hm->activesections++;
|
|
s->hmmod = hm;
|
|
|
|
s->flags = TSF_DIRTY;
|
|
|
|
hm->loadingsections+=1;
|
|
|
|
if (!scheduleload)
|
|
{ //no scheduling means that we're loading it NOW, on this thread.
|
|
s->loadstate = TSLS_LOADING1;
|
|
#ifdef LOADERTHREAD
|
|
Sys_UnlockMutex(com_resourcemutex);
|
|
#endif
|
|
return s;
|
|
}
|
|
s->loadstate = TSLS_LOADING0;
|
|
|
|
#ifdef LOADERTHREAD
|
|
Sys_UnlockMutex(com_resourcemutex);
|
|
#endif
|
|
COM_AddWork(WG_LOADER, Terr_LoadSectionWorker, s, hm, sx, sy);
|
|
return s;
|
|
}
|
|
if (!scheduleload)
|
|
{
|
|
if (s->loadstate == TSLS_LOADING0)
|
|
s->loadstate = TSLS_LOADING1;
|
|
else
|
|
s = NULL;
|
|
}
|
|
#ifdef LOADERTHREAD
|
|
Sys_UnlockMutex(com_resourcemutex);
|
|
#endif
|
|
return s;
|
|
}
|
|
|
|
//generates some water
|
|
static void *QDECL Terr_GenerateWater(hmsection_t *s, float maxheight)
|
|
{
|
|
int i;
|
|
struct hmwater_s *w;
|
|
w = Z_Malloc(sizeof(*s->water));
|
|
w->next = s->water;
|
|
s->water = w;
|
|
Q_strncpyz(w->shadername, s->hmmod->defaultwatershader, sizeof(w->shadername));
|
|
w->simple = true;
|
|
w->contentmask = FTECONTENTS_WATER;
|
|
memset(w->holes, 0, sizeof(w->holes));
|
|
for (i = 0; i < 9*9; i++)
|
|
w->heights[i] = maxheight;
|
|
w->maxheight = w->minheight = maxheight;
|
|
if (s->maxh_cull < w->maxheight)
|
|
s->maxh_cull = w->maxheight;
|
|
return w;
|
|
}
|
|
|
|
//embeds a mesh
|
|
static void QDECL Terr_AddMesh(heightmap_t *hm, int loadflags, model_t *mod, const char *modelname, vec3_t epos, vec3_t axis[3], float scale)
|
|
{
|
|
#ifndef SERVERONLY
|
|
struct hmentity_s *e, *f = NULL;
|
|
hmsection_t *s;
|
|
int min[2], max[2], coord[2];
|
|
int i;
|
|
|
|
if (!mod)
|
|
{
|
|
if (modelname)
|
|
mod = Mod_ForName(modelname, MLV_WARN);
|
|
if (!mod)
|
|
return;
|
|
}
|
|
|
|
if (!scale)
|
|
scale = 1;
|
|
|
|
if (mod->loadstate != MLS_LOADED)
|
|
Con_DPrintf("Terr_AddMesh: model is not loaded yet\n");
|
|
|
|
//I do NOT like that this depends on the size of the model.
|
|
if (axis[0][0] != 1 || axis[0][1] != 0 || axis[0][2] != 0 ||
|
|
axis[1][0] != 0 || axis[1][1] != 1 || axis[1][2] != 0 ||
|
|
axis[2][0] != 0 || axis[2][1] != 0 || axis[2][2] != 1)
|
|
{
|
|
min[0] = floor((epos[0]-mod->radius*scale) / hm->sectionsize) + CHUNKBIAS;
|
|
min[1] = floor((epos[1]-mod->radius*scale) / hm->sectionsize) + CHUNKBIAS;
|
|
min[0] = bound(hm->firstsegx, min[0], hm->maxsegx-1);
|
|
min[1] = bound(hm->firstsegy, min[1], hm->maxsegy-1);
|
|
|
|
max[0] = floor((epos[0]+mod->radius*scale) / hm->sectionsize) + CHUNKBIAS;
|
|
max[1] = floor((epos[1]+mod->radius*scale) / hm->sectionsize) + CHUNKBIAS;
|
|
max[0] = bound(hm->firstsegx, max[0], hm->maxsegx-1);
|
|
max[1] = bound(hm->firstsegy, max[1], hm->maxsegy-1);
|
|
}
|
|
else
|
|
{
|
|
min[0] = floor((epos[0]+mod->mins[0]*scale) / hm->sectionsize) + CHUNKBIAS;
|
|
min[1] = floor((epos[1]+mod->mins[1]*scale) / hm->sectionsize) + CHUNKBIAS;
|
|
min[0] = bound(hm->firstsegx, min[0], hm->maxsegx-1);
|
|
min[1] = bound(hm->firstsegy, min[1], hm->maxsegy-1);
|
|
|
|
max[0] = floor((epos[0]+mod->maxs[0]*scale) / hm->sectionsize) + CHUNKBIAS;
|
|
max[1] = floor((epos[1]+mod->maxs[1]*scale) / hm->sectionsize) + CHUNKBIAS;
|
|
max[0] = bound(hm->firstsegx, max[0], hm->maxsegx-1);
|
|
max[1] = bound(hm->firstsegy, max[1], hm->maxsegy-1);
|
|
}
|
|
|
|
Sys_LockMutex(hm->entitylock);
|
|
//try to find the ent if it already exists (don't do dupes)
|
|
for (e = hm->entities; e; e = e->next)
|
|
{
|
|
if (!e->refs)
|
|
f = e;
|
|
else
|
|
{
|
|
if (e->ent.origin[0] != epos[0] || e->ent.origin[1] != epos[1] || e->ent.origin[2] != epos[2])
|
|
continue;
|
|
if (e->ent.model != mod || e->ent.scale != scale)
|
|
continue;
|
|
if (memcmp(axis, e->ent.axis, sizeof(e->ent.axis)))
|
|
continue;
|
|
break; //looks like a match.
|
|
}
|
|
}
|
|
//allocate it if needed
|
|
if (!e)
|
|
{
|
|
if (f)
|
|
e = f; //can reuse a released one
|
|
else
|
|
{ //allocate one
|
|
e = Z_Malloc(sizeof(*e));
|
|
e->next = hm->entities;
|
|
hm->entities = e;
|
|
}
|
|
|
|
e->ent.drawflags = SCALE_ORIGIN_ORIGIN;
|
|
e->ent.scale = scale;
|
|
e->ent.playerindex = -1;
|
|
e->ent.framestate.g[FS_REG].lerpweight[0] = 1;
|
|
e->ent.topcolour = TOP_DEFAULT;
|
|
e->ent.bottomcolour = BOTTOM_DEFAULT;
|
|
e->ent.shaderRGBAf[0] = 1;
|
|
e->ent.shaderRGBAf[1] = 1;
|
|
e->ent.shaderRGBAf[2] = 1;
|
|
e->ent.shaderRGBAf[3] = 1;
|
|
VectorCopy(epos, e->ent.origin);
|
|
memcpy(e->ent.axis, axis, sizeof(e->ent.axis));
|
|
e->ent.model = mod;
|
|
}
|
|
|
|
for (coord[0] = min[0]; coord[0] <= max[0]; coord[0]++)
|
|
{
|
|
for (coord[1] = min[1]; coord[1] <= max[1]; coord[1]++)
|
|
{
|
|
s = Terr_GetSection(hm, coord[0], coord[1], loadflags|TGS_ANYSTATE);
|
|
if (!s)
|
|
continue;
|
|
|
|
//don't add pointless dupes
|
|
for (i = 0; i < s->numents; i++)
|
|
{
|
|
if (s->ents[i] == e)
|
|
break;
|
|
}
|
|
if (i < s->numents)
|
|
continue;
|
|
|
|
//FIXME: while technically correct, this causes issues with the v1 format.
|
|
s->flags |= TSF_EDITED;
|
|
|
|
//FIXME: race condition - main thread might be walking the entity list.
|
|
//FIXME: even worse: the editor might be running through this routine adding/removing entities at the same time as the loader.
|
|
if (s->maxents == s->numents)
|
|
{
|
|
s->maxents++;
|
|
s->ents = realloc(s->ents, sizeof(*s->ents)*(s->maxents));
|
|
}
|
|
s->ents[s->numents++] = e;
|
|
e->refs++;
|
|
}
|
|
}
|
|
Sys_UnlockMutex(hm->entitylock);
|
|
#endif
|
|
}
|
|
|
|
static void *Terr_ReadV1(heightmap_t *hm, hmsection_t *s, void *ptr, int len)
|
|
{
|
|
#ifndef SERVERONLY
|
|
dsmesh_v1_t *dm;
|
|
float *colours;
|
|
qbyte *lmstart;
|
|
#endif
|
|
dsection_v1_t *ds = ptr;
|
|
int i;
|
|
|
|
unsigned int flags = LittleLong(ds->flags);
|
|
s->flags |= flags & ~(TSF_INTERNAL|TSF_HASWATER_V0);
|
|
for (i = 0; i < SECTHEIGHTSIZE*SECTHEIGHTSIZE; i++)
|
|
{
|
|
s->heights[i] = LittleFloat(ds->heights[i]);
|
|
}
|
|
s->minh = ds->minh;
|
|
s->maxh = ds->maxh;
|
|
if (flags & TSF_HASWATER_V0)
|
|
Terr_GenerateWater(s, ds->waterheight);
|
|
|
|
memset(s->holes, 0, sizeof(s->holes));
|
|
for (i = 0; i < 8*8; i++)
|
|
{
|
|
int x = (i & 7);
|
|
int y = (i>>3);
|
|
int b = (1u<<(x>>1)) << ((y>>1)<<2);
|
|
if (ds->holes & b)
|
|
s->holes[y] |= 1u<<x;
|
|
}
|
|
|
|
ptr = ds+1;
|
|
|
|
#ifndef SERVERONLY
|
|
/*deal with textures*/
|
|
Q_strncpyz(s->texname[0], ds->texname[0], sizeof(s->texname[0]));
|
|
Q_strncpyz(s->texname[1], ds->texname[1], sizeof(s->texname[1]));
|
|
Q_strncpyz(s->texname[2], ds->texname[2], sizeof(s->texname[2]));
|
|
Q_strncpyz(s->texname[3], ds->texname[3], sizeof(s->texname[3]));
|
|
|
|
/*load in the mixture/lighting*/
|
|
lmstart = BZ_Malloc(SECTTEXSIZE*SECTTEXSIZE*4);
|
|
memcpy(lmstart, ds->texmap, SECTTEXSIZE*SECTTEXSIZE*4);
|
|
COM_AddWork(WG_MAIN, Terr_WorkerLoadedSectionLightmap, hm, lmstart, s->sx, s->sy);
|
|
|
|
s->mesh.colors4f_array[0] = s->colours;
|
|
if (flags & TSF_HASCOLOURS)
|
|
{
|
|
for (i = 0, colours = (float*)ptr; i < SECTHEIGHTSIZE*SECTHEIGHTSIZE; i++, colours+=4)
|
|
{
|
|
s->colours[i][0] = LittleFloat(colours[0]);
|
|
s->colours[i][1] = LittleFloat(colours[1]);
|
|
s->colours[i][2] = LittleFloat(colours[2]);
|
|
s->colours[i][3] = LittleFloat(colours[3]);
|
|
}
|
|
ptr = colours;
|
|
}
|
|
else
|
|
{
|
|
for (i = 0; i < SECTHEIGHTSIZE*SECTHEIGHTSIZE; i++)
|
|
{
|
|
s->colours[i][0] = 1;
|
|
s->colours[i][1] = 1;
|
|
s->colours[i][2] = 1;
|
|
s->colours[i][3] = 1;
|
|
}
|
|
}
|
|
|
|
/*load any static ents*/
|
|
for (i = 0, dm = (dsmesh_v1_t*)ptr; i < ds->ents_num; i++, dm = (dsmesh_v1_t*)((qbyte*)dm + dm->size))
|
|
{
|
|
vec3_t org;
|
|
org[0] = dm->axisorg[3][0] + (s->sx-CHUNKBIAS)*hm->sectionsize;
|
|
org[1] = dm->axisorg[3][1] + (s->sy-CHUNKBIAS)*hm->sectionsize;
|
|
org[2] = dm->axisorg[3][2];
|
|
Terr_AddMesh(hm, TGS_NOLOAD, NULL, (char*)(dm + 1), org, dm->axisorg, dm->scale);
|
|
}
|
|
#endif
|
|
return ptr;
|
|
}
|
|
|
|
|
|
|
|
|
|
struct terrstream_s
|
|
{
|
|
qbyte *buffer;
|
|
int maxsize;
|
|
int pos;
|
|
};
|
|
//I really hope these get inlined properly.
|
|
static int Terr_Read_SInt(struct terrstream_s *strm)
|
|
{
|
|
int val;
|
|
strm->pos = (strm->pos + sizeof(val)-1) & ~(sizeof(val)-1);
|
|
val = *(int*)(strm->buffer+strm->pos);
|
|
strm->pos += sizeof(val);
|
|
return LittleLong(val);
|
|
}
|
|
static qbyte Terr_Read_Byte(struct terrstream_s *strm)
|
|
{
|
|
qbyte val;
|
|
val = *(qbyte*)(strm->buffer+strm->pos);
|
|
strm->pos += sizeof(val);
|
|
return val;
|
|
}
|
|
static float Terr_Read_Float(struct terrstream_s *strm)
|
|
{
|
|
float val;
|
|
strm->pos = (strm->pos + sizeof(val)-1) & ~(sizeof(val)-1);
|
|
val = *(float*)(strm->buffer+strm->pos);
|
|
strm->pos += sizeof(val);
|
|
return LittleFloat(val);
|
|
}
|
|
static char *Terr_Read_String(struct terrstream_s *strm, char *val, int maxlen)
|
|
{
|
|
int len = strlen(strm->buffer + strm->pos);
|
|
maxlen = min(len, maxlen-1); //truncate
|
|
memcpy(val, strm->buffer + strm->pos, maxlen);
|
|
val[maxlen] = 0;
|
|
strm->pos += len+1;
|
|
return val;
|
|
}
|
|
#ifndef SERVERONLY
|
|
static void Terr_Write_SInt(struct terrstream_s *strm, int val)
|
|
{
|
|
val = LittleLong(val);
|
|
strm->pos = (strm->pos + sizeof(val)-1) & ~(sizeof(val)-1);
|
|
*(int*)(strm->buffer+strm->pos) = val;
|
|
strm->pos += sizeof(val);
|
|
}
|
|
static void Terr_Write_Byte(struct terrstream_s *strm, qbyte val)
|
|
{
|
|
*(qbyte*)(strm->buffer+strm->pos) = val;
|
|
strm->pos += sizeof(val);
|
|
}
|
|
static void Terr_Write_Float(struct terrstream_s *strm, float val)
|
|
{
|
|
val = LittleFloat(val);
|
|
strm->pos = (strm->pos + sizeof(val)-1) & ~(sizeof(val)-1);
|
|
*(float*)(strm->buffer+strm->pos) = val;
|
|
strm->pos += sizeof(val);
|
|
}
|
|
static void Terr_Write_String(struct terrstream_s *strm, char *val)
|
|
{
|
|
int len = strlen(val)+1;
|
|
memcpy(strm->buffer + strm->pos, val, len);
|
|
strm->pos += len;
|
|
}
|
|
static void Terr_TrimWater(hmsection_t *s)
|
|
{
|
|
int i;
|
|
struct hmwater_s *w, **link;
|
|
|
|
for (link = &s->water; (w = *link); )
|
|
{
|
|
//one has a height above the terrain?
|
|
for (i = 0; i < 9*9; i++)
|
|
if (w->heights[i] > s->minh)
|
|
break;
|
|
if (i == 9*9)
|
|
{
|
|
*link = w->next;
|
|
Z_Free(w);
|
|
continue;
|
|
}
|
|
else
|
|
link = &(*link)->next;
|
|
}
|
|
}
|
|
static void Terr_SaveV2(heightmap_t *hm, hmsection_t *s, vfsfile_t *f, int sx, int sy)
|
|
{
|
|
qbyte buffer[65536], last, delta, *lm;
|
|
struct terrstream_s strm = {buffer, sizeof(buffer), 0};
|
|
unsigned int flags = s->flags;
|
|
int i, j, x, y;
|
|
struct hmwater_s *w;
|
|
|
|
flags &= ~(TSF_INTERNAL);
|
|
flags &= ~(TSF_HASCOLOURS|TSF_HASHEIGHTS|TSF_HASSHADOW);
|
|
|
|
for (i = 0; i < SECTHEIGHTSIZE*SECTHEIGHTSIZE; i++)
|
|
{
|
|
if (s->colours[i][0] != 1 || s->colours[i][1] != 1 || s->colours[i][2] != 1 || s->colours[i][3] != 1)
|
|
{
|
|
flags |= TSF_HASCOLOURS;
|
|
break;
|
|
}
|
|
}
|
|
for (i = 0; i < SECTHEIGHTSIZE*SECTHEIGHTSIZE; i++)
|
|
{
|
|
if (s->heights[i] != s->heights[0])
|
|
{
|
|
flags |= TSF_HASHEIGHTS;
|
|
break;
|
|
}
|
|
}
|
|
|
|
lm = lightmap[s->lightmap]->lightmaps;
|
|
lm += (s->lmy * HMLMSTRIDE + s->lmx) * lightmap_bytes;
|
|
for (y = 0; y < SECTTEXSIZE; y++)
|
|
{
|
|
for (x = 0; x < SECTTEXSIZE; x++)
|
|
{
|
|
if (lm[x*4+3] != 255)
|
|
{
|
|
flags |= TSF_HASSHADOW;
|
|
y = SECTTEXSIZE;
|
|
break;
|
|
}
|
|
}
|
|
lm += (HMLMSTRIDE)*lightmap_bytes;
|
|
}
|
|
|
|
//write the flags so the loader knows what to load
|
|
Terr_Write_SInt(&strm, flags);
|
|
|
|
//if heights are compressed, only the first is present.
|
|
if (!(flags & TSF_HASHEIGHTS))
|
|
Terr_Write_Float(&strm, s->heights[0]);
|
|
else
|
|
{
|
|
for (i = 0; i < SECTHEIGHTSIZE*SECTHEIGHTSIZE; i++)
|
|
Terr_Write_Float(&strm, s->heights[i]);
|
|
}
|
|
|
|
for (i = 0; i < sizeof(s->holes); i++)
|
|
Terr_Write_Byte(&strm, s->holes[i]);
|
|
|
|
Terr_TrimWater(s);
|
|
for (j = 0, w = s->water; w; j++)
|
|
w = w->next;
|
|
Terr_Write_SInt(&strm, j);
|
|
for (i = 0, w = s->water; i < j; i++, w = w->next)
|
|
{
|
|
char *shadername = w->shader->name;
|
|
int fl = 0;
|
|
|
|
if (strcmp(shadername, hm->defaultwatershader))
|
|
fl |= 1;
|
|
for (x = 0; x < 8; x++)
|
|
if (w->holes[x])
|
|
break;
|
|
fl |= ((x==8)?0:2);
|
|
for (x = 0; x < 9*9; x++)
|
|
if (w->heights[x] != w->heights[0])
|
|
break;
|
|
fl |= ((x==9*9)?0:4);
|
|
|
|
|
|
Terr_Write_SInt(&strm, fl);
|
|
Terr_Write_SInt(&strm, w->contentmask);
|
|
if (fl & 1)
|
|
Terr_Write_String(&strm, shadername);
|
|
if (fl & 2)
|
|
{
|
|
for (x = 0; x < 8; x++)
|
|
Terr_Write_Byte(&strm, w->holes[x]);
|
|
}
|
|
if (fl & 4)
|
|
{
|
|
for (x = 0; x < 9*9; x++)
|
|
Terr_Write_Float(&strm, w->heights[x]);
|
|
}
|
|
else
|
|
Terr_Write_Float(&strm, w->heights[0]);
|
|
}
|
|
|
|
if (flags & TSF_HASCOLOURS)
|
|
{
|
|
//FIXME: bytes? channels?
|
|
for (i = 0; i < SECTHEIGHTSIZE*SECTHEIGHTSIZE; i++)
|
|
{
|
|
Terr_Write_Float(&strm, s->colours[i][0]);
|
|
Terr_Write_Float(&strm, s->colours[i][1]);
|
|
Terr_Write_Float(&strm, s->colours[i][2]);
|
|
Terr_Write_Float(&strm, s->colours[i][3]);
|
|
}
|
|
}
|
|
|
|
for (j = 0; j < 4; j++)
|
|
Terr_Write_String(&strm, s->texname[j]);
|
|
for (j = 0; j < 4; j++)
|
|
{
|
|
if (j == 3)
|
|
{
|
|
//only write the channel if it has actual data
|
|
if (!(flags & TSF_HASSHADOW))
|
|
continue;
|
|
}
|
|
else
|
|
{
|
|
//only write the data if there's actually a texture.
|
|
//its not meant to be possible to delete a texture without deleting its data too.
|
|
//
|
|
if (!*s->texname[2-j])
|
|
continue;
|
|
}
|
|
|
|
//write the channel
|
|
last = 0;
|
|
lm = lightmap[s->lightmap]->lightmaps;
|
|
lm += (s->lmy * HMLMSTRIDE + s->lmx) * lightmap_bytes;
|
|
for (y = 0; y < SECTTEXSIZE; y++)
|
|
{
|
|
for (x = 0; x < SECTTEXSIZE; x++)
|
|
{
|
|
delta = lm[x*4+j] - last;
|
|
last = lm[x*4+j];
|
|
Terr_Write_Byte(&strm, delta);
|
|
}
|
|
lm += (HMLMSTRIDE)*lightmap_bytes;
|
|
}
|
|
}
|
|
|
|
Sys_LockMutex(hm->entitylock);
|
|
Terr_Write_SInt(&strm, s->numents);
|
|
for (i = 0; i < s->numents; i++)
|
|
{
|
|
unsigned int mf;
|
|
|
|
//make sure we don't overflow. we should always be aligned at this point.
|
|
if (strm.pos > strm.maxsize/2)
|
|
{
|
|
VFS_WRITE(f, strm.buffer, strm.pos);
|
|
strm.pos = 0;
|
|
}
|
|
|
|
mf = 0;
|
|
if (s->ents[i]->ent.scale != 1)
|
|
mf |= TMF_SCALE;
|
|
Terr_Write_SInt(&strm, mf);
|
|
if (s->ents[i]->ent.model)
|
|
Terr_Write_String(&strm, s->ents[i]->ent.model->name);
|
|
else
|
|
Terr_Write_String(&strm, "*invalid");
|
|
Terr_Write_Float(&strm, s->ents[i]->ent.origin[0]+(CHUNKBIAS-sx)*hm->sectionsize);
|
|
Terr_Write_Float(&strm, s->ents[i]->ent.origin[1]+(CHUNKBIAS-sy)*hm->sectionsize);
|
|
Terr_Write_Float(&strm, s->ents[i]->ent.origin[2]);
|
|
Terr_Write_Float(&strm, s->ents[i]->ent.axis[0][0]);
|
|
Terr_Write_Float(&strm, s->ents[i]->ent.axis[0][1]);
|
|
Terr_Write_Float(&strm, s->ents[i]->ent.axis[0][2]);
|
|
Terr_Write_Float(&strm, s->ents[i]->ent.axis[1][0]);
|
|
Terr_Write_Float(&strm, s->ents[i]->ent.axis[1][1]);
|
|
Terr_Write_Float(&strm, s->ents[i]->ent.axis[1][2]);
|
|
Terr_Write_Float(&strm, s->ents[i]->ent.axis[2][0]);
|
|
Terr_Write_Float(&strm, s->ents[i]->ent.axis[2][1]);
|
|
Terr_Write_Float(&strm, s->ents[i]->ent.axis[2][2]);
|
|
if (mf & TMF_SCALE)
|
|
Terr_Write_Float(&strm, s->ents[i]->ent.scale);
|
|
}
|
|
Sys_UnlockMutex(hm->entitylock);
|
|
|
|
//reset it in case the buffer is getting a little full
|
|
strm.pos = (strm.pos + sizeof(int)-1) & ~(sizeof(int)-1);
|
|
VFS_WRITE(f, strm.buffer, strm.pos);
|
|
strm.pos = 0;
|
|
}
|
|
#ifndef SERVERONLY
|
|
static void Terr_WorkerLoadedSectionLightmap(void *ctx, void *data, size_t a, size_t b)
|
|
{
|
|
heightmap_t *hm = ctx;
|
|
hmsection_t *s = Terr_GetSection(hm, a, b, TGS_NOLOAD|TGS_ANYSTATE);
|
|
qbyte *inlm = data;
|
|
qbyte *outlm;
|
|
int y;
|
|
|
|
if (s)
|
|
if (lightmap_bytes == 4 && Terr_InitLightmap(s, false))
|
|
{
|
|
outlm = lightmap[s->lightmap]->lightmaps;
|
|
outlm += (s->lmy * HMLMSTRIDE + s->lmx) * lightmap_bytes;
|
|
for (y = 0; y < SECTTEXSIZE; y++)
|
|
{
|
|
memcpy(outlm, inlm, SECTTEXSIZE*4);
|
|
inlm += SECTTEXSIZE*4;
|
|
outlm += (HMLMSTRIDE)*lightmap_bytes;
|
|
}
|
|
}
|
|
|
|
BZ_Free(data);
|
|
}
|
|
#endif
|
|
#endif
|
|
static void *Terr_ReadV2(heightmap_t *hm, hmsection_t *s, void *ptr, int len)
|
|
{
|
|
#ifndef SERVERONLY
|
|
char modelname[MAX_QPATH];
|
|
qbyte last;
|
|
int y;
|
|
qboolean present;
|
|
qbyte *lmstart = NULL, *lm, delta;
|
|
#endif
|
|
struct terrstream_s strm = {ptr, len, 0};
|
|
float f;
|
|
int i, j, x;
|
|
unsigned int flags = Terr_Read_SInt(&strm);
|
|
|
|
s->flags |= flags & ~TSF_INTERNAL;
|
|
if (flags & TSF_HASHEIGHTS)
|
|
{
|
|
s->minh = s->maxh = s->heights[0] = Terr_Read_Float(&strm);
|
|
for (i = 1; i < SECTHEIGHTSIZE*SECTHEIGHTSIZE; i++)
|
|
{
|
|
f = Terr_Read_Float(&strm);
|
|
if (s->minh > f)
|
|
s->minh = f;
|
|
if (s->maxh < f)
|
|
s->maxh = f;
|
|
s->heights[i] = f;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
s->minh = s->maxh = f = Terr_Read_Float(&strm);
|
|
for (i = 0; i < SECTHEIGHTSIZE*SECTHEIGHTSIZE; i++)
|
|
s->heights[i] = f;
|
|
}
|
|
|
|
for (i = 0; i < sizeof(s->holes); i++)
|
|
s->holes[i] = Terr_Read_Byte(&strm);
|
|
|
|
j = Terr_Read_SInt(&strm);
|
|
for (i = 0; i < j; i++)
|
|
{
|
|
struct hmwater_s *w = Z_Malloc(sizeof(*w));
|
|
int fl = Terr_Read_SInt(&strm);
|
|
w->next = s->water;
|
|
s->water = w;
|
|
w->simple = true;
|
|
w->contentmask = Terr_Read_SInt(&strm);
|
|
if (fl & 1)
|
|
Terr_Read_String(&strm, w->shadername, sizeof(w->shadername));
|
|
else
|
|
Q_strncpyz(w->shadername, hm->defaultwatershader, sizeof(w->shadername));
|
|
if (fl & 2)
|
|
{
|
|
for (x = 0; x < 8; x++)
|
|
w->holes[i] = Terr_Read_Byte(&strm);
|
|
w->simple = false;
|
|
}
|
|
if (fl & 4)
|
|
{
|
|
for (x = 0; x < 9*9; x++)
|
|
{
|
|
w->heights[x] = Terr_Read_Float(&strm);
|
|
}
|
|
w->simple = false;
|
|
}
|
|
else
|
|
{ //all heights the same can be used as a way to compress the data
|
|
w->minheight = w->maxheight = Terr_Read_Float(&strm);
|
|
for (x = 0; x < 9*9; x++)
|
|
w->heights[x] = w->minheight = w->maxheight;
|
|
}
|
|
}
|
|
|
|
//dedicated server can stop reading here.
|
|
|
|
#ifndef SERVERONLY
|
|
if (flags & TSF_HASCOLOURS)
|
|
{
|
|
for (i = 0; i < SECTHEIGHTSIZE*SECTHEIGHTSIZE; i++)
|
|
{
|
|
s->colours[i][0] = Terr_Read_Float(&strm);
|
|
s->colours[i][1] = Terr_Read_Float(&strm);
|
|
s->colours[i][2] = Terr_Read_Float(&strm);
|
|
s->colours[i][3] = Terr_Read_Float(&strm);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
for (i = 0; i < SECTHEIGHTSIZE*SECTHEIGHTSIZE; i++)
|
|
{
|
|
s->colours[i][0] = 1;
|
|
s->colours[i][1] = 1;
|
|
s->colours[i][2] = 1;
|
|
s->colours[i][3] = 1;
|
|
}
|
|
}
|
|
|
|
for (j = 0; j < 4; j++)
|
|
Terr_Read_String(&strm, s->texname[j], sizeof(s->texname[j]));
|
|
for (j = 0; j < 4; j++)
|
|
{
|
|
if (j == 3)
|
|
present = !!(flags & TSF_HASSHADOW);
|
|
else
|
|
present = !!(*s->texname[2-j]);
|
|
|
|
//should be able to skip this if no shadows or textures
|
|
if (!lmstart)
|
|
lmstart = BZ_Malloc(SECTTEXSIZE*SECTTEXSIZE*4);
|
|
|
|
if (present)
|
|
{
|
|
//read the channel
|
|
last = 0;
|
|
lm = lmstart;
|
|
for (y = 0; y < SECTTEXSIZE; y++)
|
|
{
|
|
for (x = 0; x < SECTTEXSIZE; x++)
|
|
{
|
|
delta = Terr_Read_Byte(&strm);
|
|
last = (last+delta)&0xff;
|
|
lm[x*4+j] = last;
|
|
}
|
|
lm += x*4;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
last = ((j==3)?255:0);
|
|
lm = lmstart;
|
|
for (y = 0; y < SECTTEXSIZE; y++)
|
|
{
|
|
for (x = 0; x < SECTTEXSIZE; x++)
|
|
lm[x*4+j] = last;
|
|
lm += x*4;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (lmstart)
|
|
COM_AddWork(WG_MAIN, Terr_WorkerLoadedSectionLightmap, hm, lmstart, s->sx, s->sy);
|
|
|
|
/*load any static ents*/
|
|
j = Terr_Read_SInt(&strm);
|
|
for (i = 0; i < j; i++)
|
|
{
|
|
vec3_t axis[3];
|
|
vec3_t org;
|
|
unsigned int mf;
|
|
model_t *mod;
|
|
float scale;
|
|
mf = Terr_Read_SInt(&strm);
|
|
|
|
mod = Mod_FindName(Terr_Read_String(&strm, modelname, sizeof(modelname)));
|
|
org[0] = Terr_Read_Float(&strm);
|
|
org[1] = Terr_Read_Float(&strm);
|
|
org[2] = Terr_Read_Float(&strm);
|
|
axis[0][0] = Terr_Read_Float(&strm);
|
|
axis[0][1] = Terr_Read_Float(&strm);
|
|
axis[0][2] = Terr_Read_Float(&strm);
|
|
axis[1][0] = Terr_Read_Float(&strm);
|
|
axis[1][1] = Terr_Read_Float(&strm);
|
|
axis[1][2] = Terr_Read_Float(&strm);
|
|
axis[2][0] = Terr_Read_Float(&strm);
|
|
axis[2][1] = Terr_Read_Float(&strm);
|
|
axis[2][2] = Terr_Read_Float(&strm);
|
|
scale = (mf&TMF_SCALE)?Terr_Read_Float(&strm):1;
|
|
|
|
org[0] += (s->sx-CHUNKBIAS)*hm->sectionsize;
|
|
org[1] += (s->sy-CHUNKBIAS)*hm->sectionsize;
|
|
|
|
Terr_AddMesh(hm, TGS_NOLOAD, mod, NULL, org, axis, scale);
|
|
}
|
|
#endif
|
|
return ptr;
|
|
}
|
|
|
|
static void Terr_ClearSection(hmsection_t *s)
|
|
{
|
|
struct hmwater_s *w;
|
|
int i;
|
|
Sys_LockMutex(s->hmmod->entitylock);
|
|
for (i = 0; i < s->numents; i++)
|
|
s->ents[i]->refs-=1;
|
|
s->numents = 0;
|
|
Sys_UnlockMutex(s->hmmod->entitylock);
|
|
|
|
while(s->water)
|
|
{
|
|
w = s->water;
|
|
s->water = w->next;
|
|
Z_Free(w);
|
|
}
|
|
}
|
|
|
|
static void Terr_GenerateDefault(heightmap_t *hm, hmsection_t *s)
|
|
{
|
|
int i;
|
|
|
|
memset(s->holes, 0, sizeof(s->holes));
|
|
|
|
#ifndef SERVERONLY
|
|
Q_strncpyz(s->texname[0], "", sizeof(s->texname[0]));
|
|
Q_strncpyz(s->texname[1], "", sizeof(s->texname[1]));
|
|
Q_strncpyz(s->texname[2], "", sizeof(s->texname[2]));
|
|
Q_strncpyz(s->texname[3], hm->defaultgroundtexture, sizeof(s->texname[3]));
|
|
|
|
if (s->lightmap >= 0)
|
|
{
|
|
int j;
|
|
qbyte *lm;
|
|
|
|
lm = lightmap[s->lightmap]->lightmaps;
|
|
lm += (s->lmy * HMLMSTRIDE + s->lmx) * lightmap_bytes;
|
|
for (i = 0; i < SECTTEXSIZE; i++)
|
|
{
|
|
for (j = 0; j < SECTTEXSIZE; j++)
|
|
{
|
|
lm[j*4+0] = 0;
|
|
lm[j*4+0] = 0;
|
|
lm[j*4+0] = 0;
|
|
lm[j*4+3] = 255;
|
|
}
|
|
lm += (HMLMSTRIDE)*lightmap_bytes;
|
|
}
|
|
lightmap[s->lightmap]->modified = true;
|
|
lightmap[s->lightmap]->rectchange.l = 0;
|
|
lightmap[s->lightmap]->rectchange.t = 0;
|
|
lightmap[s->lightmap]->rectchange.r = HMLMSTRIDE;
|
|
lightmap[s->lightmap]->rectchange.b = HMLMSTRIDE;
|
|
}
|
|
for (i = 0; i < SECTHEIGHTSIZE*SECTHEIGHTSIZE; i++)
|
|
{
|
|
s->colours[i][0] = 1;
|
|
s->colours[i][1] = 1;
|
|
s->colours[i][2] = 1;
|
|
s->colours[i][3] = 1;
|
|
}
|
|
s->mesh.colors4f_array[0] = s->colours;
|
|
#endif
|
|
|
|
for (i = 0; i < SECTHEIGHTSIZE*SECTHEIGHTSIZE; i++)
|
|
s->heights[i] = hm->defaultgroundheight;
|
|
|
|
if (hm->defaultwaterheight > hm->defaultgroundheight)
|
|
Terr_GenerateWater(s, hm->defaultwaterheight);
|
|
|
|
#if 0//def DEBUG
|
|
void *f;
|
|
if (lightmap_bytes == 4 && lightmap_bgra && FS_LoadFile(va("maps/%s/splatt.png", hm->path), &f) != (qofs_t)-1)
|
|
{
|
|
//temp
|
|
int vx, vy;
|
|
int x, y;
|
|
extern qbyte *Read32BitImageFile(qbyte *buf, int len, int *width, int *height, qboolean *hasalpha, const char *fname);
|
|
int sw, sh;
|
|
qboolean hasalpha;
|
|
unsigned char *splatter = Read32BitImageFile(f, com_filesize, &sw, &sh, &hasalpha, "splattermap");
|
|
if (splatter)
|
|
{
|
|
lm = lightmap[s->lightmap]->lightmaps;
|
|
lm += (s->lmy * HMLMSTRIDE + s->lmx) * lightmap_bytes;
|
|
|
|
for (vx = 0; vx < SECTTEXSIZE; vx++)
|
|
{
|
|
x = sw * (((float)sy) + ((float)vx / (SECTTEXSIZE-1))) / hm->numsegsx;
|
|
if (x > sw-1)
|
|
x = sw-1;
|
|
for (vy = 0; vy < SECTTEXSIZE; vy++)
|
|
{
|
|
y = sh * (((float)sx) + ((float)vy / (SECTTEXSIZE-1))) / hm->numsegsy;
|
|
if (y > sh-1)
|
|
y = sh-1;
|
|
|
|
lm[2] = splatter[(y + x*sh)*4+0];
|
|
lm[1] = splatter[(y + x*sh)*4+1];
|
|
lm[0] = splatter[(y + x*sh)*4+2];
|
|
lm[3] = splatter[(y + x*sh)*4+3];
|
|
lm += 4;
|
|
}
|
|
lm += (HMLMSTRIDE - SECTTEXSIZE)*lightmap_bytes;
|
|
}
|
|
BZ_Free(splatter);
|
|
|
|
lightmap[s->lightmap]->modified = true;
|
|
lightmap[s->lightmap]->rectchange.l = 0;
|
|
lightmap[s->lightmap]->rectchange.t = 0;
|
|
lightmap[s->lightmap]->rectchange.w = HMLMSTRIDE;
|
|
lightmap[s->lightmap]->rectchange.h = HMLMSTRIDE;
|
|
}
|
|
FS_FreeFile(f);
|
|
}
|
|
|
|
if (lightmap_bytes == 4 && lightmap_bgra && !qofs_Error(FS_LoadFile(va("maps/%s/heightmap.png", hm->path), &f)))
|
|
{
|
|
//temp
|
|
int vx, vy;
|
|
int x, y;
|
|
extern qbyte *Read32BitImageFile(qbyte *buf, int len, int *width, int *height, qboolean *hasalpha, const char *fname);
|
|
int sw, sh;
|
|
float *h;
|
|
qboolean hasalpha;
|
|
unsigned char *hmimage = Read32BitImageFile(f, com_filesize, &sw, &sh, &hasalpha, "heightmap");
|
|
if (hmimage)
|
|
{
|
|
h = s->heights;
|
|
|
|
for (vx = 0; vx < SECTHEIGHTSIZE; vx++)
|
|
{
|
|
x = sw * (((float)sy) + ((float)vx / (SECTHEIGHTSIZE-1))) / hm->numsegsx;
|
|
if (x > sw-1)
|
|
x = sw-1;
|
|
for (vy = 0; vy < SECTHEIGHTSIZE; vy++)
|
|
{
|
|
y = sh * (((float)sx) + ((float)vy / (SECTHEIGHTSIZE-1))) / hm->numsegsy;
|
|
if (y > sh-1)
|
|
y = sh-1;
|
|
|
|
*h = 0;
|
|
*h += hmimage[(y + x*sh)*4+0];
|
|
*h += hmimage[(y + x*sh)*4+1]<<8;
|
|
*h += hmimage[(y + x*sh)*4+2]<<16;
|
|
*h *= 4.0f/(1<<16);
|
|
h++;
|
|
}
|
|
}
|
|
BZ_Free(hmimage);
|
|
}
|
|
FS_FreeFile(f);
|
|
}
|
|
#endif
|
|
}
|
|
|
|
static void Terr_WorkerLoadedSection(void *ctx, void *data, size_t a, size_t b)
|
|
{
|
|
hmsection_t *s = ctx;
|
|
validatelinks(&s->hmmod->recycle);
|
|
|
|
Terr_LoadSectionTextures(s);
|
|
validatelinks2(&s->hmmod->recycle, &s->recycle);
|
|
InsertLinkBefore(&s->recycle, &s->hmmod->recycle);
|
|
validatelinks(&s->hmmod->recycle);
|
|
s->hmmod->loadingsections-=1;
|
|
s->flags &= ~TSF_EDITED;
|
|
s->loadstate = TSLS_LOADED;
|
|
s->timestamp = realtime;
|
|
|
|
validatelinks(&s->hmmod->recycle);
|
|
}
|
|
static void Terr_WorkerFailedSection(void *ctx, void *data, size_t a, size_t b)
|
|
{
|
|
hmsection_t *s = ctx;
|
|
Terr_WorkerLoadedSection(ctx, data, a, b);
|
|
s->flags &= ~TSF_EDITED;
|
|
s->loadstate = TSLS_FAILED;
|
|
|
|
validatelinks(&s->hmmod->recycle);
|
|
}
|
|
|
|
void QDECL Terr_FinishedSection(hmsection_t *s, qboolean success)
|
|
{
|
|
s->flags &= ~TSF_EDITED; //its just been loaded (and was probably edited by the loader), make sure it doesn't get saved or whatever
|
|
|
|
s->loadstate = TSLS_LOADING2;
|
|
if (!success)
|
|
COM_AddWork(WG_MAIN, Terr_WorkerFailedSection, s, NULL, s->sx, s->sy);
|
|
else
|
|
COM_AddWork(WG_MAIN, Terr_WorkerLoadedSection, s, NULL, s->sx, s->sy);
|
|
}
|
|
|
|
static hmsection_t *Terr_ReadSection(heightmap_t *hm, hmsection_t *s, int ver, void *filebase, unsigned int filelen)
|
|
{
|
|
qboolean failed = false;
|
|
void *ptr = filebase;
|
|
|
|
if (ptr && ver == 1)
|
|
Terr_ReadV1(hm, s, ptr, filelen);
|
|
else if (ptr && ver == 2)
|
|
Terr_ReadV2(hm, s, ptr, filelen);
|
|
else
|
|
{
|
|
// s->flags |= TSF_RELIGHT;
|
|
Terr_GenerateDefault(hm, s);
|
|
|
|
failed = true;
|
|
}
|
|
|
|
Terr_FinishedSection(s, !failed);
|
|
|
|
return s;
|
|
}
|
|
|
|
#ifndef SERVERONLY
|
|
qboolean Terr_DownloadedSection(char *fname)
|
|
{
|
|
/*
|
|
qofs_t len;
|
|
dsection_t *fileptr;
|
|
int x, y;
|
|
heightmap_t *hm;
|
|
int ver = 0;
|
|
|
|
if (!cl.worldmodel)
|
|
return false;
|
|
|
|
hm = cl.worldmodel->terrain;
|
|
|
|
if (Terr_IsSectionFName(hm, fname, &x, &y))
|
|
{
|
|
fileptr = NULL;
|
|
len = FS_LoadFile(fname, (void**)&fileptr);
|
|
|
|
if (!qofs_Error(len) && len >= sizeof(*fileptr) && fileptr->magic == SECTION_MAGIC)
|
|
Terr_ReadSection(hm, ver, x, y, fileptr+1, len - sizeof(*fileptr));
|
|
else
|
|
Terr_ReadSection(hm, ver, x, y, NULL, 0);
|
|
|
|
if (fileptr)
|
|
FS_FreeFile(fileptr);
|
|
return true;
|
|
}
|
|
*/
|
|
return false;
|
|
}
|
|
#endif
|
|
|
|
#ifndef SERVERONLY
|
|
static void Terr_LoadSection(heightmap_t *hm, hmsection_t *s, int sx, int sy, unsigned int flags)
|
|
{
|
|
//when using networked terrain, the client will never load a section from disk, but will only load it from the server
|
|
//one section at a time.
|
|
if (mod_terrain_networked.ival && !sv.state)
|
|
{
|
|
char fname[MAX_QPATH];
|
|
if (flags & TGS_NODOWNLOAD)
|
|
return;
|
|
//try to download it now...
|
|
if (!cl.downloadlist)
|
|
CL_CheckOrEnqueDownloadFile(Terr_DiskSectionName(hm, sx, sy, fname, sizeof(fname)), Terr_TempDiskSectionName(hm, sx, sy), DLLF_OVERWRITE|DLLF_TEMPORARY);
|
|
return;
|
|
}
|
|
|
|
if (!s)
|
|
{
|
|
Terr_GenerateSection(hm, sx, sy, true);
|
|
}
|
|
}
|
|
#endif
|
|
static void Terr_LoadSectionWorker(void *ctx, void *data, size_t a, size_t b)
|
|
{
|
|
heightmap_t *hm = data;
|
|
hmsection_t *s = ctx;
|
|
int sx = a;
|
|
int sy = b;
|
|
void *diskimage;
|
|
qofs_t len;
|
|
char fname[MAX_QPATH];
|
|
|
|
//already processed, or not otherwise valid
|
|
if (s->loadstate != TSLS_LOADING0)
|
|
return;
|
|
|
|
#if SECTIONSPERBLOCK > 1
|
|
len = FS_LoadFile(Terr_DiskBlockName(hm, sx, sy, fname, sizeof(fname)), (void**)&diskimage);
|
|
if (!qofs_Error(len))
|
|
{
|
|
int offset;
|
|
int x, y;
|
|
int ver;
|
|
dblock_t *block = diskimage;
|
|
if (block->magic != SECTION_MAGIC || !(block->ver & 0x80000000))
|
|
{
|
|
s = Terr_GenerateSection(hm, sx, sy, false);
|
|
|
|
//give it a dummy so we don't constantly hit the disk
|
|
Terr_ReadSection(hm, s, 0, NULL, 0);
|
|
}
|
|
else
|
|
{
|
|
hmsection_t *sects[SECTIONSPERBLOCK*SECTIONSPERBLOCK];
|
|
|
|
sx&=~(SECTIONSPERBLOCK-1);
|
|
sy&=~(SECTIONSPERBLOCK-1);
|
|
|
|
ver = block->ver & ~0x80000000;
|
|
if (Terr_GenerateSections(hm, sx, sy, SECTIONSPERBLOCK, sects))
|
|
{
|
|
for (y = 0; y < SECTIONSPERBLOCK; y++)
|
|
for (x = 0; x < SECTIONSPERBLOCK; x++)
|
|
{
|
|
//noload avoids recursion.
|
|
s = sects[x+y*SECTIONSPERBLOCK];
|
|
if (s)
|
|
{
|
|
offset = block->offset[x + y*SECTIONSPERBLOCK];
|
|
if (!offset)
|
|
Terr_ReadSection(hm, s, ver, NULL, 0); //no data in the file for this section
|
|
else
|
|
Terr_ReadSection(hm, s, ver, (char*)diskimage + offset, len - offset);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
FS_FreeFile(diskimage);
|
|
return;
|
|
}
|
|
#endif
|
|
|
|
//legacy one-section-per-file format.
|
|
len = FS_LoadFile(Terr_DiskSectionName(hm, sx, sy, fname, sizeof(fname)), (void**)&diskimage);
|
|
if (!qofs_Error(len))
|
|
{
|
|
dsection_t *h = diskimage;
|
|
if (len >= sizeof(*h) && h->magic == SECTION_MAGIC)
|
|
{
|
|
s = Terr_GenerateSection(hm, sx, sy, false);
|
|
if (!s)
|
|
return;
|
|
Terr_ReadSection(hm, s, h->ver, h+1, len-sizeof(*h));
|
|
FS_FreeFile(diskimage);
|
|
return;
|
|
}
|
|
if (diskimage)
|
|
FS_FreeFile(diskimage);
|
|
}
|
|
|
|
if (terrainfuncs.AutogenerateSection && terrainfuncs.AutogenerateSection(hm, sx, sy, 0))
|
|
return;
|
|
|
|
s = Terr_GenerateSection(hm, sx, sy, false);
|
|
if (!s)
|
|
return;
|
|
|
|
//generate a dummy one
|
|
Terr_ReadSection(hm, s, 0, NULL, 0);
|
|
}
|
|
|
|
#ifndef SERVERONLY
|
|
static void Terr_SaveV1(heightmap_t *hm, hmsection_t *s, vfsfile_t *f, int sx, int sy)
|
|
{
|
|
int i;
|
|
dsmesh_v1_t dm;
|
|
qbyte *lm;
|
|
dsection_v1_t ds;
|
|
vec4_t dcolours[SECTHEIGHTSIZE*SECTHEIGHTSIZE];
|
|
int nothing = 0;
|
|
struct hmwater_s *w = s->water;
|
|
|
|
memset(&ds, 0, sizeof(ds));
|
|
memset(&dm, 0, sizeof(dm));
|
|
|
|
//mask off the flags which are only valid in memory
|
|
ds.flags = s->flags & ~(TSF_INTERNAL|TSF_HASWATER_V0);
|
|
|
|
//kill the haswater flag if its entirely above any possible water anyway.
|
|
if (w)
|
|
ds.flags |= TSF_HASWATER_V0;
|
|
ds.flags &= ~TSF_HASCOLOURS; //recalculated
|
|
|
|
Q_strncpyz(ds.texname[0], s->texname[0], sizeof(ds.texname[0]));
|
|
Q_strncpyz(ds.texname[1], s->texname[1], sizeof(ds.texname[1]));
|
|
Q_strncpyz(ds.texname[2], s->texname[2], sizeof(ds.texname[2]));
|
|
Q_strncpyz(ds.texname[3], s->texname[3], sizeof(ds.texname[3]));
|
|
|
|
for (i = 0; i < 8*8; i++)
|
|
{
|
|
int x = (i & 7);
|
|
int y = (i>>3);
|
|
int b = (1u<<(x>>1)) << ((y>>1)<<2);
|
|
if (s->holes[y] & (1u<<x))
|
|
ds.holes |= b;
|
|
}
|
|
|
|
//make sure the user can see the holes they just saved.
|
|
memset(s->holes, 0, sizeof(s->holes));
|
|
for (i = 0; i < 8*8; i++)
|
|
{
|
|
int x = (i & 7);
|
|
int y = (i>>3);
|
|
int b = (1u<<(x>>1)) << ((y>>1)<<2);
|
|
if (ds.holes & b)
|
|
s->holes[y] |= 1u<<x;
|
|
}
|
|
s->flags |= TSF_DIRTY;
|
|
|
|
lm = lightmap[s->lightmap]->lightmaps;
|
|
lm += (s->lmy * HMLMSTRIDE + s->lmx) * lightmap_bytes;
|
|
for (i = 0; i < SECTTEXSIZE; i++)
|
|
{
|
|
memcpy(ds.texmap + i, lm, sizeof(ds.texmap[0]));
|
|
lm += (HMLMSTRIDE)*lightmap_bytes;
|
|
}
|
|
|
|
for (i = 0; i < SECTHEIGHTSIZE*SECTHEIGHTSIZE; i++)
|
|
{
|
|
ds.heights[i] = LittleFloat(s->heights[i]);
|
|
|
|
if (s->colours[i][0] != 1 || s->colours[i][1] != 1 || s->colours[i][2] != 1 || s->colours[i][3] != 1)
|
|
{
|
|
ds.flags |= TSF_HASCOLOURS;
|
|
dcolours[i][0] = LittleFloat(s->colours[i][0]);
|
|
dcolours[i][1] = LittleFloat(s->colours[i][1]);
|
|
dcolours[i][2] = LittleFloat(s->colours[i][2]);
|
|
dcolours[i][3] = LittleFloat(s->colours[i][3]);
|
|
}
|
|
else
|
|
{
|
|
dcolours[i][0] = dcolours[i][1] = dcolours[i][2] = dcolours[i][3] = LittleFloat(1);
|
|
}
|
|
}
|
|
ds.waterheight = w?w->heights[4*8+4]:s->minh;
|
|
ds.minh = s->minh;
|
|
ds.maxh = s->maxh;
|
|
Sys_LockMutex(hm->entitylock);
|
|
ds.ents_num = s->numents;
|
|
|
|
VFS_WRITE(f, &ds, sizeof(ds));
|
|
if (ds.flags & TSF_HASCOLOURS)
|
|
VFS_WRITE(f, dcolours, sizeof(dcolours));
|
|
for (i = 0; i < s->numents; i++)
|
|
{
|
|
int pad;
|
|
dm.scale = s->ents[i]->ent.scale;
|
|
VectorCopy(s->ents[i]->ent.axis[0], dm.axisorg[0]);
|
|
VectorCopy(s->ents[i]->ent.axis[1], dm.axisorg[1]);
|
|
VectorCopy(s->ents[i]->ent.axis[2], dm.axisorg[2]);
|
|
VectorCopy(s->ents[i]->ent.origin, dm.axisorg[3]);
|
|
dm.axisorg[3][0] += (CHUNKBIAS-sx)*hm->sectionsize;
|
|
dm.axisorg[3][1] += (CHUNKBIAS-sy)*hm->sectionsize;
|
|
dm.size = sizeof(dm) + strlen(s->ents[i]->ent.model->name) + 1;
|
|
if (dm.size & 3)
|
|
pad = 4 - (dm.size&3);
|
|
else
|
|
pad = 0;
|
|
dm.size += pad;
|
|
VFS_WRITE(f, &dm, sizeof(dm));
|
|
VFS_WRITE(f, s->ents[i]->ent.model->name, strlen(s->ents[i]->ent.model->name)+1);
|
|
if (pad)
|
|
VFS_WRITE(f, ¬hing, pad);
|
|
}
|
|
Sys_UnlockMutex(hm->entitylock);
|
|
}
|
|
|
|
static void Terr_Save(heightmap_t *hm, hmsection_t *s, vfsfile_t *f, int sx, int sy, int ver)
|
|
{
|
|
if (ver == 1)
|
|
Terr_SaveV1(hm, s, f, sx, sy);
|
|
else if (ver == 2)
|
|
Terr_SaveV2(hm, s, f, sx, sy);
|
|
}
|
|
#endif
|
|
|
|
//doesn't clear edited/dirty flags or anything
|
|
static qboolean Terr_SaveSection(heightmap_t *hm, hmsection_t *s, int sx, int sy, qboolean blocksave)
|
|
{
|
|
#ifdef SERVERONLY
|
|
return true;
|
|
#else
|
|
vfsfile_t *f;
|
|
char fname[MAX_QPATH];
|
|
int x, y;
|
|
int writever = mod_terrain_savever.ival;
|
|
if (!writever)
|
|
writever = SECTION_VER_DEFAULT;
|
|
//if its invalid or doesn't contain all the data...
|
|
if (!s || s->lightmap < 0)
|
|
return true;
|
|
|
|
#if SECTIONSPERBLOCK > 1
|
|
if (blocksave)
|
|
{
|
|
dblock_t dbh;
|
|
sx = sx & ~(SECTIONSPERBLOCK-1);
|
|
sy = sy & ~(SECTIONSPERBLOCK-1);
|
|
|
|
//make sure its loaded before we replace the file
|
|
for (y = 0; y < SECTIONSPERBLOCK; y++)
|
|
{
|
|
for (x = 0; x < SECTIONSPERBLOCK; x++)
|
|
{
|
|
s = Terr_GetSection(hm, sx+x, sy+y, TGS_WAITLOAD|TGS_NODOWNLOAD);
|
|
if (s)
|
|
s->flags |= TSF_EDITED; //stop them from getting reused for something else.
|
|
}
|
|
}
|
|
|
|
//make sure all lightmap info was loaded.
|
|
COM_WorkerFullSync();
|
|
|
|
Terr_DiskBlockName(hm, sx, sy, fname, sizeof(fname));
|
|
FS_CreatePath(fname, FS_GAMEONLY);
|
|
f = FS_OpenVFS(fname, "wb", FS_GAMEONLY);
|
|
if (!f)
|
|
{
|
|
Con_Printf("Failed to open %s\n", fname);
|
|
return false;
|
|
}
|
|
|
|
memset(&dbh, 0, sizeof(dbh));
|
|
dbh.magic = LittleLong(SECTION_MAGIC);
|
|
dbh.ver = LittleLong(writever | 0x80000000);
|
|
VFS_WRITE(f, &dbh, sizeof(dbh));
|
|
for (y = 0; y < SECTIONSPERBLOCK; y++)
|
|
{
|
|
for (x = 0; x < SECTIONSPERBLOCK; x++)
|
|
{
|
|
s = Terr_GetSection(hm, sx+x, sy+y, TGS_WAITLOAD|TGS_NODOWNLOAD);
|
|
if (s && s->loadstate == TSLS_LOADED && Terr_InitLightmap(s, false))
|
|
{
|
|
dbh.offset[y*SECTIONSPERBLOCK + x] = VFS_TELL(f);
|
|
Terr_Save(hm, s, f, sx+x, sy+y, writever);
|
|
s->flags &= ~TSF_EDITED;
|
|
}
|
|
else
|
|
dbh.offset[y*SECTIONSPERBLOCK + x] = 0;
|
|
}
|
|
}
|
|
|
|
VFS_SEEK(f, 0);
|
|
VFS_WRITE(f, &dbh, sizeof(dbh));
|
|
VFS_CLOSE(f);
|
|
FS_FlushFSHashWritten(fname);
|
|
}
|
|
else
|
|
#endif
|
|
{
|
|
dsection_t dsh;
|
|
Terr_DiskSectionName(hm, sx, sy, fname, sizeof(fname));
|
|
|
|
// if (s && (s->flags & (TSF_EDITED|TSF_FAILEDLOAD)) != TSF_FAILEDLOAD)
|
|
// return FS_Remove(fname, FS_GAMEONLY); //delete the file if the section got reverted to default, and wasn't later modified.
|
|
|
|
//make sure all lightmap info was loaded.
|
|
COM_WorkerFullSync();
|
|
|
|
FS_CreatePath(fname, FS_GAMEONLY);
|
|
f = FS_OpenVFS(fname, "wb", FS_GAMEONLY);
|
|
if (!f)
|
|
{
|
|
Con_Printf("Failed to open %s\n", fname);
|
|
return false;
|
|
}
|
|
|
|
memset(&dsh, 0, sizeof(dsh));
|
|
dsh.magic = SECTION_MAGIC;
|
|
dsh.ver = writever;
|
|
VFS_WRITE(f, &dsh, sizeof(dsh));
|
|
Terr_Save(hm, s, f, sx, sy, writever);
|
|
VFS_CLOSE(f);
|
|
FS_FlushFSHashWritten(fname);
|
|
}
|
|
return true;
|
|
#endif
|
|
}
|
|
|
|
/*convienience function*/
|
|
static hmsection_t *QDECL Terr_GetSection(heightmap_t *hm, int x, int y, unsigned int flags)
|
|
{
|
|
hmcluster_t *cluster;
|
|
hmsection_t *section;
|
|
int cx = x / MAXSECTIONS;
|
|
int cy = y / MAXSECTIONS;
|
|
int sx = x & (MAXSECTIONS-1);
|
|
int sy = y & (MAXSECTIONS-1);
|
|
cluster = hm->cluster[cx + cy*MAXCLUSTERS];
|
|
if (!cluster)
|
|
section = NULL;
|
|
else
|
|
section = cluster->section[sx + sy*MAXSECTIONS];
|
|
if (!section)
|
|
{
|
|
if (flags & (TGS_LAZYLOAD|TGS_TRYLOAD|TGS_WAITLOAD))
|
|
{
|
|
if ((flags & TGS_LAZYLOAD) && hm->loadingsections)
|
|
return NULL;
|
|
section = Terr_GenerateSection(hm, x, y, true);
|
|
}
|
|
}
|
|
#ifndef SERVERONLY
|
|
//when using networked terrain, the client will never load a section from disk, but only loading it from the server
|
|
//this means we need to send a new request to download the section if it was flagged as modified.
|
|
if (!(flags & TGS_NODOWNLOAD))
|
|
if (section && (section->flags & TSF_NOTIFY) && mod_terrain_networked.ival && !sv.state)
|
|
{
|
|
//try to download it now...
|
|
if (!cl.downloadlist)
|
|
{
|
|
char fname[MAX_QPATH];
|
|
CL_CheckOrEnqueDownloadFile(Terr_DiskSectionName(hm, x, y, fname, sizeof(fname)), Terr_TempDiskSectionName(hm, x, y), DLLF_OVERWRITE|DLLF_TEMPORARY);
|
|
|
|
section->flags &= ~TSF_NOTIFY;
|
|
}
|
|
}
|
|
#endif
|
|
|
|
if (section && section->loadstate != TSLS_LOADED)
|
|
{
|
|
//wait for it to load if we're meant to be doing that.
|
|
if (flags & TGS_WAITLOAD)
|
|
{
|
|
//wait for it to load if we're meant to be doing that.
|
|
if (section->loadstate == TSLS_LOADING0)
|
|
COM_WorkerPartialSync(section, §ion->loadstate, TSLS_LOADING0);
|
|
if (section->loadstate == TSLS_LOADING1)
|
|
COM_WorkerPartialSync(section, §ion->loadstate, TSLS_LOADING1);
|
|
if (section->loadstate == TSLS_LOADING2)
|
|
COM_MainThreadFlush(); //make sure any associated lightmaps also got read+handled
|
|
}
|
|
|
|
//if it failed, generate a default (for editing)
|
|
if (section->loadstate == TSLS_FAILED && ((flags & TGS_DEFAULTONFAIL) || hm->forcedefault))
|
|
{
|
|
section->flags = (section->flags & ~TSF_EDITED);
|
|
section->loadstate = TSLS_LOADED;
|
|
Terr_ClearSection(section);
|
|
Terr_GenerateDefault(hm, section);
|
|
}
|
|
|
|
|
|
if ((section->loadstate != TSLS_LOADED) && !(flags & TGS_ANYSTATE))
|
|
section = NULL;
|
|
}
|
|
if (section)
|
|
section->timestamp = realtime;
|
|
|
|
return section;
|
|
}
|
|
|
|
/*save all currently loaded sections*/
|
|
int Heightmap_Save(heightmap_t *hm)
|
|
{
|
|
hmsection_t *s;
|
|
int x, y;
|
|
int sectionssaved = 0;
|
|
for (x = hm->firstsegx; x < hm->maxsegx; x++)
|
|
{
|
|
for (y = hm->firstsegy; y < hm->maxsegy; y++)
|
|
{
|
|
s = Terr_GetSection(hm, x, y, TGS_NOLOAD);
|
|
if (!s)
|
|
continue;
|
|
if (s->flags & TSF_EDITED)
|
|
{
|
|
/* //make sure all the parts are loaded before trying to write them, so we don't try reading partial files, which would be bad, mmkay?
|
|
for (sy = y&~(SECTIONSPERBLOCK-1); sy < y+SECTIONSPERBLOCK && sy < hm->maxsegy; sy++)
|
|
{
|
|
for (sx = x&~(SECTIONSPERBLOCK-1); sx < x+SECTIONSPERBLOCK && sx < hm->maxsegx; sx++)
|
|
{
|
|
os = Terr_GetSection(hm, sx, sy, TGS_WAITLOAD|TGS_NODOWNLOAD|TGS_NORENDER);
|
|
if (os)
|
|
os->flags |= TSF_EDITED;
|
|
}
|
|
}
|
|
*/
|
|
|
|
if (Terr_SaveSection(hm, s, x, y, true))
|
|
{
|
|
s->flags &= ~TSF_EDITED;
|
|
sectionssaved++;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
return sectionssaved;
|
|
}
|
|
|
|
#ifndef CLIENTONLY
|
|
//on servers, we can get requests to download current map sections. if so, give them it.
|
|
qboolean Terrain_LocateSection(const char *name, flocation_t *loc)
|
|
{
|
|
heightmap_t *hm;
|
|
hmsection_t *s;
|
|
int x, y;
|
|
char fname[MAX_QPATH];
|
|
|
|
//reject if its not in maps
|
|
if (Q_strncasecmp(name, "maps/", 5))
|
|
return false;
|
|
|
|
if (!sv.world.worldmodel)
|
|
return false;
|
|
hm = sv.world.worldmodel->terrain;
|
|
if (!Terr_IsSectionFName(hm, name, &x, &y))
|
|
return false;
|
|
|
|
//verify that its valid
|
|
if (strcmp(name, Terr_DiskSectionName(hm, x, y, fname, sizeof(fname))))
|
|
return false;
|
|
|
|
s = Terr_GetSection(hm, x, y, TGS_NOLOAD);
|
|
if (!s || !(s->flags & TSF_EDITED))
|
|
return false; //its not been edited, might as well just use the regular file
|
|
|
|
if (!Terr_SaveSection(hm, s, x, y, false))
|
|
return false;
|
|
|
|
return FS_FLocateFile(name, FSLF_IFFOUND, loc);
|
|
}
|
|
#endif
|
|
|
|
void Terr_DestroySection(heightmap_t *hm, hmsection_t *s, qboolean lightmapreusable)
|
|
{
|
|
if (s && s->loadstate == TSLS_LOADING0)
|
|
COM_WorkerPartialSync(s, &s->loadstate, TSLS_LOADING0);
|
|
if (s && s->loadstate == TSLS_LOADING1)
|
|
COM_WorkerPartialSync(s, &s->loadstate, TSLS_LOADING1);
|
|
if (s && s->loadstate == TSLS_LOADING2)
|
|
COM_MainThreadFlush(); //make sure any associated lightmaps also got read+handled
|
|
|
|
if (!s || s->loadstate < TSLS_LOADING2)
|
|
return;
|
|
|
|
validatelinks(&hm->recycle);
|
|
|
|
RemoveLink(&s->recycle);
|
|
validatelinks(&s->hmmod->recycle);
|
|
|
|
Terr_ClearSection(s);
|
|
|
|
#ifndef SERVERONLY
|
|
if (s->lightmap >= 0)
|
|
{
|
|
struct lmsect_s *lms;
|
|
|
|
if (lightmapreusable)
|
|
{
|
|
lms = BZ_Malloc(sizeof(*lms));
|
|
lms->lm = s->lightmap;
|
|
lms->x = s->lmx;
|
|
lms->y = s->lmy;
|
|
lms->next = hm->unusedlmsects;
|
|
hm->unusedlmsects = lms;
|
|
hm->numunusedlmsects++;
|
|
}
|
|
hm->numusedlmsects--;
|
|
}
|
|
|
|
if (hm->relight == s)
|
|
hm->relight = NULL;
|
|
|
|
#ifdef GLQUAKE
|
|
if (qrenderer == QR_OPENGL && qglDeleteBuffersARB)
|
|
{
|
|
qglDeleteBuffersARB(1, &s->vbo.coord.gl.vbo);
|
|
s->vbo.coord.gl.vbo = 0;
|
|
qglDeleteBuffersARB(1, &s->vbo.indicies.gl.vbo);
|
|
s->vbo.indicies.gl.vbo = 0;
|
|
}
|
|
#endif
|
|
//FIXME: BE_ClearVBO(&s->vbo);
|
|
|
|
Z_Free(s->ents);
|
|
Z_Free(s->mesh.xyz_array);
|
|
Z_Free(s->mesh.indexes);
|
|
#endif
|
|
|
|
Z_Free(s);
|
|
|
|
hm->activesections--;
|
|
|
|
validatelinks(&hm->recycle);
|
|
}
|
|
|
|
#ifndef SERVERONLY
|
|
//dedicated servers do not support editing. no lightmap info causes problems.
|
|
|
|
//when a terrain section has the notify flag set on the server, the server needs to go through and set out notifications to replicate it to the various clients
|
|
//so the clients know to re-download the section.
|
|
static void Terr_DoEditNotify(heightmap_t *hm)
|
|
{
|
|
int i;
|
|
char *cmd;
|
|
hmsection_t *s;
|
|
link_t *ln = &hm->recycle;
|
|
|
|
if (!sv.state)
|
|
return;
|
|
|
|
for (i = 0; i < sv.allocated_client_slots; i++)
|
|
{
|
|
if (svs.clients[i].state >= cs_connected && svs.clients[i].netchan.remote_address.type != NA_LOOPBACK)
|
|
{
|
|
if (svs.clients[i].backbuf.cursize)
|
|
return;
|
|
}
|
|
}
|
|
|
|
for (ln = &hm->recycle; ln->next != &hm->recycle; ln = &s->recycle)
|
|
{
|
|
s = (hmsection_t*)ln->next;
|
|
if (s->flags & TSF_NOTIFY)
|
|
{
|
|
s->flags &= ~TSF_NOTIFY;
|
|
cmd = va("mod_terrain_reload %s %i %i\n", hm->path, s->sx - CHUNKBIAS, s->sy - CHUNKBIAS);
|
|
for (i = 0; i < sv.allocated_client_slots; i++)
|
|
{
|
|
if (svs.clients[i].state >= cs_connected && svs.clients[i].netchan.remote_address.type != NA_LOOPBACK)
|
|
{
|
|
SV_StuffcmdToClient(&svs.clients[i], cmd);
|
|
}
|
|
}
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
|
|
#ifndef SERVERONLY
|
|
//garbage collect the oldest section, to make space for another
|
|
static qboolean Terr_Collect(heightmap_t *hm)
|
|
{
|
|
hmcluster_t *c;
|
|
hmsection_t *s;
|
|
int cx, cy;
|
|
int sx, sy;
|
|
float timeout = realtime-2; //must used no later than 2 seconds in the past
|
|
|
|
link_t *ln = &hm->recycle;
|
|
validatelinks(&hm->recycle);
|
|
for (ln = &hm->recycle; ln->next != &hm->recycle; )
|
|
{
|
|
s = (hmsection_t*)ln->next;
|
|
if ((s->flags & TSF_EDITED) || s->loadstate <= TSLS_LOADING2 || s->timestamp > timeout)
|
|
ln = &s->recycle;
|
|
else
|
|
{
|
|
cx = s->sx/MAXSECTIONS;
|
|
cy = s->sy/MAXSECTIONS;
|
|
c = hm->cluster[cx + cy*MAXCLUSTERS];
|
|
sx = s->sx & (MAXSECTIONS-1);
|
|
sy = s->sy & (MAXSECTIONS-1);
|
|
if (c->section[sx+sy*MAXSECTIONS] != s)
|
|
Sys_Error("invalid section collection");
|
|
c->section[sx+sy*MAXSECTIONS] = NULL;
|
|
|
|
#if 0
|
|
if (hm->relight == s)
|
|
hm->relight = NULL;
|
|
RemoveLink(&s->recycle);
|
|
InsertLinkAfter(&s->recycle, &hm->collected);
|
|
hm->activesections--;
|
|
#else
|
|
Terr_DestroySection(hm, s, true);
|
|
#endif
|
|
validatelinks(&hm->recycle);
|
|
return true;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
#endif
|
|
#endif
|
|
|
|
/*purge all sections, but not root
|
|
lightmaps only are purged whenever the client rudely kills lightmaps (purges all lightmaps on map changes, to cope with models/maps potentially being unloaded)
|
|
we'll reload those when its next seen.
|
|
(lightmaps will already have been destroyed, so no poking them)
|
|
*/
|
|
void Terr_PurgeTerrainModel(model_t *mod, qboolean lightmapsonly, qboolean lightmapreusable)
|
|
{
|
|
heightmap_t *hm = mod->terrain;
|
|
hmcluster_t *c;
|
|
hmsection_t *s;
|
|
int cx, cy;
|
|
int sx, sy;
|
|
|
|
validatelinks(&hm->recycle);
|
|
|
|
// Con_Printf("PrePurge: %i lm chunks used, %i unused\n", hm->numusedlmsects, hm->numunusedlmsects);
|
|
|
|
for (cy = 0; cy < MAXCLUSTERS; cy++)
|
|
for (cx = 0; cx < MAXCLUSTERS; cx++)
|
|
{
|
|
int numremaining = 0;
|
|
c = hm->cluster[cx + cy*MAXCLUSTERS];
|
|
if (!c)
|
|
continue;
|
|
|
|
for (sy = 0; sy < MAXSECTIONS; sy++)
|
|
for (sx = 0; sx < MAXSECTIONS; sx++)
|
|
{
|
|
s = c->section[sx + sy*MAXSECTIONS];
|
|
if (!s)
|
|
{
|
|
}
|
|
else if (lightmapsonly)
|
|
{
|
|
numremaining++;
|
|
#ifndef SERVERONLY
|
|
s->lightmap = -1;
|
|
#endif
|
|
}
|
|
else
|
|
{
|
|
c->section[sx+sy*MAXSECTIONS] = NULL;
|
|
|
|
validatelinks(&hm->recycle);
|
|
Terr_DestroySection(hm, s, lightmapreusable);
|
|
validatelinks(&hm->recycle);
|
|
}
|
|
}
|
|
if (!numremaining)
|
|
{
|
|
hm->cluster[cx + cy*MAXSECTIONS] = NULL;
|
|
BZ_Free(c);
|
|
validatelinks(&hm->recycle);
|
|
}
|
|
}
|
|
validatelinks(&hm->recycle);
|
|
#ifndef SERVERONLY
|
|
if (!lightmapreusable)
|
|
{
|
|
while (hm->unusedlmsects)
|
|
{
|
|
struct lmsect_s *lms;
|
|
lms = hm->unusedlmsects;
|
|
hm->unusedlmsects = lms->next;
|
|
BZ_Free(lms);
|
|
|
|
hm->numunusedlmsects--;
|
|
}
|
|
|
|
|
|
hm->recalculatebrushlighting = true;
|
|
BZ_Free(hm->brushlmremaps);
|
|
hm->brushlmremaps = NULL;
|
|
hm->brushmaxlms = 0;
|
|
}
|
|
#endif
|
|
validatelinks(&hm->recycle);
|
|
|
|
// Con_Printf("PostPurge: %i lm chunks used, %i unused\n", hm->numusedlmsects, hm->numunusedlmsects);
|
|
}
|
|
|
|
void Terr_FreeModel(model_t *mod)
|
|
{
|
|
heightmap_t *hm = mod->terrain;
|
|
if (hm)
|
|
{
|
|
validatelinks(&hm->recycle);
|
|
while(hm->numbrushes)
|
|
Terr_Brush_DeleteIdx(hm, hm->numbrushes-1);
|
|
while(hm->brushtextures)
|
|
{
|
|
brushtex_t *bt = hm->brushtextures;
|
|
#ifndef SERVERONLY
|
|
brushbatch_t *bb;
|
|
while((bb = bt->batches))
|
|
{
|
|
bt->batches = bb->next;
|
|
BE_VBO_Destroy(&bb->vbo.coord, bb->vbo.vbomem);
|
|
BE_VBO_Destroy(&bb->vbo.indicies, bb->vbo.ebomem);
|
|
BZ_Free(bb);
|
|
}
|
|
#endif
|
|
hm->brushtextures = bt->next;
|
|
BZ_Free(bt);
|
|
}
|
|
#ifdef RUNTIMELIGHTING
|
|
if (hm->relightcontext)
|
|
LightShutdown(hm->relightcontext, mod);
|
|
if (hm->lightthreadmem && !hm->inheritedlightthreadmem)
|
|
BZ_Free(hm->lightthreadmem);
|
|
#endif
|
|
BZ_Free(hm->wbrushes);
|
|
Terr_PurgeTerrainModel(mod, false, false);
|
|
while(hm->entities)
|
|
{
|
|
struct hmentity_s *n = hm->entities->next;
|
|
Z_Free(hm->entities);
|
|
hm->entities = n;
|
|
}
|
|
Sys_DestroyMutex(hm->entitylock);
|
|
Z_Free(hm);
|
|
mod->terrain = NULL;
|
|
}
|
|
}
|
|
|
|
#ifndef SERVERONLY
|
|
void Terr_DrawTerrainWater(heightmap_t *hm, float *mins, float *maxs, struct hmwater_s *w)
|
|
{
|
|
scenetris_t *t;
|
|
int flags = BEF_NOSHADOWS;
|
|
int firstv;
|
|
int y, x;
|
|
|
|
//need to filter by height too, or reflections won't work properly.
|
|
if (cl_numstris && cl_stris[cl_numstris-1].shader == w->shader && cl_stris[cl_numstris-1].flags == flags && cl_strisvertv[cl_stris[cl_numstris-1].firstvert][2] == w->maxheight)
|
|
{
|
|
t = &cl_stris[cl_numstris-1];
|
|
}
|
|
else
|
|
{
|
|
if (cl_numstris == cl_maxstris)
|
|
{
|
|
cl_maxstris+=8;
|
|
cl_stris = BZ_Realloc(cl_stris, sizeof(*cl_stris)*cl_maxstris);
|
|
}
|
|
t = &cl_stris[cl_numstris++];
|
|
t->shader = w->shader;
|
|
t->flags = flags;
|
|
t->firstidx = cl_numstrisidx;
|
|
t->firstvert = cl_numstrisvert;
|
|
t->numvert = 0;
|
|
t->numidx = 0;
|
|
}
|
|
|
|
if (!w->simple)
|
|
{
|
|
float step = (maxs[0] - mins[0]) / 8;
|
|
if (cl_numstrisidx+9*9*6 > cl_maxstrisidx)
|
|
{
|
|
cl_maxstrisidx=cl_numstrisidx+12 + 9*9*6*4;
|
|
cl_strisidx = BZ_Realloc(cl_strisidx, sizeof(*cl_strisidx)*cl_maxstrisidx);
|
|
}
|
|
if (cl_numstrisvert+9*9 > cl_maxstrisvert)
|
|
{
|
|
cl_maxstrisvert+=9*9+64;
|
|
cl_strisvertv = BZ_Realloc(cl_strisvertv, sizeof(*cl_strisvertv)*cl_maxstrisvert);
|
|
cl_strisvertt = BZ_Realloc(cl_strisvertt, sizeof(*cl_strisvertt)*cl_maxstrisvert);
|
|
cl_strisvertc = BZ_Realloc(cl_strisvertc, sizeof(*cl_strisvertc)*cl_maxstrisvert);
|
|
}
|
|
|
|
firstv = t->numvert;
|
|
for (y = 0; y < 9; y++)
|
|
{
|
|
for (x = 0; x < 9; x++)
|
|
{
|
|
cl_strisvertv[cl_numstrisvert][0] = mins[0] + step*x;
|
|
cl_strisvertv[cl_numstrisvert][1] = mins[1] + step*y;
|
|
cl_strisvertv[cl_numstrisvert][2] = w->heights[x + y*9];
|
|
cl_strisvertt[cl_numstrisvert][0] = cl_strisvertv[cl_numstrisvert][0]/64;
|
|
cl_strisvertt[cl_numstrisvert][1] = cl_strisvertv[cl_numstrisvert][1]/64;
|
|
Vector4Set(cl_strisvertc[cl_numstrisvert], 1,1,1,1)
|
|
cl_numstrisvert++;
|
|
}
|
|
}
|
|
for (y = 0; y < 8; y++)
|
|
{
|
|
for (x = 0; x < 8; x++)
|
|
{
|
|
if (w->holes[y] & (1u<<x))
|
|
continue;
|
|
cl_strisidx[cl_numstrisidx++] = firstv+(x+0)+(y+0)*9;
|
|
cl_strisidx[cl_numstrisidx++] = firstv+(x+0)+(y+1)*9;
|
|
cl_strisidx[cl_numstrisidx++] = firstv+(x+1)+(y+0)*9;
|
|
|
|
cl_strisidx[cl_numstrisidx++] = firstv+(x+1)+(y+0)*9;
|
|
cl_strisidx[cl_numstrisidx++] = firstv+(x+0)+(y+1)*9;
|
|
cl_strisidx[cl_numstrisidx++] = firstv+(x+1)+(y+1)*9;
|
|
}
|
|
}
|
|
t->numidx = cl_numstrisidx - t->firstidx;
|
|
t->numvert = cl_numstrisvert - t->firstvert;
|
|
}
|
|
else
|
|
{
|
|
if (cl_numstrisidx+12 > cl_maxstrisidx)
|
|
{
|
|
cl_maxstrisidx=cl_numstrisidx+12 + 64;
|
|
cl_strisidx = BZ_Realloc(cl_strisidx, sizeof(*cl_strisidx)*cl_maxstrisidx);
|
|
}
|
|
if (cl_numstrisvert+4 > cl_maxstrisvert)
|
|
{
|
|
cl_maxstrisvert+=64;
|
|
cl_strisvertv = BZ_Realloc(cl_strisvertv, sizeof(*cl_strisvertv)*cl_maxstrisvert);
|
|
cl_strisvertt = BZ_Realloc(cl_strisvertt, sizeof(*cl_strisvertt)*cl_maxstrisvert);
|
|
cl_strisvertc = BZ_Realloc(cl_strisvertc, sizeof(*cl_strisvertc)*cl_maxstrisvert);
|
|
}
|
|
|
|
{
|
|
VectorSet(cl_strisvertv[cl_numstrisvert], mins[0], mins[1], w->maxheight);
|
|
Vector4Set(cl_strisvertc[cl_numstrisvert], 1,1,1,1)
|
|
Vector2Set(cl_strisvertt[cl_numstrisvert], mins[0]/64, mins[1]/64);
|
|
cl_numstrisvert++;
|
|
|
|
VectorSet(cl_strisvertv[cl_numstrisvert], mins[0], maxs[1], w->maxheight);
|
|
Vector4Set(cl_strisvertc[cl_numstrisvert], 1,1,1,1)
|
|
Vector2Set(cl_strisvertt[cl_numstrisvert], mins[0]/64, maxs[1]/64);
|
|
cl_numstrisvert++;
|
|
|
|
VectorSet(cl_strisvertv[cl_numstrisvert], maxs[0], maxs[1], w->maxheight);
|
|
Vector4Set(cl_strisvertc[cl_numstrisvert], 1,1,1,1)
|
|
Vector2Set(cl_strisvertt[cl_numstrisvert], maxs[0]/64, maxs[1]/64);
|
|
cl_numstrisvert++;
|
|
|
|
VectorSet(cl_strisvertv[cl_numstrisvert], maxs[0], mins[1], w->maxheight);
|
|
Vector4Set(cl_strisvertc[cl_numstrisvert], 1,1,1,1)
|
|
Vector2Set(cl_strisvertt[cl_numstrisvert], maxs[0]/64, mins[1]/64);
|
|
cl_numstrisvert++;
|
|
}
|
|
|
|
|
|
firstv = t->numvert;
|
|
|
|
/*build the triangles*/
|
|
cl_strisidx[cl_numstrisidx++] = firstv + 0;
|
|
cl_strisidx[cl_numstrisidx++] = firstv + 1;
|
|
cl_strisidx[cl_numstrisidx++] = firstv + 2;
|
|
|
|
cl_strisidx[cl_numstrisidx++] = firstv + 0;
|
|
cl_strisidx[cl_numstrisidx++] = firstv + 2;
|
|
cl_strisidx[cl_numstrisidx++] = firstv + 3;
|
|
|
|
cl_strisidx[cl_numstrisidx++] = firstv + 3;
|
|
cl_strisidx[cl_numstrisidx++] = firstv + 2;
|
|
cl_strisidx[cl_numstrisidx++] = firstv + 1;
|
|
|
|
cl_strisidx[cl_numstrisidx++] = firstv + 3;
|
|
cl_strisidx[cl_numstrisidx++] = firstv + 1;
|
|
cl_strisidx[cl_numstrisidx++] = firstv + 0;
|
|
|
|
|
|
t->numidx = cl_numstrisidx - t->firstidx;
|
|
t->numvert = cl_numstrisvert - t->firstvert;
|
|
}
|
|
}
|
|
|
|
static void Terr_RebuildMesh(model_t *model, hmsection_t *s, int x, int y)
|
|
{
|
|
int vx, vy;
|
|
int v;
|
|
mesh_t *mesh = &s->mesh;
|
|
heightmap_t *hm = s->hmmod;
|
|
|
|
Terr_InitLightmap(s, false);
|
|
|
|
s->minh = 9999999999999999.f;
|
|
s->maxh = -9999999999999999.f;
|
|
|
|
switch(hm->mode)
|
|
{
|
|
case HMM_BLOCKS:
|
|
//tiles, like dungeon keeper
|
|
if (mesh->xyz_array)
|
|
BZ_Free(mesh->xyz_array);
|
|
{
|
|
mesh->xyz_array = BZ_Malloc((sizeof(vecV_t)+sizeof(vec2_t)+sizeof(vec2_t)) * (SECTHEIGHTSIZE-1)*(SECTHEIGHTSIZE-1)*4*3);
|
|
mesh->st_array = (void*) (mesh->xyz_array + (SECTHEIGHTSIZE-1)*(SECTHEIGHTSIZE-1)*4*3);
|
|
mesh->lmst_array[0] = (void*) (mesh->st_array + (SECTHEIGHTSIZE-1)*(SECTHEIGHTSIZE-1)*4*3);
|
|
}
|
|
mesh->numvertexes = 0;
|
|
|
|
if (mesh->indexes)
|
|
BZ_Free(mesh->indexes);
|
|
mesh->indexes = BZ_Malloc(sizeof(index_t) * SECTHEIGHTSIZE*SECTHEIGHTSIZE*6*3);
|
|
mesh->numindexes = 0;
|
|
mesh->colors4f_array[0] = NULL;
|
|
|
|
for (vy = 0; vy < SECTHEIGHTSIZE-1; vy++)
|
|
{
|
|
for (vx = 0; vx < SECTHEIGHTSIZE-1; vx++)
|
|
{
|
|
float st[2], inst[2];
|
|
#if SECTHEIGHTSIZE == 17
|
|
int holebit;
|
|
int holerow;
|
|
|
|
//skip generation of the mesh above holes
|
|
holerow = ((vy<<3)/(SECTHEIGHTSIZE-1));
|
|
holebit = 1u<<((vx<<3)/(SECTHEIGHTSIZE-1));
|
|
if (s->holes[holerow] & holebit)
|
|
continue;
|
|
#endif
|
|
|
|
//top face
|
|
v = mesh->numvertexes;
|
|
mesh->numvertexes += 4;
|
|
mesh->xyz_array[v+0][0] = (x-CHUNKBIAS + (vx+0)/(SECTHEIGHTSIZE-1.0f)) * hm->sectionsize;
|
|
mesh->xyz_array[v+0][1] = (y-CHUNKBIAS + (vy+0)/(SECTHEIGHTSIZE-1.0f)) * hm->sectionsize;
|
|
mesh->xyz_array[v+0][2] = s->heights[vx + vy*SECTHEIGHTSIZE];
|
|
|
|
mesh->xyz_array[v+1][0] = (x-CHUNKBIAS + (vx+1)/(SECTHEIGHTSIZE-1.0f)) * hm->sectionsize;
|
|
mesh->xyz_array[v+1][1] = (y-CHUNKBIAS + (vy+0)/(SECTHEIGHTSIZE-1.0f)) * hm->sectionsize;
|
|
mesh->xyz_array[v+1][2] = s->heights[vx + vy*SECTHEIGHTSIZE];
|
|
|
|
mesh->xyz_array[v+2][0] = (x-CHUNKBIAS + (vx+0)/(SECTHEIGHTSIZE-1.0f)) * hm->sectionsize;
|
|
mesh->xyz_array[v+2][1] = (y-CHUNKBIAS + (vy+1)/(SECTHEIGHTSIZE-1.0f)) * hm->sectionsize;
|
|
mesh->xyz_array[v+2][2] = s->heights[vx + vy*SECTHEIGHTSIZE];
|
|
|
|
mesh->xyz_array[v+3][0] = (x-CHUNKBIAS + (vx+1)/(SECTHEIGHTSIZE-1.0f)) * hm->sectionsize;
|
|
mesh->xyz_array[v+3][1] = (y-CHUNKBIAS + (vy+1)/(SECTHEIGHTSIZE-1.0f)) * hm->sectionsize;
|
|
mesh->xyz_array[v+3][2] = s->heights[vx + vy*SECTHEIGHTSIZE];
|
|
|
|
if (s->maxh < mesh->xyz_array[v][2])
|
|
s->maxh = mesh->xyz_array[v][2];
|
|
if (s->minh > mesh->xyz_array[v][2])
|
|
s->minh = mesh->xyz_array[v][2];
|
|
|
|
st[0] = 1.0f/hm->tilecount[0] * vx;
|
|
st[1] = 1.0f/hm->tilecount[1] * vy;
|
|
inst[0] = 0.5f/(hm->tilecount[0]*hm->tilepixcount[0]);
|
|
inst[1] = 0.5f/(hm->tilecount[1]*hm->tilepixcount[1]);
|
|
mesh->st_array[v+0][0] = st[0]+inst[0];
|
|
mesh->st_array[v+0][1] = st[1]+inst[1];
|
|
mesh->st_array[v+1][0] = st[0]-inst[0]+1.0f/hm->tilecount[0];
|
|
mesh->st_array[v+1][1] = st[1]+inst[1];
|
|
mesh->st_array[v+2][0] = st[0]+inst[0];
|
|
mesh->st_array[v+2][1] = st[1]-inst[1]+1.0f/hm->tilecount[1];
|
|
mesh->st_array[v+3][0] = st[0]-inst[0]+1.0f/hm->tilecount[0];
|
|
mesh->st_array[v+3][1] = st[1]-inst[1]+1.0f/hm->tilecount[1];
|
|
|
|
//calc the position in the range -0.5 to 0.5
|
|
mesh->lmst_array[0][v][0] = (((float)vx / (SECTHEIGHTSIZE-1))-0.5);
|
|
mesh->lmst_array[0][v][1] = (((float)vy / (SECTHEIGHTSIZE-1))-0.5);
|
|
//scale down to a half-texel
|
|
mesh->lmst_array[0][v][0] *= (SECTTEXSIZE-1.0f)/HMLMSTRIDE;
|
|
mesh->lmst_array[0][v][1] *= (SECTTEXSIZE-1.0f)/HMLMSTRIDE;
|
|
//bias it
|
|
mesh->lmst_array[0][v][0] += ((float)SECTTEXSIZE/(HMLMSTRIDE*2)) + ((float)(s->lmx) / HMLMSTRIDE);
|
|
mesh->lmst_array[0][v][1] += ((float)SECTTEXSIZE/(HMLMSTRIDE*2)) + ((float)(s->lmy) / HMLMSTRIDE);
|
|
|
|
mesh->indexes[mesh->numindexes++] = v+0;
|
|
mesh->indexes[mesh->numindexes++] = v+2;
|
|
mesh->indexes[mesh->numindexes++] = v+1;
|
|
mesh->indexes[mesh->numindexes++] = v+1;
|
|
mesh->indexes[mesh->numindexes++] = v+2;
|
|
mesh->indexes[mesh->numindexes++] = v+1+2;
|
|
|
|
|
|
//x boundary
|
|
v = mesh->numvertexes;
|
|
mesh->numvertexes += 4;
|
|
mesh->xyz_array[v+0][0] = (x-CHUNKBIAS + (vx+1)/(SECTHEIGHTSIZE-1.0f)) * hm->sectionsize;
|
|
mesh->xyz_array[v+0][1] = (y-CHUNKBIAS + (vy+0)/(SECTHEIGHTSIZE-1.0f)) * hm->sectionsize;
|
|
mesh->xyz_array[v+0][2] = s->heights[vx+0 + vy*SECTHEIGHTSIZE];
|
|
|
|
mesh->xyz_array[v+1][0] = (x-CHUNKBIAS + (vx+1)/(SECTHEIGHTSIZE-1.0f)) * hm->sectionsize;
|
|
mesh->xyz_array[v+1][1] = (y-CHUNKBIAS + (vy+0)/(SECTHEIGHTSIZE-1.0f)) * hm->sectionsize;
|
|
mesh->xyz_array[v+1][2] = s->heights[(vx+1) + vy*SECTHEIGHTSIZE];
|
|
|
|
mesh->xyz_array[v+2][0] = (x-CHUNKBIAS + (vx+1)/(SECTHEIGHTSIZE-1.0f)) * hm->sectionsize;
|
|
mesh->xyz_array[v+2][1] = (y-CHUNKBIAS + (vy+1)/(SECTHEIGHTSIZE-1.0f)) * hm->sectionsize;
|
|
mesh->xyz_array[v+2][2] = s->heights[(vx+0) + vy*SECTHEIGHTSIZE];
|
|
|
|
mesh->xyz_array[v+3][0] = (x-CHUNKBIAS + (vx+1)/(SECTHEIGHTSIZE-1.0f)) * hm->sectionsize;
|
|
mesh->xyz_array[v+3][1] = (y-CHUNKBIAS + (vy+1)/(SECTHEIGHTSIZE-1.0f)) * hm->sectionsize;
|
|
mesh->xyz_array[v+3][2] = s->heights[(vx+1) + vy*SECTHEIGHTSIZE];
|
|
|
|
if (s->maxh < mesh->xyz_array[v][2])
|
|
s->maxh = mesh->xyz_array[v][2];
|
|
if (s->minh > mesh->xyz_array[v][2])
|
|
s->minh = mesh->xyz_array[v][2];
|
|
|
|
st[0] = 1.0f/hm->tilecount[0] * vx;
|
|
st[1] = 1.0f/hm->tilecount[1] * vy;
|
|
inst[0] = 0.5f/(hm->tilecount[0]*hm->tilepixcount[0]);
|
|
inst[1] = 0.5f/(hm->tilecount[1]*hm->tilepixcount[1]);
|
|
mesh->st_array[v+0][0] = st[0]+inst[0];
|
|
mesh->st_array[v+0][1] = st[1]+inst[1];
|
|
mesh->st_array[v+1][0] = st[0]+inst[0];
|
|
mesh->st_array[v+1][1] = st[1]-inst[1]+1.0f/hm->tilecount[1];
|
|
mesh->st_array[v+2][0] = st[0]-inst[0]+1.0f/hm->tilecount[0];
|
|
mesh->st_array[v+2][1] = st[1]+inst[1];
|
|
mesh->st_array[v+3][0] = st[0]-inst[0]+1.0f/hm->tilecount[0];
|
|
mesh->st_array[v+3][1] = st[1]-inst[1]+1.0f/hm->tilecount[1];
|
|
|
|
//calc the position in the range -0.5 to 0.5
|
|
mesh->lmst_array[0][v][0] = (((float)vx / (SECTHEIGHTSIZE-1))-0.5);
|
|
mesh->lmst_array[0][v][1] = (((float)vy / (SECTHEIGHTSIZE-1))-0.5);
|
|
//scale down to a half-texel
|
|
mesh->lmst_array[0][v][0] *= (SECTTEXSIZE-1.0f)/HMLMSTRIDE;
|
|
mesh->lmst_array[0][v][1] *= (SECTTEXSIZE-1.0f)/HMLMSTRIDE;
|
|
//bias it
|
|
mesh->lmst_array[0][v][0] += ((float)SECTTEXSIZE/(HMLMSTRIDE*2)) + ((float)(s->lmx) / HMLMSTRIDE);
|
|
mesh->lmst_array[0][v][1] += ((float)SECTTEXSIZE/(HMLMSTRIDE*2)) + ((float)(s->lmy) / HMLMSTRIDE);
|
|
|
|
|
|
mesh->indexes[mesh->numindexes++] = v+0;
|
|
mesh->indexes[mesh->numindexes++] = v+2;
|
|
mesh->indexes[mesh->numindexes++] = v+1;
|
|
mesh->indexes[mesh->numindexes++] = v+1;
|
|
mesh->indexes[mesh->numindexes++] = v+2;
|
|
mesh->indexes[mesh->numindexes++] = v+1+2;
|
|
|
|
//y boundary
|
|
v = mesh->numvertexes;
|
|
mesh->numvertexes += 4;
|
|
mesh->xyz_array[v+0][0] = (x-CHUNKBIAS + (vx+0)/(SECTHEIGHTSIZE-1.0f)) * hm->sectionsize;
|
|
mesh->xyz_array[v+0][1] = (y-CHUNKBIAS + (vy+1)/(SECTHEIGHTSIZE-1.0f)) * hm->sectionsize;
|
|
mesh->xyz_array[v+0][2] = s->heights[vx + (vy+0)*SECTHEIGHTSIZE];
|
|
|
|
mesh->xyz_array[v+1][0] = (x-CHUNKBIAS + (vx+1)/(SECTHEIGHTSIZE-1.0f)) * hm->sectionsize;
|
|
mesh->xyz_array[v+1][1] = (y-CHUNKBIAS + (vy+1)/(SECTHEIGHTSIZE-1.0f)) * hm->sectionsize;
|
|
mesh->xyz_array[v+1][2] = s->heights[vx + (vy+0)*SECTHEIGHTSIZE];
|
|
|
|
mesh->xyz_array[v+2][0] = (x-CHUNKBIAS + (vx+0)/(SECTHEIGHTSIZE-1.0f)) * hm->sectionsize;
|
|
mesh->xyz_array[v+2][1] = (y-CHUNKBIAS + (vy+1)/(SECTHEIGHTSIZE-1.0f)) * hm->sectionsize;
|
|
mesh->xyz_array[v+2][2] = s->heights[vx + (vy+1)*SECTHEIGHTSIZE];
|
|
|
|
mesh->xyz_array[v+3][0] = (x-CHUNKBIAS + (vx+1)/(SECTHEIGHTSIZE-1.0f)) * hm->sectionsize;
|
|
mesh->xyz_array[v+3][1] = (y-CHUNKBIAS + (vy+1)/(SECTHEIGHTSIZE-1.0f)) * hm->sectionsize;
|
|
mesh->xyz_array[v+3][2] = s->heights[vx + (vy+1)*SECTHEIGHTSIZE];
|
|
|
|
if (s->maxh < mesh->xyz_array[v][2])
|
|
s->maxh = mesh->xyz_array[v][2];
|
|
if (s->minh > mesh->xyz_array[v][2])
|
|
s->minh = mesh->xyz_array[v][2];
|
|
|
|
st[0] = 1.0f/hm->tilecount[0] * vx;
|
|
st[1] = 1.0f/hm->tilecount[1] * vy;
|
|
inst[0] = 0.5f/(hm->tilecount[0]*hm->tilepixcount[0]);
|
|
inst[1] = 0.5f/(hm->tilecount[1]*hm->tilepixcount[1]);
|
|
mesh->st_array[v+0][0] = st[0]+inst[0];
|
|
mesh->st_array[v+0][1] = st[1]+inst[1];
|
|
mesh->st_array[v+1][0] = st[0]-inst[0]+1.0f/hm->tilecount[0];
|
|
mesh->st_array[v+1][1] = st[1]+inst[1];
|
|
mesh->st_array[v+2][0] = st[0]+inst[0];
|
|
mesh->st_array[v+2][1] = st[1]-inst[1]+1.0f/hm->tilecount[1];
|
|
mesh->st_array[v+3][0] = st[0]-inst[0]+1.0f/hm->tilecount[0];
|
|
mesh->st_array[v+3][1] = st[1]-inst[1]+1.0f/hm->tilecount[1];
|
|
|
|
//calc the position in the range -0.5 to 0.5
|
|
mesh->lmst_array[0][v][0] = (((float)vx / (SECTHEIGHTSIZE-1))-0.5);
|
|
mesh->lmst_array[0][v][1] = (((float)vy / (SECTHEIGHTSIZE-1))-0.5);
|
|
//scale down to a half-texel
|
|
mesh->lmst_array[0][v][0] *= (SECTTEXSIZE-1.0f)/HMLMSTRIDE;
|
|
mesh->lmst_array[0][v][1] *= (SECTTEXSIZE-1.0f)/HMLMSTRIDE;
|
|
//bias it
|
|
mesh->lmst_array[0][v][0] += ((float)SECTTEXSIZE/(HMLMSTRIDE*2)) + ((float)(s->lmx) / HMLMSTRIDE);
|
|
mesh->lmst_array[0][v][1] += ((float)SECTTEXSIZE/(HMLMSTRIDE*2)) + ((float)(s->lmy) / HMLMSTRIDE);
|
|
|
|
mesh->indexes[mesh->numindexes++] = v+0;
|
|
mesh->indexes[mesh->numindexes++] = v+2;
|
|
mesh->indexes[mesh->numindexes++] = v+1;
|
|
mesh->indexes[mesh->numindexes++] = v+1;
|
|
mesh->indexes[mesh->numindexes++] = v+2;
|
|
mesh->indexes[mesh->numindexes++] = v+1+2;
|
|
}
|
|
}
|
|
break;
|
|
case HMM_TERRAIN:
|
|
//smooth terrain
|
|
if (!mesh->xyz_array)
|
|
{
|
|
mesh->xyz_array = BZ_Malloc((sizeof(vecV_t)+sizeof(vec2_t)+sizeof(vec2_t)) * (SECTHEIGHTSIZE)*(SECTHEIGHTSIZE));
|
|
mesh->st_array = (void*) (mesh->xyz_array + (SECTHEIGHTSIZE)*(SECTHEIGHTSIZE));
|
|
mesh->lmst_array[0] = (void*) (mesh->st_array + (SECTHEIGHTSIZE)*(SECTHEIGHTSIZE));
|
|
}
|
|
mesh->colors4f_array[0] = s->colours;
|
|
mesh->numvertexes = 0;
|
|
/*64 quads across requires 65 verticies*/
|
|
for (vy = 0; vy < SECTHEIGHTSIZE; vy++)
|
|
{
|
|
for (vx = 0; vx < SECTHEIGHTSIZE; vx++)
|
|
{
|
|
v = mesh->numvertexes++;
|
|
mesh->xyz_array[v][0] = (x-CHUNKBIAS + vx/(SECTHEIGHTSIZE-1.0f)) * hm->sectionsize;
|
|
mesh->xyz_array[v][1] = (y-CHUNKBIAS + vy/(SECTHEIGHTSIZE-1.0f)) * hm->sectionsize;
|
|
mesh->xyz_array[v][2] = s->heights[vx + vy*SECTHEIGHTSIZE];
|
|
|
|
if (s->maxh < mesh->xyz_array[v][2])
|
|
s->maxh = mesh->xyz_array[v][2];
|
|
if (s->minh > mesh->xyz_array[v][2])
|
|
s->minh = mesh->xyz_array[v][2];
|
|
|
|
mesh->st_array[v][0] = mesh->xyz_array[v][0] / 128;
|
|
mesh->st_array[v][1] = mesh->xyz_array[v][1] / 128;
|
|
|
|
//calc the position in the range -0.5 to 0.5
|
|
mesh->lmst_array[0][v][0] = (((float)vx / (SECTHEIGHTSIZE-1))-0.5);
|
|
mesh->lmst_array[0][v][1] = (((float)vy / (SECTHEIGHTSIZE-1))-0.5);
|
|
//scale down to a half-texel
|
|
mesh->lmst_array[0][v][0] *= (SECTTEXSIZE-1.0f)/HMLMSTRIDE;
|
|
mesh->lmst_array[0][v][1] *= (SECTTEXSIZE-1.0f)/HMLMSTRIDE;
|
|
//bias it
|
|
mesh->lmst_array[0][v][0] += ((float)SECTTEXSIZE/(HMLMSTRIDE*2)) + ((float)(s->lmx) / HMLMSTRIDE);
|
|
mesh->lmst_array[0][v][1] += ((float)SECTTEXSIZE/(HMLMSTRIDE*2)) + ((float)(s->lmy) / HMLMSTRIDE);
|
|
}
|
|
}
|
|
|
|
if (!mesh->indexes)
|
|
mesh->indexes = BZ_Malloc(sizeof(index_t) * SECTHEIGHTSIZE*SECTHEIGHTSIZE*6);
|
|
|
|
mesh->numindexes = 0;
|
|
for (vy = 0; vy < SECTHEIGHTSIZE-1; vy++)
|
|
{
|
|
for (vx = 0; vx < SECTHEIGHTSIZE-1; vx++)
|
|
{
|
|
#ifndef STRICTEDGES
|
|
float d1,d2;
|
|
#endif
|
|
|
|
#if SECTHEIGHTSIZE == 17
|
|
int holerow;
|
|
int holebit;
|
|
|
|
//skip generation of the mesh above holes
|
|
holerow = ((vy<<3)/(SECTHEIGHTSIZE-1));
|
|
holebit = 1u<<((vx<<3)/(SECTHEIGHTSIZE-1));
|
|
if (s->holes[holerow] & holebit)
|
|
continue;
|
|
#endif
|
|
v = vx + vy*(SECTHEIGHTSIZE);
|
|
|
|
#ifndef STRICTEDGES
|
|
d1 = fabs(mesh->xyz_array[v][2] - mesh->xyz_array[v+1+SECTHEIGHTSIZE][2]);
|
|
d2 = fabs(mesh->xyz_array[v+1][2] - mesh->xyz_array[v+SECTHEIGHTSIZE][2]);
|
|
if (d1 < d2)
|
|
{
|
|
mesh->indexes[mesh->numindexes++] = v+0;
|
|
mesh->indexes[mesh->numindexes++] = v+1+SECTHEIGHTSIZE;
|
|
mesh->indexes[mesh->numindexes++] = v+1;
|
|
mesh->indexes[mesh->numindexes++] = v+0;
|
|
mesh->indexes[mesh->numindexes++] = v+SECTHEIGHTSIZE;
|
|
mesh->indexes[mesh->numindexes++] = v+1+SECTHEIGHTSIZE;
|
|
}
|
|
else
|
|
#endif
|
|
{
|
|
mesh->indexes[mesh->numindexes++] = v+0;
|
|
mesh->indexes[mesh->numindexes++] = v+SECTHEIGHTSIZE;
|
|
mesh->indexes[mesh->numindexes++] = v+1;
|
|
mesh->indexes[mesh->numindexes++] = v+1;
|
|
mesh->indexes[mesh->numindexes++] = v+SECTHEIGHTSIZE;
|
|
mesh->indexes[mesh->numindexes++] = v+1+SECTHEIGHTSIZE;
|
|
}
|
|
}
|
|
}
|
|
break;
|
|
}
|
|
|
|
//pure holes
|
|
if (!mesh->numindexes)
|
|
{
|
|
memset(&s->pvscache, 0, sizeof(s->pvscache));
|
|
return;
|
|
}
|
|
|
|
if (s->maxh_cull < s->maxh)
|
|
s->maxh_cull = s->maxh;
|
|
{
|
|
vec3_t mins, maxs;
|
|
mins[0] = (x-CHUNKBIAS) * hm->sectionsize;
|
|
mins[1] = (y-CHUNKBIAS) * hm->sectionsize;
|
|
mins[2] = s->minh;
|
|
maxs[0] = (x+1-CHUNKBIAS) * hm->sectionsize;
|
|
maxs[1] = (y+1-CHUNKBIAS) * hm->sectionsize;
|
|
maxs[2] = s->maxh_cull;
|
|
model->funcs.FindTouchedLeafs(model, &s->pvscache, mins, maxs);
|
|
}
|
|
|
|
#ifdef GLQUAKE
|
|
if (qrenderer == QR_OPENGL && qglGenBuffersARB)
|
|
{
|
|
if (!s->vbo.coord.gl.vbo)
|
|
{
|
|
qglGenBuffersARB(1, &s->vbo.coord.gl.vbo);
|
|
GL_SelectVBO(s->vbo.coord.gl.vbo);
|
|
}
|
|
else
|
|
GL_SelectVBO(s->vbo.coord.gl.vbo);
|
|
|
|
qglBufferDataARB(GL_ARRAY_BUFFER_ARB, (sizeof(vecV_t)+sizeof(vec2_t)+sizeof(vec2_t)+sizeof(vec4_t)) * (mesh->numvertexes), NULL, GL_STATIC_DRAW_ARB);
|
|
|
|
qglBufferSubDataARB(GL_ARRAY_BUFFER_ARB, 0, (sizeof(vecV_t)+sizeof(vec2_t)+sizeof(vec2_t)) * mesh->numvertexes, mesh->xyz_array);
|
|
if (mesh->colors4f_array[0])
|
|
qglBufferSubDataARB(GL_ARRAY_BUFFER_ARB, (sizeof(vecV_t)+sizeof(vec2_t)+sizeof(vec2_t)) * mesh->numvertexes, sizeof(vec4_t)*mesh->numvertexes, mesh->colors4f_array[0]);
|
|
GL_SelectVBO(0);
|
|
s->vbo.coord.gl.addr = 0;
|
|
s->vbo.texcoord.gl.addr = (void*)((char*)mesh->st_array - (char*)mesh->xyz_array);
|
|
s->vbo.texcoord.gl.vbo = s->vbo.coord.gl.vbo;
|
|
s->vbo.lmcoord[0].gl.addr = (void*)((char*)mesh->lmst_array[0] - (char*)mesh->xyz_array);
|
|
s->vbo.lmcoord[0].gl.vbo = s->vbo.coord.gl.vbo;
|
|
s->vbo.colours[0].gl.addr = (void*)((sizeof(vecV_t)+sizeof(vec2_t)+sizeof(vec2_t)) * mesh->numvertexes);
|
|
s->vbo.colours[0].gl.vbo = s->vbo.coord.gl.vbo;
|
|
|
|
if (!s->vbo.indicies.gl.vbo)
|
|
qglGenBuffersARB(1, &s->vbo.indicies.gl.vbo);
|
|
s->vbo.indicies.gl.addr = 0;
|
|
GL_SelectEBO(s->vbo.indicies.gl.vbo);
|
|
qglBufferDataARB(GL_ELEMENT_ARRAY_BUFFER_ARB, sizeof(index_t) * mesh->numindexes, mesh->indexes, GL_STATIC_DRAW_ARB);
|
|
GL_SelectEBO(0);
|
|
|
|
#if 1
|
|
Z_Free(mesh->xyz_array);
|
|
mesh->xyz_array = NULL;
|
|
mesh->st_array = NULL;
|
|
mesh->lmst_array[0] = NULL;
|
|
|
|
Z_Free(mesh->indexes);
|
|
mesh->indexes = NULL;
|
|
#endif
|
|
}
|
|
#endif
|
|
#ifdef VKQUAKE
|
|
if (qrenderer == QR_VULKAN)
|
|
{
|
|
void VKBE_GenBatchVBOs(vbo_t **vbochain, batch_t *firstbatch, batch_t *stopbatch);
|
|
batch_t batch = {0};
|
|
mesh_t *meshes = &s->mesh;
|
|
vbo_t *vbo = NULL;
|
|
batch.maxmeshes = 1;
|
|
batch.mesh = &meshes;
|
|
|
|
VKBE_GenBatchVBOs(&vbo, &batch, NULL);
|
|
s->vbo = *vbo;
|
|
}
|
|
#endif
|
|
#ifdef D3D11QUAKE
|
|
if (qrenderer == QR_DIRECT3D11)
|
|
{
|
|
void D3D11BE_GenBatchVBOs(vbo_t **vbochain, batch_t *firstbatch, batch_t *stopbatch);
|
|
batch_t batch = {0};
|
|
mesh_t *meshes = &s->mesh;
|
|
vbo_t *vbo = NULL;
|
|
batch.maxmeshes = 1;
|
|
batch.mesh = &meshes;
|
|
|
|
//BE_ClearVBO(&s->vbo);
|
|
D3D11BE_GenBatchVBOs(&vbo, &batch, NULL);
|
|
s->vbo = *vbo;
|
|
}
|
|
#endif
|
|
}
|
|
|
|
struct tdibctx
|
|
{
|
|
heightmap_t *hm;
|
|
int vx;
|
|
int vy;
|
|
entity_t *ent;
|
|
batch_t **batches;
|
|
qbyte *pvs;
|
|
model_t *wmodel;
|
|
};
|
|
void Terr_DrawInBounds(struct tdibctx *ctx, int x, int y, int w, int h)
|
|
{
|
|
vec3_t mins, maxs;
|
|
hmsection_t *s;
|
|
struct hmwater_s *wa;
|
|
int i, j;
|
|
batch_t *b;
|
|
heightmap_t *hm = ctx->hm;
|
|
|
|
mins[0] = (x+0 - CHUNKBIAS)*hm->sectionsize;
|
|
maxs[0] = (x+w - CHUNKBIAS)*hm->sectionsize;
|
|
|
|
mins[1] = (y+0 - CHUNKBIAS)*hm->sectionsize;
|
|
maxs[1] = (y+h - CHUNKBIAS)*hm->sectionsize;
|
|
|
|
mins[2] = r_origin[2]-999999;
|
|
maxs[2] = r_origin[2]+999999;
|
|
|
|
if (w == 1 && h == 1)
|
|
{
|
|
// if (R_CullBox(mins, maxs))
|
|
// return;
|
|
|
|
s = Terr_GetSection(hm, x, y, TGS_LAZYLOAD);
|
|
if (!s)
|
|
return;
|
|
|
|
/*move to head*/
|
|
validatelinks(&hm->recycle);
|
|
RemoveLink(&s->recycle);
|
|
validatelinks(&hm->recycle);
|
|
InsertLinkBefore(&s->recycle, &hm->recycle);
|
|
validatelinks(&hm->recycle);
|
|
|
|
if (s->lightmap < 0)
|
|
Terr_LoadSection(hm, s, x, y, TGS_NODOWNLOAD);
|
|
|
|
if (s->flags & TSF_RELIGHT)
|
|
{
|
|
if (!hm->relight)
|
|
{
|
|
hm->relight = s;
|
|
hm->relightidx = 0;
|
|
hm->relightmin[0] = mins[0];
|
|
hm->relightmin[1] = mins[1];
|
|
}
|
|
}
|
|
|
|
if (s->flags & TSF_DIRTY)
|
|
{
|
|
s->flags &= ~TSF_DIRTY;
|
|
|
|
Terr_RebuildMesh(ctx->wmodel, s, x, y);
|
|
}
|
|
|
|
if (ctx->pvs && !ctx->wmodel->funcs.EdictInFatPVS(ctx->wmodel, &s->pvscache, ctx->pvs))
|
|
return; //this section isn't in any visible bsp leafs
|
|
|
|
if (s->numents)
|
|
{
|
|
Sys_LockMutex(hm->entitylock);
|
|
//chuck out any batches for models in this section
|
|
for (i = 0; i < s->numents; i++)
|
|
{
|
|
struct hmentity_s *e = s->ents[i];
|
|
vec3_t dist;
|
|
float a, dmin, dmax;
|
|
model_t *model;
|
|
//skip the entity if its already been added to some batch this frame.
|
|
if (e->drawnframe == hm->drawnframe)
|
|
continue;
|
|
e->drawnframe = hm->drawnframe;
|
|
|
|
model = e->ent.model;
|
|
if (!model)
|
|
continue;
|
|
|
|
if (model->loadstate == MLS_NOTLOADED)
|
|
{
|
|
// if (hm->beinglazy)
|
|
// continue;
|
|
// hm->beinglazy = true;
|
|
Mod_LoadModel(model, MLV_WARN);
|
|
}
|
|
if (model->loadstate != MLS_LOADED)
|
|
continue;
|
|
|
|
VectorSubtract(e->ent.origin, r_origin, dist);
|
|
a = VectorLength(dist);
|
|
dmin = 1024 + model->radius*160;
|
|
dmax = dmin + 1024;
|
|
a = (a - dmin) / (dmax - dmin);
|
|
a = 1-a;
|
|
if (a < 0)
|
|
continue;
|
|
|
|
if (R_CullSphere(e->ent.origin, model->radius))
|
|
continue;
|
|
|
|
if (a >= 1)
|
|
{
|
|
a = 1;
|
|
e->ent.flags &= ~RF_TRANSLUCENT;
|
|
}
|
|
else
|
|
e->ent.flags |= RF_TRANSLUCENT;
|
|
e->ent.shaderRGBAf[3] = a;
|
|
switch(model->type)
|
|
{
|
|
case mod_alias:
|
|
R_GAlias_GenerateBatches(&e->ent, ctx->batches);
|
|
break;
|
|
case mod_brush:
|
|
Surf_GenBrushBatches(ctx->batches, &e->ent);
|
|
break;
|
|
default: //FIXME: no sprites! oh noes!
|
|
break;
|
|
}
|
|
}
|
|
Sys_UnlockMutex(hm->entitylock);
|
|
}
|
|
|
|
for (wa = s->water; wa; wa = wa->next)
|
|
{
|
|
mins[2] = wa->minheight;
|
|
maxs[2] = wa->maxheight;
|
|
if (!R_CullBox(mins, maxs))
|
|
{
|
|
Terr_DrawTerrainWater(hm, mins, maxs, wa);
|
|
}
|
|
}
|
|
|
|
mins[2] = s->minh;
|
|
maxs[2] = s->maxh;
|
|
|
|
// if (!BoundsIntersect(mins, maxs, r_refdef.vieworg, r_refdef.vieworg))
|
|
if (R_CullBox(mins, maxs))
|
|
return;
|
|
|
|
|
|
if (hm->texmask)
|
|
{
|
|
for (i = 0; i < 4; i++)
|
|
{
|
|
if (!*s->texname[i])
|
|
break;
|
|
if (!strcmp(s->texname[i], hm->texmask))
|
|
break;
|
|
}
|
|
if (i == 4)
|
|
{ //flicker if the surface cannot accept the named texture
|
|
int xor = (x&1)^(y&1);
|
|
if (((int)(realtime*10) & 1) ^ xor)
|
|
return;
|
|
}
|
|
}
|
|
|
|
b = BE_GetTempBatch();
|
|
if (!b)
|
|
return;
|
|
b->ent = ctx->ent;
|
|
b->shader = hm->shader;
|
|
b->flags = 0;
|
|
b->mesh = &s->amesh;
|
|
b->mesh[0] = &s->mesh;
|
|
b->meshes = 1;
|
|
b->buildmeshes = NULL;
|
|
b->skin = &s->textures;
|
|
b->texture = NULL;
|
|
b->vbo = &s->vbo;
|
|
b->lightmap[0] = s->lightmap;
|
|
for (j = 1; j < MAXRLIGHTMAPS; j++)
|
|
b->lightmap[j] = -1;
|
|
|
|
b->next = ctx->batches[b->shader->sort];
|
|
ctx->batches[b->shader->sort] = b;
|
|
}
|
|
else if (w && h)
|
|
{
|
|
//divide and conquer, radiating outwards from the view.
|
|
if (w > h)
|
|
{
|
|
i = x + w;
|
|
w = x + w/2;
|
|
if (ctx->vx >= w)
|
|
{
|
|
Terr_DrawInBounds(ctx, w, y, i-w, h);
|
|
Terr_DrawInBounds(ctx, x, y, w-x, h);
|
|
}
|
|
else
|
|
{
|
|
Terr_DrawInBounds(ctx, x, y, w-x, h);
|
|
Terr_DrawInBounds(ctx, w, y, i-w, h);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
i = y + h;
|
|
h = y + h/2;
|
|
if (ctx->vy >= h)
|
|
{
|
|
Terr_DrawInBounds(ctx, x, h, w, i-h);
|
|
Terr_DrawInBounds(ctx, x, y, w, h-y);
|
|
}
|
|
else
|
|
{
|
|
Terr_DrawInBounds(ctx, x, y, w, h-y);
|
|
Terr_DrawInBounds(ctx, x, h, w, i-h);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void Terr_DrawTerrainModel (batch_t **batches, entity_t *e)
|
|
{
|
|
extern qbyte *frustumvis;
|
|
model_t *m = e->model;
|
|
heightmap_t *hm = m->terrain;
|
|
batch_t *b;
|
|
int bounds[4], j;
|
|
struct tdibctx tdibctx;
|
|
|
|
if (!r_refdef.recurse)
|
|
{
|
|
Terr_DoEditNotify(hm);
|
|
// while (hm->activesections > 0)
|
|
// if (!Terr_Collect(hm))
|
|
// break;
|
|
while (hm->activesections > TERRAINACTIVESECTIONS)
|
|
{
|
|
if (!Terr_Collect(hm))
|
|
break;
|
|
break;
|
|
}
|
|
}
|
|
|
|
// hm->beinglazy = false;
|
|
if (hm->relight)
|
|
ted_dorelight(hm);
|
|
|
|
if (e->model == cl.worldmodel && hm->skyshader)
|
|
{
|
|
b = BE_GetTempBatch();
|
|
if (b)
|
|
{
|
|
for (j = 0; j < MAXRLIGHTMAPS; j++)
|
|
b->lightmap[j] = -1;
|
|
b->ent = e;
|
|
b->shader = hm->skyshader;
|
|
b->flags = 0;
|
|
b->mesh = &hm->askymesh;
|
|
b->mesh[0] = &hm->skymesh;
|
|
b->meshes = 1;
|
|
b->buildmeshes = NULL;
|
|
b->skin = NULL;
|
|
b->texture = NULL;
|
|
// vbo = b->vbo = hm->vbo[x+y*MAXSECTIONS];
|
|
b->vbo = NULL;
|
|
|
|
b->next = batches[b->shader->sort];
|
|
batches[b->shader->sort] = b;
|
|
}
|
|
}
|
|
|
|
Terr_Brush_Draw(hm, batches, e);
|
|
|
|
if (r_refdef.globalfog.density || r_refdef.maxdist>0)
|
|
{
|
|
float culldist;
|
|
extern cvar_t r_fog_exp2;
|
|
|
|
if (r_refdef.globalfog.density)
|
|
{
|
|
//figure out the eyespace distance required to reach that fog value
|
|
culldist = log(0.5/255.0f);
|
|
if (r_fog_exp2.ival)
|
|
culldist = sqrt(culldist / (-r_refdef.globalfog.density * r_refdef.globalfog.density));
|
|
else
|
|
culldist = culldist / (-r_refdef.globalfog.density);
|
|
//anything drawn beyond this point is fully obscured by fog
|
|
culldist += 4096;
|
|
}
|
|
else
|
|
culldist = 999999999999999.f;
|
|
|
|
if (culldist > r_refdef.maxdist && r_refdef.maxdist>0)
|
|
culldist = r_refdef.maxdist;
|
|
|
|
bounds[0] = bound(hm->firstsegx, (r_refdef.vieworg[0] + (CHUNKBIAS + 0)*hm->sectionsize - culldist) / hm->sectionsize, hm->maxsegx);
|
|
bounds[1] = bound(hm->firstsegx, (r_refdef.vieworg[0] + (CHUNKBIAS + 1)*hm->sectionsize + culldist) / hm->sectionsize, hm->maxsegx);
|
|
bounds[2] = bound(hm->firstsegy, (r_refdef.vieworg[1] + (CHUNKBIAS + 0)*hm->sectionsize - culldist) / hm->sectionsize, hm->maxsegy);
|
|
bounds[3] = bound(hm->firstsegy, (r_refdef.vieworg[1] + (CHUNKBIAS + 1)*hm->sectionsize + culldist) / hm->sectionsize, hm->maxsegy);
|
|
}
|
|
else
|
|
{
|
|
bounds[0] = hm->firstsegx;
|
|
bounds[1] = hm->maxsegx;
|
|
bounds[2] = hm->firstsegy;
|
|
bounds[3] = hm->maxsegy;
|
|
}
|
|
//FIXME: project the near+far clip planes onto the screen, generate bounds from those, instead of the above overkill code.
|
|
|
|
hm->drawnframe+=1;
|
|
tdibctx.hm = hm;
|
|
tdibctx.batches = batches;
|
|
tdibctx.ent = e;
|
|
tdibctx.vx = (r_refdef.vieworg[0] + CHUNKBIAS*hm->sectionsize) / hm->sectionsize;
|
|
tdibctx.vy = (r_refdef.vieworg[1] + CHUNKBIAS*hm->sectionsize) / hm->sectionsize;
|
|
tdibctx.wmodel = e->model;
|
|
tdibctx.pvs = (e->model == cl.worldmodel)?frustumvis:NULL;
|
|
validatelinks(&hm->recycle);
|
|
Terr_DrawInBounds(&tdibctx, bounds[0], bounds[2], bounds[1]-bounds[0], bounds[3]-bounds[2]);
|
|
|
|
validatelinks(&hm->recycle);
|
|
/*{
|
|
trace_t trace;
|
|
vec3_t player_mins = {-16, -16, -24};
|
|
vec3_t player_maxs = {16, 16, 32};
|
|
vec3_t start, end;
|
|
VectorCopy(cl.playerview[0].simorg, start);
|
|
VectorCopy(start, end);
|
|
start[0] += 5;
|
|
end[2] -= 100;
|
|
Heightmap_Trace(cl.worldmodel, 0, 0, NULL, start, end, player_mins, player_maxs, false, ~0, &trace);
|
|
}*/
|
|
}
|
|
|
|
void Terrain_ClipDecal(fragmentdecal_t *dec, float *center, float radius, model_t *model)
|
|
{
|
|
int min[2], max[2], mint[2], maxt[2];
|
|
int x, y, tx, ty;
|
|
vecV_t vert[6];
|
|
hmsection_t *s;
|
|
heightmap_t *hm = model->terrain;
|
|
min[0] = floor((center[0] - radius)/(hm->sectionsize)) + CHUNKBIAS;
|
|
min[1] = floor((center[1] - radius)/(hm->sectionsize)) + CHUNKBIAS;
|
|
max[0] = ceil((center[0] + radius)/(hm->sectionsize)) + CHUNKBIAS;
|
|
max[1] = ceil((center[1] + radius)/(hm->sectionsize)) + CHUNKBIAS;
|
|
|
|
min[0] = bound(hm->firstsegx, min[0], hm->maxsegx);
|
|
min[1] = bound(hm->firstsegy, min[1], hm->maxsegy);
|
|
max[0] = bound(hm->firstsegx, max[0], hm->maxsegx);
|
|
max[1] = bound(hm->firstsegy, max[1], hm->maxsegy);
|
|
|
|
for (y = min[1]; y < max[1]; y++)
|
|
{
|
|
for (x = min[0]; x < max[0]; x++)
|
|
{
|
|
s = Terr_GetSection(hm, x, y, TGS_WAITLOAD);
|
|
if (!s)
|
|
continue;
|
|
|
|
mint[0] = floor((center[0] - radius)*(SECTHEIGHTSIZE-1)/(hm->sectionsize) + (CHUNKBIAS - x)*(SECTHEIGHTSIZE-1));
|
|
mint[1] = floor((center[1] - radius)*(SECTHEIGHTSIZE-1)/(hm->sectionsize) + (CHUNKBIAS - y)*(SECTHEIGHTSIZE-1));
|
|
maxt[0] = ceil((center[0] + radius)*(SECTHEIGHTSIZE-1)/(hm->sectionsize) + (CHUNKBIAS - x)*(SECTHEIGHTSIZE-1));
|
|
maxt[1] = ceil((center[1] + radius)*(SECTHEIGHTSIZE-1)/(hm->sectionsize) + (CHUNKBIAS - y)*(SECTHEIGHTSIZE-1));
|
|
|
|
mint[0] = bound(0, mint[0], (SECTHEIGHTSIZE-1));
|
|
mint[1] = bound(0, mint[1], (SECTHEIGHTSIZE-1));
|
|
maxt[0] = bound(0, maxt[0], (SECTHEIGHTSIZE-1));
|
|
maxt[1] = bound(0, maxt[1], (SECTHEIGHTSIZE-1));
|
|
|
|
for (ty = mint[1]; ty < maxt[1]; ty++)
|
|
{
|
|
for (tx = mint[0]; tx < maxt[0]; tx++)
|
|
{
|
|
#ifndef STRICTEDGES
|
|
float d1, d2;
|
|
d1 = fabs(s->heights[(tx+0) + (ty+0)*SECTHEIGHTSIZE] - s->heights[(tx+1) + (ty+1)*SECTHEIGHTSIZE]);
|
|
d2 = fabs(s->heights[(tx+1) + (ty+0)*SECTHEIGHTSIZE] - s->heights[(tx+0) + (ty+1)*SECTHEIGHTSIZE]);
|
|
if (d1 < d2)
|
|
{
|
|
vert[0][0] = (x-CHUNKBIAS)*hm->sectionsize + (tx+0)/(float)(SECTHEIGHTSIZE-1)*hm->sectionsize;vert[0][1] = (y-CHUNKBIAS)*hm->sectionsize + (ty+0)/(float)(SECTHEIGHTSIZE-1)*hm->sectionsize;
|
|
vert[1][0] = (x-CHUNKBIAS)*hm->sectionsize + (tx+1)/(float)(SECTHEIGHTSIZE-1)*hm->sectionsize;vert[1][1] = (y-CHUNKBIAS)*hm->sectionsize + (ty+1)/(float)(SECTHEIGHTSIZE-1)*hm->sectionsize;
|
|
vert[2][0] = (x-CHUNKBIAS)*hm->sectionsize + (tx+1)/(float)(SECTHEIGHTSIZE-1)*hm->sectionsize;vert[2][1] = (y-CHUNKBIAS)*hm->sectionsize + (ty+0)/(float)(SECTHEIGHTSIZE-1)*hm->sectionsize;
|
|
|
|
vert[3][0] = (x-CHUNKBIAS)*hm->sectionsize + (tx+0)/(float)(SECTHEIGHTSIZE-1)*hm->sectionsize;vert[3][1] = (y-CHUNKBIAS)*hm->sectionsize + (ty+0)/(float)(SECTHEIGHTSIZE-1)*hm->sectionsize;
|
|
vert[4][0] = (x-CHUNKBIAS)*hm->sectionsize + (tx+0)/(float)(SECTHEIGHTSIZE-1)*hm->sectionsize;vert[4][1] = (y-CHUNKBIAS)*hm->sectionsize + (ty+1)/(float)(SECTHEIGHTSIZE-1)*hm->sectionsize;
|
|
vert[5][0] = (x-CHUNKBIAS)*hm->sectionsize + (tx+1)/(float)(SECTHEIGHTSIZE-1)*hm->sectionsize;vert[5][1] = (y-CHUNKBIAS)*hm->sectionsize + (ty+1)/(float)(SECTHEIGHTSIZE-1)*hm->sectionsize;
|
|
|
|
vert[0][2] = s->heights[(tx+0) + (ty+0)*SECTHEIGHTSIZE];
|
|
vert[1][2] = s->heights[(tx+1) + (ty+1)*SECTHEIGHTSIZE];
|
|
vert[2][2] = s->heights[(tx+1) + (ty+0)*SECTHEIGHTSIZE];
|
|
vert[3][2] = s->heights[(tx+0) + (ty+0)*SECTHEIGHTSIZE];
|
|
vert[4][2] = s->heights[(tx+0) + (ty+1)*SECTHEIGHTSIZE];
|
|
vert[5][2] = s->heights[(tx+1) + (ty+1)*SECTHEIGHTSIZE];
|
|
}
|
|
else
|
|
#endif
|
|
{
|
|
vert[0][0] = (x-CHUNKBIAS)*hm->sectionsize + (tx+0)/(float)(SECTHEIGHTSIZE-1)*hm->sectionsize;vert[0][1] = (y-CHUNKBIAS)*hm->sectionsize + (ty+0)/(float)(SECTHEIGHTSIZE-1)*hm->sectionsize;
|
|
vert[1][0] = (x-CHUNKBIAS)*hm->sectionsize + (tx+0)/(float)(SECTHEIGHTSIZE-1)*hm->sectionsize;vert[1][1] = (y-CHUNKBIAS)*hm->sectionsize + (ty+1)/(float)(SECTHEIGHTSIZE-1)*hm->sectionsize;
|
|
vert[2][0] = (x-CHUNKBIAS)*hm->sectionsize + (tx+1)/(float)(SECTHEIGHTSIZE-1)*hm->sectionsize;vert[2][1] = (y-CHUNKBIAS)*hm->sectionsize + (ty+0)/(float)(SECTHEIGHTSIZE-1)*hm->sectionsize;
|
|
|
|
vert[3][0] = (x-CHUNKBIAS)*hm->sectionsize + (tx+1)/(float)(SECTHEIGHTSIZE-1)*hm->sectionsize;vert[3][1] = (y-CHUNKBIAS)*hm->sectionsize + (ty+0)/(float)(SECTHEIGHTSIZE-1)*hm->sectionsize;
|
|
vert[4][0] = (x-CHUNKBIAS)*hm->sectionsize + (tx+0)/(float)(SECTHEIGHTSIZE-1)*hm->sectionsize;vert[4][1] = (y-CHUNKBIAS)*hm->sectionsize + (ty+1)/(float)(SECTHEIGHTSIZE-1)*hm->sectionsize;
|
|
vert[5][0] = (x-CHUNKBIAS)*hm->sectionsize + (tx+1)/(float)(SECTHEIGHTSIZE-1)*hm->sectionsize;vert[5][1] = (y-CHUNKBIAS)*hm->sectionsize + (ty+1)/(float)(SECTHEIGHTSIZE-1)*hm->sectionsize;
|
|
|
|
vert[0][2] = s->heights[(tx+0) + (ty+0)*SECTHEIGHTSIZE];
|
|
vert[1][2] = s->heights[(tx+0) + (ty+1)*SECTHEIGHTSIZE];
|
|
vert[2][2] = s->heights[(tx+1) + (ty+0)*SECTHEIGHTSIZE];
|
|
vert[3][2] = s->heights[(tx+1) + (ty+0)*SECTHEIGHTSIZE];
|
|
vert[4][2] = s->heights[(tx+0) + (ty+1)*SECTHEIGHTSIZE];
|
|
vert[5][2] = s->heights[(tx+1) + (ty+1)*SECTHEIGHTSIZE];
|
|
}
|
|
|
|
//fixme: per-section shaders for clutter info. this kinda sucks.
|
|
Fragment_ClipPoly(dec, 3, &vert[0][0], hm->shader);
|
|
Fragment_ClipPoly(dec, 3, &vert[3][0], hm->shader);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
#endif
|
|
|
|
unsigned int Heightmap_PointContentsHM(heightmap_t *hm, float clipmipsz, vec3_t org)
|
|
{
|
|
float x, y;
|
|
float z, tz;
|
|
int sx, sy;
|
|
unsigned int holerow;
|
|
unsigned int holebit;
|
|
hmsection_t *s;
|
|
struct hmwater_s *w;
|
|
unsigned int contents;
|
|
const float wbias = CHUNKBIAS * hm->sectionsize;
|
|
|
|
sx = (org[0]+wbias)/hm->sectionsize;
|
|
sy = (org[1]+wbias)/hm->sectionsize;
|
|
if (sx < hm->firstsegx || sy < hm->firstsegy)
|
|
return hm->exteriorcontents;
|
|
if (sx >= hm->maxsegx || sy >= hm->maxsegy)
|
|
return hm->exteriorcontents;
|
|
s = Terr_GetSection(hm, sx, sy, TGS_TRYLOAD | TGS_ANYSTATE);
|
|
if (!s || s->loadstate != TSLS_LOADED)
|
|
{
|
|
if (s && s->loadstate == TSLS_FAILED)
|
|
return hm->exteriorcontents;
|
|
return FTECONTENTS_SOLID;
|
|
}
|
|
|
|
x = (org[0]+wbias - (sx*hm->sectionsize))*(SECTHEIGHTSIZE-1)/hm->sectionsize;
|
|
y = (org[1]+wbias - (sy*hm->sectionsize))*(SECTHEIGHTSIZE-1)/hm->sectionsize;
|
|
z = (org[2]+clipmipsz);
|
|
|
|
if (z < s->minh-16)
|
|
return hm->exteriorcontents;
|
|
|
|
sx = x; x-=sx;
|
|
sy = y; y-=sy;
|
|
|
|
holerow = ((sy<<3)/(SECTHEIGHTSIZE-1));
|
|
holebit = 1u<<((sx<<3)/(SECTHEIGHTSIZE-1));
|
|
if (s->holes[holerow] & (1u<<holebit))
|
|
return FTECONTENTS_EMPTY;
|
|
|
|
//made of two triangles:
|
|
if (x+y>1) //the 1, 1 triangle
|
|
{
|
|
float v1, v2, v3;
|
|
v3 = 1-y;
|
|
v2 = x+y-1;
|
|
v1 = 1-x;
|
|
//0, 1
|
|
//1, 1
|
|
//1, 0
|
|
tz = (s->heights[(sx+0)+(sy+1)*SECTHEIGHTSIZE]*v1 +
|
|
s->heights[(sx+1)+(sy+1)*SECTHEIGHTSIZE]*v2 +
|
|
s->heights[(sx+1)+(sy+0)*SECTHEIGHTSIZE]*v3);
|
|
}
|
|
else
|
|
{
|
|
float v1, v2, v3;
|
|
v1 = y;
|
|
v2 = x;
|
|
v3 = 1-y-x;
|
|
|
|
//0, 1
|
|
//1, 0
|
|
//0, 0
|
|
tz = (s->heights[(sx+0)+(sy+1)*SECTHEIGHTSIZE]*v1 +
|
|
s->heights[(sx+1)+(sy+0)*SECTHEIGHTSIZE]*v2 +
|
|
s->heights[(sx+0)+(sy+0)*SECTHEIGHTSIZE]*v3);
|
|
}
|
|
if (z <= tz)
|
|
return FTECONTENTS_SOLID; //contained within
|
|
|
|
contents = FTECONTENTS_EMPTY;
|
|
for (w = s->water; w; w = w->next)
|
|
{
|
|
if (w->holes[holerow] & (1u<<holebit))
|
|
continue;
|
|
if (z < w->maxheight) //FIXME
|
|
contents |= w->contentmask;
|
|
}
|
|
return contents;
|
|
}
|
|
|
|
unsigned int Heightmap_PointContents(model_t *model, vec3_t axis[3], vec3_t org)
|
|
{
|
|
heightmap_t *hm = model->terrain;
|
|
unsigned int cont;
|
|
brushes_t *br;
|
|
unsigned int i, j;
|
|
float dist;
|
|
|
|
cont = Heightmap_PointContentsHM(hm, 0, org);
|
|
if (cont & FTECONTENTS_SOLID)
|
|
return cont;
|
|
|
|
|
|
for (i = 0; i < hm->numbrushes; i++)
|
|
{
|
|
br = &hm->wbrushes[i];
|
|
|
|
for (j = 0; j < br->numplanes; j++)
|
|
{
|
|
/*
|
|
for (k=0 ; k<3 ; k++)
|
|
{
|
|
if (in_normals[j][k] < 0)
|
|
best[k] = br->maxs[k];
|
|
else
|
|
best[k] = br->mins[k];
|
|
}
|
|
*/
|
|
dist = DotProduct (org/*best*/, br->planes[j]);
|
|
dist = br->planes[j][3] - dist;
|
|
if (dist < 0)
|
|
break;
|
|
}
|
|
if (j == br->numplanes)
|
|
{
|
|
cont |= br->contents;
|
|
}
|
|
}
|
|
|
|
return cont;
|
|
}
|
|
unsigned int Heightmap_NativeBoxContents(model_t *model, int hulloverride, framestate_t *framestate, vec3_t axis[3], vec3_t org, vec3_t mins, vec3_t maxs)
|
|
{
|
|
heightmap_t *hm = model->terrain;
|
|
return Heightmap_PointContentsHM(hm, mins[2], org);
|
|
}
|
|
|
|
void Heightmap_Normal(heightmap_t *hm, vec3_t org, vec3_t norm)
|
|
{
|
|
#if 0
|
|
norm[0] = 0;
|
|
norm[1] = 0;
|
|
norm[2] = 1;
|
|
#else
|
|
float x, y;
|
|
int sx, sy;
|
|
vec3_t d1, d2;
|
|
const float wbias = CHUNKBIAS * hm->sectionsize;
|
|
hmsection_t *s;
|
|
|
|
norm[0] = 0;
|
|
norm[1] = 0;
|
|
norm[2] = 1;
|
|
|
|
sx = (org[0]+wbias)/hm->sectionsize;
|
|
sy = (org[1]+wbias)/hm->sectionsize;
|
|
if (sx < hm->firstsegx || sy < hm->firstsegy)
|
|
return;
|
|
if (sx >= hm->maxsegx || sy >= hm->maxsegy)
|
|
return;
|
|
s = Terr_GetSection(hm, sx, sy, TGS_TRYLOAD);
|
|
if (!s)
|
|
return;
|
|
|
|
x = (org[0]+wbias - (sx*hm->sectionsize))*(SECTHEIGHTSIZE-1)/hm->sectionsize;
|
|
y = (org[1]+wbias - (sy*hm->sectionsize))*(SECTHEIGHTSIZE-1)/hm->sectionsize;
|
|
|
|
sx = x; x-=sx;
|
|
sy = y; y-=sy;
|
|
|
|
if (x+y>1) //the 1, 1 triangle
|
|
{
|
|
//0, 1
|
|
//1, 1
|
|
//1, 0
|
|
d1[0] = (hm->sectionsize / SECTHEIGHTSIZE);
|
|
d1[1] = 0;
|
|
d1[2] = (s->heights[(sx+1)+(sy+1)*SECTHEIGHTSIZE] - s->heights[(sx+0)+(sy+1)*SECTHEIGHTSIZE]);
|
|
d2[0] = 0;
|
|
d2[1] = (hm->sectionsize / SECTHEIGHTSIZE);
|
|
d2[2] = (s->heights[(sx+1)+(sy+1)*SECTHEIGHTSIZE] - s->heights[(sx+1)+(sy+0)*SECTHEIGHTSIZE]);
|
|
}
|
|
else
|
|
{ //the 0,0 triangle
|
|
//0, 1
|
|
//1, 0
|
|
//0, 0
|
|
d1[0] = (hm->sectionsize / SECTHEIGHTSIZE);
|
|
d1[1] = 0;
|
|
d1[2] = (s->heights[(sx+1)+(sy+0)*SECTHEIGHTSIZE] - s->heights[(sx+0)+(sy+0)*SECTHEIGHTSIZE]);
|
|
d2[0] = 0;
|
|
d2[1] = (hm->sectionsize / SECTHEIGHTSIZE);
|
|
d2[2] = (s->heights[(sx+0)+(sy+1)*SECTHEIGHTSIZE] - s->heights[(sx+0)+(sy+0)*SECTHEIGHTSIZE]);
|
|
}
|
|
|
|
VectorNormalize(d1);
|
|
VectorNormalize(d2);
|
|
CrossProduct(d1, d2, norm);
|
|
VectorNormalize(norm);
|
|
#endif
|
|
}
|
|
|
|
typedef struct {
|
|
vec3_t start;
|
|
vec3_t end;
|
|
vec3_t impact;
|
|
vec4_t plane;
|
|
vec3_t mins;
|
|
vec3_t maxs;
|
|
vec3_t absmins;
|
|
vec3_t absmaxs;
|
|
vec3_t up;
|
|
vec3_t capsulesize;
|
|
enum {ispoint, iscapsule, isbox} shape;
|
|
qboolean startsolid;
|
|
double nearfrac;
|
|
float truefrac;
|
|
float htilesize;
|
|
heightmap_t *hm;
|
|
int contents;
|
|
int hitcontentsmask;
|
|
trace_t *result;
|
|
|
|
#ifdef _DEBUG
|
|
qboolean debug;
|
|
#endif
|
|
} hmtrace_t;
|
|
|
|
static int Heightmap_Trace_Brush(hmtrace_t *tr, vec4_t *planes, int numplanes, brushes_t *brushinfo)
|
|
{
|
|
qboolean startout;
|
|
float *enterplane;
|
|
double enterfrac, exitfrac, nearfrac=0;
|
|
double enterdist=0;
|
|
double dist, d1, d2, f;
|
|
unsigned int i, j;
|
|
vec3_t ofs;
|
|
|
|
startout = false;
|
|
enterplane= NULL;
|
|
enterfrac = -1;
|
|
exitfrac = 10;
|
|
for (i = 0; i < numplanes; i++)
|
|
{
|
|
/*calculate the distance based upon the shape of the object we're tracing for*/
|
|
switch(tr->shape)
|
|
{
|
|
default:
|
|
case isbox: // general box case
|
|
// push the plane out apropriately for mins/maxs
|
|
|
|
// FIXME: use signbits into 8 way lookup for each mins/maxs
|
|
for (j=0 ; j<3 ; j++)
|
|
{
|
|
if (planes[i][j] < 0)
|
|
ofs[j] = tr->maxs[j];
|
|
else
|
|
ofs[j] = tr->mins[j];
|
|
}
|
|
dist = DotProduct (ofs, planes[i]);
|
|
dist = planes[i][3] - dist;
|
|
break;
|
|
case iscapsule:
|
|
dist = DotProduct(tr->up, planes[i]);
|
|
dist = dist*(tr->capsulesize[(dist<0)?1:2]) - tr->capsulesize[0];
|
|
dist = planes[i][3] - dist;
|
|
break;
|
|
case ispoint: // special point case
|
|
dist = planes[i][3];
|
|
break;
|
|
}
|
|
|
|
|
|
d1 = DotProduct (tr->start, planes[i]) - dist;
|
|
d2 = DotProduct (tr->end, planes[i]) - dist;
|
|
|
|
//if we're fully outside any plane, then we cannot possibly enter the brush, skip to the next one
|
|
if (d1 > 0 && d2 >= d1)
|
|
return false;
|
|
|
|
if (d1 > 0)
|
|
startout = true;
|
|
|
|
//if we're fully inside the plane, then whatever is happening is not relevent for this plane
|
|
if (d1 < 0 && d2 <= 0)
|
|
continue;
|
|
|
|
f = (d1) / (d1-d2);
|
|
if (d1 > d2)
|
|
{
|
|
//entered the brush. favour the furthest fraction to avoid extended edges (yay for convex shapes)
|
|
if (enterfrac < f)
|
|
{
|
|
enterfrac = f;
|
|
nearfrac = (d1 - (0.03125)) / (d1-d2);
|
|
enterplane = planes[i];
|
|
enterdist = dist;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
//left the brush, favour the nearest plane (smallest frac)
|
|
if (exitfrac > f)
|
|
{
|
|
exitfrac = f;
|
|
}
|
|
}
|
|
}
|
|
|
|
//non-point traces need to clip against the brush's edges
|
|
if (brushinfo && tr->shape != ispoint && brushinfo->axialplanes != 0x3f)
|
|
{
|
|
static vec3_t axis[] = {{1,0,0},{0,1,0},{0,0,1},{-1,0,0},{0,-1,0},{0,0,-1}};
|
|
for (i = 0; i < 6; i++)
|
|
{
|
|
// if (brushinfo->axialplanes & (1u<<i))
|
|
// continue; //should have already checked this plane.
|
|
if (i >= 3)
|
|
{
|
|
/*calculate the distance based upon the shape of the object we're tracing for*/
|
|
switch(tr->shape)
|
|
{
|
|
default:
|
|
case isbox:
|
|
dist = -tr->maxs[i-3];
|
|
dist = -brushinfo->mins[i-3] - dist;
|
|
break;
|
|
case iscapsule:
|
|
dist = -tr->up[i-3];
|
|
dist = dist*(tr->capsulesize[(dist<0)?1:2]) - tr->capsulesize[0];
|
|
dist = -brushinfo->mins[i-3] - dist;
|
|
break;
|
|
case ispoint:
|
|
dist = -brushinfo->mins[i-3];
|
|
break;
|
|
}
|
|
d1 = -tr->start[i-3] - dist;
|
|
d2 = -tr->end[i-3] - dist;
|
|
}
|
|
else
|
|
{
|
|
switch(tr->shape)
|
|
{
|
|
default:
|
|
case isbox:
|
|
dist = brushinfo->maxs[i] - tr->mins[i];
|
|
break;
|
|
case iscapsule:
|
|
dist = tr->up[i];
|
|
dist = dist*(tr->capsulesize[(dist<0)?1:2]) - tr->capsulesize[0];
|
|
dist = brushinfo->maxs[i] - dist;
|
|
break;
|
|
case ispoint:
|
|
dist = brushinfo->maxs[i];
|
|
break;
|
|
}
|
|
d1 = (tr->start[i]) - dist;
|
|
d2 = (tr->end[i]) - dist;
|
|
}
|
|
|
|
//if we're fully outside any plane, then we cannot possibly enter the brush, skip to the next one
|
|
if (d1 > 0 && d2 >= d1)
|
|
return false;
|
|
|
|
if (d1 > 0)
|
|
startout = true;
|
|
|
|
//if we're fully inside the plane, then whatever is happening is not relevent for this plane
|
|
if (d1 <= 0 && d2 <= 0)
|
|
continue;
|
|
|
|
f = (d1) / (d1-d2);
|
|
if (d1 > d2)
|
|
{
|
|
//entered the brush. favour the furthest fraction to avoid extended edges (yay for convex shapes)
|
|
if (enterfrac < f)
|
|
{
|
|
enterfrac = f;
|
|
nearfrac = (d1 - (0.03125)) / (d1-d2);
|
|
enterplane = axis[i];
|
|
enterdist = dist;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
//left the brush, favour the nearest plane (smallest frac)
|
|
if (exitfrac > f)
|
|
{
|
|
exitfrac = f;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if (!startout)
|
|
{
|
|
|
|
#if 0//def _DEBUG
|
|
if (tr->debug)
|
|
{
|
|
vecV_t facepoints[256];
|
|
unsigned int numpoints;
|
|
|
|
for (i = 0; i < numplanes; i++)
|
|
{
|
|
scenetris_t *t;
|
|
extern shader_t *shader_draw_fill;
|
|
//generate points now (so we know the correct mins+maxs for the brush, and whether the plane is relevent)
|
|
numpoints = Terr_GenerateBrushFace(facepoints, countof(facepoints), planes, numplanes, planes[i]);
|
|
|
|
|
|
if (cl_numstrisvert+numpoints > cl_maxstrisvert)
|
|
break;
|
|
if (cl_numstrisidx+(numpoints-2)*3 > cl_maxstrisidx)
|
|
break;
|
|
|
|
if (cl_numstris == cl_maxstris)
|
|
{
|
|
cl_maxstris+=8;
|
|
cl_stris = BZ_Realloc(cl_stris, sizeof(*cl_stris)*cl_maxstris);
|
|
}
|
|
t = &cl_stris[cl_numstris++];
|
|
t->shader = shader_draw_fill;
|
|
t->flags = 0;
|
|
t->firstidx = cl_numstrisidx;
|
|
t->firstvert = cl_numstrisvert;
|
|
for (j = 2; j < numpoints; j++)
|
|
{
|
|
cl_strisidx[cl_numstrisidx++] = 0;
|
|
cl_strisidx[cl_numstrisidx++] = j-1;
|
|
cl_strisidx[cl_numstrisidx++] = j;
|
|
}
|
|
for (j = 0; j < numpoints; j++)
|
|
{
|
|
VectorCopy(facepoints[j], cl_strisvertv[cl_numstrisvert]);
|
|
cl_strisvertv[cl_numstrisvert][2] += 1;
|
|
Vector4Set(cl_strisvertc[cl_numstrisvert], 1, 0, 0, 0.2);
|
|
Vector2Set(cl_strisvertt[cl_numstrisvert], 0, 0);
|
|
cl_numstrisvert++;
|
|
}
|
|
t->numidx = cl_numstrisidx - t->firstidx;
|
|
t->numvert = cl_numstrisvert-t->firstvert;
|
|
}
|
|
}
|
|
#endif
|
|
|
|
|
|
tr->startsolid = true;
|
|
return false;
|
|
}
|
|
if (enterfrac != -1 && enterfrac < exitfrac)
|
|
{
|
|
//impact!
|
|
if (enterfrac < tr->truefrac)
|
|
{
|
|
if (nearfrac < 0)
|
|
nearfrac = 0;
|
|
tr->nearfrac = nearfrac;
|
|
tr->truefrac = enterfrac;
|
|
tr->plane[3] = enterdist;
|
|
VectorCopy(enterplane, tr->plane);
|
|
|
|
|
|
#if 0//def _DEBUG
|
|
if (tr->debug)
|
|
{
|
|
vecV_t facepoints[256];
|
|
unsigned int numpoints;
|
|
|
|
for (i = 0; i < numplanes; i++)
|
|
{
|
|
scenetris_t *t;
|
|
extern shader_t *shader_draw_fill;
|
|
//generate points now (so we know the correct mins+maxs for the brush, and whether the plane is relevent)
|
|
numpoints = Terr_GenerateBrushFace(facepoints, countof(facepoints), planes, numplanes, planes[i]);
|
|
|
|
|
|
if (cl_numstrisvert+numpoints > cl_maxstrisvert)
|
|
break;
|
|
if (cl_numstrisidx+(numpoints-2)*3 > cl_maxstrisidx)
|
|
break;
|
|
|
|
if (cl_numstris == cl_maxstris)
|
|
{
|
|
cl_maxstris+=8;
|
|
cl_stris = BZ_Realloc(cl_stris, sizeof(*cl_stris)*cl_maxstris);
|
|
}
|
|
t = &cl_stris[cl_numstris++];
|
|
t->shader = shader_draw_fill;
|
|
t->flags = 0;
|
|
t->firstidx = cl_numstrisidx;
|
|
t->firstvert = cl_numstrisvert;
|
|
for (j = 2; j < numpoints; j++)
|
|
{
|
|
cl_strisidx[cl_numstrisidx++] = 0;
|
|
cl_strisidx[cl_numstrisidx++] = j-1;
|
|
cl_strisidx[cl_numstrisidx++] = j;
|
|
}
|
|
for (j = 0; j < numpoints; j++)
|
|
{
|
|
VectorCopy(facepoints[j], cl_strisvertv[cl_numstrisvert]);
|
|
cl_strisvertv[cl_numstrisvert][2] += 1;
|
|
Vector4Set(cl_strisvertc[cl_numstrisvert], 0, 1, 0, 0.2);
|
|
Vector2Set(cl_strisvertt[cl_numstrisvert], 0, 0);
|
|
cl_numstrisvert++;
|
|
}
|
|
t->numidx = cl_numstrisidx - t->firstidx;
|
|
t->numvert = cl_numstrisvert-t->firstvert;
|
|
}
|
|
}
|
|
#endif
|
|
|
|
|
|
return ((vec4_t*)enterplane - planes)+1;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
//sx,sy are the tile coord
|
|
//note that tile SECTHEIGHTSIZE-1 does not exist, as the last sample overlaps the first sample of the next section
|
|
static void Heightmap_Trace_Square(hmtrace_t *tr, int tx, int ty)
|
|
{
|
|
vec3_t d[2];
|
|
vec3_t p[4];
|
|
vec4_t n[6];
|
|
int i;
|
|
|
|
#ifndef STRICTEDGES
|
|
float d1, d2;
|
|
#endif
|
|
int sx, sy;
|
|
hmsection_t *s;
|
|
unsigned int holerow;
|
|
unsigned int holebit;
|
|
|
|
sx = tx/(SECTHEIGHTSIZE-1);
|
|
sy = ty/(SECTHEIGHTSIZE-1);
|
|
if (sx < tr->hm->firstsegx || sx >= tr->hm->maxsegx ||
|
|
sy < tr->hm->firstsegy || sy >= tr->hm->maxsegy)
|
|
s = NULL;
|
|
else
|
|
s = Terr_GetSection(tr->hm, sx, sy, TGS_TRYLOAD|TGS_WAITLOAD|TGS_ANYSTATE);
|
|
|
|
if (!s || s->loadstate != TSLS_LOADED)
|
|
{
|
|
if ((tr->hitcontentsmask & tr->hm->exteriorcontents) || (s && s->loadstate != TSLS_FAILED))
|
|
{
|
|
//you're not allowed to walk into sections that have not loaded.
|
|
//might as well check the entire section instead of just one tile
|
|
Vector4Set(n[0], 1, 0, 0, (tx/(SECTHEIGHTSIZE-1) + 1 - CHUNKBIAS)*tr->hm->sectionsize);
|
|
Vector4Set(n[1], -1, 0, 0, -(tx/(SECTHEIGHTSIZE-1) + 0 - CHUNKBIAS)*tr->hm->sectionsize);
|
|
Vector4Set(n[2], 0, 1, 0, (ty/(SECTHEIGHTSIZE-1) + 1 - CHUNKBIAS)*tr->hm->sectionsize);
|
|
Vector4Set(n[3], 0, -1, 0, -(ty/(SECTHEIGHTSIZE-1) + 0 - CHUNKBIAS)*tr->hm->sectionsize);
|
|
Heightmap_Trace_Brush(tr, n, 4, NULL);
|
|
}
|
|
return;
|
|
}
|
|
|
|
if (s->traceseq != tr->hm->traceseq && s->numents)
|
|
{
|
|
s->traceseq = tr->hm->traceseq;
|
|
Sys_LockMutex(tr->hm->entitylock);
|
|
for (i = 0; i < s->numents; i++)
|
|
{
|
|
vec3_t start_l, end_l;
|
|
trace_t etr;
|
|
model_t *model;
|
|
if (s->ents[i]->traceseq == tr->hm->traceseq)
|
|
continue;
|
|
s->ents[i]->traceseq = tr->hm->traceseq;
|
|
model = s->ents[i]->ent.model;
|
|
//FIXME: IGNORE the entity if it isn't loaded yet? surely that's bad?
|
|
if (!model || model->loadstate != MLS_LOADED || !model->funcs.NativeTrace)
|
|
continue;
|
|
//figure out where on the submodel the trace is.
|
|
VectorSubtract (tr->start, s->ents[i]->ent.origin, start_l);
|
|
VectorSubtract (tr->end, s->ents[i]->ent.origin, end_l);
|
|
// start_l[2] -= tr->mins[2];
|
|
// end_l[2] -= tr->mins[2];
|
|
VectorScale(start_l, s->ents[i]->ent.scale, start_l);
|
|
VectorScale(end_l, s->ents[i]->ent.scale, end_l);
|
|
|
|
//skip if the local trace points are outside the model's bounds
|
|
/* for (j = 0; j < 3; j++)
|
|
{
|
|
if (start_l[j]+tr->mins[j] > model->maxs[j] && end_l[j]+tr->mins[j] > model->maxs[j])
|
|
continue;
|
|
if (start_l[j]+tr->maxs[j] < model->mins[j] && end_l[j]+tr->maxs[j] < model->mins[j])
|
|
continue;
|
|
}
|
|
*/
|
|
//do the trace
|
|
memset(&etr, 0, sizeof(etr));
|
|
etr.fraction = 1;
|
|
model->funcs.NativeTrace (model, 0, &s->ents[i]->ent.framestate, s->ents[i]->ent.axis, start_l, end_l, tr->mins, tr->maxs, tr->shape == iscapsule, tr->hitcontentsmask, &etr);
|
|
|
|
if (etr.startsolid)
|
|
{ //many many bsp objects are not enclosed 'properly' (qbsp strips any surfaces outside the world).
|
|
//this means that such bsps extend to infinity, resulting in sudden glitchy stuck issues when you enter a section containing such a bsp
|
|
//so if we started solid, constrain that solidity to the volume of the submodel
|
|
VectorCopy (s->ents[i]->ent.axis[0], n[0]);
|
|
VectorNegate(s->ents[i]->ent.axis[0], n[1]);
|
|
VectorCopy (s->ents[i]->ent.axis[1], n[2]);
|
|
VectorNegate(s->ents[i]->ent.axis[1], n[3]);
|
|
VectorCopy (s->ents[i]->ent.axis[2], n[4]);
|
|
VectorNegate(s->ents[i]->ent.axis[2], n[5]);
|
|
n[0][3] = DotProduct(n[0], s->ents[i]->ent.origin) + model->maxs[0];
|
|
n[1][3] = DotProduct(n[1], s->ents[i]->ent.origin) + -model->mins[0];
|
|
n[2][3] = DotProduct(n[2], s->ents[i]->ent.origin) + model->maxs[1];
|
|
n[3][3] = DotProduct(n[3], s->ents[i]->ent.origin) + -model->mins[1];
|
|
n[4][3] = DotProduct(n[4], s->ents[i]->ent.origin) + model->maxs[2];
|
|
n[5][3] = DotProduct(n[5], s->ents[i]->ent.origin) + -model->mins[2];
|
|
Heightmap_Trace_Brush(tr, n, 6, NULL);
|
|
}
|
|
else
|
|
{
|
|
tr->result->startsolid |= etr.startsolid;
|
|
tr->result->allsolid |= etr.allsolid;
|
|
if (etr.fraction < tr->nearfrac)
|
|
{
|
|
tr->contents = etr.contents;
|
|
tr->truefrac = etr.truefraction;
|
|
tr->nearfrac = etr.fraction;
|
|
tr->plane[3] = etr.plane.dist;
|
|
tr->plane[0] = etr.plane.normal[0];
|
|
tr->plane[1] = etr.plane.normal[1];
|
|
tr->plane[2] = etr.plane.normal[2];
|
|
}
|
|
}
|
|
}
|
|
Sys_UnlockMutex(tr->hm->entitylock);
|
|
}
|
|
|
|
sx = tx - CHUNKBIAS*(SECTHEIGHTSIZE-1);
|
|
sy = ty - CHUNKBIAS*(SECTHEIGHTSIZE-1);
|
|
|
|
tx = tx % (SECTHEIGHTSIZE-1);
|
|
ty = ty % (SECTHEIGHTSIZE-1);
|
|
|
|
holerow = ((ty<<3)/(SECTHEIGHTSIZE-1));
|
|
holebit = 1u<<((tx<<3)/(SECTHEIGHTSIZE-1));
|
|
if (s->holes[holerow] & holebit)
|
|
return; //no collision with holes
|
|
|
|
switch(tr->hm->mode)
|
|
{
|
|
case HMM_BLOCKS:
|
|
//left-most
|
|
Vector4Set(n[0], -1, 0, 0, -tr->htilesize*(sx+0));
|
|
//bottom-most
|
|
Vector4Set(n[1], 0, 1, 0, tr->htilesize*(sy+1));
|
|
//right-most
|
|
Vector4Set(n[2], 1, 0, 0, tr->htilesize*(sx+1));
|
|
//top-most
|
|
Vector4Set(n[3], 0, -1, 0, -tr->htilesize*(sy+0));
|
|
//top
|
|
Vector4Set(n[4], 0, 0, 1, s->heights[(tx+0)+(ty+0)*SECTHEIGHTSIZE]);
|
|
|
|
Heightmap_Trace_Brush(tr, n, 5, NULL);
|
|
return;
|
|
case HMM_TERRAIN:
|
|
VectorSet(p[0], tr->htilesize*(sx+0), tr->htilesize*(sy+0), s->heights[(tx+0)+(ty+0)*SECTHEIGHTSIZE]);
|
|
VectorSet(p[1], tr->htilesize*(sx+1), tr->htilesize*(sy+0), s->heights[(tx+1)+(ty+0)*SECTHEIGHTSIZE]);
|
|
VectorSet(p[2], tr->htilesize*(sx+0), tr->htilesize*(sy+1), s->heights[(tx+0)+(ty+1)*SECTHEIGHTSIZE]);
|
|
VectorSet(p[3], tr->htilesize*(sx+1), tr->htilesize*(sy+1), s->heights[(tx+1)+(ty+1)*SECTHEIGHTSIZE]);
|
|
|
|
VectorSet(n[5], 0, 0, 1);
|
|
#ifndef STRICTEDGES
|
|
d1 = fabs(p[0][2] - p[3][2]);
|
|
d2 = fabs(p[1][2] - p[2][2]);
|
|
if (d1 < d2)
|
|
{
|
|
/*generate the brush (in world space*/
|
|
{
|
|
VectorSubtract(p[3], p[0], d[0]);
|
|
VectorSubtract(p[2], p[0], d[1]);
|
|
//left-most
|
|
Vector4Set(n[0], -1, 0, 0, -tr->htilesize*(sx+0));
|
|
//bottom-most
|
|
Vector4Set(n[1], 0, 1, 0, tr->htilesize*(sy+1));
|
|
//top-right
|
|
VectorSet(n[2], 0.70710678118654752440084436210485, -0.70710678118654752440084436210485, 0);
|
|
n[2][3] = DotProduct(n[2], p[0]);
|
|
//top
|
|
VectorNormalize(d[0]);
|
|
VectorNormalize(d[1]);
|
|
CrossProduct(d[0], d[1], n[3]);
|
|
VectorNormalize(n[3]);
|
|
n[3][3] = DotProduct(n[3], p[0]);
|
|
//down
|
|
VectorNegate(n[3], n[4]);
|
|
n[4][3] = DotProduct(n[4], p[0]) - n[4][2]*TERRAINTHICKNESS;
|
|
|
|
n[5][3] = max(p[0][2], p[2][2]);
|
|
n[5][3] = max(n[5][3], p[3][2]);
|
|
Heightmap_Trace_Brush(tr, n, 6, NULL);
|
|
}
|
|
|
|
{
|
|
VectorSubtract(p[3], p[0], d[0]);
|
|
VectorSubtract(p[3], p[1], d[1]);
|
|
|
|
//right-most
|
|
Vector4Set(n[0], 1, 0, 0, tr->htilesize*(sx+1));
|
|
//top-most
|
|
Vector4Set(n[1], 0, -1, 0, -tr->htilesize*(sy+0));
|
|
//bottom-left
|
|
VectorSet(n[2], -0.70710678118654752440084436210485, 0.70710678118654752440084436210485, 0);
|
|
n[2][3] = DotProduct(n[2], p[0]);
|
|
//top
|
|
VectorNormalize(d[0]);
|
|
VectorNormalize(d[1]);
|
|
CrossProduct(d[0], d[1], n[3]);
|
|
VectorNormalize(n[3]);
|
|
n[3][3] = DotProduct(n[3], p[0]);
|
|
//down
|
|
VectorNegate(n[3], n[4]);
|
|
n[4][3] = DotProduct(n[4], p[0]) - n[4][2]*TERRAINTHICKNESS;
|
|
|
|
n[5][3] = max(p[0][2], p[1][2]);
|
|
n[5][3] = max(n[5][3], p[3][2]);
|
|
Heightmap_Trace_Brush(tr, n, 6, NULL);
|
|
}
|
|
}
|
|
else
|
|
#endif
|
|
{
|
|
/*generate the brush (in world space*/
|
|
{
|
|
VectorSubtract(p[1], p[0], d[0]);
|
|
VectorSubtract(p[2], p[0], d[1]);
|
|
//left-most
|
|
Vector4Set(n[0], -1, 0, 0, -tr->htilesize*(sx+0));
|
|
//top-most
|
|
Vector4Set(n[1], 0, -1, 0, -tr->htilesize*(sy+0));
|
|
//bottom-right
|
|
VectorSet(n[2], 0.70710678118654752440084436210485, 0.70710678118654752440084436210485, 0);
|
|
n[2][3] = DotProduct(n[2], p[1]);
|
|
//top
|
|
VectorNormalize(d[0]);
|
|
VectorNormalize(d[1]);
|
|
CrossProduct(d[0], d[1], n[3]);
|
|
VectorNormalize(n[3]);
|
|
n[3][3] = DotProduct(n[3], p[1]);
|
|
//down
|
|
VectorNegate(n[3], n[4]);
|
|
n[4][3] = DotProduct(n[4], p[1]) - n[4][2]*TERRAINTHICKNESS;
|
|
|
|
n[5][3] = max(p[0][2], p[1][2]);
|
|
n[5][3] = max(n[5][3], p[2][2]);
|
|
Heightmap_Trace_Brush(tr, n, 6, NULL);
|
|
}
|
|
{
|
|
VectorSubtract(p[3], p[2], d[0]);
|
|
VectorSubtract(p[3], p[1], d[1]);
|
|
|
|
//right-most
|
|
Vector4Set(n[0], 1, 0, 0, tr->htilesize*(sx+1));
|
|
//bottom-most
|
|
Vector4Set(n[1], 0, 1, 0, tr->htilesize*(sy+1));
|
|
//top-left
|
|
VectorSet(n[2], -0.70710678118654752440084436210485, -0.70710678118654752440084436210485, 0);
|
|
n[2][3] = DotProduct(n[2], p[1]);
|
|
//top
|
|
VectorNormalize(d[0]);
|
|
VectorNormalize(d[1]);
|
|
CrossProduct(d[0], d[1], n[3]);
|
|
VectorNormalize(n[3]);
|
|
n[3][3] = DotProduct(n[3], p[1]);
|
|
//down
|
|
VectorNegate(n[3], n[4]);
|
|
n[4][3] = DotProduct(n[4], p[1]) - n[4][2]*TERRAINTHICKNESS;
|
|
|
|
n[5][3] = max(p[1][2], p[2][2]);
|
|
n[5][3] = max(n[5][3], p[3][2]);
|
|
Heightmap_Trace_Brush(tr, n, 6, NULL);
|
|
}
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
|
|
#define DIST_EPSILON 0
|
|
/*
|
|
Heightmap_TraceRecurse
|
|
Traces an arbitary box through a heightmap. (interface with outside)
|
|
|
|
Why is recursion good?
|
|
1: it is consistant with bsp models. :)
|
|
2: it allows us to use any size model we want
|
|
3: we don't have to work out the height of the terrain every X units, but can be more precise.
|
|
|
|
Obviously, we don't care all that much about 1
|
|
*/
|
|
qboolean Heightmap_Trace(struct model_s *model, int hulloverride, framestate_t *framestate, vec3_t mataxis[3], vec3_t start, vec3_t end, vec3_t mins, vec3_t maxs, qboolean capsule, unsigned int against, struct trace_s *trace)
|
|
{
|
|
vec2_t pos;
|
|
vec2_t frac;
|
|
vec2_t emins;
|
|
vec2_t emaxs;
|
|
vec3_t tmp;
|
|
int ipos[2], npos[2];
|
|
int x, y, e;
|
|
int axis;
|
|
int breaklimit = 1000;
|
|
float zbias;
|
|
hmtrace_t hmtrace;
|
|
hmtrace.hm = model->terrain;
|
|
hmtrace.hm->traceseq++;
|
|
hmtrace.htilesize = hmtrace.hm->sectionsize / (SECTHEIGHTSIZE-1);
|
|
hmtrace.nearfrac = hmtrace.truefrac = 1;
|
|
hmtrace.contents = 0;
|
|
hmtrace.hitcontentsmask = against;
|
|
|
|
hmtrace.plane[0] = 0;
|
|
hmtrace.plane[1] = 0;
|
|
hmtrace.plane[2] = 0;
|
|
hmtrace.plane[3] = 0;
|
|
if (capsule)
|
|
{
|
|
hmtrace.shape = iscapsule;
|
|
zbias = 0;
|
|
|
|
if (mataxis)
|
|
VectorSet(hmtrace.up, mataxis[0][2], -mataxis[1][2], mataxis[2][2]);
|
|
else
|
|
VectorSet(hmtrace.up, 0, 0, 1);
|
|
|
|
//determine the capsule sizes
|
|
hmtrace.capsulesize[0] = ((maxs[0]-mins[0]) + (maxs[1]-mins[1]))/4.0;
|
|
hmtrace.capsulesize[1] = maxs[2];
|
|
hmtrace.capsulesize[2] = mins[2];
|
|
// zbias = (trace_capsulesize[1] > -hmtrace.capsulesize[2])?hmtrace.capsulesize[1]:-hmtrace.capsulesize[2];
|
|
hmtrace.capsulesize[1] -= hmtrace.capsulesize[0];
|
|
hmtrace.capsulesize[2] += hmtrace.capsulesize[0];
|
|
|
|
zbias = 0;
|
|
}
|
|
else if (mins[0] || mins[1] || mins[2] || maxs[0] || maxs[1] || maxs[2])
|
|
{
|
|
hmtrace.shape = isbox;
|
|
zbias = 0;
|
|
}
|
|
else
|
|
{
|
|
hmtrace.shape = ispoint;
|
|
zbias = mins[2];
|
|
}
|
|
|
|
memset(trace, 0, sizeof(*trace));
|
|
hmtrace.result = trace;
|
|
hmtrace.startsolid = false;
|
|
|
|
//to tile space
|
|
hmtrace.start[0] = (start[0]);
|
|
hmtrace.start[1] = (start[1]);
|
|
hmtrace.start[2] = (start[2] + zbias);
|
|
hmtrace.end[0] = (end[0]);
|
|
hmtrace.end[1] = (end[1]);
|
|
hmtrace.end[2] = (end[2] + zbias);
|
|
|
|
// mins = vec3_origin;
|
|
// maxs = vec3_origin;
|
|
|
|
VectorCopy(mins, hmtrace.mins);
|
|
VectorCopy(maxs, hmtrace.maxs);
|
|
|
|
//determine extents
|
|
VectorAdd(hmtrace.start, hmtrace.mins, hmtrace.absmins);
|
|
VectorCopy(hmtrace.absmins, hmtrace.absmaxs);
|
|
VectorAdd(hmtrace.start, hmtrace.maxs, tmp);
|
|
AddPointToBounds (tmp, hmtrace.absmins, hmtrace.absmaxs);
|
|
VectorAdd(hmtrace.end, hmtrace.mins, tmp);
|
|
AddPointToBounds (tmp, hmtrace.absmins, hmtrace.absmaxs);
|
|
VectorAdd(hmtrace.end, hmtrace.maxs, tmp);
|
|
AddPointToBounds (tmp, hmtrace.absmins, hmtrace.absmaxs);
|
|
hmtrace.absmaxs[0] += 1;
|
|
hmtrace.absmaxs[1] += 1;
|
|
hmtrace.absmaxs[2] += 1;
|
|
hmtrace.absmins[0] -= 1;
|
|
hmtrace.absmins[1] -= 1;
|
|
hmtrace.absmins[2] -= 1;
|
|
|
|
//figure out where we are in terms of tiles
|
|
pos[0] = (hmtrace.start[0]+CHUNKBIAS*hmtrace.hm->sectionsize)/hmtrace.htilesize;
|
|
pos[1] = (hmtrace.start[1]+CHUNKBIAS*hmtrace.hm->sectionsize)/hmtrace.htilesize;
|
|
|
|
emins[0] = (mins[0]-1.5)/hmtrace.htilesize;
|
|
emins[1] = (mins[1]-1.5)/hmtrace.htilesize;
|
|
emaxs[0] = (maxs[0]+1.5)/hmtrace.htilesize;
|
|
emaxs[1] = (maxs[1]+1.5)/hmtrace.htilesize;
|
|
|
|
//Test code
|
|
if (0)
|
|
{
|
|
vec2_t minb, maxb;
|
|
Vector2Copy(pos, minb);
|
|
Vector2Copy(pos, maxb);
|
|
npos[0] = (hmtrace.end[0]+CHUNKBIAS*hmtrace.hm->sectionsize)/hmtrace.htilesize;
|
|
npos[1] = (hmtrace.end[1]+CHUNKBIAS*hmtrace.hm->sectionsize)/hmtrace.htilesize;
|
|
if (npos[0] > pos[0])
|
|
maxb[0] = pos[0];
|
|
else
|
|
minb[0] = pos[0];
|
|
if (npos[1] > pos[1])
|
|
maxb[1] = pos[1];
|
|
else
|
|
minb[1] = pos[1];
|
|
|
|
minb[0] += emins[0];
|
|
minb[1] += emins[1];
|
|
maxb[0] += emaxs[0];
|
|
maxb[1] += emaxs[1];
|
|
|
|
for (y = floor(minb[1]); y <= ceil(maxb[1]); y++)
|
|
for (x = floor(minb[0]); x <= ceil(maxb[0]); x++)
|
|
Heightmap_Trace_Square(&hmtrace, x, y);
|
|
}
|
|
|
|
//make sure the start tile is valid
|
|
for (y = floor(pos[1] + emins[1]); y <= ceil(pos[1] + emaxs[1]); y++)
|
|
for (x = floor(pos[0] + emins[0]); x <= ceil(pos[0] + emaxs[0]); x++)
|
|
Heightmap_Trace_Square(&hmtrace, x, y);
|
|
|
|
//now walk over the terrain
|
|
if (hmtrace.end[0] != hmtrace.start[0] || hmtrace.end[1] != hmtrace.start[1])
|
|
{
|
|
vec2_t dir, trstart, trdist;
|
|
|
|
//figure out the leading point
|
|
for (axis = 0; axis < 2; axis++)
|
|
{
|
|
trdist[axis] = hmtrace.end[axis]-hmtrace.start[axis];
|
|
dir[axis] = (hmtrace.end[axis] - hmtrace.start[axis])/hmtrace.htilesize;
|
|
|
|
if (dir[axis] > 0)
|
|
{
|
|
ipos[axis] = pos[axis] + emins[axis];
|
|
trstart[axis] = CHUNKBIAS*hmtrace.hm->sectionsize + (maxs[axis]) + hmtrace.start[axis];
|
|
}
|
|
else
|
|
{
|
|
ipos[axis] = pos[axis] + emaxs[axis];
|
|
trstart[axis] = CHUNKBIAS*hmtrace.hm->sectionsize + (mins[axis]) + hmtrace.start[axis];
|
|
}
|
|
trstart[axis] /= hmtrace.htilesize;
|
|
trdist[axis] /= hmtrace.htilesize;
|
|
}
|
|
for(;;)
|
|
{
|
|
if (breaklimit--< 0)
|
|
break;
|
|
for (axis = 0; axis < 2; axis++)
|
|
{
|
|
if (dir[axis] > 0)
|
|
{
|
|
npos[axis] = ipos[axis]+1;
|
|
frac[axis] = (npos[axis]-trstart[axis])/trdist[axis];
|
|
}
|
|
else if (dir[axis] < 0)
|
|
{
|
|
npos[axis] = ipos[axis];
|
|
frac[axis] = (ipos[axis]-trstart[axis])/trdist[axis];
|
|
}
|
|
else
|
|
frac[axis] = 1000000000000000.0;
|
|
}
|
|
|
|
//which side are we going down?
|
|
if (frac[0] < frac[1])
|
|
axis = 0;
|
|
else
|
|
axis = 1;
|
|
|
|
if (frac[axis] >= 1)
|
|
break;
|
|
|
|
//progress to the crossed boundary
|
|
if (dir[axis] < 0)
|
|
ipos[axis] = ipos[axis]-1;
|
|
else
|
|
ipos[axis] = ipos[axis]+1;
|
|
|
|
axis = !axis;
|
|
if (dir[axis] > 0)
|
|
{ //leading edge is on the right, so start on the left and keep going until we hit the leading edge
|
|
npos[0] = ipos[0];
|
|
npos[1] = ipos[1];
|
|
|
|
npos[axis] -= ceil(emins[axis]-emaxs[axis]);
|
|
e = ipos[axis];
|
|
|
|
npos[axis] -= 1;
|
|
e++;
|
|
|
|
for (; npos[axis] <= e; npos[axis]++)
|
|
Heightmap_Trace_Square(&hmtrace, npos[0], npos[1]);
|
|
}
|
|
else
|
|
{
|
|
//leading edge is on the left
|
|
npos[0] = ipos[0];
|
|
npos[1] = ipos[1];
|
|
e = ipos[axis] + ceil(emaxs[axis]-emins[axis]);
|
|
|
|
npos[axis] -= 1;
|
|
e++;
|
|
|
|
for (; npos[axis] <= e; npos[axis]++)
|
|
Heightmap_Trace_Square(&hmtrace, npos[0], npos[1]);
|
|
}
|
|
|
|
// axis = !axis;
|
|
//and make sure our position on the other axis is correct, for the next time around the loop
|
|
// if (frac[axis] > hmtrace.truefrac)
|
|
// break;
|
|
}
|
|
}
|
|
|
|
//FIXME: optimise into the section grid
|
|
{
|
|
brushes_t *brushes = hmtrace.hm->wbrushes;
|
|
int count = hmtrace.hm->numbrushes;
|
|
for (count = hmtrace.hm->numbrushes; count-->0; brushes++)
|
|
{
|
|
if (brushes->contents & against)
|
|
{
|
|
int face;
|
|
if (hmtrace.absmaxs[0] < brushes->mins[0] ||
|
|
hmtrace.absmaxs[1] < brushes->mins[1] ||
|
|
hmtrace.absmaxs[2] < brushes->mins[2])
|
|
continue;
|
|
if (hmtrace.absmins[0] > brushes->maxs[0] ||
|
|
hmtrace.absmins[1] > brushes->maxs[1] ||
|
|
hmtrace.absmins[2] > brushes->maxs[2])
|
|
continue;
|
|
face = Heightmap_Trace_Brush(&hmtrace, brushes->planes, brushes->numplanes, brushes);
|
|
if (face)
|
|
{
|
|
trace->brush_id = brushes->id;
|
|
trace->brush_face = face;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
trace->plane.dist = hmtrace.plane[3];
|
|
trace->plane.normal[0] = hmtrace.plane[0];
|
|
trace->plane.normal[1] = hmtrace.plane[1];
|
|
trace->plane.normal[2] = hmtrace.plane[2];
|
|
|
|
trace->startsolid = trace->allsolid = hmtrace.startsolid;
|
|
if (hmtrace.nearfrac < 0)
|
|
hmtrace.nearfrac = 0;
|
|
trace->fraction = hmtrace.nearfrac;
|
|
trace->truefraction = hmtrace.truefrac;
|
|
VectorInterpolate(start, hmtrace.nearfrac, end, trace->endpos);
|
|
return trace->fraction < 1;
|
|
}
|
|
|
|
qboolean Heightmap_Trace_Test(struct model_s *model, int hulloverride, framestate_t *framestate, vec3_t mataxis[3], vec3_t start, vec3_t end, vec3_t mins, vec3_t maxs, qboolean capsule, unsigned int against, struct trace_s *trace)
|
|
{
|
|
qboolean ret = Heightmap_Trace(model, hulloverride, framestate, mataxis, start, end, mins, maxs, capsule, against, trace);
|
|
|
|
if (!trace->startsolid)
|
|
{
|
|
trace_t testtrace;
|
|
Heightmap_Trace(model, hulloverride, framestate, mataxis, trace->endpos, trace->endpos, mins, maxs, capsule, against, &testtrace);
|
|
if (testtrace.startsolid)
|
|
{
|
|
Con_DPrintf("Trace became solid\n");
|
|
trace->fraction = 0;
|
|
VectorCopy(start, trace->endpos);
|
|
trace->startsolid = trace->allsolid = true;
|
|
}
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
typedef struct
|
|
{
|
|
int id;
|
|
int pos[3];
|
|
} hmpvs_t;
|
|
typedef struct
|
|
{
|
|
int id;
|
|
int min[3], max[3];
|
|
} hmpvsent_t;
|
|
unsigned int Heightmap_FatPVS (model_t *mod, vec3_t org, qbyte *pvsbuffer, unsigned int pvssize, qboolean add)
|
|
{
|
|
//embed the org onto the pvs
|
|
hmpvs_t *hmpvs = (hmpvs_t*)pvsbuffer;
|
|
hmpvs->id = 0xdeadbeef;
|
|
VectorCopy(org, hmpvs->pos);
|
|
return sizeof(*hmpvs);
|
|
}
|
|
|
|
#ifndef CLIENTONLY
|
|
qboolean Heightmap_EdictInFatPVS (model_t *mod, struct pvscache_s *edict, qbyte *pvsdata)
|
|
{
|
|
heightmap_t *hm = mod->terrain;
|
|
int o[3], i;
|
|
hmpvs_t *hmpvs = (hmpvs_t*)pvsdata;
|
|
hmpvsent_t *hmed = (hmpvsent_t*)edict;
|
|
|
|
if (!hm->culldistance)
|
|
return true;
|
|
|
|
//check distance
|
|
for (i = 0; i < 3; i++)
|
|
{
|
|
if (hmpvs->pos[i] < hmed->min[i])
|
|
o[i] = hmed->min[i] - hmpvs->pos[i];
|
|
else if (hmpvs->pos[i] > hmed->max[i])
|
|
o[i] = hmed->max[i] - hmpvs->pos[i];
|
|
else
|
|
o[i] = 0;
|
|
}
|
|
|
|
return DotProduct(o,o) < hm->culldistance;
|
|
}
|
|
|
|
void Heightmap_FindTouchedLeafs (model_t *mod, pvscache_t *ent, float *mins, float *maxs)
|
|
{
|
|
hmpvsent_t *hmed = (hmpvsent_t*)ent;
|
|
|
|
VectorCopy(mins, hmed->min);
|
|
VectorCopy(maxs, hmed->max);
|
|
}
|
|
#endif
|
|
|
|
void Heightmap_LightPointValues (model_t *mod, vec3_t point, vec3_t res_diffuse, vec3_t res_ambient, vec3_t res_dir)
|
|
{
|
|
res_diffuse[0] = 128;
|
|
res_diffuse[1] = 128;
|
|
res_diffuse[2] = 128;
|
|
res_ambient[0] = 64;
|
|
res_ambient[1] = 64;
|
|
res_ambient[2] = 64;
|
|
res_dir[0] = 1;//sin(time);
|
|
res_dir[1] = 0;//cos(time);
|
|
res_dir[2] = 0;//sin(time);
|
|
VectorNormalize(res_dir);
|
|
}
|
|
void Heightmap_StainNode (mnode_t *node, float *parms)
|
|
{
|
|
}
|
|
void Heightmap_MarkLights (dlight_t *light, int bit, mnode_t *node)
|
|
{
|
|
}
|
|
|
|
qbyte *Heightmap_ClusterPVS (model_t *model, int num, qbyte *buffer, unsigned int buffersize)
|
|
{
|
|
return NULL;
|
|
// static qbyte heightmappvs = 255;
|
|
// return &heightmappvs;
|
|
}
|
|
int Heightmap_ClusterForPoint (model_t *model, vec3_t point)
|
|
{
|
|
return -1;
|
|
}
|
|
|
|
#ifndef SERVERONLY
|
|
static unsigned char *QDECL Terr_GetLightmap(hmsection_t *s, int idx, qboolean edit)
|
|
{
|
|
unsigned char *lm;
|
|
int x = idx % SECTTEXSIZE, y = idx / SECTTEXSIZE;
|
|
if (s->lightmap < 0)
|
|
{
|
|
Terr_LoadSection(s->hmmod, s, s->sx, s->sy, true);
|
|
Terr_InitLightmap(s, true);
|
|
}
|
|
if (s->lightmap < 0)
|
|
return NULL;
|
|
|
|
if (edit)
|
|
{
|
|
s->flags |= TSF_EDITED;
|
|
|
|
lightmap[s->lightmap]->modified = true;
|
|
lightmap[s->lightmap]->rectchange.l = 0;
|
|
lightmap[s->lightmap]->rectchange.t = 0;
|
|
lightmap[s->lightmap]->rectchange.r = HMLMSTRIDE;
|
|
lightmap[s->lightmap]->rectchange.b = HMLMSTRIDE;
|
|
}
|
|
lm = lightmap[s->lightmap]->lightmaps;
|
|
lm += ((s->lmy+y) * HMLMSTRIDE + (s->lmx+x)) * lightmap_bytes;
|
|
return lm;
|
|
}
|
|
static void ted_dorelight(heightmap_t *hm)
|
|
{
|
|
unsigned char *lm = Terr_GetLightmap(hm->relight, 0, true);
|
|
int x, y;
|
|
#define EXPAND 2
|
|
vec3_t surfnorms[(SECTTEXSIZE+EXPAND*2)*(SECTTEXSIZE+EXPAND*2)];
|
|
// float scaletab[EXPAND*2*EXPAND*2];
|
|
vec3_t ldir = {0.4, 0.7, 2};
|
|
hmsection_t *s = hm->relight;
|
|
s->flags &= ~TSF_RELIGHT;
|
|
hm->relight = NULL;
|
|
|
|
if (s->lightmap < 0)
|
|
return;
|
|
|
|
for (y = -EXPAND; y < SECTTEXSIZE+EXPAND; y++)
|
|
for (x = -EXPAND; x < SECTTEXSIZE+EXPAND; x++)
|
|
{
|
|
vec3_t pos;
|
|
pos[0] = hm->relightmin[0] + (x*hm->sectionsize/(SECTTEXSIZE-1));
|
|
pos[1] = hm->relightmin[1] + (y*hm->sectionsize/(SECTTEXSIZE-1));
|
|
pos[2] = 0;
|
|
Heightmap_Normal(s->hmmod, pos, surfnorms[x+EXPAND + (y+EXPAND)*(SECTTEXSIZE+EXPAND*2)]);
|
|
}
|
|
|
|
VectorNormalize(ldir);
|
|
|
|
for (y = 0; y < SECTTEXSIZE; y++, lm += (HMLMSTRIDE-SECTTEXSIZE)*4)
|
|
for (x = 0; x < SECTTEXSIZE; x++, lm += 4)
|
|
{
|
|
vec3_t norm;
|
|
float d;
|
|
int sx,sy;
|
|
VectorClear(norm);
|
|
for (sy = -EXPAND; sy <= EXPAND; sy++)
|
|
for (sx = -EXPAND; sx <= EXPAND; sx++)
|
|
{
|
|
d = sqrt((EXPAND*2+1)*(EXPAND*2+1) - sx*sx+sy*sy);
|
|
VectorMA(norm, d, surfnorms[x+sx+EXPAND + (y+sy+EXPAND)*(SECTTEXSIZE+EXPAND*2)], norm);
|
|
}
|
|
|
|
VectorNormalize(norm);
|
|
d = DotProduct(ldir, norm);
|
|
if (d < 0)
|
|
d = 0;
|
|
// lm[0] = norm[0]*127 + 128;
|
|
// lm[1] = norm[1]*127 + 128;
|
|
// lm[2] = norm[2]*127 + 128;
|
|
lm[3] = 127 + d*128;
|
|
}
|
|
|
|
lightmap[s->lightmap]->modified = true;
|
|
lightmap[s->lightmap]->rectchange.l = 0;
|
|
lightmap[s->lightmap]->rectchange.t = 0;
|
|
lightmap[s->lightmap]->rectchange.r = HMLMSTRIDE;
|
|
lightmap[s->lightmap]->rectchange.b = HMLMSTRIDE;
|
|
}
|
|
static void ted_sethole(void *ctx, hmsection_t *s, int idx, float wx, float wy, float w)
|
|
{
|
|
unsigned int row = idx/9;
|
|
unsigned int col = idx%9;
|
|
unsigned int bit;
|
|
unsigned int mask;
|
|
if (row == 8 || col == 8)
|
|
return; //meh, our painting function is written with an overlap of 1
|
|
if (w <= 0)
|
|
return;
|
|
mask = 1u<<(col);
|
|
if (*(float*)ctx > 0)
|
|
bit = mask;
|
|
else
|
|
bit = 0;
|
|
s->flags |= TSF_NOTIFY|TSF_DIRTY|TSF_EDITED;
|
|
s->holes[row] = (s->holes[row] & ~mask) | bit;
|
|
}
|
|
static void ted_heighttally(void *ctx, hmsection_t *s, int idx, float wx, float wy, float w)
|
|
{
|
|
/*raise the terrain*/
|
|
((float*)ctx)[0] += s->heights[idx]*w;
|
|
((float*)ctx)[1] += w;
|
|
}
|
|
static void ted_heightsmooth(void *ctx, hmsection_t *s, int idx, float wx, float wy, float w)
|
|
{
|
|
s->flags |= TSF_NOTIFY|TSF_DIRTY|TSF_EDITED|TSF_RELIGHT;
|
|
/*interpolate the terrain towards a certain value*/
|
|
|
|
if (IS_NAN(s->heights[idx]))
|
|
s->heights[idx] = *(float*)ctx;
|
|
else
|
|
s->heights[idx] = s->heights[idx]*(1-w) + w**(float*)ctx;
|
|
}
|
|
static void ted_heightdebug(void *ctx, hmsection_t *s, int idx, float wx, float wy, float w)
|
|
{
|
|
int tx = idx/SECTHEIGHTSIZE, ty = idx % SECTHEIGHTSIZE;
|
|
s->flags |= TSF_NOTIFY|TSF_DIRTY|TSF_EDITED|TSF_RELIGHT;
|
|
/*interpolate the terrain towards a certain value*/
|
|
|
|
if (tx == 16)
|
|
tx = 0;
|
|
if (ty == 16)
|
|
ty = 0;
|
|
|
|
// if (ty < tx)
|
|
// tx = ty;
|
|
s->heights[idx] = (tx>>1) * 32 + (ty>>1) * 32;
|
|
}
|
|
static void ted_heightraise(void *ctx, hmsection_t *s, int idx, float wx, float wy, float strength)
|
|
{
|
|
s->flags |= TSF_NOTIFY|TSF_DIRTY|TSF_EDITED|TSF_RELIGHT;
|
|
/*raise the terrain*/
|
|
s->heights[idx] += strength;
|
|
}
|
|
static void ted_heightset(void *ctx, hmsection_t *s, int idx, float wx, float wy, float strength)
|
|
{
|
|
s->flags |= TSF_NOTIFY|TSF_DIRTY|TSF_EDITED|TSF_RELIGHT;
|
|
/*set the terrain to a specific value*/
|
|
s->heights[idx] = *(float*)ctx;
|
|
}
|
|
|
|
static void ted_waterset(void *ctx, hmsection_t *s, int idx, float wx, float wy, float strength)
|
|
{
|
|
struct hmwater_s *w = s->water;
|
|
if (!w)
|
|
w = Terr_GenerateWater(s, *(float*)ctx);
|
|
s->flags |= TSF_NOTIFY|TSF_DIRTY|TSF_EDITED;
|
|
|
|
//FIXME: water doesn't render properly. don't let people make dodgy water regions because they can't see it.
|
|
//this is temp code.
|
|
//for (idx = 0; idx < 9*9; idx++)
|
|
//w->heights[idx] = *(float*)ctx;
|
|
//end fixme
|
|
|
|
w->heights[idx] = *(float*)ctx;
|
|
if (w->minheight > w->heights[idx])
|
|
w->minheight = w->heights[idx];
|
|
if (w->maxheight < w->heights[idx])
|
|
w->maxheight = w->heights[idx];
|
|
|
|
//FIXME: what about holes?
|
|
}
|
|
|
|
static void ted_texconcentrate(void *ctx, hmsection_t *s, int idx, float wx, float wy, float w)
|
|
{
|
|
unsigned char *lm = Terr_GetLightmap(s, idx, true);
|
|
s->flags |= TSF_NOTIFY|TSF_EDITED;
|
|
|
|
/*concentrate the lightmap values to a single channel*/
|
|
if (lm[0] > lm[1] && lm[0] > lm[2] && lm[0] > (255-(lm[0]+lm[1]+lm[2])))
|
|
{
|
|
lm[0] = lm[0]*(1-w) + 255*(w);
|
|
lm[1] = lm[1]*(1-w) + 0*(w);
|
|
lm[2] = lm[2]*(1-w) + 0*(w);
|
|
}
|
|
else if (lm[1] > lm[2] && lm[1] > (255-(lm[0]+lm[1]+lm[2])))
|
|
{
|
|
lm[0] = lm[0]*(1-w) + 0*(w);
|
|
lm[1] = lm[1]*(1-w) + 255*(w);
|
|
lm[2] = lm[2]*(1-w) + 0*(w);
|
|
}
|
|
else if (lm[2] > (255-(lm[0]+lm[1]+lm[2])))
|
|
{
|
|
lm[0] = lm[0]*(1-w) + 0*(w);
|
|
lm[1] = lm[1]*(1-w) + 0*(w);
|
|
lm[2] = lm[2]*(1-w) + 255*(w);
|
|
}
|
|
else
|
|
{
|
|
lm[0] = lm[0]*(1-w) + 0*(w);
|
|
lm[1] = lm[1]*(1-w) + 0*(w);
|
|
lm[2] = lm[2]*(1-w) + 0*(w);
|
|
}
|
|
}
|
|
|
|
static void ted_texnoise(void *ctx, hmsection_t *s, int idx, float wx, float wy, float w)
|
|
{
|
|
unsigned char *lm = Terr_GetLightmap(s, idx, true);
|
|
vec4_t v;
|
|
float sc;
|
|
|
|
s->flags |= TSF_NOTIFY|TSF_EDITED;
|
|
|
|
/*randomize the lightmap somewhat (you'll probably want to concentrate it a bit after)*/
|
|
v[0] = (rand()&255);
|
|
v[1] = (rand()&255);
|
|
v[2] = (rand()&255);
|
|
v[3] = (rand()&255);
|
|
sc = v[0] + v[1] + v[2] + v[3];
|
|
Vector4Scale(v, 255/sc, v);
|
|
|
|
lm[0] = lm[0]*(1-w) + (v[0]*(w));
|
|
lm[1] = lm[1]*(1-w) + (v[1]*(w));
|
|
lm[2] = lm[2]*(1-w) + (v[2]*(w));
|
|
}
|
|
|
|
static void ted_texpaint(void *ctx, hmsection_t *s, int idx, float wx, float wy, float w)
|
|
{
|
|
unsigned char *lm = Terr_GetLightmap(s, idx, true);
|
|
const char *texname = ctx;
|
|
int t;
|
|
vec4_t newval;
|
|
if (w > 1)
|
|
w = 1;
|
|
|
|
s->flags |= TSF_NOTIFY|TSF_EDITED;
|
|
|
|
for (t = 0; t < 4; t++)
|
|
{
|
|
if (!strncmp(s->texname[t], texname, sizeof(s->texname[t])-1))
|
|
{
|
|
int extra;
|
|
newval[0] = (t == 0);
|
|
newval[1] = (t == 1);
|
|
newval[2] = (t == 2);
|
|
newval[3] = (t == 3);
|
|
extra = 255 - (lm[0]+lm[1]+lm[2]);
|
|
lm[2] = lm[2]*(1-w) + (255*newval[0]*(w));
|
|
lm[1] = lm[1]*(1-w) + (255*newval[1]*(w));
|
|
lm[0] = lm[0]*(1-w) + (255*newval[2]*(w));
|
|
extra = extra*(1-w) + (255*newval[3]*(w));
|
|
|
|
//the extra stuff is to cope with numerical precision. add any lost values to the new texture instead of the implicit one
|
|
extra = 255 - (extra+lm[0]+lm[1]+lm[2]);
|
|
if (t != 3)
|
|
lm[2-t] += extra;
|
|
return;
|
|
}
|
|
}
|
|
|
|
/*special handling to make a section accept the first texture painted on it as a base texture. no more chessboard*/
|
|
if (!*s->texname[0] && !*s->texname[1] && !*s->texname[2] && !*s->texname[3])
|
|
{
|
|
Q_strncpyz(s->texname[3], texname, sizeof(s->texname[3]));
|
|
Terr_LoadSectionTextures(s);
|
|
|
|
for (idx = 0; idx < SECTTEXSIZE*SECTTEXSIZE; idx++)
|
|
{
|
|
lm = Terr_GetLightmap(s, idx, true);
|
|
lm[2] = 0;
|
|
lm[1] = 0;
|
|
lm[0] = 0;
|
|
}
|
|
return;
|
|
}
|
|
for (t = 0; t < 4; t++)
|
|
{
|
|
if (!*s->texname[t])
|
|
{
|
|
Q_strncpyz(s->texname[t], texname, sizeof(s->texname[t]));
|
|
|
|
newval[0] = (t == 0);
|
|
newval[1] = (t == 1);
|
|
newval[2] = (t == 2);
|
|
lm[2] = lm[2]*(1-w) + (255*newval[0]*(w));
|
|
lm[1] = lm[1]*(1-w) + (255*newval[1]*(w));
|
|
lm[0] = lm[0]*(1-w) + (255*newval[2]*(w));
|
|
|
|
Terr_LoadSectionTextures(s);
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
|
|
static void ted_texreplace(void *ctx, hmsection_t *s, int idx, float wx, float wy, float w)
|
|
{
|
|
if (w > 0)
|
|
ted_texpaint(ctx, s, idx, wx, wy, 1);
|
|
}
|
|
|
|
/*
|
|
static void ted_texlight(void *ctx, hmsection_t *s, int idx, float wx, float wy, float w)
|
|
{
|
|
unsigned char *lm = ted_getlightmap(s, idx);
|
|
vec3_t pos, pos2;
|
|
vec3_t norm, tnorm;
|
|
vec3_t ldir = {0.4, 0.7, 2};
|
|
float d;
|
|
int x,y;
|
|
trace_t tr;
|
|
VectorClear(norm);
|
|
for (y = -4; y < 4; y++)
|
|
for (x = -4; x < 4; x++)
|
|
{
|
|
pos[0] = wx - (CHUNKBIAS + x/64.0) * s->hmmod->sectionsize;
|
|
pos[1] = wy - (CHUNKBIAS + y/64.0) * s->hmmod->sectionsize;
|
|
#if 0
|
|
pos[2] = 10000;
|
|
pos2[0] = wx - (CHUNKBIAS + x/64.0) * s->hmmod->sectionsize;
|
|
pos2[1] = wy - (CHUNKBIAS + y/64.0) * s->hmmod->sectionsize;
|
|
pos2[2] = -10000;
|
|
Heightmap_Trace(cl.worldmodel, 0, 0, NULL, pos, pos2, vec3_origin, vec3_origin, FTECONTENTS_SOLID, &tr);
|
|
VectorCopy(tr.plane.normal, tnorm);
|
|
#else
|
|
Heightmap_Normal(s->hmmod, pos, tnorm);
|
|
#endif
|
|
d = sqrt(32 - x*x+y*y);
|
|
VectorMA(norm, d, tnorm, norm);
|
|
}
|
|
|
|
VectorNormalize(ldir);
|
|
VectorNormalize(norm);
|
|
d = DotProduct(ldir, norm);
|
|
if (d < 0)
|
|
d = 0;
|
|
lm[3] = d*255;
|
|
}
|
|
*/
|
|
static void ted_texset(void *ctx, hmsection_t *s, int idx, float wx, float wy, float w)
|
|
{
|
|
unsigned char *lm = Terr_GetLightmap(s, idx, true);
|
|
if (w > 1)
|
|
w = 1;
|
|
s->flags |= TSF_NOTIFY|TSF_EDITED;
|
|
|
|
lm[2] = lm[2]*(1-w) + (255*((float*)ctx)[0]*(w));
|
|
lm[1] = lm[1]*(1-w) + (255*((float*)ctx)[1]*(w));
|
|
lm[0] = lm[0]*(1-w) + (255*((float*)ctx)[2]*(w));
|
|
}
|
|
|
|
static void ted_textally(void *ctx, hmsection_t *s, int idx, float wx, float wy, float w)
|
|
{
|
|
unsigned char *lm = Terr_GetLightmap(s, idx, false);
|
|
((float*)ctx)[0] += lm[0]*w;
|
|
((float*)ctx)[1] += lm[1]*w;
|
|
((float*)ctx)[2] += lm[2]*w;
|
|
((float*)ctx)[3] += w;
|
|
}
|
|
|
|
static void ted_tint(void *ctx, hmsection_t *s, int idx, float wx, float wy, float w)
|
|
{
|
|
float *col = s->colours[idx];
|
|
float *newval = ctx;
|
|
if (w > 1)
|
|
w = 1;
|
|
s->flags |= TSF_NOTIFY|TSF_DIRTY|TSF_EDITED|TSF_HASCOLOURS; /*dirty because of the vbo*/
|
|
col[0] = col[0]*(1-w) + (newval[0]*(w));
|
|
col[1] = col[1]*(1-w) + (newval[1]*(w));
|
|
col[2] = col[2]*(1-w) + (newval[2]*(w));
|
|
col[3] = col[3]*(1-w) + (newval[3]*(w));
|
|
}
|
|
|
|
enum
|
|
{
|
|
tid_linear,
|
|
tid_exponential,
|
|
tid_square_linear,
|
|
tid_square_exponential,
|
|
tid_flat
|
|
};
|
|
//calls 'func' for each tile upon the terrain. the 'tile' can be either height or texel
|
|
static void ted_itterate(heightmap_t *hm, int distribution, float *pos, float radius, float strength, int steps, void(*func)(void *ctx, hmsection_t *s, int idx, float wx, float wy, float strength), void *ctx)
|
|
{
|
|
int tx, ty;
|
|
float wx, wy;
|
|
float sc[2];
|
|
int min[2], max[2];
|
|
int sx,sy;
|
|
hmsection_t *s;
|
|
float w, xd, yd;
|
|
|
|
if (radius < 0)
|
|
{
|
|
radius *= -1;
|
|
distribution |= 2;
|
|
}
|
|
|
|
min[0] = floor((pos[0] - radius)/(hm->sectionsize) - 1.5);
|
|
min[1] = floor((pos[1] - radius)/(hm->sectionsize) - 1.5);
|
|
max[0] = ceil((pos[0] + radius)/(hm->sectionsize) + 1.5);
|
|
max[1] = ceil((pos[1] + radius)/(hm->sectionsize) + 1.5);
|
|
|
|
min[0] = bound(hm->firstsegx, min[0], hm->maxsegx);
|
|
min[1] = bound(hm->firstsegy, min[1], hm->maxsegy);
|
|
max[0] = bound(hm->firstsegx, max[0], hm->maxsegx);
|
|
max[1] = bound(hm->firstsegy, max[1], hm->maxsegy);
|
|
|
|
sc[0] = hm->sectionsize/(steps-1);
|
|
sc[1] = hm->sectionsize/(steps-1);
|
|
|
|
for (sy = min[1]; sy < max[1]; sy++)
|
|
{
|
|
for (sx = min[0]; sx < max[0]; sx++)
|
|
{
|
|
s = Terr_GetSection(hm, sx, sy, TGS_WAITLOAD|TGS_DEFAULTONFAIL);
|
|
if (!s)
|
|
continue;
|
|
|
|
for (ty = 0; ty < steps; ty++)
|
|
{
|
|
wy = (sy*(steps-1.0) + ty)*sc[1];
|
|
yd = wy - pos[1];// - sc[1]/4;
|
|
for (tx = 0; tx < steps; tx++)
|
|
{
|
|
/*both heights and textures have an overlapping/matching sample at the edge, there's no need for any half-pixels or anything here*/
|
|
wx = (sx*(steps-1.0) + tx)*sc[0];
|
|
xd = wx - pos[0];// - sc[0]/4;
|
|
|
|
switch(distribution)
|
|
{
|
|
case tid_exponential:
|
|
w = radius*radius - (xd*xd+yd*yd);
|
|
if (w > 0)
|
|
func(ctx, s, tx+ty*steps, wx, wy, sqrt(w)*strength/(radius));
|
|
break;
|
|
case tid_linear:
|
|
w = radius - sqrt(xd*xd+yd*yd);
|
|
if (w > 0)
|
|
func(ctx, s, tx+ty*steps, wx, wy, w*strength/(radius));
|
|
break;
|
|
case tid_square_exponential:
|
|
w = max(fabs(xd), fabs(yd));
|
|
w = radius*radius - w*w;
|
|
if (w > 0)
|
|
func(ctx, s, tx+ty*steps, wx, wy, sqrt(w)*strength/(radius));
|
|
break;
|
|
case tid_square_linear:
|
|
w = max(fabs(xd), fabs(yd));
|
|
w = radius - w;
|
|
if (w > 0)
|
|
func(ctx, s, tx+ty*steps, wx, wy, w*strength/(radius));
|
|
break;
|
|
case tid_flat:
|
|
w = max(fabs(xd), fabs(yd));
|
|
w = radius - w;
|
|
if (w > 0)
|
|
func(ctx, s, tx+ty*steps, wx, wy, strength);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void ted_texkill(hmsection_t *s, const char *killtex)
|
|
{
|
|
int x, y, t, to;
|
|
if (!s)
|
|
return;
|
|
for (t = 0; t < 4; t++)
|
|
{
|
|
if (!strcmp(s->texname[t], killtex))
|
|
{
|
|
unsigned char *lm = Terr_GetLightmap(s, 0, true);
|
|
s->flags |= TSF_EDITED;
|
|
s->texname[t][0] = 0;
|
|
for (to = 0; to < 4; to++)
|
|
if (*s->texname[to])
|
|
break;
|
|
if (to == 4)
|
|
to = 0;
|
|
|
|
if (to == 0 || to == 2)
|
|
to = 2 - to;
|
|
if (t == 0 || t == 2)
|
|
t = 2 - t;
|
|
|
|
for (y = 0; y < SECTTEXSIZE; y++)
|
|
{
|
|
for (x = 0; x < SECTTEXSIZE; x++, lm+=4)
|
|
{
|
|
if (t == 3)
|
|
{
|
|
//to won't be 3
|
|
lm[to] = lm[to] + (255 - (lm[0] + lm[1] + lm[2]));
|
|
}
|
|
else
|
|
{
|
|
if (to != 3)
|
|
lm[to] = (lm[to]+lm[t])&0xff;
|
|
lm[t] = 0;
|
|
}
|
|
}
|
|
lm += SECTTEXSIZE*4*(LMCHUNKS-1);
|
|
}
|
|
if (t == 0 || t == 2)
|
|
t = 2 - t;
|
|
Terr_LoadSectionTextures(s);
|
|
}
|
|
}
|
|
}
|
|
|
|
void QCBUILTIN PF_terrain_edit(pubprogfuncs_t *prinst, struct globalvars_s *pr_globals)
|
|
{
|
|
world_t *vmw = prinst->parms->user;
|
|
int action = G_FLOAT(OFS_PARM0);
|
|
vec3_t pos;// G_VECTOR(OFS_PARM1);
|
|
float radius = G_FLOAT(OFS_PARM2);
|
|
float quant = G_FLOAT(OFS_PARM3);
|
|
int modelindex = ((wedict_t*)PROG_TO_EDICT(prinst, *vmw->g.self))->v->modelindex;
|
|
// G_FLOAT(OFS_RETURN) = Heightmap_Edit(w->worldmodel, action, pos, radius, quant);
|
|
model_t *mod = vmw->Get_CModel(vmw, modelindex);
|
|
heightmap_t *hm;
|
|
vec4_t tally;
|
|
|
|
G_FLOAT(OFS_RETURN) = 0;
|
|
|
|
if (!mod || !mod->terrain)
|
|
{
|
|
if (mod && mod->loadstate == MLS_LOADING)
|
|
COM_WorkerPartialSync(mod, &mod->loadstate, MLS_LOADING);
|
|
}
|
|
if (mod->loadstate != MLS_LOADED)
|
|
return;
|
|
|
|
switch(action)
|
|
{
|
|
case ter_ent_get:
|
|
{
|
|
int idx = G_INT(OFS_PARM1);
|
|
if (!mod->numentityinfo)
|
|
Mod_ParseEntities(mod);
|
|
if (idx >= mod->numentityinfo || !mod->entityinfo[idx].keyvals)
|
|
G_INT(OFS_RETURN) = 0;
|
|
else
|
|
G_INT(OFS_RETURN) = PR_TempString(prinst, mod->entityinfo[idx].keyvals);
|
|
}
|
|
return;
|
|
case ter_ent_set:
|
|
{
|
|
int idx = G_INT(OFS_PARM1);
|
|
const char *newvals;
|
|
if (!mod->numentityinfo)
|
|
Mod_ParseEntities(mod);
|
|
if (idx < mod->numentityinfo)
|
|
{
|
|
Z_Free(mod->entityinfo[idx].keyvals);
|
|
mod->entityinfo[idx].keyvals = NULL;
|
|
}
|
|
if (G_INT(OFS_PARM2))
|
|
{
|
|
newvals = PR_GetStringOfs(prinst, OFS_PARM2);
|
|
if (idx >= mod->numentityinfo)
|
|
Z_ReallocElements((void**)&mod->entityinfo, &mod->numentityinfo, idx+64, sizeof(*mod->entityinfo));
|
|
mod->entityinfo[idx].keyvals = Z_StrDup(newvals);
|
|
}
|
|
else
|
|
newvals = NULL;
|
|
G_INT(OFS_RETURN) = 0;
|
|
|
|
#ifndef CLIENTONLY
|
|
if (sv.state && modelindex > 0)
|
|
{
|
|
MSG_WriteByte(&sv.multicast, svcfte_brushedit);
|
|
MSG_WriteShort(&sv.multicast, modelindex);
|
|
MSG_WriteByte(&sv.multicast, newvals?hmcmd_ent_edit:hmcmd_ent_remove);
|
|
MSG_WriteLong(&sv.multicast, idx);
|
|
if (newvals)
|
|
MSG_WriteString(&sv.multicast, newvals);
|
|
SV_MulticastProtExt(vec3_origin, MULTICAST_ALL_R, ~0, 0, 0);
|
|
//tell ssqc, csqc will be told by the server
|
|
}
|
|
else
|
|
#endif
|
|
#ifndef SERVERONLY
|
|
if (cls.state && modelindex > 0)
|
|
{
|
|
MSG_WriteByte(&cls.netchan.message, clcfte_brushedit);
|
|
MSG_WriteShort(&cls.netchan.message, modelindex);
|
|
MSG_WriteByte(&cls.netchan.message, newvals?hmcmd_ent_edit:hmcmd_ent_remove);
|
|
MSG_WriteLong(&cls.netchan.message, idx);
|
|
if (newvals)
|
|
MSG_WriteString(&cls.netchan.message, newvals);
|
|
|
|
#ifdef CSQC_DAT
|
|
CSQC_MapEntityEdited(idx, newvals);
|
|
#endif
|
|
}
|
|
else
|
|
#endif
|
|
{
|
|
#ifdef CSQC_DAT
|
|
CSQC_MapEntityEdited(idx, newvals);
|
|
#endif
|
|
}
|
|
}
|
|
return;
|
|
case ter_ent_add:
|
|
{
|
|
// int idx = G_INT(OFS_PARM1);
|
|
// const char *news = PR_GetStringOfs(prinst, OFS_PARM2);
|
|
G_INT(OFS_RETURN) = mod->numentityinfo;
|
|
|
|
}
|
|
return;
|
|
case ter_ent_count:
|
|
if (!mod->numentityinfo)
|
|
Mod_ParseEntities(mod);
|
|
G_INT(OFS_RETURN) = mod->numentityinfo;
|
|
return;
|
|
case ter_ents_wipe:
|
|
G_INT(OFS_RETURN) = PR_TempString(prinst, Mod_GetEntitiesString(mod));
|
|
Mod_SetEntitiesString(mod, "", true);
|
|
return;
|
|
case ter_ents_concat:
|
|
{
|
|
char *newv;
|
|
const char *olds = Mod_GetEntitiesString(mod);
|
|
const char *news = PR_GetStringOfs(prinst, OFS_PARM1);
|
|
size_t oldlen = strlen(olds);
|
|
size_t newlen = strlen(news);
|
|
newv = Z_Malloc(oldlen + newlen + 1);
|
|
memcpy(newv, olds, oldlen);
|
|
memcpy(newv+oldlen, news, newlen);
|
|
newv[oldlen + newlen] = 0;
|
|
Z_Free((char*)olds);
|
|
G_FLOAT(OFS_RETURN) = oldlen + newlen;
|
|
|
|
Mod_SetEntitiesString(mod, newv, false);
|
|
if (mod->terrain)
|
|
{
|
|
hm = mod->terrain;
|
|
hm->entsdirty = true;
|
|
}
|
|
}
|
|
return;
|
|
case ter_ents_get:
|
|
G_INT(OFS_RETURN) = PR_TempString(prinst, Mod_GetEntitiesString(mod));
|
|
return;
|
|
case ter_save:
|
|
if (mod->terrain)
|
|
{
|
|
quant = Heightmap_Save(mod->terrain);
|
|
Con_DPrintf("ter_save: %g sections saved\n", quant);
|
|
}
|
|
G_FLOAT(OFS_RETURN) = quant;
|
|
/*
|
|
if (mod->type == mod_brush)
|
|
{
|
|
Con_Printf("that model isn't a suitable worldmodel\n");
|
|
return;
|
|
}
|
|
else
|
|
{
|
|
FS_CreatePath(fname, FS_GAMEONLY);
|
|
file = FS_OpenVFS(fname, "wb", FS_GAMEONLY);
|
|
if (!file)
|
|
Con_Printf("unable to open %s\n", fname);
|
|
else
|
|
{
|
|
Terr_WriteMapFile(file, mod);
|
|
VFS_CLOSE(file);
|
|
}
|
|
}*/
|
|
return;
|
|
}
|
|
|
|
if (!mod->terrain)
|
|
{
|
|
char basename[MAX_QPATH];
|
|
COM_FileBase(mod->name, basename, sizeof(basename));
|
|
mod->terrain = Mod_LoadTerrainInfo(mod, basename, true);
|
|
hm = mod->terrain;
|
|
if (!hm)
|
|
return;
|
|
Terr_FinishTerrain(mod);
|
|
}
|
|
hm = mod->terrain;
|
|
|
|
pos[0] = G_FLOAT(OFS_PARM1+0) + hm->sectionsize * CHUNKBIAS;
|
|
pos[1] = G_FLOAT(OFS_PARM1+1) + hm->sectionsize * CHUNKBIAS;
|
|
pos[2] = G_FLOAT(OFS_PARM1+2);
|
|
|
|
switch(action)
|
|
{
|
|
case ter_reload:
|
|
G_FLOAT(OFS_RETURN) = 1;
|
|
Terr_PurgeTerrainModel(mod, false, true);
|
|
break;
|
|
case ter_sethole:
|
|
/* {
|
|
int x, y;
|
|
hmsection_t *s;
|
|
x = pos[0]*4 / hm->sectionsize;
|
|
y = pos[1]*4 / hm->sectionsize;
|
|
x = bound(hm->firstsegx*4, x, hm->maxsegx*4-1);
|
|
y = bound(hm->firstsegy*4, y, hm->maxsegy*4-1);
|
|
|
|
s = Terr_GetSection(hm, x/4, y/4, TGS_FORCELOAD);
|
|
if (!s)
|
|
return;
|
|
ted_sethole(&quant, s, (x&3) + (y&3)*4, x/4, y/4, 0);
|
|
}
|
|
*/
|
|
pos[0] -= 0.5 * hm->sectionsize / 8;
|
|
pos[1] -= 0.5 * hm->sectionsize / 8;
|
|
ted_itterate(hm, tid_linear, pos, radius, 1, 9, ted_sethole, &quant);
|
|
break;
|
|
case ter_height_set:
|
|
ted_itterate(hm, tid_linear, pos, radius, 1, SECTHEIGHTSIZE, ted_heightset, &quant);
|
|
break;
|
|
case ter_height_flatten:
|
|
tally[0] = 0;
|
|
tally[1] = 0;
|
|
ted_itterate(hm, tid_exponential, pos, radius, 1, SECTHEIGHTSIZE, ted_heighttally, &tally);
|
|
tally[0] /= tally[1];
|
|
if (IS_NAN(tally[0]))
|
|
tally[0] = 0;
|
|
ted_itterate(hm, tid_exponential, pos, radius, quant, SECTHEIGHTSIZE, ted_heightsmooth, &tally);
|
|
|
|
ted_itterate(hm, tid_exponential, pos, radius, quant, SECTHEIGHTSIZE, ted_heightdebug, &tally);
|
|
break;
|
|
case ter_height_smooth:
|
|
tally[0] = 0;
|
|
tally[1] = 0;
|
|
ted_itterate(hm, tid_linear, pos, radius, 1, SECTHEIGHTSIZE, ted_heighttally, &tally);
|
|
tally[0] /= tally[1];
|
|
if (IS_NAN(tally[0]))
|
|
tally[0] = 0;
|
|
ted_itterate(hm, tid_linear, pos, radius, quant, SECTHEIGHTSIZE, ted_heightsmooth, &tally);
|
|
break;
|
|
case ter_height_spread:
|
|
tally[0] = 0;
|
|
tally[1] = 0;
|
|
ted_itterate(hm, tid_exponential, pos, radius/2, 1, SECTHEIGHTSIZE, ted_heighttally, &tally);
|
|
tally[0] /= tally[1];
|
|
if (IS_NAN(tally[0]))
|
|
tally[0] = 0;
|
|
ted_itterate(hm, tid_exponential, pos, radius, 1, SECTHEIGHTSIZE, ted_heightsmooth, &tally);
|
|
break;
|
|
case ter_water_set:
|
|
ted_itterate(hm, tid_linear, pos, radius, 1, 9, ted_waterset, &quant);
|
|
break;
|
|
case ter_lower:
|
|
quant *= -1;
|
|
case ter_raise:
|
|
ted_itterate(hm, tid_exponential, pos, radius, quant, SECTHEIGHTSIZE, ted_heightraise, &quant);
|
|
break;
|
|
case ter_tint:
|
|
ted_itterate(hm, tid_exponential, pos, radius, quant, SECTHEIGHTSIZE, ted_tint, G_VECTOR(OFS_PARM4)); //and parm5 too
|
|
break;
|
|
// case ter_mixset:
|
|
// ted_itterate(hm, tid_exponential, pos, radius, 1, SECTTEXSIZE, ted_mixset, G_VECTOR(OFS_PARM4));
|
|
// break;
|
|
case ter_tex_blend:
|
|
ted_itterate(hm, tid_exponential, pos, radius, quant/10, SECTTEXSIZE, ted_texpaint, (void*)PR_GetStringOfs(prinst, OFS_PARM4));
|
|
break;
|
|
case ter_tex_replace:
|
|
ted_itterate(hm, tid_exponential, pos, radius, 1, SECTTEXSIZE, ted_texreplace, (void*)PR_GetStringOfs(prinst, OFS_PARM3));
|
|
break;
|
|
case ter_tex_concentrate:
|
|
ted_itterate(hm, tid_exponential, pos, radius, 1, SECTTEXSIZE, ted_texconcentrate, NULL);
|
|
break;
|
|
case ter_tex_noise:
|
|
ted_itterate(hm, tid_exponential, pos, radius, 1, SECTTEXSIZE, ted_texnoise, NULL);
|
|
break;
|
|
case ter_tex_blur:
|
|
Vector4Set(tally, 0, 0, 0, 0);
|
|
ted_itterate(hm, tid_exponential, pos, radius, 1, SECTTEXSIZE, ted_textally, &tally);
|
|
VectorScale(tally, 1/(tally[3]*255), tally);
|
|
ted_itterate(hm, tid_exponential, pos, radius, quant, SECTTEXSIZE, ted_texset, &tally);
|
|
break;
|
|
case ter_tex_get:
|
|
{
|
|
int x, y;
|
|
hmsection_t *s;
|
|
x = pos[0] / hm->sectionsize;
|
|
y = pos[1] / hm->sectionsize;
|
|
x = bound(hm->firstsegx, x, hm->maxsegx-1);
|
|
y = bound(hm->firstsegy, y, hm->maxsegy-1);
|
|
|
|
s = Terr_GetSection(hm, x, y, TGS_WAITLOAD|TGS_DEFAULTONFAIL);
|
|
if (!s)
|
|
return;
|
|
x = bound(0, quant, 3);
|
|
G_INT(OFS_RETURN) = PR_TempString(prinst, s->texname[x]);
|
|
}
|
|
break;
|
|
case ter_tex_mask:
|
|
Z_Free(hm->texmask);
|
|
hm->texmask = NULL;
|
|
|
|
if (G_INT(OFS_PARM1) == 0)
|
|
hm->texmask = NULL;
|
|
else
|
|
hm->texmask = Z_StrDup(PR_GetStringOfs(prinst, OFS_PARM1));
|
|
break;
|
|
case ter_tex_kill:
|
|
{
|
|
int x, y;
|
|
x = pos[0] / hm->sectionsize;
|
|
y = pos[1] / hm->sectionsize;
|
|
x = bound(hm->firstsegx, x, hm->maxsegx-1);
|
|
y = bound(hm->firstsegy, y, hm->maxsegy-1);
|
|
|
|
ted_texkill(Terr_GetSection(hm, x, y, TGS_WAITLOAD|TGS_DEFAULTONFAIL), PR_GetStringOfs(prinst, OFS_PARM4));
|
|
}
|
|
break;
|
|
case ter_reset:
|
|
{
|
|
int x, y;
|
|
hmsection_t *s;
|
|
x = pos[0] / hm->sectionsize;
|
|
y = pos[1] / hm->sectionsize;
|
|
x = bound(hm->firstsegx, x, hm->maxsegx-1);
|
|
y = bound(hm->firstsegy, y, hm->maxsegy-1);
|
|
s = Terr_GetSection(hm, x, y, TGS_WAITLOAD|TGS_DEFAULTONFAIL);
|
|
if (s)
|
|
{
|
|
s->flags = (s->flags & ~TSF_EDITED);
|
|
Terr_ClearSection(s);
|
|
Terr_GenerateDefault(hm, s);
|
|
}
|
|
}
|
|
break;
|
|
case ter_mesh_add:
|
|
{
|
|
vec3_t axis[3];
|
|
wedict_t *ed = G_WEDICT(prinst, OFS_PARM1);
|
|
//FIXME: modeltype pitch inversion
|
|
AngleVectorsFLU(ed->v->angles, axis[0], axis[1], axis[2]);
|
|
Terr_AddMesh(hm, TGS_WAITLOAD|TGS_DEFAULTONFAIL, vmw->Get_CModel(vmw, ed->v->modelindex), NULL, ed->v->origin, axis, ed->xv->scale);
|
|
}
|
|
break;
|
|
case ter_mesh_kill:
|
|
{
|
|
int i;
|
|
// entity_t *e;
|
|
int x, y;
|
|
// float r;
|
|
hmsection_t *s;
|
|
x = pos[0] / hm->sectionsize;
|
|
y = pos[1] / hm->sectionsize;
|
|
x = bound(hm->firstsegx, x, hm->maxsegx-1);
|
|
y = bound(hm->firstsegy, y, hm->maxsegy-1);
|
|
|
|
s = Terr_GetSection(hm, x, y, TGS_WAITLOAD|TGS_DEFAULTONFAIL);
|
|
if (!s)
|
|
return;
|
|
|
|
Sys_LockMutex(hm->entitylock);
|
|
//FIXME: this doesn't work properly.
|
|
if (s->numents)
|
|
{
|
|
for (i = 0; i < s->numents; i++)
|
|
s->ents[i]->refs -= 1;
|
|
s->flags |= TSF_EDITED;
|
|
s->numents = 0;
|
|
}
|
|
Sys_UnlockMutex(hm->entitylock);
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
#else
|
|
static unsigned char *QDECL Terr_GetLightmap(hmsection_t *s, int idx, qboolean edit)
|
|
{
|
|
return NULL;
|
|
}
|
|
void QCBUILTIN PF_terrain_edit(pubprogfuncs_t *prinst, struct globalvars_s *pr_globals)
|
|
{
|
|
G_FLOAT(OFS_RETURN) = 0;
|
|
}
|
|
#endif
|
|
|
|
void Terr_ParseEntityLump(model_t *mod, heightmap_t *heightmap)
|
|
{
|
|
char key[128];
|
|
char value[2048];
|
|
const char *data = Mod_GetEntitiesString(mod);
|
|
|
|
heightmap->sectionsize = 1024;
|
|
heightmap->mode = HMM_TERRAIN;
|
|
heightmap->culldistance = 4096*4096;
|
|
heightmap->forcedefault = false;
|
|
|
|
heightmap->defaultgroundheight = 0;
|
|
heightmap->defaultwaterheight = 0;
|
|
Q_snprintfz(heightmap->defaultwatershader, sizeof(heightmap->defaultwatershader), "water/%s", heightmap->path);
|
|
Q_strncpyz(heightmap->defaultgroundtexture, "", sizeof(heightmap->defaultgroundtexture));
|
|
|
|
if (data)
|
|
if ((data=COM_ParseOut(data, key, sizeof(key)))) //read the map info.
|
|
if (key[0] == '{')
|
|
while (1)
|
|
{
|
|
if (!(data=COM_ParseOut(data, key, sizeof(key))))
|
|
break; // error
|
|
if (key[0] == '}')
|
|
break; // end of worldspawn
|
|
if (key[0] == '_')
|
|
memmove(key, key+1, strlen(key)); //_ vars are for comments/utility stuff that arn't visible to progs and for compat. We want to support these stealth things.
|
|
if (!((data=COM_ParseOut(data, value, sizeof(value)))))
|
|
break; // error
|
|
if (!strcmp("segmentsize", key))
|
|
heightmap->sectionsize = atof(value);
|
|
else if (!strcmp("minxsegment", key))
|
|
heightmap->firstsegx = atoi(value);
|
|
else if (!strcmp("minysegment", key))
|
|
heightmap->firstsegy = atoi(value);
|
|
else if (!strcmp("maxxsegment", key))
|
|
heightmap->maxsegx = atoi(value);
|
|
else if (!strcmp("maxysegment", key))
|
|
heightmap->maxsegy = atoi(value);
|
|
else if (!strcmp("forcedefault", key))
|
|
heightmap->forcedefault = !!atoi(value);
|
|
else if (!strcmp("defaultwaterheight", key))
|
|
heightmap->defaultwaterheight = atof(value);
|
|
else if (!strcmp("defaultgroundheight", key))
|
|
heightmap->defaultgroundheight = atof(value);
|
|
else if (!strcmp("defaultgroundtexture", key))
|
|
Q_strncpyz(heightmap->defaultgroundtexture, value, sizeof(heightmap->defaultgroundtexture));
|
|
else if (!strcmp("defaultwatertexture", key))
|
|
Q_strncpyz(heightmap->defaultwatershader, value, sizeof(heightmap->defaultwatershader));
|
|
else if (!strcmp("culldistance", key))
|
|
{
|
|
heightmap->culldistance = atof(value);
|
|
heightmap->culldistance *= heightmap->culldistance;
|
|
}
|
|
else if (!strcmp("skybox", key))
|
|
Q_strncpyz(heightmap->skyname, value, sizeof(heightmap->skyname));
|
|
else if (!strcmp("tiles", key))
|
|
{
|
|
char *d;
|
|
heightmap->mode = HMM_BLOCKS;
|
|
d = value;
|
|
d = COM_ParseOut(d, key, sizeof(key));
|
|
heightmap->tilepixcount[0] = atoi(key);
|
|
d = COM_ParseOut(d, key, sizeof(key));
|
|
heightmap->tilepixcount[1] = atoi(key);
|
|
d = COM_ParseOut(d, key, sizeof(key));
|
|
heightmap->tilecount[0] = atoi(key);
|
|
d = COM_ParseOut(d, key, sizeof(key));
|
|
heightmap->tilecount[1] = atoi(key);
|
|
}
|
|
}
|
|
|
|
/*bias and bound it*/
|
|
heightmap->firstsegx += CHUNKBIAS;
|
|
heightmap->firstsegy += CHUNKBIAS;
|
|
heightmap->maxsegx += CHUNKBIAS;
|
|
heightmap->maxsegy += CHUNKBIAS;
|
|
if (heightmap->firstsegx < 0)
|
|
heightmap->firstsegx = 0;
|
|
if (heightmap->firstsegy < 0)
|
|
heightmap->firstsegy = 0;
|
|
if (heightmap->maxsegx > CHUNKLIMIT)
|
|
heightmap->maxsegx = CHUNKLIMIT;
|
|
if (heightmap->maxsegy > CHUNKLIMIT)
|
|
heightmap->maxsegy = CHUNKLIMIT;
|
|
}
|
|
|
|
void Terr_FinishTerrain(model_t *mod)
|
|
{
|
|
#ifndef SERVERONLY
|
|
heightmap_t *hm = mod->terrain;
|
|
if (qrenderer != QR_NONE)
|
|
{
|
|
if (*hm->skyname)
|
|
hm->skyshader = R_RegisterCustom(va("skybox_%s", hm->skyname), SUF_NONE, Shader_DefaultSkybox, NULL);
|
|
else
|
|
hm->skyshader = NULL;
|
|
|
|
switch (hm->mode)
|
|
{
|
|
case HMM_BLOCKS:
|
|
hm->shader = R_RegisterShader("terraintileshader", SUF_NONE,
|
|
"{\n"
|
|
"{\n"
|
|
"map $diffuse\n"
|
|
"}\n"
|
|
"}\n"
|
|
);
|
|
break;
|
|
case HMM_TERRAIN:
|
|
hm->shader = R_RegisterShader(hm->groundshadername, SUF_LIGHTMAP,
|
|
"{\n"
|
|
"bemode rtlight\n"
|
|
"{\n"
|
|
"{\n"
|
|
"map $diffuse\n"
|
|
"blendfunc add\n"
|
|
"}\n"
|
|
//FIXME: these maps are a legacy thing, and could be removed if third-party glsl properly contains s_diffuse
|
|
"{\n"
|
|
"map $upperoverlay\n"
|
|
"}\n"
|
|
"{\n"
|
|
"map $loweroverlay\n"
|
|
"}\n"
|
|
"{\n"
|
|
"map $fullbright\n"
|
|
"}\n"
|
|
"{\n"
|
|
"map $lightmap\n"
|
|
"}\n"
|
|
"{\n"
|
|
"map $shadowmap\n"
|
|
"}\n"
|
|
"{\n"
|
|
"map $lightcubemap\n"
|
|
"}\n"
|
|
//woo, one glsl to rule them all
|
|
"program terrain#RTLIGHT\n"
|
|
"}\n"
|
|
"bemode depthdark\n"
|
|
"{\n"
|
|
"program depthonly\n"
|
|
"{\n"
|
|
"depthwrite\n"
|
|
"}\n"
|
|
"}\n"
|
|
"bemode depthonly\n"
|
|
"{\n"
|
|
"program depthonly\n"
|
|
"{\n"
|
|
"depthwrite\n"
|
|
"maskcolor\n"
|
|
"}\n"
|
|
"}\n"
|
|
|
|
//FIXME: these maps are a legacy thing, and could be removed if third-party glsl properly contains s_diffuse
|
|
"{\n"
|
|
"map $diffuse\n"
|
|
"}\n"
|
|
"{\n"
|
|
"map $upperoverlay\n"
|
|
"}\n"
|
|
"{\n"
|
|
"map $loweroverlay\n"
|
|
"}\n"
|
|
"{\n"
|
|
"map $fullbright\n"
|
|
"}\n"
|
|
"{\n"
|
|
"map $lightmap\n"
|
|
"}\n"
|
|
"program terrain\n"
|
|
"if r_terraindebug\n"
|
|
"program terraindebug\n"
|
|
"endif\n"
|
|
"}\n"
|
|
);
|
|
break;
|
|
}
|
|
}
|
|
#endif
|
|
}
|
|
|
|
#ifndef SERVERONLY
|
|
void Terr_Brush_Draw(heightmap_t *hm, batch_t **batches, entity_t *e)
|
|
{
|
|
batch_t *b;
|
|
size_t i, j;
|
|
vbobctx_t ctx;
|
|
|
|
brushbatch_t *bb;
|
|
brushtex_t *bt;
|
|
brushes_t *br;
|
|
|
|
struct {
|
|
vecV_t coord[65536];
|
|
vec2_t texcoord[65536];
|
|
vec2_t lmcoord[65536];
|
|
vec3_t normal[65536];
|
|
vec3_t svector[65536];
|
|
vec3_t tvector[65536];
|
|
index_t index[65535];
|
|
} *arrays = NULL;
|
|
size_t numverts = 0;
|
|
size_t numindicies = 0;
|
|
int w, h, lmnum;
|
|
float scale[2];
|
|
lightmapinfo_t *lm;
|
|
qboolean dorelight = true;
|
|
|
|
#ifdef RUNTIMELIGHTING
|
|
//FIXME: lightmaps
|
|
//if we're enabling lightmaps, make sure all surfaces have known sizes first.
|
|
//allocate lightmap space for all surfaces, and then rebuild all textures.
|
|
//if a surface is modified, clear its lightmap to -1 and when its batches are rebuilt, it'll unlight naturally.
|
|
|
|
if (hm->entsdirty)
|
|
{
|
|
model_t *mod = e->model;
|
|
if (mod->submodelof)
|
|
mod = mod->submodelof;
|
|
hm->entsdirty = false;
|
|
LightReloadEntities(hm->relightcontext, Mod_GetEntitiesString(mod), true);
|
|
|
|
//FIXME: figure out some way to hint this without having to relight the entire frigging world.
|
|
for (bt = hm->brushtextures; bt; bt = bt->next)
|
|
for (i = 0, br = hm->wbrushes; i < hm->numbrushes; i++, br++)
|
|
for (j = 0; j < br->numplanes; j++)
|
|
br->faces[j].relight = true;
|
|
}
|
|
|
|
if (hm->recalculatebrushlighting && !r_fullbright.ival)
|
|
{
|
|
unsigned int lmcount;
|
|
unsigned int lmblocksize = 512;//LMBLOCK_SIZE_MAX
|
|
hm->recalculatebrushlighting = false;
|
|
|
|
if (!hm->relightcontext)
|
|
{
|
|
for (numverts = 0, numindicies = 0, i = 0, br = hm->wbrushes; i < hm->numbrushes; i++, br++)
|
|
{
|
|
for (j = 0; j < br->numplanes; j++)
|
|
{
|
|
br->faces[j].lightmap = -1;
|
|
br->faces[j].lmbase[0] = 0;
|
|
br->faces[j].lmbase[1] = 0;
|
|
}
|
|
}
|
|
for (bt = hm->brushtextures; bt; bt = bt->next)
|
|
{
|
|
bt->rebuild = true;
|
|
bt->firstlm = 0;
|
|
bt->lmcount = 0;
|
|
}
|
|
|
|
BZ_Free(hm->brushlmremaps);
|
|
hm->brushlmremaps = NULL;
|
|
hm->brushmaxlms = 0;
|
|
}
|
|
else
|
|
{
|
|
Mod_LightmapAllocInit(&hm->brushlmalloc, false, lmblocksize, lmblocksize, 0);
|
|
hm->brushlmscale = 1.0/lmblocksize;
|
|
|
|
//textures is to try to ensure that they are allocated consecutively.
|
|
for (bt = hm->brushtextures; bt; bt = bt->next)
|
|
{
|
|
bt->firstlm = hm->brushlmalloc.lmnum;
|
|
for (numverts = 0, numindicies = 0, i = 0, br = hm->wbrushes; i < hm->numbrushes; i++, br++)
|
|
{
|
|
for (j = 0; j < br->numplanes; j++)
|
|
{
|
|
if (br->faces[j].tex == bt)
|
|
{
|
|
if (br->faces[j].lightdata)
|
|
{
|
|
Mod_LightmapAllocBlock(&hm->brushlmalloc, br->faces[j].lmextents[0], br->faces[j].lmextents[1], &br->faces[j].lmbase[0], &br->faces[j].lmbase[1], &br->faces[j].lightmap);
|
|
br->faces[j].relit = true;
|
|
}
|
|
else
|
|
{ //this surface has no lightmap info or something.
|
|
br->faces[j].lightmap = -1;
|
|
br->faces[j].lmbase[0] = 0;
|
|
br->faces[j].lmbase[1] = 0;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
bt->rebuild = true;
|
|
bt->lmcount = hm->brushlmalloc.lmnum - bt->firstlm;
|
|
if (hm->brushlmalloc.allocated[0])
|
|
bt->lmcount++;
|
|
if (hm->brushlmalloc.deluxe)
|
|
{
|
|
bt->firstlm *= 2;
|
|
bt->lmcount *= 2;
|
|
}
|
|
}
|
|
|
|
lmcount = hm->brushlmalloc.lmnum;
|
|
if (hm->brushlmalloc.allocated[0])
|
|
lmcount++;
|
|
if (hm->brushlmalloc.deluxe)
|
|
lmcount *= 2;
|
|
|
|
if (lmcount > hm->brushmaxlms)
|
|
{
|
|
int first;
|
|
hm->brushlmremaps = BZ_Realloc(hm->brushlmremaps, sizeof(*hm->brushlmremaps) * lmcount);
|
|
first = Surf_NewLightmaps(lmcount - hm->brushmaxlms, hm->brushlmalloc.width, hm->brushlmalloc.height, hm->brushlmalloc.deluxe);
|
|
|
|
while(hm->brushmaxlms < lmcount)
|
|
hm->brushlmremaps[hm->brushmaxlms++] = first++;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (hm->relightcontext && !r_fullbright.ival)
|
|
for (i = 0, br = hm->wbrushes; i < hm->numbrushes; i++, br++)
|
|
{
|
|
for (j = 0; j < br->numplanes; j++)
|
|
{
|
|
if (br->faces[j].relight && dorelight)
|
|
{
|
|
qbyte styles[4] = {0,255,255,255};
|
|
int texsize[2] = {br->faces[j].lmextents[0]-1, br->faces[j].lmextents[1]-1};
|
|
vec2_t exactmins, exactmaxs;
|
|
int m, k;
|
|
vec2_t lm;
|
|
for (m = 0; m < br->faces[j].numpoints; m++)
|
|
{
|
|
for (k = 0; k < 2; k++)
|
|
{
|
|
lm[k] = DotProduct(br->faces[j].points[m], br->faces[j].stdir[k]) + br->faces[j].stdir[k][3];
|
|
if (m == 0)
|
|
exactmins[k] = exactmaxs[k] = lm[k];
|
|
else if (lm[k] > exactmaxs[k])
|
|
exactmaxs[k] = lm[k];
|
|
else if (lm[k] < exactmins[k])
|
|
exactmins[k] = lm[k];
|
|
}
|
|
}
|
|
|
|
dorelight = false;
|
|
br->faces[j].relight = false;
|
|
LightPlane (hm->relightcontext, hm->lightthreadmem, styles, br->faces[j].lightdata, NULL, br->planes[j], br->faces[j].stdir, exactmins, exactmaxs, br->faces[j].lmbias, texsize, br->faces[j].lmscale); //special version that doesn't know what a face is or anything.
|
|
br->faces[j].relit = true;
|
|
}
|
|
if (br->faces[j].relit && br->faces[j].lightmap >= 0)
|
|
{
|
|
int s,t;
|
|
qbyte *out, *in;
|
|
lm = lightmap[hm->brushlmremaps[br->faces[j].lightmap]];
|
|
|
|
br->faces[j].relit = false;
|
|
|
|
lm->modified = true;
|
|
lm->rectchange.l = 0;
|
|
lm->rectchange.t = 0;
|
|
lm->rectchange.r = lm->width;
|
|
lm->rectchange.b = lm->height;
|
|
|
|
in = br->faces[j].lightdata;
|
|
out = lm->lightmaps + (br->faces[j].lmbase[1] * lm->width + br->faces[j].lmbase[0]) * lightmap_bytes;
|
|
if (lightmap_bytes == 4)
|
|
{
|
|
if (lightmap_bgra)
|
|
{
|
|
for (t = 0; t < br->faces[j].lmextents[1]; t++)
|
|
{
|
|
for (s = 0; s < br->faces[j].lmextents[0]; s++)
|
|
{
|
|
*out++ = in[2];
|
|
*out++ = in[1];
|
|
*out++ = in[0];
|
|
*out++ = 0xff;
|
|
in+=3;
|
|
}
|
|
out += (lm->width - br->faces[j].lmextents[0]) * 4;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
for (t = 0; t < br->faces[j].lmextents[1]; t++)
|
|
{
|
|
for (s = 0; s < br->faces[j].lmextents[0]; s++)
|
|
{
|
|
*out++ = in[0];
|
|
*out++ = in[1];
|
|
*out++ = in[2];
|
|
*out++ = 0xff;
|
|
in+=3;
|
|
}
|
|
out += (lm->width - br->faces[j].lmextents[0]) * 4;
|
|
}
|
|
}
|
|
}
|
|
else if (lightmap_bytes == 3)
|
|
{
|
|
if (lightmap_bgra)
|
|
{
|
|
for (t = 0; t < br->faces[j].lmextents[1]; t++)
|
|
{
|
|
for (s = 0; s < br->faces[j].lmextents[0]; s++)
|
|
{
|
|
*out++ = in[2];
|
|
*out++ = in[1];
|
|
*out++ = in[0];
|
|
in+=3;
|
|
}
|
|
out += (lm->width - br->faces[j].lmextents[0]) * 3;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
for (t = 0; t < br->faces[j].lmextents[1]; t++)
|
|
{
|
|
for (s = 0; s < br->faces[j].lmextents[0]; s++)
|
|
{
|
|
*out++ = in[0];
|
|
*out++ = in[1];
|
|
*out++ = in[2];
|
|
in+=3;
|
|
}
|
|
out += (lm->width - br->faces[j].lmextents[0]) * 3;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
#endif
|
|
|
|
for (bt = hm->brushtextures; bt; bt = bt->next)
|
|
{
|
|
if (!bt->shader)
|
|
{
|
|
#ifdef PACKAGE_TEXWAD
|
|
miptex_t *tx = W_GetMipTex(bt->shadername);
|
|
#else
|
|
const miptex_t *tx = NULL;
|
|
#endif
|
|
|
|
if (!Q_strcasecmp(bt->shadername, "clip"))
|
|
bt->shader = R_RegisterShader(bt->shadername, SUF_LIGHTMAP, "{\nsurfaceparm nodraw\n}");
|
|
else
|
|
bt->shader = R_RegisterCustom (bt->shadername, SUF_LIGHTMAP, Shader_DefaultBSPQ1, NULL);
|
|
// bt->shader = R_RegisterShader_Lightmap(bt->shadername);
|
|
|
|
if (!Q_strncasecmp(bt->shadername, "sky", 3) && tx)
|
|
R_InitSky (bt->shader, bt->shadername, (qbyte*)tx + tx->offsets[0], tx->width, tx->height);
|
|
else if (tx)
|
|
{
|
|
qbyte *mips[4] = {(qbyte*)tx + tx->offsets[0], (qbyte*)tx + tx->offsets[1], (qbyte*)tx + tx->offsets[2], (qbyte*)tx + tx->offsets[3]};
|
|
unsigned int mapflags = SHADER_HASPALETTED | SHADER_HASDIFFUSE | SHADER_HASFULLBRIGHT | SHADER_HASNORMALMAP | SHADER_HASGLOSS;
|
|
R_BuildLegacyTexnums(bt->shader, tx->name, NULL, mapflags, 0, TF_MIP4_SOLID8, tx->width, tx->height, mips, NULL);
|
|
}
|
|
else
|
|
R_BuildDefaultTexnums(NULL, bt->shader);
|
|
|
|
if (tx)
|
|
{
|
|
if (!bt->shader->width)
|
|
bt->shader->width = tx->width;
|
|
if (!bt->shader->height)
|
|
bt->shader->height = tx->height;
|
|
BZ_Free(tx);
|
|
}
|
|
}
|
|
|
|
if (bt->rebuild)
|
|
{
|
|
//FIXME: don't block.
|
|
if (R_GetShaderSizes(bt->shader, &w, &h, false) < 0)
|
|
continue;
|
|
bt->rebuild = false;
|
|
|
|
if (w<1) w = 64;
|
|
if (h<1) h = 64;
|
|
scale[0] = 1.0/w; //I hate needing this.
|
|
scale[1] = 1.0/h;
|
|
|
|
while(bt->batches)
|
|
{
|
|
bb = bt->batches;
|
|
bt->batches = bb->next;
|
|
|
|
BE_VBO_Destroy(&bb->vbo.coord, bb->vbo.vbomem);
|
|
BE_VBO_Destroy(&bb->vbo.indicies, bb->vbo.ebomem);
|
|
BZ_Free(bb);
|
|
}
|
|
|
|
if (!arrays)
|
|
arrays = BZ_Malloc(sizeof(*arrays));
|
|
|
|
for (lmnum = -1; lmnum < bt->firstlm+bt->lmcount; ((lmnum==-1)?(lmnum=bt->firstlm):(lmnum=lmnum+1)))
|
|
{
|
|
i = 0;
|
|
br = hm->wbrushes;
|
|
for (numverts = 0, numindicies = 0; i < hm->numbrushes; i++, br++)
|
|
{
|
|
//if a single batch has too many verts, cut it off before it overflows our maximum batch size, and hope we don't get a really really complex brush.
|
|
if (numverts > 0xf000 || numindicies > 0xf000)
|
|
break;
|
|
|
|
for (j = 0; j < br->numplanes; j++)
|
|
{
|
|
if (br->faces[j].tex == bt && !br->selected && br->faces[j].lightmap == lmnum)
|
|
{
|
|
size_t k, o;
|
|
float s,t;
|
|
|
|
for (k = 0, o = numverts; k < br->faces[j].numpoints; k++, o++)
|
|
{
|
|
VectorCopy(br->faces[j].points[k], arrays->coord[o]);
|
|
VectorCopy(br->planes[j], arrays->normal[o]);
|
|
VectorCopy(br->faces[j].stdir[0], arrays->svector[o]);
|
|
VectorCopy(br->faces[j].stdir[1], arrays->tvector[o]);
|
|
|
|
//compute the texcoord planes
|
|
s = (DotProduct(arrays->svector[o], arrays->coord[o]) + br->faces[j].stdir[0][3]);
|
|
t = (DotProduct(arrays->tvector[o], arrays->coord[o]) + br->faces[j].stdir[1][3]);
|
|
arrays->texcoord[o][0] = s * scale[0];
|
|
arrays->texcoord[o][1] = t * scale[1];
|
|
|
|
//maths, maths, and more maths.
|
|
arrays->lmcoord[o][0] = (br->faces[j].lmbase[0]+0.5 + s/br->faces[j].lmscale-br->faces[j].lmbias[0]) * hm->brushlmscale;
|
|
arrays->lmcoord[o][1] = (br->faces[j].lmbase[1]+0.5 + t/br->faces[j].lmscale-br->faces[j].lmbias[1]) * hm->brushlmscale;
|
|
}
|
|
for (k = 2; k < br->faces[j].numpoints; k++)
|
|
{ //triangle fans
|
|
arrays->index[numindicies++] = numverts + 0;
|
|
arrays->index[numindicies++] = numverts + k-1;
|
|
arrays->index[numindicies++] = numverts + k-0;
|
|
}
|
|
numverts += br->faces[j].numpoints;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (numverts || numindicies)
|
|
{
|
|
bb = Z_Malloc(sizeof(*bb) + (sizeof(bb->mesh.xyz_array[0])+sizeof(arrays->texcoord[0])+sizeof(arrays->lmcoord[0])+sizeof(arrays->normal[0])+sizeof(arrays->svector[0])+sizeof(arrays->tvector[0])) * numverts);
|
|
bb->next = bt->batches;
|
|
bt->batches = bb;
|
|
bb->lightmap = lmnum;
|
|
BE_VBO_Begin(&ctx, (sizeof(arrays->coord[0])+sizeof(arrays->texcoord[0])+sizeof(arrays->lmcoord[0])+sizeof(arrays->normal[0])+sizeof(arrays->svector[0])+sizeof(arrays->tvector[0])) * numverts);
|
|
BE_VBO_Data(&ctx, arrays->coord, sizeof(arrays->coord [0])*numverts, &bb->vbo.coord);
|
|
BE_VBO_Data(&ctx, arrays->texcoord, sizeof(arrays->texcoord [0])*numverts, &bb->vbo.texcoord);
|
|
BE_VBO_Data(&ctx, arrays->lmcoord, sizeof(arrays->lmcoord [0])*numverts, &bb->vbo.lmcoord[0]);
|
|
BE_VBO_Data(&ctx, arrays->normal, sizeof(arrays->normal [0])*numverts, &bb->vbo.normals);
|
|
BE_VBO_Data(&ctx, arrays->svector, sizeof(arrays->svector [0])*numverts, &bb->vbo.svector);
|
|
BE_VBO_Data(&ctx, arrays->tvector, sizeof(arrays->tvector [0])*numverts, &bb->vbo.tvector);
|
|
BE_VBO_Finish(&ctx, arrays->index, sizeof(arrays->index [0])*numindicies, &bb->vbo.indicies, &bb->vbo.vbomem, &bb->vbo.ebomem);
|
|
|
|
bb->mesh.xyz_array = (vecV_t*)(bb+1);
|
|
memcpy(bb->mesh.xyz_array, arrays->coord, sizeof(*bb->mesh.xyz_array) * numverts);
|
|
bb->mesh.st_array = (vec2_t*)(bb->mesh.xyz_array+numverts);
|
|
memcpy(bb->mesh.st_array, arrays->texcoord, sizeof(*bb->mesh.st_array) * numverts);
|
|
bb->mesh.lmst_array[0] = (vec2_t*)(bb->mesh.st_array+numverts);
|
|
memcpy(bb->mesh.lmst_array[0], arrays->lmcoord, sizeof(*bb->mesh.lmst_array) * numverts);
|
|
bb->mesh.normals_array = (vec3_t*)(bb->mesh.lmst_array[0]+numverts);
|
|
memcpy(bb->mesh.normals_array, arrays->normal, sizeof(*bb->mesh.normals_array) * numverts);
|
|
bb->mesh.snormals_array = (vec3_t*)(bb->mesh.normals_array+numverts);
|
|
memcpy(bb->mesh.snormals_array, arrays->svector, sizeof(*bb->mesh.snormals_array) * numverts);
|
|
bb->mesh.tnormals_array = (vec3_t*)(bb->mesh.snormals_array+numverts);
|
|
memcpy(bb->mesh.tnormals_array, arrays->tvector, sizeof(*bb->mesh.tnormals_array) * numverts);
|
|
|
|
bb->pmesh = &bb->mesh;
|
|
bb->mesh.numindexes = numindicies;
|
|
bb->mesh.numvertexes = numverts;
|
|
}
|
|
}
|
|
}
|
|
|
|
for(bb = bt->batches; bb; bb = bb->next)
|
|
{
|
|
b = BE_GetTempBatch();
|
|
if (b)
|
|
{
|
|
j = 0;
|
|
if (bb->lightmap >= 0)
|
|
b->lightmap[j++] = r_fullbright.ival?-1:hm->brushlmremaps[bb->lightmap];
|
|
for (; j < MAXRLIGHTMAPS; j++)
|
|
b->lightmap[j] = -1;
|
|
b->ent = e;
|
|
b->shader = bt->shader;
|
|
b->flags = 0;
|
|
b->mesh = &bb->pmesh;
|
|
b->meshes = 1;
|
|
b->buildmeshes = NULL;
|
|
b->skin = NULL;
|
|
b->texture = NULL;
|
|
b->vbo = &bb->vbo;
|
|
|
|
b->next = batches[b->shader->sort];
|
|
batches[b->shader->sort] = b;
|
|
}
|
|
}
|
|
}
|
|
if (arrays)
|
|
BZ_Free(arrays);
|
|
}
|
|
#endif
|
|
|
|
static brushtex_t *Terr_Brush_FindTexture(heightmap_t *hm, const char *texname)
|
|
{
|
|
brushtex_t *bt;
|
|
if (!hm)
|
|
return NULL;
|
|
|
|
for (bt = hm->brushtextures; bt; bt = bt->next)
|
|
{
|
|
if (!strcmp(bt->shadername, texname))
|
|
return bt;
|
|
}
|
|
bt = Z_Malloc(sizeof(*bt));
|
|
bt->next = hm->brushtextures;
|
|
hm->brushtextures = bt;
|
|
Q_strncpyz(bt->shadername, texname, sizeof(bt->shadername));
|
|
|
|
return bt;
|
|
}
|
|
|
|
static brushes_t *Terr_Brush_Insert(model_t *model, heightmap_t *hm, brushes_t *brush)
|
|
{
|
|
vecV_t facepoints[64];
|
|
unsigned int iface, oface, j, k;
|
|
unsigned int numpoints;
|
|
brushes_t *out;
|
|
vec2_t mins, maxs;
|
|
vec2_t lm;
|
|
|
|
if (!hm)
|
|
{
|
|
if (model && model->loadstate == MLS_LOADING)
|
|
COM_WorkerPartialSync(model, &model->loadstate, MLS_LOADING);
|
|
if (model && model->loadstate == MLS_LOADED)
|
|
{
|
|
char basename[MAX_QPATH];
|
|
COM_FileBase(model->name, basename, sizeof(basename));
|
|
model->terrain = Mod_LoadTerrainInfo(model, basename, true);
|
|
hm = model->terrain;
|
|
if (!hm)
|
|
return NULL;
|
|
Terr_FinishTerrain(model);
|
|
}
|
|
else
|
|
return NULL;
|
|
}
|
|
|
|
hm->wbrushes = BZ_Realloc(hm->wbrushes, sizeof(*hm->wbrushes) * (hm->numbrushes+1));
|
|
out = &hm->wbrushes[hm->numbrushes];
|
|
out->selected = false;
|
|
out->contents = brush->contents;
|
|
out->axialplanes = 0;
|
|
|
|
out->planes = BZ_Malloc((sizeof(*out->planes)+sizeof(*out->faces)) * brush->numplanes);
|
|
out->faces = (void*)(out->planes+brush->numplanes);
|
|
ClearBounds(out->mins, out->maxs);
|
|
for (iface = 0, oface = 0; iface < brush->numplanes; iface++)
|
|
{
|
|
for (j = 0; j < oface; j++)
|
|
{
|
|
if (out->planes[j][0] == brush->planes[iface][0] &&
|
|
out->planes[j][1] == brush->planes[iface][1] &&
|
|
out->planes[j][2] == brush->planes[iface][2] &&
|
|
out->planes[j][3] == brush->planes[iface][3])
|
|
break;
|
|
}
|
|
if (j < oface)
|
|
{
|
|
Con_DPrintf("duplicate plane\n");
|
|
continue;
|
|
}
|
|
|
|
//generate points now (so we know the correct mins+maxs for the brush, and whether the plane is relevent)
|
|
numpoints = Fragment_ClipPlaneToBrush(facepoints, sizeof(facepoints)/sizeof(facepoints[0]), brush->planes, sizeof(*brush->planes), brush->numplanes, brush->planes[iface]);
|
|
if (!numpoints)
|
|
{
|
|
Con_DPrintf("redundant face\n");
|
|
continue; //this surface was chopped away entirely, and isn't relevant.
|
|
}
|
|
|
|
//copy the basic face info out so we can save/restore/query/edit it later.
|
|
Vector4Copy(brush->planes[iface], out->planes[oface]);
|
|
out->faces[oface].tex = brush->faces[iface].tex;
|
|
Vector4Copy(brush->faces[iface].stdir[0], out->faces[oface].stdir[0]);
|
|
Vector4Copy(brush->faces[iface].stdir[1], out->faces[oface].stdir[1]);
|
|
|
|
if (out->planes[oface][0] == 1)
|
|
out->axialplanes |= 1u<<0;
|
|
else if (out->planes[oface][1] == 1)
|
|
out->axialplanes |= 1u<<1;
|
|
else if (out->planes[oface][2] == 1)
|
|
out->axialplanes |= 1u<<2;
|
|
else if (out->planes[oface][0] == -1)
|
|
out->axialplanes |= 1u<<3;
|
|
else if (out->planes[oface][1] == -1)
|
|
out->axialplanes |= 1u<<4;
|
|
else if (out->planes[oface][2] == -1)
|
|
out->axialplanes |= 1u<<5;
|
|
|
|
//make sure this stuff is rebuilt properly.
|
|
out->faces[oface].tex->rebuild = true;
|
|
|
|
//keep this stuff cached+reused, so everything is consistant. also work out min/max lightmap texture coords
|
|
out->faces[oface].points = BZ_Malloc(numpoints * sizeof(*out->faces[oface].points));
|
|
Vector2Set(mins, 0, 0);
|
|
Vector2Set(maxs, 0, 0);
|
|
for (j = 0; j < numpoints; j++)
|
|
{
|
|
AddPointToBounds(facepoints[j], out->mins, out->maxs);
|
|
VectorCopy(facepoints[j], out->faces[oface].points[j]);
|
|
for (k = 0; k < 2; k++)
|
|
{
|
|
lm[k] = DotProduct(out->faces[oface].points[j], out->faces[oface].stdir[k]) + out->faces[oface].stdir[k][3];
|
|
if (j == 0)
|
|
mins[k] = maxs[k] = lm[k];
|
|
else if (lm[k] > maxs[k])
|
|
maxs[k] = lm[k];
|
|
else if (lm[k] < mins[k])
|
|
mins[k] = lm[k];
|
|
}
|
|
}
|
|
out->faces[oface].numpoints = numpoints;
|
|
|
|
//determine lightmap scale, and extents. rescale the lightmap if it ought to have been subdivided.
|
|
out->faces[oface].relight = true;
|
|
out->faces[oface].lmscale = 16;
|
|
for (k = 0; k < 2; )
|
|
{
|
|
out->faces[oface].lmbias[k] = floor(mins[k]/out->faces[oface].lmscale);
|
|
out->faces[oface].lmextents[k] = ceil((maxs[k])/out->faces[oface].lmscale)-out->faces[oface].lmbias[k]+1;
|
|
if (out->faces[oface].lmextents[k] > 128)
|
|
{ //surface is too large for lightmap data. just drop its resolution, because splitting the face in plane-defined geometry is a bad idea.
|
|
if (out->faces[oface].lmscale > 256)
|
|
{
|
|
out->faces[oface].relight = false;
|
|
k++;
|
|
}
|
|
else
|
|
{
|
|
out->faces[oface].lmscale *= 2;
|
|
k = 0;
|
|
}
|
|
}
|
|
else
|
|
k++;
|
|
}
|
|
out->faces[oface].lightmap = -1;
|
|
out->faces[oface].lmbase[0] = 0;
|
|
out->faces[oface].lmbase[1] = 0;
|
|
if (out->faces[oface].relight)
|
|
{
|
|
out->faces[oface].lightdata = BZ_Malloc(out->faces[oface].lmextents[0] * out->faces[oface].lmextents[1] * 3);
|
|
memset(out->faces[oface].lightdata, 0x3f, out->faces[oface].lmextents[0]*out->faces[oface].lmextents[1]*3);
|
|
}
|
|
else
|
|
out->faces[oface].lightdata = NULL;
|
|
|
|
// Con_Printf("lm extents: %u %u (%i points)\n", out->faces[oface].lmextents[0], out->faces[oface].lmextents[1], numpoints);
|
|
oface++;
|
|
}
|
|
if (oface < 4)
|
|
{ //a brush with less than 4 planes cannot be a valid convex area (but can happen when certain redundant planes are chopped out). don't accept creation
|
|
//(we often get 2-plane brushes if the sides are sucked in)
|
|
for (j = 0; j < oface; j++)
|
|
{
|
|
BZ_Free(out->faces[j].lightdata);
|
|
BZ_Free(out->faces[j].points);
|
|
}
|
|
BZ_Free(out->planes);
|
|
return NULL;
|
|
}
|
|
out->numplanes = oface;
|
|
if (brush->id)
|
|
out->id = brush->id;
|
|
else
|
|
{
|
|
unsigned int i;
|
|
//loop to avoid creating two brushes with the same id
|
|
do
|
|
{
|
|
out->id = (++hm->brushidseq)&0x00ffffff;
|
|
#ifndef SERVERONLY
|
|
if (cls.state) //avoid networking conflicts by having each node generating its own private ids
|
|
out->id |= (cl.playerview[0].playernum+1)<<24;
|
|
#endif
|
|
|
|
for (i = 0; i < hm->numbrushes; i++)
|
|
{
|
|
if (hm->wbrushes[i].id == out->id)
|
|
break;
|
|
}
|
|
} while (i != hm->numbrushes);
|
|
}
|
|
// Con_Printf("brush %u (%i faces)\n", out->id, oface);
|
|
|
|
hm->numbrushes+=1;
|
|
hm->brushesedited = true;
|
|
|
|
hm->recalculatebrushlighting = true; //lightmaps need to be reallocated
|
|
|
|
//make sure the brush's bounds are added to the containing model.
|
|
AddPointToBounds(out->mins, model->mins, model->maxs);
|
|
AddPointToBounds(out->maxs, model->mins, model->maxs);
|
|
|
|
return out;
|
|
}
|
|
|
|
static void Terr_Brush_DeleteIdx(heightmap_t *hm, size_t idx)
|
|
{
|
|
int i;
|
|
brushes_t *br = &hm->wbrushes[idx];
|
|
if (!hm)
|
|
return;
|
|
|
|
for (i = 0; i < br->numplanes; i++)
|
|
{
|
|
BZ_Free(br->faces[i].lightdata);
|
|
BZ_Free(br->faces[i].points);
|
|
br->faces[i].tex->rebuild = true;
|
|
}
|
|
|
|
BZ_Free(br->planes);
|
|
hm->numbrushes--;
|
|
hm->brushesedited = true;
|
|
//plug the hole with some other brush.
|
|
if (idx < hm->numbrushes)
|
|
hm->wbrushes[idx] = hm->wbrushes[hm->numbrushes];
|
|
}
|
|
static qboolean Terr_Brush_DeleteId(heightmap_t *hm, unsigned int brushid)
|
|
{
|
|
size_t i;
|
|
brushes_t *br;
|
|
if (!hm)
|
|
return false;
|
|
|
|
for (i = 0; i < hm->numbrushes; i++)
|
|
{
|
|
br = &hm->wbrushes[i];
|
|
if (br->id == brushid)
|
|
{
|
|
Terr_Brush_DeleteIdx(hm, i);
|
|
return true;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
|
|
#ifdef _WIN32
|
|
#include <malloc.h>
|
|
#else
|
|
#include <alloca.h>
|
|
#endif
|
|
|
|
static void Brush_Serialise(sizebuf_t *sb, brushes_t *br)
|
|
{
|
|
unsigned int i;
|
|
MSG_WriteLong(sb, br->id);
|
|
MSG_WriteLong(sb, br->contents);
|
|
MSG_WriteLong(sb, br->numplanes);
|
|
|
|
for (i = 0; i < br->numplanes; i++)
|
|
{
|
|
MSG_WriteString(sb, br->faces[i].tex->shadername);
|
|
|
|
MSG_WriteFloat(sb, br->planes[i][0]);
|
|
MSG_WriteFloat(sb, br->planes[i][1]);
|
|
MSG_WriteFloat(sb, br->planes[i][2]);
|
|
MSG_WriteFloat(sb, br->planes[i][3]);
|
|
|
|
MSG_WriteFloat(sb, br->faces[i].stdir[0][0]);
|
|
MSG_WriteFloat(sb, br->faces[i].stdir[0][1]);
|
|
MSG_WriteFloat(sb, br->faces[i].stdir[0][2]);
|
|
MSG_WriteFloat(sb, br->faces[i].stdir[0][3]);
|
|
|
|
MSG_WriteFloat(sb, br->faces[i].stdir[1][0]);
|
|
MSG_WriteFloat(sb, br->faces[i].stdir[1][1]);
|
|
MSG_WriteFloat(sb, br->faces[i].stdir[1][2]);
|
|
MSG_WriteFloat(sb, br->faces[i].stdir[1][3]);
|
|
}
|
|
}
|
|
static qboolean Brush_Deserialise(heightmap_t *hm, brushes_t *br)
|
|
{
|
|
unsigned int i;
|
|
unsigned int maxplanes = br->numplanes;
|
|
br->id = MSG_ReadLong();
|
|
br->contents = MSG_ReadLong();
|
|
br->numplanes = MSG_ReadLong();
|
|
|
|
if (br->numplanes > maxplanes)
|
|
return false;
|
|
|
|
for (i = 0; i < br->numplanes; i++)
|
|
{
|
|
//FIXME: as a server, we probably want to reject the brush if we exceed some texnum/memory limitation, so clients can't just spam new textures endlessly.
|
|
br->faces[i].tex = Terr_Brush_FindTexture(hm, MSG_ReadString());
|
|
|
|
br->planes[i][0] = MSG_ReadFloat();
|
|
br->planes[i][1] = MSG_ReadFloat();
|
|
br->planes[i][2] = MSG_ReadFloat();
|
|
br->planes[i][3] = MSG_ReadFloat();
|
|
|
|
br->faces[i].stdir[0][0] = MSG_ReadFloat();
|
|
br->faces[i].stdir[0][1] = MSG_ReadFloat();
|
|
br->faces[i].stdir[0][2] = MSG_ReadFloat();
|
|
br->faces[i].stdir[0][3] = MSG_ReadFloat();
|
|
|
|
br->faces[i].stdir[1][0] = MSG_ReadFloat();
|
|
br->faces[i].stdir[1][1] = MSG_ReadFloat();
|
|
br->faces[i].stdir[1][2] = MSG_ReadFloat();
|
|
br->faces[i].stdir[1][3] = MSG_ReadFloat();
|
|
}
|
|
return true;
|
|
}
|
|
|
|
#ifndef SERVERONLY
|
|
heightmap_t *CL_BrushEdit_ForceContext(model_t *mod)
|
|
{
|
|
heightmap_t *hm = mod?mod->terrain:NULL;
|
|
if (!hm)
|
|
{
|
|
if (mod && mod->loadstate == MLS_LOADING)
|
|
COM_WorkerPartialSync(mod, &mod->loadstate, MLS_LOADING);
|
|
if (mod && mod->loadstate == MLS_LOADED)
|
|
{
|
|
char basename[MAX_QPATH];
|
|
COM_FileBase(mod->name, basename, sizeof(basename));
|
|
mod->terrain = Mod_LoadTerrainInfo(mod, basename, true);
|
|
hm = mod->terrain;
|
|
if (!hm)
|
|
return NULL;
|
|
Terr_FinishTerrain(mod);
|
|
}
|
|
else
|
|
return NULL;
|
|
}
|
|
return hm;
|
|
}
|
|
|
|
void CL_Parse_BrushEdit(void)
|
|
{
|
|
unsigned int modelindex = MSG_ReadShort();
|
|
int cmd = MSG_ReadByte();
|
|
|
|
model_t *mod = (modelindex<countof(cl.model_precache))?cl.model_precache[modelindex]:NULL;
|
|
heightmap_t *hm = mod?mod->terrain:NULL;
|
|
|
|
#ifdef CLIENTONLY
|
|
const qboolean ignore = false;
|
|
#else
|
|
const qboolean ignore = (sv.state>=ss_loading); //if we're the server then we already have this info. don't break anything (this info is present for demos).
|
|
#endif
|
|
|
|
if (cmd == hmcmd_brush_delete)
|
|
{
|
|
int id = MSG_ReadLong();
|
|
if (ignore)
|
|
return; //ignore if we're the server, we should already have it anyway.
|
|
Terr_Brush_DeleteId(hm, id);
|
|
}
|
|
else if (cmd == hmcmd_brush_insert) //1=create/replace
|
|
{
|
|
brushes_t brush;
|
|
|
|
hm = CL_BrushEdit_ForceContext(mod); //do this early, to ensure that the textures are correct
|
|
|
|
memset(&brush, 0, sizeof(brush));
|
|
brush.numplanes = 128;
|
|
brush.planes = alloca(sizeof(*brush.planes) * brush.numplanes);
|
|
brush.faces = alloca(sizeof(*brush.faces) * brush.numplanes);
|
|
if (!Brush_Deserialise(hm, &brush))
|
|
Host_EndGame("CL_Parse_BrushEdit: unparsable brush\n");
|
|
if (ignore)
|
|
return; //ignore if we're the server, we should already have it anyway (but might need it for demos, hence why its still sent).
|
|
if (brush.id)
|
|
{
|
|
int i;
|
|
if (cls.demoplayback)
|
|
Terr_Brush_DeleteId(hm, brush.id);
|
|
else
|
|
{
|
|
for (i = 0; i < hm->numbrushes; i++)
|
|
{
|
|
brushes_t *br = &hm->wbrushes[i];
|
|
if (br->id == brush.id)
|
|
return; //we already have it. assume we just edited it.
|
|
}
|
|
}
|
|
}
|
|
Terr_Brush_Insert(mod, hm, &brush);
|
|
}
|
|
else if (cmd == hmcmd_prespawning)
|
|
{ //delete all
|
|
if (ignore)
|
|
return; //ignore if we're the server, we should already have it anyway.
|
|
|
|
hm = CL_BrushEdit_ForceContext(mod); //make sure we don't end up with any loaded brushes.
|
|
if (hm)
|
|
{
|
|
while(hm->numbrushes)
|
|
Terr_Brush_DeleteIdx(hm, hm->numbrushes-1);
|
|
}
|
|
}
|
|
else if (cmd == hmcmd_prespawned)
|
|
{
|
|
}
|
|
else if (cmd == hmcmd_ent_edit)
|
|
{ //ent edit
|
|
int id = MSG_ReadLong();
|
|
const char *data = MSG_ReadString();
|
|
if (id > 0xffff)
|
|
return;
|
|
if (id >= mod->numentityinfo)
|
|
Z_ReallocElements((void**)&mod->entityinfo, &mod->numentityinfo, id+64, sizeof(*mod->entityinfo));
|
|
if (id < mod->numentityinfo)
|
|
{
|
|
if (!ignore)
|
|
{
|
|
Z_Free(mod->entityinfo[id].keyvals);
|
|
mod->entityinfo[id].keyvals = Z_StrDup(data);
|
|
}
|
|
CSQC_MapEntityEdited(id, data);
|
|
}
|
|
}
|
|
else if (cmd == hmcmd_ent_remove)
|
|
{
|
|
int id = MSG_ReadLong();
|
|
if (sv.state >= ss_loading)
|
|
return; //if we're the server then this will already have been done. don't clobber from internal latency
|
|
if (id < mod->numentityinfo)
|
|
{
|
|
Z_Free(mod->entityinfo[id].keyvals);
|
|
mod->entityinfo[id].keyvals = NULL;
|
|
}
|
|
}
|
|
else
|
|
Host_EndGame("CL_Parse_BrushEdit: unknown command %i\n", cmd);
|
|
}
|
|
#endif
|
|
#ifndef CLIENTONLY
|
|
qboolean SV_Prespawn_Brushes(sizebuf_t *msg, unsigned int *modelindex, unsigned int *lastid)
|
|
{
|
|
//lastid starts at 0
|
|
unsigned int bestid, i;
|
|
brushes_t *best;
|
|
model_t *mod;
|
|
heightmap_t *hm;
|
|
while(1)
|
|
{
|
|
if (*modelindex < MAX_PRECACHE_MODELS)
|
|
mod = sv.models[*modelindex];
|
|
else
|
|
mod = NULL;
|
|
if (!mod)
|
|
{
|
|
if (!(*modelindex)++)
|
|
continue;
|
|
return false;
|
|
}
|
|
hm = mod->terrain;
|
|
if (!hm || !hm->brushesedited)
|
|
{
|
|
*modelindex+=1;
|
|
*lastid = 0;
|
|
continue;
|
|
}
|
|
|
|
if (!*lastid)
|
|
{ //make sure the client starts with a clean slate.
|
|
MSG_WriteByte(msg, svcfte_brushedit);
|
|
MSG_WriteShort(msg, *modelindex);
|
|
MSG_WriteByte(msg, hmcmd_prespawning);
|
|
}
|
|
|
|
//weird loop to try to ensure we never miss any brushes.
|
|
//get the lowest index that is 1 higher than our previous.
|
|
for (best = NULL, bestid = ~0u, i = 0; i < hm->numbrushes; i++)
|
|
{
|
|
unsigned int bid = hm->wbrushes[i].id;
|
|
if (bid > *lastid && bid <= bestid)
|
|
{
|
|
best = &hm->wbrushes[i];
|
|
bestid = best->id;
|
|
if (bestid == *lastid+1)
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (best)
|
|
{
|
|
MSG_WriteByte(msg, svcfte_brushedit);
|
|
MSG_WriteShort(msg, *modelindex);
|
|
MSG_WriteByte(msg, hmcmd_brush_insert);
|
|
Brush_Serialise(msg, best);
|
|
*lastid = bestid;
|
|
return true;
|
|
}
|
|
|
|
*modelindex+=1;
|
|
*lastid = 0;
|
|
}
|
|
}
|
|
qboolean SV_Parse_BrushEdit(void)
|
|
{
|
|
qboolean authorise = (host_client->penalties & BAN_MAPPER) || (host_client->netchan.remote_address.type == NA_LOOPBACK);
|
|
unsigned int modelindex = MSG_ReadShort();
|
|
int cmd = MSG_ReadByte();
|
|
model_t *mod = (modelindex<countof(sv.models))?sv.models[modelindex]:NULL;
|
|
heightmap_t *hm = mod?mod->terrain:NULL;
|
|
if (cmd == hmcmd_brush_delete)
|
|
{ //delete
|
|
unsigned int brushid = MSG_ReadLong();
|
|
if (!authorise)
|
|
{
|
|
SV_PrintToClient(host_client, PRINT_MEDIUM, "Brush editing ignored: you are not a mapper\n");
|
|
return true;
|
|
}
|
|
Terr_Brush_DeleteId(hm, brushid);
|
|
|
|
MSG_WriteByte(&sv.multicast, svcfte_brushedit);
|
|
MSG_WriteShort(&sv.multicast, modelindex);
|
|
MSG_WriteByte(&sv.multicast, hmcmd_brush_delete);
|
|
MSG_WriteLong(&sv.multicast, brushid);
|
|
SV_MulticastProtExt(vec3_origin, MULTICAST_ALL_R, ~0, 0, 0);
|
|
return true;
|
|
}
|
|
else if (cmd == hmcmd_brush_insert)
|
|
{
|
|
brushes_t brush;
|
|
memset(&brush, 0, sizeof(brush));
|
|
brush.numplanes = 128;
|
|
brush.planes = alloca(sizeof(*brush.planes) * brush.numplanes);
|
|
brush.faces = alloca(sizeof(*brush.faces) * brush.numplanes);
|
|
if (!Brush_Deserialise(hm, &brush))
|
|
{
|
|
Con_Printf("SV_Parse_BrushEdit: %s sent an unparsable brush\n", host_client->name);
|
|
return false;
|
|
}
|
|
if (!authorise)
|
|
{
|
|
SV_PrintToClient(host_client, PRINT_MEDIUM, "Brush editing ignored: you are not a mapper\n");
|
|
return true;
|
|
}
|
|
|
|
Terr_Brush_DeleteId(hm, brush.id);
|
|
if (!Terr_Brush_Insert(mod, hm, &brush))
|
|
return true; //looks mostly valid, but something was degenerate. fpu precision...
|
|
|
|
//FIXME: expand the world entity's sizes if needed?
|
|
|
|
MSG_WriteByte(&sv.multicast, svcfte_brushedit);
|
|
MSG_WriteShort(&sv.multicast, modelindex);
|
|
MSG_WriteByte(&sv.multicast, hmcmd_brush_insert);
|
|
Brush_Serialise(&sv.multicast, &brush);
|
|
SV_MulticastProtExt(vec3_origin, MULTICAST_ALL_R, ~0, 0, 0);
|
|
return true;
|
|
}
|
|
else if (cmd == hmcmd_ent_edit || cmd == hmcmd_ent_remove)
|
|
{
|
|
size_t entid = MSG_ReadLong();
|
|
char *keyvals = (cmd == hmcmd_ent_edit)?MSG_ReadString():NULL;
|
|
if (mod->submodelof != mod)
|
|
return true;
|
|
if (!authorise)
|
|
{
|
|
SV_PrintToClient(host_client, PRINT_MEDIUM, "Entity editing ignored: you are not a mapper\n");
|
|
return true;
|
|
}
|
|
|
|
MSG_WriteByte(&sv.multicast, svcfte_brushedit);
|
|
MSG_WriteShort(&sv.multicast, modelindex);
|
|
MSG_WriteByte(&sv.multicast, keyvals?hmcmd_ent_edit:hmcmd_ent_remove);
|
|
MSG_WriteLong(&sv.multicast, entid);
|
|
if (keyvals)
|
|
MSG_WriteString(&sv.multicast, keyvals);
|
|
SV_MulticastProtExt(vec3_origin, MULTICAST_ALL_R, ~0, 0, 0);
|
|
}
|
|
else
|
|
{
|
|
Con_Printf("SV_Parse_BrushEdit: %s sent an unknown command: %i\n", host_client->name, cmd);
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
#endif
|
|
|
|
typedef struct
|
|
{
|
|
int shadername;
|
|
vec3_t planenormal;
|
|
float planedist;
|
|
vec3_t sdir;
|
|
float sbias;
|
|
vec3_t tdir;
|
|
float tbias;
|
|
} qcbrushface_t;
|
|
|
|
static void *validateqcpointer(pubprogfuncs_t *prinst, size_t qcptr, size_t elementsize, size_t elementcount, qboolean allownull)
|
|
{
|
|
//make sure that the sizes can't overflow
|
|
if (elementcount > 0x10000)
|
|
{
|
|
PR_BIError(prinst, "brush: elementcount %u is too large\n", (unsigned int)elementcount);
|
|
return NULL;
|
|
}
|
|
if (qcptr < 0 || qcptr+(elementsize*elementcount) > prinst->stringtablesize)
|
|
{
|
|
PR_BIError(prinst, "brush: invalid qc pointer\n");
|
|
return NULL;
|
|
}
|
|
if (!qcptr)
|
|
{
|
|
if (!allownull)
|
|
PR_BIError(prinst, "brush: null qc pointer\n");
|
|
return NULL;
|
|
}
|
|
return prinst->stringtable + qcptr;
|
|
}
|
|
// {"brush_get", PF_brush_get, 0, 0, 0, 0, D(qcbrushface "int(float modelidx, int brushid, brushface_t *out_faces, int maxfaces, int *out_contents)", "Queries a brush's information. You must pre-allocate the face array for the builtin to write to. Return value is the number of faces retrieved, 0 on error.")},
|
|
void QCBUILTIN PF_brush_get(pubprogfuncs_t *prinst, struct globalvars_s *pr_globals)
|
|
{
|
|
world_t *vmw = prinst->parms->user;
|
|
model_t *mod = vmw->Get_CModel(vmw, G_FLOAT(OFS_PARM0));
|
|
heightmap_t *hm = mod?mod->terrain:NULL;
|
|
unsigned int brushid = G_INT(OFS_PARM1);
|
|
unsigned int maxfaces = G_INT(OFS_PARM3);
|
|
qcbrushface_t *out_faces = validateqcpointer(prinst, G_INT(OFS_PARM2), sizeof(*out_faces), maxfaces, true);
|
|
unsigned int *out_contents = validateqcpointer(prinst, G_INT(OFS_PARM4), sizeof(*out_contents), 1, true);
|
|
unsigned int fa, i;
|
|
brushes_t *br;
|
|
|
|
//assume the worst.
|
|
G_INT(OFS_RETURN) = 0;
|
|
if (out_contents)
|
|
*out_contents = 0;
|
|
|
|
if (!hm)
|
|
return;
|
|
|
|
for (i = 0; i < hm->numbrushes; i++)
|
|
{
|
|
br = &hm->wbrushes[i];
|
|
if (br->id == brushid)
|
|
{
|
|
if (out_contents)
|
|
*out_contents = br->contents;
|
|
if (!out_faces)
|
|
G_INT(OFS_RETURN) = br->numplanes;
|
|
else
|
|
{
|
|
maxfaces = min(br->numplanes, maxfaces);
|
|
|
|
for (fa = 0; fa < maxfaces; fa++)
|
|
{
|
|
out_faces->shadername = PR_TempString(prinst, br->faces[fa].tex->shadername);
|
|
VectorCopy(br->planes[fa], out_faces->planenormal);
|
|
out_faces->planedist = br->planes[fa][3];
|
|
|
|
VectorCopy(br->faces[fa].stdir[0], out_faces->sdir);
|
|
out_faces->sbias = br->faces[fa].stdir[0][3];
|
|
VectorCopy(br->faces[fa].stdir[1], out_faces->tdir);
|
|
out_faces->tbias = br->faces[fa].stdir[1][3];
|
|
|
|
out_faces++;
|
|
}
|
|
G_INT(OFS_RETURN) = fa;
|
|
}
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
// {"brush_create", PF_brush_create, 0, 0, 0, 0, D("int(float modelidx, brushface_t *in_faces, int numfaces, int contents)", "Inserts a new brush into the model. Return value is the new brush's id.")},
|
|
void QCBUILTIN PF_brush_create(pubprogfuncs_t *prinst, struct globalvars_s *pr_globals)
|
|
{
|
|
world_t *vmw = prinst->parms->user;
|
|
int modelindex = G_FLOAT(OFS_PARM0);
|
|
model_t *mod = vmw->Get_CModel(vmw, modelindex);
|
|
heightmap_t *hm = mod?mod->terrain:NULL;
|
|
unsigned int numfaces = G_INT(OFS_PARM2);
|
|
qcbrushface_t *in_faces = validateqcpointer(prinst, G_INT(OFS_PARM1), sizeof(*in_faces), numfaces, false);
|
|
unsigned int contents = G_INT(OFS_PARM3);
|
|
unsigned int brushid = (prinst->callargc > 4)?G_INT(OFS_PARM4):0; //to simplify edits
|
|
|
|
unsigned int i;
|
|
brushes_t brush, *nb;
|
|
vec4_t *planes;
|
|
struct brushface_s *faces;
|
|
|
|
G_INT(OFS_RETURN) = 0;
|
|
|
|
if (!hm)
|
|
{
|
|
if (mod && mod->loadstate == MLS_LOADING)
|
|
COM_WorkerPartialSync(mod, &mod->loadstate, MLS_LOADING);
|
|
if (mod && mod->loadstate == MLS_LOADED)
|
|
{
|
|
char basename[MAX_QPATH];
|
|
COM_FileBase(mod->name, basename, sizeof(basename));
|
|
mod->terrain = Mod_LoadTerrainInfo(mod, basename, true);
|
|
hm = mod->terrain;
|
|
if (!hm)
|
|
return;
|
|
Terr_FinishTerrain(mod);
|
|
}
|
|
else
|
|
return;
|
|
}
|
|
|
|
//if we're creating one that already exists, then assume that its a move.
|
|
if (brushid && Terr_Brush_DeleteId(hm, brushid))
|
|
{
|
|
#ifndef CLIENTONLY
|
|
if (sv.state && modelindex > 0)
|
|
{
|
|
MSG_WriteByte(&sv.multicast, svcfte_brushedit);
|
|
MSG_WriteShort(&sv.multicast, modelindex);
|
|
MSG_WriteByte(&sv.multicast, hmcmd_brush_delete);
|
|
MSG_WriteLong(&sv.multicast, brushid);
|
|
SV_MulticastProtExt(vec3_origin, MULTICAST_ALL_R, ~0, 0, 0);
|
|
}
|
|
else
|
|
#endif
|
|
#ifndef SERVERONLY
|
|
if (cls.state && modelindex > 0)
|
|
{
|
|
MSG_WriteByte(&cls.netchan.message, clcfte_brushedit);
|
|
MSG_WriteShort(&cls.netchan.message, modelindex);
|
|
MSG_WriteByte(&cls.netchan.message, hmcmd_brush_delete);
|
|
MSG_WriteLong(&cls.netchan.message, brushid);
|
|
}
|
|
#else
|
|
{
|
|
}
|
|
#endif
|
|
}
|
|
|
|
planes = alloca(sizeof(*planes) * numfaces);
|
|
faces = alloca(sizeof(*faces) * numfaces);
|
|
for (i = 0; i < numfaces; i++)
|
|
{
|
|
VectorCopy(in_faces[i].planenormal, planes[i]);
|
|
planes[i][3] = in_faces[i].planedist;
|
|
|
|
faces[i].tex = Terr_Brush_FindTexture(hm, PR_GetString(prinst, in_faces[i].shadername));
|
|
|
|
VectorCopy(in_faces[i].sdir, faces[i].stdir[0]);
|
|
faces[i].stdir[0][3] = in_faces[i].sbias;
|
|
VectorCopy(in_faces[i].tdir, faces[i].stdir[1]);
|
|
faces[i].stdir[1][3] = in_faces[i].tbias;
|
|
}
|
|
|
|
//now emit it
|
|
brush.id = 0;
|
|
brush.contents = contents;
|
|
brush.numplanes = numfaces;
|
|
brush.planes = planes;
|
|
brush.faces = faces;
|
|
if (numfaces)
|
|
{
|
|
nb = Terr_Brush_Insert(mod, hm, &brush);
|
|
if (nb)
|
|
{
|
|
G_INT(OFS_RETURN) = nb->id;
|
|
#ifndef CLIENTONLY
|
|
if (sv.state && modelindex > 0)
|
|
{
|
|
MSG_WriteByte(&sv.multicast, svcfte_brushedit);
|
|
MSG_WriteShort(&sv.multicast, modelindex);
|
|
MSG_WriteByte(&sv.multicast, hmcmd_brush_insert);
|
|
Brush_Serialise(&sv.multicast, nb);
|
|
SV_MulticastProtExt(vec3_origin, MULTICAST_ALL_R, ~0, 0, 0);
|
|
return;
|
|
}
|
|
#endif
|
|
#ifndef SERVERONLY
|
|
if (cls.state && modelindex > 0)
|
|
{
|
|
MSG_WriteByte(&cls.netchan.message, clcfte_brushedit);
|
|
MSG_WriteShort(&cls.netchan.message, modelindex);
|
|
MSG_WriteByte(&cls.netchan.message, hmcmd_brush_insert);
|
|
Brush_Serialise(&cls.netchan.message, nb);
|
|
return;
|
|
}
|
|
#endif
|
|
}
|
|
}
|
|
}
|
|
// {"brush_delete", PF_brush_delete, 0, 0, 0, 0, D("void(float modelidx, int brushid)", "Destroys the specified brush.")},
|
|
void QCBUILTIN PF_brush_delete(pubprogfuncs_t *prinst, struct globalvars_s *pr_globals)
|
|
{
|
|
world_t *vmw = prinst->parms->user;
|
|
int modelindex = G_FLOAT(OFS_PARM0);
|
|
model_t *mod = vmw->Get_CModel(vmw, modelindex);
|
|
heightmap_t *hm = mod?mod->terrain:NULL;
|
|
unsigned int brushid = G_INT(OFS_PARM1);
|
|
|
|
if (!hm)
|
|
return;
|
|
|
|
Terr_Brush_DeleteId(hm, brushid);
|
|
|
|
#ifndef CLIENTONLY
|
|
if (sv.state && modelindex > 0)
|
|
{
|
|
MSG_WriteByte(&sv.multicast, svcfte_brushedit);
|
|
MSG_WriteShort(&sv.multicast, modelindex);
|
|
MSG_WriteByte(&sv.multicast, hmcmd_brush_delete);
|
|
MSG_WriteLong(&sv.multicast, brushid);
|
|
SV_MulticastProtExt(vec3_origin, MULTICAST_ALL_R, ~0, 0, 0);
|
|
return;
|
|
}
|
|
#endif
|
|
#ifndef SERVERONLY
|
|
if (cls.state && modelindex > 0)
|
|
{
|
|
MSG_WriteByte(&cls.netchan.message, clcfte_brushedit);
|
|
MSG_WriteShort(&cls.netchan.message, modelindex);
|
|
MSG_WriteByte(&cls.netchan.message, hmcmd_brush_delete);
|
|
MSG_WriteLong(&cls.netchan.message, brushid);
|
|
return;
|
|
}
|
|
#endif
|
|
}
|
|
// {"brush_selected", PF_brush_selected, 0, 0, 0, 0, D("float(float modelid, int brushid, int faceid, float selectedstate)", "Allows you to easily set transient visual properties of a brush. If brush/face is -1, applies to all. returns old value. selectedstate=-1 changes nothing (called for its return value).")},
|
|
void QCBUILTIN PF_brush_selected(pubprogfuncs_t *prinst, struct globalvars_s *pr_globals)
|
|
{
|
|
world_t *vmw = prinst->parms->user;
|
|
model_t *mod = vmw->Get_CModel(vmw, G_FLOAT(OFS_PARM0));
|
|
heightmap_t *hm = mod?mod->terrain:NULL;
|
|
unsigned int brushid = G_INT(OFS_PARM1);
|
|
// unsigned int faceid = G_INT(OFS_PARM2);
|
|
unsigned int state = G_FLOAT(OFS_PARM3);
|
|
unsigned int i;
|
|
brushes_t *br;
|
|
|
|
G_FLOAT(OFS_RETURN) = 0;
|
|
if (!hm)
|
|
return;
|
|
|
|
// hm->recalculatebrushlighting = true;
|
|
|
|
for (i = 0; i < hm->numbrushes; i++)
|
|
{
|
|
br = &hm->wbrushes[i];
|
|
if (br->id == brushid)
|
|
{
|
|
G_FLOAT(OFS_RETURN) = br->selected;
|
|
|
|
if (state >= 0)
|
|
{
|
|
if (br->selected != state)
|
|
{
|
|
for (i = 0; i < br->numplanes; i++)
|
|
{
|
|
br->faces[i].tex->rebuild = true;
|
|
br->faces[i].relight = true;
|
|
}
|
|
br->selected = state;
|
|
}
|
|
}
|
|
// return;
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
|
|
// {"brush_calcfacepoints",PF_brush_calcfacepoints,0,0, 0, 0, D("int(int faceid, brushface_t *in_faces, int numfaces, vector *points, int maxpoints)", "Determines the points of the specified face, if the specified brush were to actually be created.")},
|
|
void QCBUILTIN PF_brush_calcfacepoints(pubprogfuncs_t *prinst, struct globalvars_s *pr_globals)
|
|
{
|
|
unsigned int faceid = G_INT(OFS_PARM0);
|
|
unsigned int numfaces = G_INT(OFS_PARM2);
|
|
qcbrushface_t *in_faces = validateqcpointer(prinst, G_INT(OFS_PARM1), sizeof(*in_faces), numfaces, false);
|
|
unsigned int maxpoints = G_INT(OFS_PARM4);
|
|
vec3_t *out_verts = validateqcpointer(prinst, G_INT(OFS_PARM3), sizeof(*out_verts), maxpoints, false);
|
|
vecV_t facepoints[256];
|
|
vec4_t planes[256];
|
|
unsigned int j, numpoints;
|
|
|
|
faceid--;
|
|
if (faceid < 0 || faceid >= numfaces)
|
|
{
|
|
G_INT(OFS_RETURN) = 0;
|
|
return;
|
|
}
|
|
|
|
//make sure this isn't a dupe face
|
|
for (j = 0; j < faceid; j++)
|
|
{
|
|
if (in_faces[j].planenormal[0] == in_faces[faceid].planenormal[0] &&
|
|
in_faces[j].planenormal[1] == in_faces[faceid].planenormal[1] &&
|
|
in_faces[j].planenormal[2] == in_faces[faceid].planenormal[2] &&
|
|
in_faces[j].planedist == in_faces[faceid].planedist)
|
|
{
|
|
G_INT(OFS_RETURN) = 0;
|
|
return;
|
|
}
|
|
}
|
|
|
|
//generate a list that Terr_GenerateBrushFace can actually use, silly, but lets hope this isn't needed to be nippy
|
|
for (j = 0; j < numfaces; j++)
|
|
{
|
|
VectorCopy(in_faces[j].planenormal, planes[j]);
|
|
planes[j][3] = in_faces[j].planedist;
|
|
}
|
|
|
|
//generate points now (so we know the correct mins+maxs for the brush, and whether the plane is relevent)
|
|
numpoints = Fragment_ClipPlaneToBrush(facepoints, countof(facepoints), planes, sizeof(*planes), numfaces, planes[faceid]);
|
|
G_INT(OFS_RETURN) = numpoints;
|
|
if (numpoints > maxpoints)
|
|
numpoints = maxpoints;
|
|
|
|
//... and copy them out without padding. yeah, silly.
|
|
for (j = 0; j < numpoints; j++)
|
|
{
|
|
VectorCopy(facepoints[j], out_verts[j]);
|
|
}
|
|
}
|
|
|
|
// {"brush_getfacepoints",PF_brush_getfacepoints,0,0, 0, 0, D("int(float modelid, int brushid, int faceid, vector *points, int maxpoints)", "Allows you to easily set transient visual properties of a brush. If brush/face is -1, applies to all. returns old value. selectedstate=-1 changes nothing (called for its return value).")},
|
|
void QCBUILTIN PF_brush_getfacepoints(pubprogfuncs_t *prinst, struct globalvars_s *pr_globals)
|
|
{
|
|
world_t *vmw = prinst->parms->user;
|
|
model_t *mod = vmw->Get_CModel(vmw, G_FLOAT(OFS_PARM0));
|
|
heightmap_t *hm = mod?mod->terrain:NULL;
|
|
unsigned int brushid = G_INT(OFS_PARM1);
|
|
unsigned int faceid = G_INT(OFS_PARM2);
|
|
unsigned int maxpoints = G_INT(OFS_PARM4), p;
|
|
vec3_t *out_verts = validateqcpointer(prinst, G_INT(OFS_PARM3), sizeof(*out_verts), maxpoints, false);
|
|
size_t i;
|
|
brushes_t *br;
|
|
|
|
G_INT(OFS_RETURN) = 0;
|
|
|
|
if (!hm)
|
|
return;
|
|
|
|
for (i = 0; i < hm->numbrushes; i++)
|
|
{
|
|
br = &hm->wbrushes[i];
|
|
if (br->id == brushid)
|
|
{
|
|
if (!faceid)
|
|
{
|
|
if (maxpoints >= 2)
|
|
{
|
|
VectorCopy(br->mins, out_verts[0]);
|
|
VectorCopy(br->maxs, out_verts[1]);
|
|
G_INT(OFS_RETURN) = 2;
|
|
}
|
|
else if (maxpoints == 1)
|
|
{
|
|
VectorInterpolate(br->mins, 0.5, br->maxs, out_verts[0]);
|
|
G_INT(OFS_RETURN) = 1;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
faceid--;
|
|
if (faceid >= br->numplanes)
|
|
break;
|
|
maxpoints = min(maxpoints, br->faces[faceid].numpoints);
|
|
for (p = 0; p < maxpoints; p++)
|
|
VectorCopy(br->faces[faceid].points[p], out_verts[p]);
|
|
G_INT(OFS_RETURN) = p;
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
// {"brush_findinvolume",PF_brush_findinvolume,0,0, 0, 0, D("int(float modelid, vector *planes, float *dists, int numplanes, int *out_brushes, int *out_faces, int maxresults)", "Allows you to easily obtain a list of brushes+faces within the given bounding region. If out_faces is not null, the same brush might be listed twice.")},
|
|
void QCBUILTIN PF_brush_findinvolume(pubprogfuncs_t *prinst, struct globalvars_s *pr_globals)
|
|
{
|
|
world_t *vmw = prinst->parms->user;
|
|
model_t *mod = vmw->Get_CModel(vmw, G_FLOAT(OFS_PARM0));
|
|
heightmap_t *hm = mod?mod->terrain:NULL;
|
|
int in_numplanes = G_INT(OFS_PARM3);
|
|
vec3_t *in_normals = validateqcpointer(prinst, G_INT(OFS_PARM1), sizeof(*in_normals), in_numplanes, false);
|
|
float *in_distances = validateqcpointer(prinst, G_INT(OFS_PARM2), sizeof(*in_distances), in_numplanes, false);
|
|
unsigned int maxresults = G_INT(OFS_PARM6);
|
|
unsigned int *out_brushids = validateqcpointer(prinst, G_INT(OFS_PARM4), sizeof(*out_brushids), maxresults, false);
|
|
unsigned int *out_faceids = G_INT(OFS_PARM5)?validateqcpointer(prinst, G_INT(OFS_PARM5), sizeof(*out_faceids), maxresults, false):NULL;
|
|
unsigned int i, j, k, r = 0;
|
|
brushes_t *br;
|
|
vec3_t best;
|
|
float dist;
|
|
|
|
//find all brushes/faces with a vetex within the region
|
|
//the brush is inside if any every plane has at least one vertex on the inner side
|
|
|
|
if (hm)
|
|
for (i = 0; i < hm->numbrushes; i++)
|
|
{
|
|
br = &hm->wbrushes[i];
|
|
|
|
for (j = 0; j < in_numplanes; j++)
|
|
{
|
|
for (k=0 ; k<3 ; k++)
|
|
{
|
|
if (in_normals[j][k] < 0)
|
|
best[k] = br->maxs[k];
|
|
else
|
|
best[k] = br->mins[k];
|
|
}
|
|
dist = DotProduct (best, in_normals[j]);
|
|
dist = in_distances[j] - dist;
|
|
if (dist <= 0) //don't find coplanar brushes. add an epsilon if you need this.
|
|
break;
|
|
}
|
|
if (j == in_numplanes)
|
|
{
|
|
//the box had some point on the near side of every single plane, and thus must contain at least part of the box
|
|
if (r == maxresults)
|
|
break; //ran out
|
|
out_brushids[r] = br->id;
|
|
if (out_faceids) //FIXME: handle this properly.
|
|
out_faceids[r] = 0;
|
|
r++;
|
|
}
|
|
}
|
|
G_INT(OFS_RETURN) = r;
|
|
}
|
|
|
|
void Terr_WriteBrushInfo(vfsfile_t *file, brushes_t *br)
|
|
{
|
|
//( -0 -0 16 ) ( -0 -0 32 ) ( 64 -0 16 ) texname [x y z d] [x y z d] rotation sscale tscale
|
|
float *point[3];
|
|
int i;
|
|
|
|
VFS_PRINTF(file, "\n{");
|
|
for (i = 0; i < br->numplanes; i++)
|
|
{
|
|
point[0] = br->faces[i].points[0];
|
|
point[1] = br->faces[i].points[1];
|
|
point[2] = br->faces[i].points[2];
|
|
|
|
//%.9g is 'meant' to be lossless for a standard ieee single-precision float. (%.17g for a double)
|
|
VFS_PRINTF(file, "\n( %.9g %.9g %.9g ) ( %.9g %.9g %.9g ) ( %.9g %.9g %.9g ) \"%s\" [ %.9g %.9g %.9g %.9g ] [ %.9g %.9g %.9g %.9g ] 0 1 1",
|
|
point[0][0], point[0][1], point[0][2],
|
|
point[1][0], point[1][1], point[1][2],
|
|
point[2][0], point[2][1], point[2][2],
|
|
br->faces[i].tex?br->faces[i].tex->shadername:"",
|
|
br->faces[i].stdir[0][0], br->faces[i].stdir[0][1], br->faces[i].stdir[0][2], br->faces[i].stdir[0][3],
|
|
br->faces[i].stdir[1][0], br->faces[i].stdir[1][1], br->faces[i].stdir[1][2], br->faces[i].stdir[1][3]
|
|
);
|
|
}
|
|
VFS_PRINTF(file, "\n}");
|
|
}
|
|
void Terr_WriteMapFile(vfsfile_t *file, model_t *mod)
|
|
{
|
|
char token[8192];
|
|
int nest = 0;
|
|
const char *start, *entities = Mod_GetEntitiesString(mod);
|
|
int i;
|
|
unsigned int entnum = 0;
|
|
heightmap_t *hm;
|
|
|
|
hm = mod->terrain;
|
|
if (hm && hm->exteriorcontents != FTECONTENTS_EMPTY)
|
|
VFS_WRITE(file, "terrain\n", 8);
|
|
|
|
start = entities;
|
|
while(entities)
|
|
{
|
|
entities = COM_ParseOut(entities, token, sizeof(token));
|
|
if (token[0] == '}' && token[1] == 0)
|
|
{
|
|
nest--;
|
|
if (!nest)
|
|
{
|
|
if (!entnum)
|
|
{
|
|
// VFS_PRINTF(file, "\n//Worldspawn brushes go here");
|
|
|
|
hm = mod->terrain;
|
|
if (hm)
|
|
for (i = 0; i < hm->numbrushes; i++)
|
|
Terr_WriteBrushInfo(file, &hm->wbrushes[i]);
|
|
}
|
|
entnum++;
|
|
}
|
|
}
|
|
else if (token[0] == '{' && token[1] == 0)
|
|
{
|
|
nest++;
|
|
}
|
|
else
|
|
{
|
|
if (!strcmp(token, "model"))
|
|
{
|
|
int submodelnum;
|
|
entities = COM_ParseOut(entities, token, sizeof(token));
|
|
|
|
if (*token == '*')
|
|
submodelnum = atoi(token+1);
|
|
else
|
|
submodelnum = 0;
|
|
|
|
if (submodelnum)
|
|
{
|
|
model_t *submod;
|
|
|
|
Q_snprintfz(token, sizeof(token), "*%i:%s", submodelnum, mod->name);
|
|
submod = Mod_FindName (token);
|
|
|
|
// VFS_PRINTF(file, "\nBrushes for %s go here", token);
|
|
hm = submod->terrain;
|
|
if (hm)
|
|
{
|
|
for (i = 0; i < hm->numbrushes; i++)
|
|
Terr_WriteBrushInfo(file, &hm->wbrushes[i]);
|
|
|
|
start = entities;
|
|
}
|
|
}
|
|
}
|
|
else
|
|
entities = COM_ParseOut(entities, token, sizeof(token));
|
|
}
|
|
VFS_WRITE(file, start, entities - start);
|
|
start = entities;
|
|
}
|
|
}
|
|
void Mod_Terrain_Save_f(void)
|
|
{
|
|
vfsfile_t *file;
|
|
model_t *mod;
|
|
const char *mapname = Cmd_Argv(1);
|
|
char fname[MAX_QPATH];
|
|
if (Cmd_IsInsecure())
|
|
{
|
|
Con_Printf("Please use this command via the console\n");
|
|
return;
|
|
}
|
|
if (*mapname)
|
|
mod = Mod_FindName(va("maps/%s", mapname));
|
|
#ifndef SERVERONLY
|
|
else if (cls.state)
|
|
mod = cl.worldmodel;
|
|
#endif
|
|
else
|
|
mod = NULL;
|
|
|
|
if (!mod)
|
|
{
|
|
Con_Printf("no model loaded by that name\n");
|
|
return;
|
|
}
|
|
if (mod->loadstate != MLS_LOADED)
|
|
{
|
|
Con_Printf("that model isn't fully loaded\n");
|
|
return;
|
|
}
|
|
if (*Cmd_Argv(2))
|
|
Q_snprintfz(fname, sizeof(fname), "maps/%s.map", Cmd_Argv(2));
|
|
else
|
|
Q_snprintfz(fname, sizeof(fname), "%s", mod->name);
|
|
|
|
if (mod->type != mod_heightmap)
|
|
{
|
|
//warning: brushes are not saved unless its a .map
|
|
COM_StripExtension(mod->name, fname, sizeof(fname));
|
|
Q_strncatz(fname, ".ent", sizeof(fname));
|
|
|
|
FS_CreatePath(fname, FS_GAMEONLY);
|
|
file = FS_OpenVFS(fname, "wb", FS_GAMEONLY);
|
|
if (!file)
|
|
Con_Printf("unable to open %s\n", fname);
|
|
else
|
|
{
|
|
const char *s = Mod_GetEntitiesString(mod);
|
|
VFS_WRITE(file, s, strlen(s));
|
|
VFS_CLOSE(file);
|
|
FS_FlushFSHashWritten(fname);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
FS_CreatePath(fname, FS_GAMEONLY);
|
|
file = FS_OpenVFS(fname, "wb", FS_GAMEONLY);
|
|
if (!file)
|
|
Con_Printf("unable to open %s\n", fname);
|
|
else
|
|
{
|
|
Terr_WriteMapFile(file, mod);
|
|
VFS_CLOSE(file);
|
|
FS_FlushFSHashWritten(fname);
|
|
}
|
|
}
|
|
}
|
|
qboolean Terr_ReformEntitiesLump(model_t *mod, heightmap_t *hm, char *entities)
|
|
{
|
|
char token[8192];
|
|
int nest = 0;
|
|
int buflen = strlen(entities);
|
|
char *out, *outstart, *start;
|
|
int i;
|
|
int submodelnum = 0;
|
|
qboolean isdetail = false;
|
|
qboolean foundsubmodel = false;
|
|
qboolean inbrush = false;
|
|
int numplanes = 0;
|
|
vec4_t planes[64];
|
|
struct brushface_s faces[64];
|
|
int brushcontents = FTECONTENTS_SOLID;
|
|
heightmap_t *subhm = NULL;
|
|
model_t *submod = NULL;
|
|
|
|
#ifdef RUNTIMELIGHTING
|
|
hm->entsdirty = true;
|
|
hm->relightcontext = LightStartup(NULL, mod, false, false);
|
|
hm->lightthreadmem = BZ_Malloc(lightthreadctxsize);
|
|
hm->inheritedlightthreadmem = false;
|
|
#endif
|
|
|
|
/*FIXME: we need to re-form the entities lump to insert model fields as appropriate*/
|
|
outstart = out = Z_Malloc(buflen+1);
|
|
|
|
while(entities)
|
|
{
|
|
start = entities;
|
|
entities = COM_ParseOut(entities, token, sizeof(token));
|
|
if (token[0] == '}' && token[1] == 0)
|
|
{
|
|
nest--;
|
|
if (inbrush)
|
|
{
|
|
brushes_t brush;
|
|
//finish the brush
|
|
brush.contents = brushcontents;
|
|
brush.numplanes = numplanes;
|
|
brush.planes = planes;
|
|
brush.faces = faces;
|
|
brush.id = 0;
|
|
if (numplanes && subhm)
|
|
{
|
|
qboolean oe = subhm->brushesedited;
|
|
Terr_Brush_Insert(submod, subhm, &brush);
|
|
subhm->brushesedited = oe;
|
|
}
|
|
numplanes = 0;
|
|
inbrush = false;
|
|
continue;
|
|
}
|
|
}
|
|
else if (token[0] == '{' && token[1] == 0)
|
|
{
|
|
nest++;
|
|
if (nest == 1)
|
|
{ //entering a new entity
|
|
foundsubmodel = false;
|
|
isdetail = false;
|
|
}
|
|
if (nest == 2)
|
|
{
|
|
if (isdetail) //func_detail injects its brushes into the world model for some reason.
|
|
{
|
|
submod = mod;
|
|
subhm = hm;
|
|
}
|
|
else if (!foundsubmodel)
|
|
{
|
|
foundsubmodel = true;
|
|
if (submodelnum)
|
|
{
|
|
Q_snprintfz(token, sizeof(token), "*%i", submodelnum);
|
|
*out++ = 'm';
|
|
*out++ = 'o';
|
|
*out++ = 'd';
|
|
*out++ = 'e';
|
|
*out++ = 'l';
|
|
*out++ = ' ';
|
|
*out++ = '\"';
|
|
for (i = 0; token[i]; i++)
|
|
*out++ = token[i];
|
|
*out++ = '\"';
|
|
*out++ = ' ';
|
|
|
|
Q_snprintfz(token, sizeof(token), "*%i:%s", submodelnum, mod->name);
|
|
submod = Mod_FindName (token);
|
|
if (submod->loadstate == MLS_NOTLOADED)
|
|
{
|
|
submod->type = mod_heightmap;
|
|
Mod_SetEntitiesString(submod, "", true);
|
|
subhm = submod->terrain = Mod_LoadTerrainInfo(submod, submod->name, true);
|
|
|
|
subhm->exteriorcontents = FTECONTENTS_EMPTY;
|
|
|
|
ClearBounds(submod->mins, submod->maxs);
|
|
|
|
submod->funcs.NativeTrace = Heightmap_Trace_Test;
|
|
submod->funcs.PointContents = Heightmap_PointContents;
|
|
submod->funcs.NativeContents = Heightmap_NativeBoxContents;
|
|
submod->funcs.LightPointValues = Heightmap_LightPointValues;
|
|
submod->funcs.StainNode = Heightmap_StainNode;
|
|
submod->funcs.MarkLights = Heightmap_MarkLights;
|
|
submod->funcs.ClusterForPoint = Heightmap_ClusterForPoint;
|
|
submod->funcs.ClusterPVS = Heightmap_ClusterPVS;
|
|
#ifndef CLIENTONLY
|
|
submod->funcs.FindTouchedLeafs = Heightmap_FindTouchedLeafs;
|
|
submod->funcs.EdictInFatPVS = Heightmap_EdictInFatPVS;
|
|
submod->funcs.FatPVS = Heightmap_FatPVS;
|
|
#endif
|
|
submod->loadstate = MLS_LOADED;
|
|
|
|
#ifdef RUNTIMELIGHTING
|
|
subhm->relightcontext = LightStartup(hm->relightcontext, submod, false, false);
|
|
subhm->lightthreadmem = hm->lightthreadmem;
|
|
subhm->inheritedlightthreadmem = true;
|
|
#endif
|
|
}
|
|
else
|
|
subhm = NULL;
|
|
}
|
|
else
|
|
{
|
|
submod = mod;
|
|
subhm = hm;
|
|
}
|
|
submodelnum++;
|
|
}
|
|
inbrush = true;
|
|
continue;
|
|
}
|
|
}
|
|
else if (inbrush)
|
|
{
|
|
//parse a plane
|
|
//Quake: ( -0 -0 16 ) ( -0 -0 32 ) ( 64 -0 16 ) texname 0 -32 rotation sscale tscale
|
|
//hexen2: ( -0 -0 16 ) ( -0 -0 32 ) ( 64 -0 16 ) texname 0 -32 rotation sscale tscale utterlypointless
|
|
//Valve: ( -0 -0 16 ) ( -0 -0 32 ) ( 64 -0 16 ) texname [x y z d] [x y z d] rotation sscale tscale
|
|
//fte : ( px py pz pd ) texname [sx sy sz sd] [tx ty tz td] 0 1 1
|
|
brushtex_t *bt;
|
|
vec3_t d1,d2;
|
|
vec3_t points[3];
|
|
vec4_t texplane[2];
|
|
float scale[2], rot;
|
|
int p;
|
|
qboolean hlstyle = false;
|
|
memset(points, 0, sizeof(points));
|
|
for (p = 0; p < 3; p++)
|
|
{
|
|
if (token[0] != '(' || token[1] != 0)
|
|
break;
|
|
entities = COM_ParseOut(entities, token, sizeof(token));
|
|
points[p][0] = atof(token);
|
|
entities = COM_ParseOut(entities, token, sizeof(token));
|
|
points[p][1] = atof(token);
|
|
entities = COM_ParseOut(entities, token, sizeof(token));
|
|
points[p][2] = atof(token);
|
|
entities = COM_ParseOut(entities, token, sizeof(token));
|
|
if (token[0] != ')' || token[1] != 0)
|
|
{
|
|
// VectorClear(points[1]);
|
|
// VectorClear(points[2]);
|
|
points[1][0] = atof(token);
|
|
entities = COM_ParseOut(entities, token, sizeof(token));
|
|
if (p == 0 && !strcmp(token, ")"))
|
|
p = 4; //we just managed to read an entire plane instead of 3 points.
|
|
break;
|
|
}
|
|
entities = COM_ParseOut(entities, token, sizeof(token));
|
|
}
|
|
if (p < 3)
|
|
{
|
|
Con_Printf(CON_ERROR "%s: malformed brush\n", mod->name);
|
|
return false;
|
|
}
|
|
if (numplanes == sizeof(planes)/sizeof(planes[0]))
|
|
{
|
|
Con_Printf(CON_ERROR "%s: too many planes in brush\n", mod->name);
|
|
return false;
|
|
}
|
|
|
|
bt = Terr_Brush_FindTexture(subhm, token);
|
|
if (*token == '*')
|
|
{
|
|
if (!Q_strncasecmp(token, "*lava", 5))
|
|
brushcontents = FTECONTENTS_LAVA;
|
|
else if (!Q_strncasecmp(token, "*slime", 5))
|
|
brushcontents = FTECONTENTS_SLIME;
|
|
else
|
|
brushcontents = FTECONTENTS_WATER;
|
|
}
|
|
else if (!Q_strncasecmp(token, "*sky", 4))
|
|
brushcontents = FTECONTENTS_SKY;
|
|
else if (!Q_strcasecmp(token, "clip"))
|
|
brushcontents = FTECONTENTS_PLAYERCLIP;
|
|
else
|
|
brushcontents = FTECONTENTS_SOLID;
|
|
|
|
//FIXME: halflife format has the entire [x y z dist] plane specified.
|
|
entities = COM_ParseOut(entities, token, sizeof(token));
|
|
if (*token == '[')
|
|
{
|
|
hlstyle = true;
|
|
|
|
entities = COM_ParseOut(entities, token, sizeof(token));
|
|
texplane[0][0] = atof(token);
|
|
entities = COM_ParseOut(entities, token, sizeof(token));
|
|
texplane[0][1] = atof(token);
|
|
entities = COM_ParseOut(entities, token, sizeof(token));
|
|
texplane[0][2] = atof(token);
|
|
entities = COM_ParseOut(entities, token, sizeof(token));
|
|
texplane[0][3] = atof(token);
|
|
|
|
entities = COM_ParseOut(entities, token, sizeof(token));
|
|
//]
|
|
entities = COM_ParseOut(entities, token, sizeof(token));
|
|
//[
|
|
|
|
entities = COM_ParseOut(entities, token, sizeof(token));
|
|
texplane[1][0] = atof(token);
|
|
entities = COM_ParseOut(entities, token, sizeof(token));
|
|
texplane[1][1] = atof(token);
|
|
entities = COM_ParseOut(entities, token, sizeof(token));
|
|
texplane[1][2] = atof(token);
|
|
entities = COM_ParseOut(entities, token, sizeof(token));
|
|
texplane[1][3] = atof(token);
|
|
|
|
entities = COM_ParseOut(entities, token, sizeof(token));
|
|
//]
|
|
}
|
|
else
|
|
{
|
|
VectorClear(texplane[0]);
|
|
VectorClear(texplane[1]);
|
|
texplane[0][3] = atof(token);
|
|
entities = COM_ParseOut(entities, token, sizeof(token));
|
|
texplane[1][3] = atof(token);
|
|
}
|
|
|
|
entities = COM_ParseOut(entities, token, sizeof(token));
|
|
rot = atof(token);
|
|
entities = COM_ParseOut(entities, token, sizeof(token));
|
|
scale[0] = atof(token);
|
|
entities = COM_ParseOut(entities, token, sizeof(token));
|
|
scale[1] = atof(token);
|
|
|
|
//hexen2 has some extra junk that is useless - some 'light' value, but its never used and should normally be -1.
|
|
while (*entities == ' ' || *entities == '\t')
|
|
entities++;
|
|
if (*entities == '-' || (*entities >= '0' && *entities <= '9'))
|
|
entities = COM_ParseOut(entities, token, sizeof(token));
|
|
|
|
//okay, that's all the actual parsing, now try to make sense of this plane.
|
|
if (p == 4)
|
|
{ //parsed an actual plane
|
|
VectorCopy(points[0], planes[numplanes]);
|
|
planes[numplanes][3] = points[1][0];
|
|
}
|
|
else
|
|
{ //parsed 3 points.
|
|
VectorSubtract(points[0], points[1], d1);
|
|
VectorSubtract(points[2], points[1], d2);
|
|
CrossProduct(d1, d2, planes[numplanes]);
|
|
VectorNormalize(planes[numplanes]);
|
|
planes[numplanes][3] = DotProduct(points[1], planes[numplanes]);
|
|
}
|
|
faces[numplanes].tex = bt;
|
|
|
|
//quake's .maps use the normal to decide which texture directions to use in some lame axially-aligned way.
|
|
if (!hlstyle)
|
|
{
|
|
float a=fabs(planes[numplanes][0]),b=fabs(planes[numplanes][1]),c=fabs(planes[numplanes][2]);
|
|
if (a>=b&&a>=c)
|
|
texplane[0][1] = 1;
|
|
else
|
|
texplane[0][0] = 1;
|
|
if (c>a&&c>b)
|
|
texplane[1][1] = -1;
|
|
else
|
|
texplane[1][2] = -1;
|
|
}
|
|
|
|
if (rot)
|
|
{
|
|
int mas, mat;
|
|
float s,t;
|
|
float a = rot*(M_PI/180);
|
|
float cosa = cos(a), sina=sin(a);
|
|
for (mas=0; mas<2&&!texplane[0][mas]; mas++);
|
|
for (mat=0; mat<2&&!texplane[1][mat]; mat++);
|
|
for (i = 0; i < 2; i++)
|
|
{
|
|
s = cosa*texplane[i][mas] - sina*texplane[i][mat];
|
|
t = sina*texplane[i][mas] + cosa*texplane[i][mat];
|
|
texplane[i][mas] = s;
|
|
texplane[i][mat] = t;
|
|
}
|
|
}
|
|
|
|
if (!scale[0]) scale[0] = 1;
|
|
if (!scale[1]) scale[1] = 1;
|
|
VectorScale(texplane[0], 1.0/scale[0], faces[numplanes].stdir[0]);
|
|
VectorScale(texplane[1], 1.0/scale[1], faces[numplanes].stdir[1]);
|
|
faces[numplanes].stdir[0][3] = texplane[0][3];
|
|
faces[numplanes].stdir[1][3] = texplane[1][3];
|
|
|
|
numplanes++;
|
|
continue;
|
|
}
|
|
else
|
|
{
|
|
if (!strcmp(token, "classname"))
|
|
{
|
|
entities = COM_ParseOut(entities, token, sizeof(token));
|
|
|
|
if (!strcmp(token, "func_detail"))
|
|
isdetail = true;
|
|
}
|
|
else
|
|
entities = COM_ParseOut(entities, token, sizeof(token));
|
|
}
|
|
while(start < entities)
|
|
*out++ = *start++;
|
|
}
|
|
*out = 0;
|
|
|
|
Mod_SetEntitiesString(mod, outstart, false);
|
|
|
|
mod->numsubmodels = submodelnum;
|
|
|
|
return true;
|
|
}
|
|
|
|
qboolean QDECL Terr_LoadTerrainModel (model_t *mod, void *buffer, size_t bufsize)
|
|
{
|
|
int exterior = FTECONTENTS_SOLID;
|
|
heightmap_t *hm;
|
|
|
|
char token[MAX_QPATH];
|
|
int sectsize = 0;
|
|
char *src;
|
|
|
|
src = COM_ParseOut(buffer, token, sizeof(token));
|
|
if (!strcmp(token, "terrain"))
|
|
buffer = src;
|
|
else if (!strcmp(token, "{"))
|
|
exterior = FTECONTENTS_EMPTY;
|
|
else
|
|
{
|
|
Con_Printf(CON_ERROR "%s wasn't terrain map\n", mod->name); //shouldn't happen
|
|
return false;
|
|
}
|
|
|
|
mod->type = mod_heightmap;
|
|
|
|
ClearBounds(mod->mins, mod->maxs);
|
|
|
|
hm = Z_Malloc(sizeof(*hm));
|
|
ClearLink(&hm->recycle);
|
|
// ClearLink(&hm->collected);
|
|
COM_FileBase(mod->name, hm->path, sizeof(hm->path));
|
|
|
|
if (!Terr_ReformEntitiesLump(mod, hm, buffer))
|
|
return false;
|
|
|
|
strcpy(hm->groundshadername, "terrainshader");
|
|
strcpy(hm->skyname, "sky1");
|
|
|
|
hm->entitylock = Sys_CreateMutex();
|
|
hm->sectionsize = sectsize;
|
|
if (exterior)
|
|
{
|
|
hm->firstsegx = -1;
|
|
hm->firstsegy = -1;
|
|
hm->maxsegx = +1;
|
|
hm->maxsegy = +1;
|
|
}
|
|
else
|
|
{
|
|
hm->firstsegx = 0;
|
|
hm->firstsegy = 0;
|
|
hm->maxsegx = 0;
|
|
hm->maxsegy = 0;
|
|
}
|
|
hm->exteriorcontents = exterior; //sky outside the map
|
|
|
|
Terr_ParseEntityLump(mod, hm);
|
|
|
|
if (hm->firstsegx != hm->maxsegx)
|
|
{
|
|
vec3_t point;
|
|
point[0] = (hm->firstsegx - CHUNKBIAS) * hm->sectionsize;
|
|
point[1] = (hm->firstsegy - CHUNKBIAS) * hm->sectionsize;
|
|
point[2] = -999999999999999999999999.f;
|
|
AddPointToBounds(point, mod->mins, mod->maxs);
|
|
point[0] = (hm->maxsegx - CHUNKBIAS) * hm->sectionsize;
|
|
point[1] = (hm->maxsegy - CHUNKBIAS) * hm->sectionsize;
|
|
point[2] = 999999999999999999999999.f;
|
|
AddPointToBounds(point, mod->mins, mod->maxs);
|
|
}
|
|
|
|
mod->funcs.NativeTrace = Heightmap_Trace_Test;
|
|
mod->funcs.PointContents = Heightmap_PointContents;
|
|
|
|
mod->funcs.NativeContents = Heightmap_NativeBoxContents;
|
|
|
|
mod->funcs.LightPointValues = Heightmap_LightPointValues;
|
|
mod->funcs.StainNode = Heightmap_StainNode;
|
|
mod->funcs.MarkLights = Heightmap_MarkLights;
|
|
|
|
mod->funcs.ClusterForPoint = Heightmap_ClusterForPoint;
|
|
mod->funcs.ClusterPVS = Heightmap_ClusterPVS;
|
|
|
|
#ifndef CLIENTONLY
|
|
mod->funcs.FindTouchedLeafs = Heightmap_FindTouchedLeafs;
|
|
mod->funcs.EdictInFatPVS = Heightmap_EdictInFatPVS;
|
|
mod->funcs.FatPVS = Heightmap_FatPVS;
|
|
#endif
|
|
/* mod->hulls[0].funcs.HullPointContents = Heightmap_PointContents;
|
|
mod->hulls[1].funcs.HullPointContents = Heightmap_PointContents;
|
|
mod->hulls[2].funcs.HullPointContents = Heightmap_PointContents;
|
|
mod->hulls[3].funcs.HullPointContents = Heightmap_PointContents;
|
|
*/
|
|
|
|
mod->terrain = hm;
|
|
|
|
#ifdef RUNTIMELIGHTING
|
|
if (hm->relightcontext)
|
|
{
|
|
LightReloadEntities(hm->relightcontext, Mod_GetEntitiesString(mod), true);
|
|
hm->entsdirty = false;
|
|
}
|
|
#endif
|
|
|
|
validatelinks(&hm->recycle);
|
|
return true;
|
|
}
|
|
|
|
void *Mod_LoadTerrainInfo(model_t *mod, char *loadname, qboolean force)
|
|
{
|
|
heightmap_t *hm;
|
|
heightmap_t potential;
|
|
if (!Mod_GetEntitiesString(mod))
|
|
return NULL;
|
|
|
|
memset(&potential, 0, sizeof(potential));
|
|
Terr_ParseEntityLump(mod, &potential);
|
|
|
|
if (potential.firstsegx >= potential.maxsegx || potential.firstsegy >= potential.maxsegy)
|
|
{
|
|
//figure out the size such that it encompases the entire bsp.
|
|
potential.firstsegx = floor(mod->mins[0] / potential.sectionsize) + CHUNKBIAS;
|
|
potential.firstsegy = floor(mod->mins[1] / potential.sectionsize) + CHUNKBIAS;
|
|
potential.maxsegx = ceil(mod->maxs[0] / potential.sectionsize) + CHUNKBIAS;
|
|
potential.maxsegy = ceil(mod->maxs[1] / potential.sectionsize) + CHUNKBIAS;
|
|
if (*loadname=='*')
|
|
{
|
|
potential.firstsegx = bound(0, potential.firstsegx, CHUNKLIMIT);
|
|
potential.firstsegy = bound(0, potential.firstsegy, CHUNKLIMIT);
|
|
potential.maxsegx = bound(potential.firstsegx, potential.maxsegx, CHUNKLIMIT);
|
|
potential.maxsegy = bound(potential.firstsegx, potential.maxsegy, CHUNKLIMIT);
|
|
}
|
|
else
|
|
{//bound it, such that 0 0 will always be loaded.
|
|
potential.firstsegx = bound(0, potential.firstsegx, CHUNKBIAS);
|
|
potential.firstsegy = bound(0, potential.firstsegy, CHUNKBIAS);
|
|
potential.maxsegx = bound(CHUNKBIAS+1, potential.maxsegx, CHUNKLIMIT);
|
|
potential.maxsegy = bound(CHUNKBIAS+1, potential.maxsegy, CHUNKLIMIT);
|
|
}
|
|
|
|
if (!force)
|
|
{
|
|
char sect[MAX_QPATH];
|
|
Q_snprintfz(sect, sizeof(sect), "maps/%s/sect_%03x_%03x.hms", loadname, potential.firstsegx + (potential.maxsegx-potential.firstsegx)/2, potential.firstsegy + (potential.maxsegy-potential.firstsegy)/2);
|
|
if (!COM_FCheckExists(sect))
|
|
{
|
|
Q_snprintfz(sect, sizeof(sect), "maps/%s/block_00_00.hms", loadname);
|
|
if (!COM_FCheckExists(sect))
|
|
return NULL;
|
|
}
|
|
}
|
|
}
|
|
|
|
hm = Z_Malloc(sizeof(*hm));
|
|
*hm = potential;
|
|
hm->entitylock = Sys_CreateMutex();
|
|
ClearLink(&hm->recycle);
|
|
Q_strncpyz(hm->path, loadname, sizeof(hm->path));
|
|
Q_strncpyz(hm->groundshadername, "terrainshader", sizeof(hm->groundshadername));
|
|
|
|
hm->exteriorcontents = FTECONTENTS_EMPTY; //bsp geometry outside the heightmap
|
|
|
|
return hm;
|
|
}
|
|
|
|
#ifndef SERVERONLY
|
|
void Mod_Terrain_Create_f(void)
|
|
{
|
|
int x,y;
|
|
hmsection_t *s;
|
|
heightmap_t *hm;
|
|
char *mname;
|
|
char *mapdesc;
|
|
char *skyname;
|
|
char *groundname;
|
|
char *watername;
|
|
char *groundheight;
|
|
char *waterheight;
|
|
vfsfile_t *file;
|
|
model_t mod;
|
|
memset(&mod, 0, sizeof(mod));
|
|
if (Cmd_Argc() < 2)
|
|
{
|
|
Con_Printf("%s: NAME \"DESCRIPTION\" SKYNAME DEFAULTGROUNDTEX DEFAULTHEIGHT DEFAULTWATER DEFAULTWATERHEIGHT\nGenerates a fresh maps/foo.hmp file. You may wish to edit it with notepad later to customise it. You will need csaddon.dat in order to edit the actual terrain.\n", Cmd_Argv(0));
|
|
return;
|
|
}
|
|
mname = va("maps/%s.hmp", Cmd_Argv(1));
|
|
|
|
mapdesc = Cmd_Argv(2); if (!*mapdesc) mapdesc = Cmd_Argv(1);
|
|
skyname = Cmd_Argv(3); if (!*skyname) skyname = "sky1";
|
|
groundname = Cmd_Argv(4); if (!*groundname) groundname = "default";
|
|
groundheight = Cmd_Argv(5); if (!*groundheight) groundheight = "0";
|
|
watername = Cmd_Argv(6); if (!*watername) watername = "";
|
|
waterheight = Cmd_Argv(7); if (!*waterheight) waterheight = "1024";
|
|
Mod_SetEntitiesString(&mod, va(
|
|
"{\n"
|
|
"classname \"worldspawn\"\n"
|
|
"message \"%s\"\n"
|
|
"_sky sky1\n"
|
|
"_fog 0.02\n"
|
|
"_segmentsize 1024\n"
|
|
"_minxsegment -2048\n"
|
|
"_minysegment -2048\n"
|
|
"_maxxsegment 2048\n"
|
|
"_maxysegment 2048\n"
|
|
"//_defaultgroundtexture \"city4_2\"\n"
|
|
"//_defaultwatertexture \"*water2\"\n"
|
|
"//_defaultgroundheight -1024\n"
|
|
"//_defaultwaterheight 0\n" //hurrah, sea level.
|
|
// "_tiles 64 64 8 8\n"
|
|
"}\n"
|
|
"{\n"
|
|
"classname info_player_start\n"
|
|
"origin \"0 0 1024\"\n"
|
|
"}\n"
|
|
, Cmd_Argv(2)), true);
|
|
|
|
mod.type = mod_heightmap;
|
|
mod.terrain = hm = Z_Malloc(sizeof(*hm));
|
|
Terr_ParseEntityLump(&mod, hm);
|
|
hm->entitylock = Sys_CreateMutex();
|
|
ClearLink(&hm->recycle);
|
|
Q_strncpyz(hm->path, Cmd_Argv(1), sizeof(hm->path));
|
|
Q_strncpyz(hm->groundshadername, "terrainshader", sizeof(hm->groundshadername));
|
|
hm->exteriorcontents = FTECONTENTS_SOLID;
|
|
|
|
|
|
for (x = CHUNKBIAS-1; x < CHUNKBIAS+1; x++)
|
|
for (y = CHUNKBIAS-1; y < CHUNKBIAS+1; y++)
|
|
Terr_GetSection(hm, x, y, TGS_TRYLOAD|TGS_DEFAULTONFAIL);
|
|
|
|
for (x = CHUNKBIAS-1; x < CHUNKBIAS+1; x++)
|
|
for (y = CHUNKBIAS-1; y < CHUNKBIAS+1; y++)
|
|
{
|
|
s = Terr_GetSection(hm, x, y, TGS_WAITLOAD|TGS_DEFAULTONFAIL);
|
|
if (s && (s->flags & (TSF_EDITED|TSF_DIRTY)))
|
|
{
|
|
Terr_InitLightmap(s, false);
|
|
Terr_SaveSection(hm, s, x, y, true);
|
|
}
|
|
}
|
|
|
|
if (COM_FCheckExists(mname))
|
|
{
|
|
Con_Printf("%s: already exists, not overwriting.\n", mname);
|
|
return;
|
|
}
|
|
FS_CreatePath(mname, FS_GAMEONLY);
|
|
file = FS_OpenVFS(mname, "wb", FS_GAMEONLY);
|
|
if (!file)
|
|
Con_Printf("unable to open %s\n", mname);
|
|
else
|
|
{
|
|
Terr_WriteMapFile(file, &mod);
|
|
VFS_CLOSE(file);
|
|
Con_Printf("Wrote %s\n", mname);
|
|
FS_FlushFSHashWritten(mname);
|
|
}
|
|
Mod_SetEntitiesString(&mod, NULL, false);
|
|
Terr_FreeModel(&mod);
|
|
}
|
|
#endif
|
|
//reads in the terrain a tile at a time, and writes it out again.
|
|
//the new version will match our current format version.
|
|
//this is mostly so I can strip out old format revisions...
|
|
#ifndef SERVERONLY
|
|
void Mod_Terrain_Convert_f(void)
|
|
{
|
|
model_t *mod;
|
|
heightmap_t *hm;
|
|
if (Cmd_FromGamecode())
|
|
return;
|
|
|
|
if (Cmd_Argc() >= 2)
|
|
mod = Mod_FindName(va("maps/%s.hmp", Cmd_Argv(1)));
|
|
else if (cls.state)
|
|
mod = cl.worldmodel;
|
|
else
|
|
mod = NULL;
|
|
if (!mod || mod->type == mod_dummy)
|
|
return;
|
|
hm = mod->terrain;
|
|
if (!hm)
|
|
return;
|
|
|
|
{
|
|
char *texkill = Cmd_Argv(2);
|
|
hmsection_t *s;
|
|
int x, sx;
|
|
int y, sy;
|
|
|
|
while(Terr_Collect(hm)) //collect as many as we can now, so when we collect later, the one that's collected is fresh.
|
|
;
|
|
for (y = hm->firstsegy; y < hm->maxsegy; y+=SECTIONSPERBLOCK)
|
|
{
|
|
Sys_Printf("%g%% complete\n", 100 * (y-hm->firstsegy)/(float)(hm->maxsegy-hm->firstsegy));
|
|
for (x = hm->firstsegx; x < hm->maxsegx; x+=SECTIONSPERBLOCK)
|
|
{
|
|
for (sy = y; sy < y+SECTIONSPERBLOCK && sy < hm->maxsegy; sy++)
|
|
{
|
|
for (sx = x; sx < x+SECTIONSPERBLOCK && sx < hm->maxsegx; sx++)
|
|
{
|
|
s = Terr_GetSection(hm, sx, sy, TGS_WAITLOAD|TGS_NODOWNLOAD|TGS_NORENDER);
|
|
if (s)
|
|
{
|
|
if (*texkill)
|
|
ted_texkill(s, texkill);
|
|
s->flags |= TSF_EDITED;
|
|
}
|
|
}
|
|
}
|
|
for (sy = y; sy < y+SECTIONSPERBLOCK && sy < hm->maxsegy; sy++)
|
|
{
|
|
for (sx = x; sx < x+SECTIONSPERBLOCK && sx < hm->maxsegx; sx++)
|
|
{
|
|
s = Terr_GetSection(hm, sx, sy, TGS_WAITLOAD|TGS_NODOWNLOAD|TGS_NORENDER);
|
|
if (s)
|
|
{
|
|
if (s->flags & TSF_EDITED)
|
|
{
|
|
if (Terr_SaveSection(hm, s, sx, sy, true))
|
|
{
|
|
s->flags &= ~TSF_EDITED;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
while(Terr_Collect(hm))
|
|
;
|
|
}
|
|
}
|
|
Sys_Printf("%g%% complete\n", 100.0f);
|
|
}
|
|
}
|
|
#endif
|
|
void Mod_Terrain_Reload_f(void)
|
|
{
|
|
model_t *mod;
|
|
heightmap_t *hm;
|
|
if (Cmd_Argc() >= 2)
|
|
mod = Mod_FindName(va("maps/%s.hmp", Cmd_Argv(1)));
|
|
#ifndef SERVERONLY
|
|
else if (cls.state)
|
|
mod = cl.worldmodel;
|
|
#endif
|
|
else
|
|
mod = NULL;
|
|
if (!mod || mod->type == mod_dummy)
|
|
return;
|
|
hm = mod->terrain;
|
|
if (!hm)
|
|
return;
|
|
|
|
if (Cmd_Argc() >= 4)
|
|
{
|
|
hmsection_t *s;
|
|
int sx = atoi(Cmd_Argv(2)) + CHUNKBIAS;
|
|
int sy = atoi(Cmd_Argv(3)) + CHUNKBIAS;
|
|
if (hm)
|
|
{
|
|
s = Terr_GetSection(hm, sx, sy, TGS_NOLOAD);
|
|
if (s)
|
|
{
|
|
s->flags |= TSF_NOTIFY;
|
|
}
|
|
}
|
|
}
|
|
else
|
|
Terr_PurgeTerrainModel(mod, false, true);
|
|
}
|
|
|
|
terrainfuncs_t *QDECL Terr_GetTerrainFuncs(void)
|
|
{
|
|
return &terrainfuncs;
|
|
}
|
|
|
|
void Terr_Init(void)
|
|
{
|
|
terrainfuncs.GenerateWater = Terr_GenerateWater;
|
|
terrainfuncs.InitLightmap = Terr_InitLightmap;
|
|
terrainfuncs.AddMesh = Terr_AddMesh;
|
|
terrainfuncs.GetLightmap = Terr_GetLightmap;
|
|
terrainfuncs.GetSection = Terr_GetSection;
|
|
terrainfuncs.GenerateSections = Terr_GenerateSections;
|
|
terrainfuncs.FinishedSection = Terr_FinishedSection;
|
|
|
|
Cvar_Register(&mod_terrain_networked, "Terrain");
|
|
Cvar_Register(&mod_terrain_defaulttexture, "Terrain");
|
|
Cvar_Register(&mod_terrain_savever, "Terrain");
|
|
Cmd_AddCommand("mod_terrain_save", Mod_Terrain_Save_f);
|
|
Cmd_AddCommand("mod_terrain_reload", Mod_Terrain_Reload_f);
|
|
#ifndef SERVERONLY
|
|
Cmd_AddCommand("mod_terrain_create", Mod_Terrain_Create_f);
|
|
Cmd_AddCommandD("mod_terrain_convert", Mod_Terrain_Convert_f, "mod_terrain_convert [mapname] [texkill]\nConvert a terrain to the current format. If texkill is specified, only tiles with the named texture will be converted, and tiles with that texture will be stripped. This is a slow operation.");
|
|
#endif
|
|
|
|
Mod_RegisterModelFormatText(NULL, "FTE Heightmap Map (hmp)", "terrain", Terr_LoadTerrainModel);
|
|
Mod_RegisterModelFormatText(NULL, "Quake Map Format (map)", "{", Terr_LoadTerrainModel);
|
|
}
|
|
#endif
|