/* See gl_terrain.h for terminology, networking notes, etc. */ //FIXME: render in lightmap batches. generate vbos accordingly. //FIXME: assign to lightmaps by matching textures. should be able to get up to 65536/(17*17)=226 per section before index limits hit, 16*16=256 allows for 1024*1024 lightmaps. //FIXME: sort texture blend names to reduce combinations #include "quakedef.h" #ifdef TERRAIN #include "glquake.h" #include "shader.h" #include "com_mesh.h" #include "pr_common.h" #include "gl_terrain.h" static plugterrainfuncs_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."); cvar_t mod_terrain_sundir = CVARD("mod_terrain_sundir", "0.4 0.7 2", "The direction of the sun (vector will be normalised)."); cvar_t mod_terrain_ambient = CVARD("mod_terrain_ambient", "0.5", "Proportion of ambient light."); cvar_t mod_terrain_shadows = CVARD("mod_terrain_shadows", "0", "Cast rays to determine whether parts of the terrain should be in shadow."); cvar_t mod_terrain_shadow_dist = CVARD("mod_terrain_shadow_dist", "2048", "How far rays should be cast in order to look for occlusing geometry."); cvar_t mod_terrain_brushlights = CVARD("mod_map_lights", "0", "Calculates lighting on brushes/patches."); cvar_t mod_terrain_brushtexscale = CVARD("mod_map_texscale", "1", "Defines the scale of texture texels. Use 1 for quake+quake2 maps, and 0.5 for quake3 maps."); enum { hmcmd_brush_delete, //brush OR patch destruction hmcmd_brush_insert, //brush creation hmcmd_prespawning, //sent before initial inserts hmcmd_prespawned, //sent just after initial inserts hmcmd_patch_insert, //patch creation 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;*/ } 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); #ifdef HAVE_CLIENT static void ted_dorelight(model_t *m, heightmap_t *hm); static void Terr_WorkerLoadedSectionLightmap(void *ctx, void *data, size_t a, size_t b); static qboolean Terr_Collect(heightmap_t *hm); static void Terr_Brush_Draw(heightmap_t *hm, batch_t **batches, entity_t *e); 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 Terr_LoadSectionTextures(hmsection_t *s) { #ifdef HAVE_CLIENT 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 (NULL, w->shadername, SUF_NONE, Shader_DefaultWaterShader, NULL); R_BuildDefaultTexnums(NULL, w->shader, IF_WORLDTEX); //this might get expensive. hideously so. } #endif } static qboolean QDECL Terr_InitLightmap(hmsection_t *s, qboolean initialise) { #ifndef HAVE_CLIENT return false; #else heightmap_t *hm = s->hmmod; if (s->lightmap < 0) { struct lmsect_s *lms; Sys_LockMutex(com_resourcemutex); while (!hm->unusedlmsects) { int lm; int i; Sys_UnlockMutex(com_resourcemutex); lm = Surf_NewLightmaps(1, SECTTEXSIZE*LMCHUNKS, SECTTEXSIZE*LMCHUNKS, PTI_BGRA8, false); Sys_LockMutex(com_resourcemutex); 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++; Sys_UnlockMutex(com_resourcemutex); Z_Free(lms); initialise = true; } if (initialise && s->lightmap >= 0) { int x, y; unsigned char *lm = lightmap[s->lightmap]->lightmaps; unsigned int pixbytes = lightmap[s->lightmap]->pixbytes; lm += (s->lmy * HMLMSTRIDE + s->lmx) * pixbytes; 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)*pixbytes; } } 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; } #ifdef HAVE_CLIENT 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 #ifdef HAVE_SERVER 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; } #endif 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; #ifdef HAVE_CLIENT 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; } #ifdef HAVE_CLIENT 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; } #ifdef HAVE_CLIENT s->lightmap = -1; 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) { #ifdef HAVE_CLIENT 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; } #ifdef HEXEN2 e->ent.drawflags = SCALE_ORIGIN_ORIGIN; #endif 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) { #ifdef HAVE_CLIENT 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<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; } #ifdef HAVE_CLIENT 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; unsigned int pixbytes; 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; } } pixbytes = lightmap[s->lightmap]->pixbytes; lm = lightmap[s->lightmap]->lightmaps; lm += (s->lmy * HMLMSTRIDE + s->lmx) * pixbytes; 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)*pixbytes; } //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; pixbytes = lightmap[s->lightmap]->pixbytes; lm = lightmap[s->lightmap]->lightmaps; lm += (s->lmy * HMLMSTRIDE + s->lmx) * pixbytes; 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)*pixbytes; } } 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; } #ifdef HAVE_CLIENT 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 (Terr_InitLightmap(s, false)) { int pixbytes = lightmap[s->lightmap]->pixbytes; outlm = lightmap[s->lightmap]->lightmaps; outlm += (s->lmy * HMLMSTRIDE + s->lmx) * pixbytes; for (y = 0; y < SECTTEXSIZE; y++) { memcpy(outlm, inlm, SECTTEXSIZE*4); inlm += SECTTEXSIZE*4; outlm += (HMLMSTRIDE)*pixbytes; } } BZ_Free(data); } #endif #endif static void *Terr_ReadV2(heightmap_t *hm, hmsection_t *s, void *ptr, int len) { #ifdef HAVE_CLIENT 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. #ifdef HAVE_CLIENT 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)); #ifdef HAVE_CLIENT 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 = lightmap[s->lightmap]->lightmaps; int pixbytes = lightmap[s->lightmap]->pixbytes; lm += (s->lmy * HMLMSTRIDE + s->lmx) * pixbytes; 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)*pixbytes; } 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; } #ifdef HAVE_CLIENT 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 #ifdef HAVE_CLIENT 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); } #ifdef HAVE_CLIENT 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; int pixbytes; 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<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<flags |= TSF_DIRTY; pixbytes = lightmap[s->lightmap]->pixbytes; lm = lightmap[s->lightmap]->lightmaps; lm += (s->lmy * HMLMSTRIDE + s->lmx) * pixbytes; for (i = 0; i < SECTTEXSIZE; i++) { memcpy(ds.texmap + i, lm, sizeof(ds.texmap[0])); lm += (HMLMSTRIDE)*pixbytes; } 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) { #ifndef HAVE_CLIENT 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); } } #ifdef HAVE_CLIENT //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; } #ifdef HAVE_SERVER //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; { int cx = s->sx/MAXSECTIONS; int cy = s->sy/MAXSECTIONS; hmcluster_t *c = hm->cluster[cx + cy*MAXCLUSTERS]; int sx = s->sx & (MAXSECTIONS-1); int sy = s->sy & (MAXSECTIONS-1); if (c->section[sx+sy*MAXSECTIONS] != s) Sys_Error("Section %i,%i already destroyed...\n", s->sx, s->sy); c->section[sx+sy*MAXSECTIONS] = NULL; } validatelinks(&hm->recycle); RemoveLink(&s->recycle); validatelinks(&s->hmmod->recycle); Terr_ClearSection(s); #ifdef HAVE_CLIENT 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) { if (qglDeleteBuffersARB) { if (s->vbo.coord.gl.vbo) { qglDeleteBuffersARB(1, &s->vbo.coord.gl.vbo); s->vbo.coord.gl.vbo = 0; } if (s->vbo.indicies.gl.vbo) { qglDeleteBuffersARB(1, &s->vbo.indicies.gl.vbo); s->vbo.indicies.gl.vbo = 0; } } } else #endif { BE_ClearVBO(&s->vbo, true); } Z_Free(s->ents); Z_Free(s->mesh.xyz_array); Z_Free(s->mesh.indexes); #endif Z_Free(s); hm->activesections--; validatelinks(&hm->recycle); } #ifdef HAVE_CLIENT //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) { #ifdef HAVE_SERVER 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; } } #endif } //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 /*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; COM_WorkerFullSync(); //should probably be inside the caller or something. make sure there's no loaders still loading lightmaps when lightmaps are going to be nuked. 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++; #ifdef HAVE_CLIENT s->lightmap = -1; #endif } else { 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); #ifdef HAVE_CLIENT 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; #ifdef HAVE_CLIENT 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->seed); Z_Free(hm); mod->terrain = NULL; } } #ifdef HAVE_CLIENT 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_stris_ExpandVerts(cl_numstrisvert+9*9+64); 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<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_stris_ExpandVerts(cl_numstrisvert+64); { 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 0 if (qrenderer == QR_OPENGL && qglGenBuffersARB) { vbobctx_t ctx; size_t vertsize = sizeof(*mesh->xyz_array)+sizeof(*mesh->st_array)+sizeof(*mesh->lmst_array)+(mesh->colors4f_array?sizeof(*mesh->colors4f_array):0); BE_VBO_Begin(&ctx, vertsize * mesh->numvertexes); BE_VBO_Data(&ctx, mesh->xyz_array, sizeof(*mesh->xyz_array) * mesh->numvertexes, &s->vbo.coord); BE_VBO_Data(&ctx, mesh->st_array, sizeof(*mesh->st_array) * mesh->numvertexes, &s->vbo.texcoord); BE_VBO_Data(&ctx, mesh->lmst_array, sizeof(*mesh->lmst_array) * mesh->numvertexes, &s->vbo.lmcoord[0]); if (mesh->colors4f_array) BE_VBO_Data(&ctx, mesh->colors4f_array, sizeof(*mesh->colors4f_array) * mesh->numvertexes, &s->vbo.colours[0]); BE_VBO_Finish(&ctx, mesh->indexes, sizeof(*mesh->indexes)*mesh->numindexes, &s->vbo.indicies, NULL, NULL); } #else 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 #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 D3D9QUAKE if (qrenderer == QR_DIRECT3D9) { void D3D9BE_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); D3D9BE_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, NULL)) 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) { 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(m, 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.globalfog.alpha>=1) || r_refdef.maxdist>0) { float culldist; extern cvar_t r_fog_exp2; if (r_refdef.globalfog.density&&r_refdef.globalfog.alpha>=1) { //fogalpha<1 means you can always see through it, so don't cull when its invisible. //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 < hm->maxdrawdist) culldist = hm->maxdrawdist; 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)?r_refdef.scenevis: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, const 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<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<maxheight) //FIXME contents |= w->contentmask; } return contents; } unsigned int Heightmap_PointContents(model_t *model, const vec3_t axis[3], const 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]; if (br->patch) continue; //infinitely thin... 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, const framestate_t *framestate, const vec3_t axis[3], const vec3_t org, const vec3_t mins, const vec3_t maxs) { heightmap_t *hm = model->terrain; return Heightmap_PointContentsHM(hm, mins[2], org); } float Heightmap_Normal(heightmap_t *hm, vec2_t org, vec3_t norm) //returns the z { #if 0 float z = 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; float z; 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 hm->defaultgroundheight; if (sx >= hm->maxsegx || sy >= hm->maxsegy) return hm->defaultgroundheight; s = Terr_GetSection(hm, sx, sy, TGS_TRYLOAD); if (!s) return hm->defaultgroundheight; 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]); z = (s->heights[(sx+0)+(sy+1)*SECTHEIGHTSIZE]*(1-y) + s->heights[(sx+1)+(sy+1)*SECTHEIGHTSIZE]*(x+y-1) + s->heights[(sx+1)+(sy+0)*SECTHEIGHTSIZE]*(1-x)); } 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]); z = (s->heights[(sx+0)+(sy+1)*SECTHEIGHTSIZE]*(y) + s->heights[(sx+1)+(sy+0)*SECTHEIGHTSIZE]*(x) + s->heights[(sx+0)+(sy+0)*SECTHEIGHTSIZE]*(1-y-x)); } VectorNormalize(d1); VectorNormalize(d2); CrossProduct(d1, d2, norm); VectorNormalize(norm); #endif return z; } 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; #ifdef HAVE_CLIENT shader_t *Terr_GetShader(model_t *mod, trace_t *trace) { heightmap_t *hm = mod?mod->terrain:NULL; unsigned int brushid = trace->brush_id; unsigned int fa, i; brushes_t *br; if (!brushid) return NULL; if (!hm) return NULL; for (i = 0; i < hm->numbrushes; i++) { br = &hm->wbrushes[i]; if (br->id == trace->brush_id) { if (br->patch) return br->patch->tex->shader; fa = trace->brush_face-1; if (fa >= br->numplanes) return NULL; return br->faces[fa].tex->shader; } } return NULL; } #endif 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<= 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; } static qboolean Heightmap_Trace_Quad(hmtrace_t *tr, const float *v0, const float *v1, const float *v2, const float *v3) { //super lame shite. be lazy and just use a bbox static vec4_t n[6] = { {-1, 0, 0, 0}, { 0,-1, 0, 0}, { 0, 0,-1, 0}, { 1, 0, 0, 0}, { 0, 1, 0, 0}, { 0, 0, 1, 0}, }; vec3_t d[2]; const float epsilon = 1.0/64; VectorCopy(v0, d[0]); VectorCopy(v0, d[1]); AddPointToBounds(v1, d[0], d[1]); AddPointToBounds(v2, d[0], d[1]); AddPointToBounds(v3, d[0], d[1]); //I'm implementing this primarily for selecting patches. //decals are often infinitely thin things. //so expand them by a tiny amount in the hopes that traces will hit patches before the wall they're coplanar with. n[0][3] =-d[0][0]+epsilon; n[1][3] =-d[0][1]+epsilon; n[2][3] =-d[0][2]+epsilon; n[3][3] = d[1][0]+epsilon; n[4][3] = d[1][1]+epsilon; n[5][3] = d[1][2]+epsilon; return Heightmap_Trace_Brush(tr, n, 6, NULL) != 0; } static qboolean Heightmap_Trace_Patch(hmtrace_t *tr, brushes_t *brushinfo) { const struct patchdata_s *patch = brushinfo->patch; unsigned int w, h, x, y; qboolean ret = false; if (!patch->tessvert) { const struct qcpatchvert_s *r1 = patch->cp, *r2; w = patch->numcp[0]; h = patch->numcp[1]; for (y = 0, r2 = r1 + w; y < h-1; y++) { for (x = 0; x < w-1; x++, r1++, r2++) ret |= Heightmap_Trace_Quad(tr, r1[0].v, r1[1].v, r2[0].v, r1[1].v); r1++; r2++; } } else { const struct patchtessvert_s *r1 = patch->tessvert, *r2; w = patch->tesssize[0]; h = patch->tesssize[1]; for (y = 0, r2 = r1 + w; y < h-1; y++) { for (x = 0; x < w-1; x++, r1++, r2++) ret |= Heightmap_Trace_Quad(tr, r1[0].v, r1[1].v, r2[0].v, r1[1].v); r1++; r2++; } } return ret; } //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, const framestate_t *framestate, const vec3_t mataxis[3], const vec3_t start, const vec3_t end, const vec3_t mins, const 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); } //trace against the heightmap, if it exists. if (hmtrace.hm->maxsegx != hmtrace.hm->firstsegx) { //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; } } } //now trace against the brushes. //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; if (brushes->patch) { if (Heightmap_Trace_Patch(&hmtrace, brushes)) face = -1; else face = 0; } else 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, const framestate_t *framestate, const vec3_t mataxis[3], const vec3_t start, const vec3_t end, const vec3_t mins, const 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) { //FIXME: this code should not be needed. trace_t testtrace; Heightmap_Trace(model, hulloverride, framestate, mataxis, trace->endpos, trace->endpos, mins, maxs, capsule, against, &testtrace); if (testtrace.startsolid) { //yup, we're bugged. 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, const vec3_t org, pvsbuffer_t *fte_restrict pvsbuffer, qboolean add) { //embed the org onto the pvs hmpvs_t *hmpvs; if (pvsbuffer->buffersize < sizeof(*hmpvs)) pvsbuffer->buffer = BZ_Realloc(pvsbuffer->buffer, pvsbuffer->buffersize=sizeof(*hmpvs)); hmpvs = (hmpvs_t*)pvsbuffer->buffer; hmpvs->id = 0xdeadbeef; VectorCopy(org, hmpvs->pos); return sizeof(*hmpvs); } #ifdef HAVE_SERVER qboolean Heightmap_EdictInFatPVS (model_t *mod, const struct pvscache_s *edict, const qbyte *pvsdata, const int *areas) { heightmap_t *hm = mod->terrain; int o[3], i; const hmpvs_t *hmpvs = (const hmpvs_t*)pvsdata; const hmpvsent_t *hmed = (const hmpvsent_t*)edict; if (!hm->culldistance || !hmpvs) 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, const float *mins, const float *maxs) { hmpvsent_t *hmed = (hmpvsent_t*)ent; VectorCopy(mins, hmed->min); VectorCopy(maxs, hmed->max); } #endif void Heightmap_LightPointValues (model_t *mod, const 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 (model_t *mod, float *parms) { } void Heightmap_MarkLights (dlight_t *light, dlightbitmask_t bit, mnode_t *node) { } qbyte *Heightmap_ClusterPVS (model_t *model, int num, pvsbuffer_t *buffer, pvsmerge_t merge) { return NULL; // static qbyte heightmappvs = 255; // return &heightmappvs; } int Heightmap_ClusterForPoint (model_t *model, const vec3_t point, int *area) { if (area) *area = 0; return -1; } #ifdef HAVE_CLIENT static unsigned char *QDECL Terr_GetLightmap(hmsection_t *s, int idx, qboolean edit) { 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; } return lightmap[s->lightmap]->lightmaps + ((s->lmy+y) * HMLMSTRIDE + (s->lmx+x)) * lightmap[s->lightmap]->pixbytes; } static void ted_dorelight(model_t *m, heightmap_t *hm) { unsigned char *lm = Terr_GetLightmap(hm->relight, 0, true); int x, y, k; #define EXPAND 2 vec3_t surfnorms[(SECTTEXSIZE+EXPAND*2)*(SECTTEXSIZE+EXPAND*2)]; vec3_t surfpoint[(SECTTEXSIZE+EXPAND*2)*(SECTTEXSIZE+EXPAND*2)]; // float scaletab[EXPAND*2*EXPAND*2]; vec3_t ldir; hmsection_t *s = hm->relight; float ambient, diffuse; trace_t trace; s->flags &= ~TSF_RELIGHT; hm->relight = NULL; if (s->lightmap < 0) return; ambient = 255*mod_terrain_ambient.value; diffuse = 255-ambient; for (y = -EXPAND; y < SECTTEXSIZE+EXPAND; y++) for (x = -EXPAND; x < SECTTEXSIZE+EXPAND; x++) { k = x+EXPAND + (y+EXPAND)*(SECTTEXSIZE+EXPAND*2); surfpoint[k][0] = hm->relightmin[0] + (x*hm->sectionsize/(SECTTEXSIZE-1)); surfpoint[k][1] = hm->relightmin[1] + (y*hm->sectionsize/(SECTTEXSIZE-1)); surfpoint[k][2] = Heightmap_Normal(s->hmmod, surfpoint[k], surfnorms[k])+0.1; } VectorNormalize2(mod_terrain_sundir.vec4, 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; else if (mod_terrain_shadows.ival) { float *point = surfpoint[x+EXPAND + (y+EXPAND)*(SECTTEXSIZE+EXPAND*2)]; vec3_t sun; VectorMA(point, mod_terrain_shadow_dist.value, ldir, sun); if (m->funcs.NativeTrace(m, 0, NULL, NULL, point, sun, vec3_origin, vec3_origin, false, FTECONTENTS_SOLID|FTECONTENTS_BODY, &trace)) d = 0; } // lm[0] = norm[0]*127 + 128; // lm[1] = norm[1]*127 + 128; // lm[2] = norm[2]*127 + 128; lm[3] = ambient + d*diffuse; } 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) return; if (mod->loadstate == MLS_LOADING) COM_WorkerPartialSync(mod, &mod->loadstate, MLS_LOADING); if (mod->loadstate != MLS_LOADED) return; switch(action) { case ter_ent_get: { unsigned 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: { unsigned int idx = G_INT(OFS_PARM1); int id; const char *newvals; if (idx >= MAX_EDICTS) //we need some sanity limit... many ents will get removed like lights so this one isn't quite correct, but it'll be in the right sort of ballpark. { G_INT(OFS_RETURN) = 0; return; } //if there's no ents, then that's a problem. make sure that there's at least a worldspawn. if (!mod->numentityinfo) Mod_ParseEntities(mod); //make sure we don't have any cached entities string, by wiping it all. Z_Free((char*)mod->entities_raw); mod->entities_raw = NULL; G_INT(OFS_RETURN) = 0; if (idx < mod->numentityinfo) { if (!G_INT(OFS_PARM2) && !mod->entityinfo[idx].keyvals) return; //no-op Z_Free(mod->entityinfo[idx].keyvals); mod->entityinfo[idx].keyvals = NULL; id = mod->entityinfo[idx].id; } else id = 0; 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); if (!id) id = (idx+1) | ((cl.playerview[0].playernum+1)<<24); mod->entityinfo[idx].id = id; } else { newvals = NULL; if (idx < mod->numentityinfo) mod->entityinfo[idx].id = 0; } #ifdef HAVE_SERVER 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, id); 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 SSQC_MapEntityEdited(modelindex, idx, newvals); } else #endif #ifdef HAVE_CLIENT 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, id); if (newvals) MSG_WriteString(&cls.netchan.message, newvals); #ifdef CSQC_DAT CSQC_MapEntityEdited(modelindex, idx, newvals); #endif } else #endif { #ifdef CSQC_DAT CSQC_MapEntityEdited(modelindex, 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_deprecated: G_INT(OFS_RETURN) = PR_TempString(prinst, Mod_GetEntitiesString(mod)); Mod_SetEntitiesString(mod, "", true); return; case ter_ents_concat_deprecated: { 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_TPrintf("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_TPrintf("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("drawdist", key)) heightmap->maxdrawdist = atof(value); else if (!strcmp("seed", key)) { Z_Free(heightmap->seed); heightmap->seed = Z_StrDup(value); } else if (!strcmp("exterior", key)) { heightmap->legacyterrain = false; if (!strcmp(value, "empty") || !strcmp(value, "")) heightmap->exteriorcontents = FTECONTENTS_EMPTY; else if (!strcmp(value, "sky")) heightmap->exteriorcontents = FTECONTENTS_SKY; else if (!strcmp(value, "lava")) heightmap->exteriorcontents = FTECONTENTS_LAVA; else //if (!strcmp(value, "solid")) heightmap->exteriorcontents = FTECONTENTS_SOLID; } 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) { #ifdef HAVE_CLIENT heightmap_t *hm = mod->terrain; if (qrenderer != QR_NONE) { if (*hm->skyname) { hm->skyshader = R_RegisterCustom(mod, va("skybox_%s", hm->skyname), SUF_NONE, Shader_DefaultSkybox, NULL); if (!hm->skyshader->skydome) hm->skyshader = 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" "rgbgen vertex\n" "alphagen vertex\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 } #ifdef HAVE_CLIENT 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]; vec4_t rgba[65536]; index_t index[65535]; } *arrays = NULL; size_t numverts = 0; size_t numindicies = 0; int w, h, lmnum; float scale[2]; #ifdef RUNTIMELIGHTING lightmapinfo_t *lm; qboolean dorelight = true; //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; if (hm->relightcontext) 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, PTI_BGRA8, 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) { lightstyleindex_t styles[max(2,MAXCPULIGHTMAPS)] = {0,INVALID_LIGHTSTYLE}; 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, NULL, 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]) * lm->pixbytes; switch(lm->fmt) { default: Sys_Error("Bad terrain lightmap format %i\n", lm->fmt); break; case PTI_BGRA8: case PTI_BGRX8: 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; } break; case PTI_RGBA8: case PTI_RGBX8: 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; } break; case PTI_BGR8: 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; } break; case PTI_RGB8: 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; } break; case PTI_A2BGR10: for (t = 0; t < br->faces[j].lmextents[1]; t++) { for (s = 0; s < br->faces[j].lmextents[0]; s++) { *(unsigned int*)out = (0x3<<30) | (in[2]<<22) | (in[1]<<12) | (in[0]<<2); out+=4; in+=3; } out += (lm->width - br->faces[j].lmextents[0]) * 4; } break; /*case PTI_E5BGR9: for (t = 0; t < br->faces[j].lmextents[1]; t++) { for (s = 0; s < br->faces[j].lmextents[0]; s++) { *(unsigned int*)out = Surf_PackE5BRG9(in[0], in[1], in[2], 8); out+=4; in+=3; } out += (lm->width - br->faces[j].lmextents[0]) * 4; } break;*/ case PTI_L8: for (t = 0; t < br->faces[j].lmextents[1]; t++) { for (s = 0; s < br->faces[j].lmextents[0]; s++) { *out++ = max(max(in[0], in[1]), in[2]); in+=3; } out += (lm->width - br->faces[j].lmextents[0]); } break; case PTI_RGBA32F: for (t = 0; t < br->faces[j].lmextents[1]; t++) { for (s = 0; s < br->faces[j].lmextents[0]; s++) { ((float*)out)[0] = in[0]/255.0; ((float*)out)[1] = in[1]/255.0; ((float*)out)[2] = in[2]/255.0; ((float*)out)[3] = 1.0; out+=16; in+=3; } out += (lm->width - br->faces[j].lmextents[0]) * 16; } break; /*case PTI_RGBA16F: for (t = 0; t < br->faces[j].lmextents[1]; t++) { for (s = 0; s < br->faces[j].lmextents[0]; s++) { Surf_PackRGB16F(in[0], in[1], in[2], 255); out+=8; in+=3; } out += (lm->width - br->faces[j].lmextents[0]) * 8; } break;*/ case PTI_RGB565: case PTI_RGBA4444: case PTI_RGBA5551: case PTI_ARGB4444: case PTI_ARGB1555: break; } } } } #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 bt->shader = R_RegisterCustom (NULL, va("textures/%s", bt->shadername), SUF_LIGHTMAP, NULL, NULL); if (!bt->shader) { if (!Q_strcasecmp(bt->shadername, "clip") || !Q_strcasecmp(bt->shadername, "hint") || !Q_strcasecmp(bt->shadername, "skip")) bt->shader = R_RegisterShader(bt->shadername, SUF_LIGHTMAP, "{\nsurfaceparm nodraw\n}"); else bt->shader = R_RegisterCustom (NULL, 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, TF_SOLID8, (qbyte*)tx + tx->offsets[0], tx->width, tx->height); else if (tx) { unsigned int mapflags = SHADER_HASPALETTED | SHADER_HASDIFFUSE | SHADER_HASFULLBRIGHT | SHADER_HASNORMALMAP | SHADER_HASGLOSS; R_BuildLegacyTexnums(bt->shader, tx->name, NULL, mapflags, 0, TF_SOLID8, tx->width, tx->height, (qbyte*)tx + tx->offsets[0], NULL); } else R_BuildDefaultTexnums(NULL, bt->shader, IF_WORLDTEX); 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] = mod_terrain_brushtexscale.value/w; //I hate needing this. scale[1] = mod_terrain_brushtexscale.value/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 (;i < hm->numbrushes;) { for (numverts = 0, numindicies = 0; i < hm->numbrushes; i++, br++) { if (br->selected) continue; if (br->patch) { //this one's a patch if (br->patch->tex == bt && lmnum == -1) { int x, y; index_t r1, r2; if (br->patch->tessvert && !br->selected) { //tessellated version of the patch. //make sure we don't overflow anything. size_t newverts = br->patch->tesssize[0]*br->patch->tesssize[1], newindexes = (br->patch->tesssize[0]-1)*(br->patch->tesssize[1]-1)*6; if (numverts+newverts >= 0xffff || numindicies+newindexes >= 0xffff) break; for (y = 0, r1 = numverts, r2 = 0; y < br->patch->tesssize[1]; y++) { for (x = 0; x < br->patch->tesssize[0]; x++, r1++, r2++) { VectorCopy(br->patch->tessvert[r2].v, arrays->coord[r1]); Vector2Copy(br->patch->tessvert[r2].tc, arrays->texcoord[r1]); Vector4Copy(br->patch->tessvert[r2].rgba, arrays->rgba[r1]); //lame Vector2Copy(br->patch->tessvert[r2].tc, arrays->lmcoord[r1]); } } for (y = 0, r1 = numverts, r2 = r1 + br->patch->tesssize[0]; y < br->patch->tesssize[1]-1; y++) { for (x = 0; x < br->patch->tesssize[0]-1; x++, r1++, r2++) { arrays->index[numindicies++] = r1; arrays->index[numindicies++] = r1+1; arrays->index[numindicies++] = r2; arrays->index[numindicies++] = r1+1; arrays->index[numindicies++] = r2+1; arrays->index[numindicies++] = r2; } r1++; r2++; } Mod_AccumulateTextureVectors(arrays->coord, arrays->texcoord, arrays->normal, arrays->svector, arrays->tvector, arrays->index+numindicies-newindexes, newindexes, true); Mod_NormaliseTextureVectors(arrays->normal+numverts, arrays->svector+numverts, arrays->tvector+numverts, newverts, true); numverts += newverts; } else { //control-point representation. //make sure we don't overflow anything. size_t newverts = br->patch->numcp[0]*br->patch->numcp[1], newindexes = (br->patch->numcp[0]-1)*(br->patch->numcp[1]-1)*6; if (numverts+newverts >= 0xffff || numindicies+newindexes >= 0xffff) break; for (y = 0, r1 = numverts, r2 = 0; y < br->patch->numcp[1]; y++) { for (x = 0; x < br->patch->numcp[0]; x++, r1++, r2++) { VectorCopy(br->patch->cp[r2].v, arrays->coord[r1]); Vector2Copy(br->patch->cp[r2].tc, arrays->texcoord[r1]); Vector4Copy(br->patch->cp[r2].rgba, arrays->rgba[r1]); //lame Vector2Copy(br->patch->cp[r2].tc, arrays->lmcoord[r1]); } } for (y = 0, r1 = numverts, r2 = r1 + br->patch->numcp[0]; y < br->patch->numcp[1]-1; y++) { for (x = 0; x < br->patch->numcp[0]-1; x++, r1++, r2++) { arrays->index[numindicies++] = r1; arrays->index[numindicies++] = r1+1; arrays->index[numindicies++] = r2; arrays->index[numindicies++] = r1+1; arrays->index[numindicies++] = r2+1; arrays->index[numindicies++] = r2; } r1++; r2++; } Mod_AccumulateTextureVectors(arrays->coord, arrays->texcoord, arrays->normal, arrays->svector, arrays->tvector, arrays->index+numindicies-newindexes, newindexes, true); Mod_NormaliseTextureVectors(arrays->normal+numverts, arrays->svector+numverts, arrays->tvector+numverts, newverts, true); numverts += newverts; } } } else { //regular brush //make sure we don't overflow anything. size_t newverts = 0, newindexes = 0; for (j = 0; j < br->numplanes; j++) if (br->faces[j].tex == bt && !br->selected && br->faces[j].lightmap == lmnum) newverts += br->faces[j].numpoints, newindexes += (br->faces[j].numpoints-2)*3; if (numverts+newverts >= 0xffff || numindicies+newindexes >= 0xffff) 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]); Vector4Set(arrays->rgba[o], 1.0, 1.0, 1.0, 1.0); //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])+sizeof(arrays->rgba[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])+sizeof(arrays->rgba[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_Data(&ctx, arrays->rgba, sizeof(arrays->rgba [0])*numverts, &bb->vbo.colours[0]); 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->mesh.colors4f_array[0] = (vec4_t*)(bb->mesh.tnormals_array+numverts); memcpy(bb->mesh.colors4f_array[0], arrays->rgba, sizeof(*bb->mesh.colors4f_array[0]) * numverts); bb->pmesh = &bb->mesh; bb->mesh.numindexes = numindicies; bb->mesh.numvertexes = numverts; numverts = 0; numindicies = 0; } } } } 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->patch = NULL; out->planes = NULL; out->faces = NULL; out->numplanes = 0; out->ispatch = !!brush->patch; out->selected = false; ClearBounds(out->mins, out->maxs); if (brush->numplanes) { out->planes = BZ_Malloc((sizeof(*out->planes)+sizeof(*out->faces)) * brush->numplanes); out->faces = (void*)(out->planes+brush->numplanes); 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++; } out->numplanes = oface; } if (brush->patch) { out->patch = BZ_Malloc(sizeof(*out->patch)-sizeof(out->patch->cp) + sizeof(*out->patch->cp)*brush->patch->numcp[0]*brush->patch->numcp[1]); memcpy(out->patch, brush->patch, sizeof(*out->patch)-sizeof(out->patch->cp) + sizeof(*out->patch->cp)*brush->patch->numcp[0]*brush->patch->numcp[1]); numpoints = out->patch->numcp[0]*out->patch->numcp[1]; //FIXME: lightmap... for (j = 0; j < numpoints; j++) AddPointToBounds(out->patch->cp[j].v, out->mins, out->maxs); out->patch->tex->rebuild = true; } if ((out->numplanes < 4 && out->numplanes) || (out->numplanes && out->patch) || (!out->numplanes && !out->patch)) { //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 < out->numplanes; j++) { BZ_Free(out->faces[j].lightdata); BZ_Free(out->faces[j].points); } BZ_Free(out->planes); BZ_Free(out->patch); return NULL; } 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; #ifdef HAVE_CLIENT 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); if (out->patch && (out->patch->subdiv[0] || out->patch->subdiv[1])) out->patch->tessvert = PatchInfo_Evaluate(out->patch->cp, out->patch->numcp, out->patch->subdiv, out->patch->tesssize); return out; } static brushes_t *Terr_Patch_Insert(model_t *model, heightmap_t *hm, brushtex_t *patch_tex, unsigned patch_w, unsigned patch_h, unsigned subdiv_w, unsigned subdiv_h, qcpatchvert_t *patch_v, int stride) { int x, y; brushes_t brush; //finish the brush brush.contents = FTECONTENTS_SOLID; brush.numplanes = 0; brush.planes = NULL; brush.faces = NULL; brush.id = 0; brush.patch = alloca(sizeof(*brush.patch)-sizeof(brush.patch->cp) + sizeof(*brush.patch->cp)*patch_w*patch_h); brush.patch->tex = patch_tex; brush.patch->numcp[0] = patch_w; brush.patch->numcp[1] = patch_h; brush.patch->subdiv[0] = subdiv_w; brush.patch->subdiv[1] = subdiv_h; brush.patch->tessvert = NULL; for (y = 0; y < patch_h; y++) { for (x = 0; x < patch_w; x++) { VectorCopy(patch_v[x].v, brush.patch->cp[x + y*patch_w].v); Vector2Copy(patch_v[x].tc, brush.patch->cp[x + y*patch_w].tc); Vector4Copy(patch_v[x].rgba, brush.patch->cp[x + y*patch_w].rgba); //brush.patch->verts[x + y*patch_w].norm //brush.patch->verts[x + y*patch_w].sdir //brush.patch->verts[x + y*patch_w].tdir } patch_v += stride; } return Terr_Brush_Insert(model, hm, &brush); } 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); if (br->patch) { BZ_Free(br->patch->tessvert); BZ_Free(br->patch); } 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; } static void Patch_Serialise(sizebuf_t *sb, brushes_t *br) { qbyte flags = 0; unsigned int i, m = br->patch->numcp[0]*br->patch->numcp[1]; for (i = 0; i < m; i++) { if (br->patch->cp[i].rgba[0] != 1) flags |= 1; if (br->patch->cp[i].rgba[1] != 1) flags |= 2; if (br->patch->cp[i].rgba[2] != 1) flags |= 4; if (br->patch->cp[i].rgba[3] != 1) flags |= 8; } MSG_WriteLong(sb, br->id); MSG_WriteLong(sb, br->contents); MSG_WriteShort(sb, br->patch->numcp[0]); MSG_WriteShort(sb, br->patch->numcp[1]); MSG_WriteByte(sb, flags); MSG_WriteString(sb, br->patch->tex->shadername); MSG_WriteShort(sb, br->patch->subdiv[0]); MSG_WriteShort(sb, br->patch->subdiv[1]); for (i = 0; i < m; i++) { MSG_WriteFloat(sb, br->patch->cp[i].v[0]); MSG_WriteFloat(sb, br->patch->cp[i].v[1]); MSG_WriteFloat(sb, br->patch->cp[i].v[2]); MSG_WriteFloat(sb, br->patch->cp[i].tc[0]); MSG_WriteFloat(sb, br->patch->cp[i].tc[1]); if (flags&1) MSG_WriteFloat(sb, br->patch->cp[i].rgba[0]); if (flags&2) MSG_WriteFloat(sb, br->patch->cp[i].rgba[1]); if (flags&4) MSG_WriteFloat(sb, br->patch->cp[i].rgba[2]); if (flags&8) MSG_WriteFloat(sb, br->patch->cp[i].rgba[3]); } } static size_t Patch_DeserialiseHeader(brushes_t *br) { unsigned int numcp[2]; br->id = MSG_ReadLong(); br->contents = MSG_ReadLong(); br->numplanes = numcp[0] = (unsigned short)MSG_ReadShort(); br->axialplanes = numcp[1] = (unsigned short)MSG_ReadShort(); if (numcp[0]*numcp[1] > 8192) return 0; //too many. reject it as bad. return sizeof(*br->patch) + sizeof(*br->patch->cp)*(numcp[0]*numcp[1]-countof(br->patch->cp)); } static qboolean Patch_Deserialise(heightmap_t *hm, brushes_t *br, void *mem) { struct qcpatchvert_s vert; qboolean flags; unsigned int i, m; flags = MSG_ReadByte(); br->patch = mem; br->patch->numcp[0] = br->numplanes; br->patch->numcp[1] = br->axialplanes; br->numplanes = br->axialplanes = 0; m = br->patch->numcp[0]*br->patch->numcp[1]; //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->patch->tex = Terr_Brush_FindTexture(hm, MSG_ReadString()); br->patch->subdiv[0] = MSG_ReadShort(); br->patch->subdiv[1] = MSG_ReadShort(); for (i = 0; i < m; i++) { vert.v[0] = MSG_ReadFloat(); vert.v[1] = MSG_ReadFloat(); vert.v[2] = MSG_ReadFloat(); vert.tc[0] = MSG_ReadFloat(); vert.tc[1] = MSG_ReadFloat(); vert.rgba[0] = (flags&1)?MSG_ReadFloat():1; vert.rgba[1] = (flags&2)?MSG_ReadFloat():1; vert.rgba[2] = (flags&4)?MSG_ReadFloat():1; vert.rgba[3] = (flags&8)?MSG_ReadFloat():1; br->patch->cp[i] = vert; } return true; } 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 size_t Brush_DeserialiseHeader(brushes_t *br, qboolean ispatch) { br->ispatch = ispatch; if (br->ispatch) return Patch_DeserialiseHeader(br); br->id = MSG_ReadLong(); br->contents = MSG_ReadLong(); br->numplanes = MSG_ReadLong(); if (br->numplanes > 8192) return 0; //abusive return sizeof(*br->faces) * br->numplanes + sizeof(*br->planes) * br->numplanes; } static qboolean Brush_Deserialise(heightmap_t *hm, brushes_t *br, void *mem) { unsigned int i; if (br->ispatch) return Patch_Deserialise(hm, br, mem); br->faces = mem; br->planes = (vec4_t*)(br->faces + br->numplanes); 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(); //FIXME: can we optimise this part? a flag to say whether its needed? 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; } #ifdef HAVE_CLIENT 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 = (modelindexterrain:NULL; #ifdef HAVE_SERVER 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). #else const qboolean ignore = false; #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 || cmd == hmcmd_patch_insert) //1=create/replace { brushes_t brush; size_t tempmemsize; hm = CL_BrushEdit_ForceContext(mod); //do this early, to ensure that the textures are correct memset(&brush, 0, sizeof(brush)); tempmemsize = Brush_DeserialiseHeader(&brush, (cmd == hmcmd_patch_insert)); if (!tempmemsize) Host_EndGame("CL_Parse_BrushEdit: unparsable %s\n", brush.ispatch?"patch":"brush"); if (!Brush_Deserialise(hm, &brush, alloca(tempmemsize))) Host_EndGame("CL_Parse_BrushEdit: unparsable %s\n", brush.ispatch?"patch":"brush"); if (!ignore) //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 || cmd == hmcmd_ent_remove) { //ent edit int id = MSG_ReadLong(); const char *data; int idx = mod->numentityinfo, i; if (cmd == hmcmd_ent_edit) data = MSG_ReadString(); else data = NULL; //convert id to idx for (i = 0; i < mod->numentityinfo; i++) { if (mod->entityinfo[i].id == id) { idx = i; break; } if (!mod->entityinfo[i].keyvals) idx = i; } //FIXME: cap the maximum data sizes (both count and storage, to prevent DOS attacks). if (idx == mod->numentityinfo && data) Z_ReallocElements((void**)&mod->entityinfo, &mod->numentityinfo, mod->numentityinfo+64, sizeof(*mod->entityinfo)); if (idx < mod->numentityinfo) { if (!ignore) { mod->entityinfo[idx].id = id; Z_Free(mod->entityinfo[idx].keyvals); if (data) mod->entityinfo[idx].keyvals = Z_StrDup(data); else mod->entityinfo[idx].keyvals = NULL; #ifdef CSQC_DAT CSQC_MapEntityEdited(modelindex, idx, data); #endif } } } else Host_EndGame("CL_Parse_BrushEdit: unknown command %i\n", cmd); } #endif #ifdef HAVE_SERVER 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); if (best->patch) { MSG_WriteByte(msg, hmcmd_patch_insert); Patch_Serialise(msg, best); } else { 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 = (modelindexterrain: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 || cmd == hmcmd_patch_insert) { brushes_t brush; size_t tempmemsize; memset(&brush, 0, sizeof(brush)); brush.ispatch = (cmd == hmcmd_patch_insert); tempmemsize = Brush_DeserialiseHeader(&brush, cmd == hmcmd_patch_insert); if (!tempmemsize) { Con_Printf("SV_Parse_BrushEdit: %s sent an abusive %s\n", host_client->name, brush.ispatch?"patch":"brush"); return false; } if (!Brush_Deserialise(hm, &brush, alloca(tempmemsize))) { 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, cmd); if (cmd == hmcmd_patch_insert) Patch_Serialise(&sv.multicast, &brush); else 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) { unsigned int 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"); //FIXME: undo the client's edit? or is that rude? return true; } //FIXME: need to update the server's entity list //SSQC_MapEntityEdited(idx, newvals); 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 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+(elementsize*elementcount) > (size_t)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; } // {"patch_getcp", PF_patch_getcp, 0, 0, 0, 0, D(qcpatchvert "int(float modelidx, int patchid, patchvert_t *out_controlverts, int maxcp, __out patchinfo_t out_info)", "Queries a patch's information. You must pre-allocate the face array for the builtin to write to. Return value is the total number of control verts that were retrieved, 0 on error.")}, void QCBUILTIN PF_patch_getcp(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 patchid = G_INT(OFS_PARM1); unsigned int maxverts = G_INT(OFS_PARM3); qcpatchvert_t *out_verts = validateqcpointer(prinst, G_INT(OFS_PARM2), sizeof(*out_verts), maxverts, true); qcpatchinfo_t *out_info = validateqcpointer(prinst, G_INT(OFS_PARM4), sizeof(*out_info), 1, true); unsigned int i, j; brushes_t *br; //assume the worst. G_INT(OFS_RETURN) = 0; if (out_info) memset(out_info, 0, sizeof(*out_info)); if (!hm) return; for (i = 0; i < hm->numbrushes; i++) { br = &hm->wbrushes[i]; if (br->id == patchid) { if (!br->patch) return; if (out_info) { out_info->contents = br->contents; out_info->cp_width = br->patch->numcp[0]; out_info->cp_height = br->patch->numcp[1]; out_info->subdiv_x = br->patch->subdiv[0]; out_info->subdiv_y = br->patch->subdiv[1]; out_info->shadername = PR_TempString(prinst, br->patch->tex->shadername); } if (!out_verts) G_INT(OFS_RETURN) = br->patch->numcp[0]*br->patch->numcp[1]; else { maxverts = min(br->patch->numcp[0]*br->patch->numcp[1], maxverts); for (j = 0; j < maxverts; j++) { VectorCopy(br->patch->cp[j].v, out_verts->v); Vector2Copy(br->patch->cp[j].tc, out_verts->tc); Vector4Copy(br->patch->cp[j].rgba, out_verts->rgba); out_verts++; } G_INT(OFS_RETURN) = j; } return; } } } // {"patch_evaluate", PF_patch_evaluate, 0, 0, 0, 0, D("int(patchvert_t *in_controlverts, patchvert_t *out_renderverts, int maxout, patchinfo_t *inout_info)", "Calculates the geometry of a hyperthetical patch.")}, void QCBUILTIN PF_patch_evaluate(pubprogfuncs_t *prinst, struct globalvars_s *pr_globals) { qcpatchinfo_t *inout_info = validateqcpointer(prinst, G_INT(OFS_PARM3), sizeof(*inout_info), 1, false); unsigned int maxverts = G_INT(OFS_PARM2); qcpatchvert_t *out_verts = validateqcpointer(prinst, G_INT(OFS_PARM1), sizeof(*out_verts), maxverts, true); qcpatchvert_t *in_cp = validateqcpointer(prinst, G_INT(OFS_PARM0), sizeof(*in_cp), inout_info->cp_width*inout_info->cp_height, false); unsigned short numcp[] = {inout_info->cp_width, inout_info->cp_height}, size[2]; short subdiv[] = {inout_info->subdiv_x, inout_info->subdiv_y}; patchtessvert_t *working_verts = PatchInfo_Evaluate(in_cp, numcp, subdiv, size); unsigned int i; if (working_verts) { if (out_verts) { maxverts = min(maxverts, size[0]*size[1]); for (i = 0; i < maxverts; i++) { VectorCopy(working_verts[i].v, out_verts[i].v); Vector4Copy(working_verts[i].rgba, out_verts[i].rgba); Vector2Copy(working_verts[i].tc, out_verts[i].tc); } } BZ_Free(working_verts); inout_info->cp_width = size[0]; //not really controlpoints, but the data works the same. inout_info->cp_height = size[1]; } else inout_info->cp_width = inout_info->cp_height = 0; //erk... inout_info->subdiv_x = inout_info->subdiv_y = 0; //make it as explicit tessellation, so we can maybe skip this. G_INT(OFS_RETURN) = maxverts; } // {"patch_getmesh", PF_patch_getmesh, 0, 0, 0, 0, D("int(float modelidx, int patchid, patchvert_t *out_verts, int maxverts, patchinfo_t *out_info)", "Queries a patch's information. You must pre-allocate the face array for the builtin to write to. Return value is the total number of control verts that were retrieved, 0 on error.")}, void QCBUILTIN PF_patch_getmesh(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 patchid = G_INT(OFS_PARM1); unsigned int maxverts = G_INT(OFS_PARM3); qcpatchvert_t *out_verts = validateqcpointer(prinst, G_INT(OFS_PARM2), sizeof(*out_verts), maxverts, true); qcpatchinfo_t *out_info = validateqcpointer(prinst, G_INT(OFS_PARM4), sizeof(*out_info), 1, true); unsigned int i, j; brushes_t *br; //assume the worst. G_INT(OFS_RETURN) = 0; if (out_info) memset(out_info, 0, sizeof(*out_info)); if (!hm) return; for (i = 0; i < hm->numbrushes; i++) { br = &hm->wbrushes[i]; if (br->id == patchid) { if (!br->patch) return; if (out_info) { out_info->contents = br->contents; out_info->cp_width = br->patch->tesssize[0]; out_info->cp_height = br->patch->tesssize[1]; out_info->subdiv_x = 0; out_info->subdiv_y = 0; out_info->shadername = PR_TempString(prinst, br->patch->tex->shadername); } if (!out_verts) G_INT(OFS_RETURN) = br->patch->tesssize[0]*br->patch->tesssize[1]; else { maxverts = min(br->patch->tesssize[0]*br->patch->tesssize[1], maxverts); for (j = 0; j < maxverts; j++) { VectorCopy(br->patch->tessvert[j].v, out_verts->v); Vector2Copy(br->patch->tessvert[j].tc, out_verts->tc); Vector4Copy(br->patch->tessvert[j].rgba, out_verts->rgba); out_verts++; } G_INT(OFS_RETURN) = j; } return; } } } // {"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 (br->patch) return; 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, optional int prevbrushid=0)", "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, numfaces==0); 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)) { #ifdef HAVE_SERVER 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 #ifdef HAVE_CLIENT 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; //these are for compat with (q2/)q3 so as to not be lossy even if they're not really used. faces[i].surfaceflags = 0; faces[i].surfacevalue = 0; } //now emit it brush.id = 0; brush.contents = contents; brush.numplanes = numfaces; brush.planes = planes; brush.faces = faces; brush.patch = NULL; if (numfaces) { nb = Terr_Brush_Insert(mod, hm, &brush); if (nb) { G_INT(OFS_RETURN) = nb->id; #ifdef HAVE_SERVER 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 #ifdef HAVE_CLIENT 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 } } } //{"patch_create", PF_patch_create, 0, 0, 0, 0, D("int(float modelidx, int oldpatchid, patchvert_t *in_controlverts, patchinfo_t in_info)", "Inserts a new patch into the model. Return value is the new patch's id.")}, void QCBUILTIN PF_patch_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 brushid = G_INT(OFS_PARM1); //to simplify edits qcpatchinfo_t *info = (qcpatchinfo_t*)&G_INT(OFS_PARM3); unsigned int totalcp = info->cp_width*info->cp_width; qcpatchvert_t *in_cverts = validateqcpointer(prinst, G_INT(OFS_PARM2), sizeof(*in_cverts), totalcp, false); unsigned int i; brushes_t brush, *nb; 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)) { #ifdef HAVE_SERVER 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 #ifdef HAVE_CLIENT 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 } brush.patch = alloca(sizeof(*brush.patch) + sizeof(brush.patch->cp[0])*(totalcp-countof(brush.patch->cp))); memset (brush.patch, 0, sizeof(*brush.patch) - sizeof(brush.patch->cp)); brush.patch->numcp[0] = info->cp_width; brush.patch->numcp[1] = info->cp_height; brush.patch->subdiv[0] = info->subdiv_x; brush.patch->subdiv[1] = info->subdiv_y; brush.patch->tex = Terr_Brush_FindTexture(hm, PR_GetString(prinst, info->shadername)); for (i = 0; i < totalcp; i++) { VectorCopy(in_cverts[i].v, brush.patch->cp[i].v); Vector2Copy(in_cverts[i].tc, brush.patch->cp[i].tc); Vector4Copy(in_cverts[i].rgba, brush.patch->cp[i].rgba); } //now emit it brush.id = 0; brush.contents = info->contents; brush.numplanes = 0; brush.planes = NULL; if (info->cp_width > 1 && info->cp_width > 1) { nb = Terr_Brush_Insert(mod, hm, &brush); if (nb) { G_INT(OFS_RETURN) = nb->id; #ifdef HAVE_SERVER if (sv.state && modelindex > 0) { MSG_WriteByte(&sv.multicast, svcfte_brushedit); MSG_WriteShort(&sv.multicast, modelindex); MSG_WriteByte(&sv.multicast, hmcmd_patch_insert); Patch_Serialise(&sv.multicast, nb); SV_MulticastProtExt(vec3_origin, MULTICAST_ALL_R, ~0, 0, 0); return; } #endif #ifdef HAVE_CLIENT if (cls.state && modelindex > 0) { MSG_WriteByte(&cls.netchan.message, clcfte_brushedit); MSG_WriteShort(&cls.netchan.message, modelindex); MSG_WriteByte(&cls.netchan.message, hmcmd_patch_insert); Patch_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); #ifdef HAVE_SERVER 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 #ifdef HAVE_CLIENT 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); 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) { if (br->patch) { br->patch->tex->rebuild = true; // br->patch->relight = true; } else { 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) { size_t faceid = G_INT(OFS_PARM0); size_t numfaces = G_INT(OFS_PARM2); qcbrushface_t *in_faces = validateqcpointer(prinst, G_INT(OFS_PARM1), sizeof(*in_faces), numfaces, false); size_t 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 ((size_t)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 (br->patch) { int w = br->patch->numcp[0]; int h = br->patch->numcp[1]; int x = faceid % (w-1); int y = faceid / (w-1); if (x >= w-1 || y >= h-1) break; if (maxpoints >= 1) VectorCopy(br->patch->cp[(x+0)+(y+0)*w].v, out_verts[0]); if (maxpoints >= 2) VectorCopy(br->patch->cp[(x+1)+(y+0)*w].v, out_verts[1]); if (maxpoints >= 3) VectorCopy(br->patch->cp[(x+1)+(y+1)*w].v, out_verts[2]); if (maxpoints >= 3) VectorCopy(br->patch->cp[(x+0)+(y+1)*w].v, out_verts[3]); p = min(4, maxpoints); } else { 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) { float *point[3]; int i, x, y; const qboolean valve220 = true; VFS_PRINTF(file, "\n{"); if (br->patch) { qboolean hasrgba = false; for (y = 0; y < br->patch->numcp[1]*br->patch->numcp[0]; y++) { if (br->patch->cp[y].rgba[0] != 1.0 || br->patch->cp[y].rgba[1] != 1.0 || br->patch->cp[y].rgba[2] != 1.0 || br->patch->cp[y].rgba[3] != 1.0) break; } hasrgba = (y < br->patch->numcp[1]*br->patch->numcp[0]); if (br->patch->subdiv[0]>=0 && br->patch->subdiv[1]>=0) { VFS_PRINTF(file, "\n\tpatchDef3%s\n\t{\n\t\t\"%s\"\n\t\t( %u %u %u %u %.9g %.9g %.9g )\n\t\t(\n", hasrgba?"WS":"", br->patch->tex?br->patch->tex->shadername:"", br->patch->numcp[0]/*width*/, br->patch->numcp[1]/*height*/, br->patch->subdiv[0]/*width*/, br->patch->subdiv[1]/*height*/, 0.0/*rotation*/, 1.0/*xscale*/, 1.0/*yscale*/); } else { VFS_PRINTF(file, "\n\tpatchDef2%s\n\t{\n\t\t\"%s\"\n\t\t( %u %u %.9g %.9g %.9g )\n\t\t(\n", hasrgba?"WS":"", br->patch->tex?br->patch->tex->shadername:"", br->patch->numcp[0]/*width*/, br->patch->numcp[1]/*height*/, 0.0/*rotation*/, 1.0/*xscale*/, 1.0/*yscale*/); } for (y = 0; y < br->patch->numcp[1]; y++) { VFS_PRINTF(file, "\t\t\t(\n"); for (x = 0; x < br->patch->numcp[0]; x++) { const char *fmt; if (hasrgba) fmt = "\t\t\t\t( %.9g %.9g %.9g %.9g %.9g %.9g %.9g %.9g %.9g )\n"; else fmt = "\t\t\t\t( %.9g %.9g %.9g %.9g %.9g )\n"; //q3 compat. VFS_PRINTF(file, fmt, br->patch->cp[x + y*br->patch->numcp[0]].v[0], br->patch->cp[x + y*br->patch->numcp[0]].v[1], br->patch->cp[x + y*br->patch->numcp[0]].v[2], br->patch->cp[x + y*br->patch->numcp[0]].tc[0], br->patch->cp[x + y*br->patch->numcp[0]].tc[1], br->patch->cp[x + y*br->patch->numcp[0]].rgba[0], br->patch->cp[x + y*br->patch->numcp[0]].rgba[1], br->patch->cp[x + y*br->patch->numcp[0]].rgba[2], br->patch->cp[x + y*br->patch->numcp[0]].rgba[3]); } VFS_PRINTF(file, "\t\t\t)\n"); } VFS_PRINTF(file, "\t\t)\n\t}\n"); } else { for (i = 0; i < br->numplanes; i++) { const char *texname, *s; point[0] = br->faces[i].points[0]; point[1] = br->faces[i].points[1]; point[2] = br->faces[i].points[2]; //valve 220 format: //(-0 -0 16) (-0 -0 32) (64 -0 16) texname [x y z d] [x y z d] rotation sscale tscale //don't treat whitespace as optional, even if it works with qbsp it'll screw up third party editors. //%.9g is 'meant' to be lossless for a standard ieee single-precision float. (%.17g for a double) //write the 3 points-on-plane. I really hope its not degenerate VFS_PRINTF(file, "\n( %.9g %.9g %.9g ) ( %.9g %.9g %.9g ) ( %.9g %.9g %.9g )", 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] ); //write the name - if it contains markup or control chars, or other weird glyphs then be sure to quote it. //we could unconditionally quote it, but that can and will screw up some editor somewhere (like trenchbroom...) for (s = texname = br->faces[i].tex?br->faces[i].tex->shadername:""; *s; s++) { if (*s <= 32 || *s >= 127 || *s == '\\' || *s == '(' || *s == '[' || *s == '{' || *s == ')' || *s == ']' || *s == '}') break; // } VFS_PRINTF(file, (!*texname || *s)?" \"%s\"":" %s", texname); if (valve220) { VFS_PRINTF(file, " [ %.9g %.9g %.9g %.9g ] [ %.9g %.9g %.9g %.9g ] 0 1 1", 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] ); } else { float soffset, toffset, rotation, sscale, tscale; //FIXME: project onto the axial plane, then figure out new values. soffset = toffset = 0; rotation = 0; sscale = tscale = 1; VFS_PRINTF(file, " %.9g %.9g %.9g %.9g %.9g", soffset, toffset, rotation, sscale, tscale); } //historical note: Q2 used contents|surfaceflags|value. // however, Q3 uses the contents value exclusively for a detail flag. everything else comes from shaders. if (br->contents != FTECONTENTS_SOLID || br->faces[i].surfaceflags || br->faces[i].surfacevalue) VFS_PRINTF(file, " %i %i %i", br->contents, br->faces[i].surfaceflags, br->faces[i].surfacevalue); // else if (hexen2) // VFS_PRINTF(file, " -1"); //Light } } 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->legacyterrain) 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)); } if (entities) 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)); #ifdef HAVE_CLIENT 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, mod_modifier, sizeof(fname)); Q_strncatz(fname, ".ent", sizeof(fname)); FS_CreatePath(fname, FS_GAMEONLY); file = FS_OpenVFS(fname, "wb", FS_GAMEONLY); if (!file) Con_TPrintf("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_TPrintf("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 foundsubmodel = false; qboolean inbrush = false; int brushcontents = FTECONTENTS_SOLID; heightmap_t *subhm = NULL; model_t *submod = NULL; const char *brushpunct = "(){}[]"; //use an empty string for better compat with vanilla qbsp... //brush planes int numplanes = 0; vec4_t planes[256]; struct brushface_s faces[countof(planes)]; //patch info brushtex_t *patch_tex=NULL; int patchsz[2]={0,0}, patchsubdiv[2]={-1,-1}; qcpatchvert_t patch_v[64][64]; #ifdef RUNTIMELIGHTING hm->entsdirty = true; hm->relightcontext = mod_terrain_brushlights.ival?LightStartup(NULL, mod, mod_terrain_brushlights.ival>1, false):NULL; 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_ParseTokenOut(entities, brushpunct, token, sizeof(token), NULL); if (!entities) { if (inbrush || nest) { Con_Printf(CON_ERROR "%s: File truncated?\n", mod->name); return false; } break; } else if (token[0] == '}' && token[1] == 0) { nest--; if (inbrush) { if (subhm) { qboolean oe = subhm->brushesedited; if (numplanes) { brushes_t brush; //finish the brush brush.contents = brushcontents; brush.numplanes = numplanes; brush.planes = planes; brush.faces = faces; brush.id = 0; brush.patch = NULL; Terr_Brush_Insert(submod, subhm, &brush); } else if (patch_tex) Terr_Patch_Insert(submod, subhm, patch_tex, patchsz[0], patchsz[1], patchsubdiv[0], patchsubdiv[1], patch_v[0], countof(patch_v[0])); subhm->brushesedited = oe; } numplanes = 0; inbrush = false; patch_tex = NULL; brushcontents = FTECONTENTS_SOLID; continue; } } else if (token[0] == '{' && token[1] == 0) { nest++; if (nest == 1) { //entering a new entity foundsubmodel = false; } if (nest == 2) { if (!foundsubmodel) { foundsubmodel = true; if (submodelnum) { Q_snprintfz(token, sizeof(token), "*%i", submodelnum); *out++ = '\n'; *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; #ifdef HAVE_SERVER submod->funcs.FindTouchedLeafs = Heightmap_FindTouchedLeafs; submod->funcs.EdictInFatPVS = Heightmap_EdictInFatPVS; submod->funcs.FatPVS = Heightmap_FatPVS; #endif submod->loadstate = MLS_LOADED; submod->pvsbytes = sizeof(hmpvs_t); #ifdef RUNTIMELIGHTING if (hm->relightcontext) 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 (!nest) { Con_Printf(CON_ERROR "%s: junk found\n", mod->name); return false; } else if (inbrush && (!strcmp(token, "patchDef2") || !strcmp(token, "patchDef3") || !strcmp(token, "patchDef2WS") || !strcmp(token, "patchDef3WS"))) { int x, y; qboolean patchdef3 = !!strchr(token, '3'); //explict tessellation info (doom3-like) qboolean parsergba = !!strstr(token, "WS"); //fancy alternative with rgba colours per control point patchsz[0] = patchsz[1] = 0; patchsubdiv[0] = patchsubdiv[1] = -1; if (numplanes || patch_tex) { Con_Printf(CON_ERROR "%s: mixed patch+planes\n", mod->name); return false; } memset(patch_v, 0, sizeof(patch_v)); entities = COM_ParseTokenOut(entities, brushpunct, token, sizeof(token), NULL); if (strcmp(token, "{")) {Con_Printf(CON_ERROR "%s: invalid patch\n", mod->name);return false;} entities = COM_ParseTokenOut(entities, brushpunct, token, sizeof(token), NULL); /*parse texture name*/ patch_tex = Terr_Brush_FindTexture(subhm, token); entities = COM_ParseTokenOut(entities, brushpunct, token, sizeof(token), NULL); if (strcmp(token, "(")) {Con_Printf(CON_ERROR "%s: invalid patch\n", mod->name);return false;} entities = COM_ParseTokenOut(entities, brushpunct, token, sizeof(token), NULL); /*patch_w = atof(token);*/ entities = COM_ParseTokenOut(entities, brushpunct, token, sizeof(token), NULL); /*patch_h = atof(token);*/ if (patchdef3) { entities = COM_ParseTokenOut(entities, brushpunct, token, sizeof(token), NULL); patchsubdiv[0] = atof(token); entities = COM_ParseTokenOut(entities, brushpunct, token, sizeof(token), NULL); patchsubdiv[1] = atof(token); } entities = COM_ParseTokenOut(entities, brushpunct, token, sizeof(token), NULL); /*rotation = atof(token);*/ entities = COM_ParseTokenOut(entities, brushpunct, token, sizeof(token), NULL); /*xscale = atof(token);*/ entities = COM_ParseTokenOut(entities, brushpunct, token, sizeof(token), NULL); /*yscale = atof(token);*/ entities = COM_ParseTokenOut(entities, brushpunct, token, sizeof(token), NULL); if (strcmp(token, ")")) {Con_Printf(CON_ERROR "%s: invalid patch\n", mod->name);return false;} entities = COM_ParseTokenOut(entities, brushpunct, token, sizeof(token), NULL); if (strcmp(token, "(")) {Con_Printf(CON_ERROR "%s: invalid patch\n", mod->name);return false;} entities = COM_ParseTokenOut(entities, brushpunct, token, sizeof(token), NULL); y = 0; while (!strcmp(token, "(")) { entities = COM_ParseTokenOut(entities, brushpunct, token, sizeof(token), NULL); x = 0; while (!strcmp(token, "(")) { entities = COM_ParseTokenOut(entities, brushpunct, token, sizeof(token), NULL); patch_v[y][x].v[0] = atof(token); entities = COM_ParseTokenOut(entities, brushpunct, token, sizeof(token), NULL); patch_v[y][x].v[1] = atof(token); entities = COM_ParseTokenOut(entities, brushpunct, token, sizeof(token), NULL); patch_v[y][x].v[2] = atof(token); entities = COM_ParseTokenOut(entities, brushpunct, token, sizeof(token), NULL); patch_v[y][x].tc[0] = atof(token); entities = COM_ParseTokenOut(entities, brushpunct, token, sizeof(token), NULL); patch_v[y][x].tc[1] = atof(token); if (parsergba) { //the following four lines are stupid. entities = COM_ParseTokenOut(entities, brushpunct, token, sizeof(token), NULL); if (strcmp(token, ")")) {Con_Printf(CON_ERROR "%s: invalid patch\n", mod->name);return false;} entities = COM_ParseTokenOut(entities, brushpunct, token, sizeof(token), NULL); if (strcmp(token, "(")) {Con_Printf(CON_ERROR "%s: invalid patch\n", mod->name);return false;} entities = COM_ParseTokenOut(entities, brushpunct, token, sizeof(token), NULL); patch_v[y][x].rgba[0] = atof(token); entities = COM_ParseTokenOut(entities, brushpunct, token, sizeof(token), NULL); patch_v[y][x].rgba[1] = atof(token); entities = COM_ParseTokenOut(entities, brushpunct, token, sizeof(token), NULL); patch_v[y][x].rgba[2] = atof(token); entities = COM_ParseTokenOut(entities, brushpunct, token, sizeof(token), NULL); patch_v[y][x].rgba[3] = atof(token); } else { //no data provided, use default values. patch_v[y][x].rgba[0] = patch_v[y][x].rgba[1] = patch_v[y][x].rgba[2] = patch_v[y][x].rgba[3] = 1.0; } entities = COM_ParseTokenOut(entities, brushpunct, token, sizeof(token), NULL); if (strcmp(token, ")")) {Con_Printf(CON_ERROR "%s: invalid patch\n", mod->name);return false;} entities = COM_ParseTokenOut(entities, brushpunct, token, sizeof(token), NULL); if (x < countof(patch_v[y])-1) x++; } if (patchsz[0] < x) patchsz[0] = x; if (strcmp(token, ")")) {Con_Printf(CON_ERROR "%s: invalid patch\n", mod->name);return false;} entities = COM_ParseTokenOut(entities, brushpunct, token, sizeof(token), NULL); if (y < countof(patch_v)-1) y++; } patchsz[1] = y; if (strcmp(token, ")")) {Con_Printf(CON_ERROR "%s: invalid patch\n", mod->name);return false;} entities = COM_ParseTokenOut(entities, brushpunct, token, sizeof(token), NULL); if (strcmp(token, "}")) {Con_Printf(CON_ERROR "%s: invalid patch\n", mod->name);return false;} continue; } else if (inbrush) { //parse a plane //Quake: ( -0 -0 16 ) ( -0 -0 32 ) ( 64 -0 16 ) texname soffset toffset rotation sscale tscale //Hexen2: ( -0 -0 16 ) ( -0 -0 32 ) ( 64 -0 16 ) texname soffset toffset rotation sscale tscale surfvalue //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 [x y z d] [x y z d] rotation sscale tscale contents surfflags surfvalue //Quake2: ( -0 -0 16 ) ( -0 -0 32 ) ( 64 -0 16 ) texname soffset toffset rotation sscale tscale contents surfflags surfvalue //Quake3: ( -0 -0 16 ) ( -0 -0 32 ) ( 64 -0 16 ) texname soffset toffset rotation sscale tscale detailfl surfflags surfvalue //Q3 BP: brushDef { ( -0 -0 16 ) ( -0 -0 32 ) ( 64 -0 16 ) ( ( x y o ) ( x y o ) ) texname detailfl surfflags surfvalue } //generate tangent+bitangent from the normal to generate base texcoords, then transform by the given 2*3 matrix. I prefer valve's way - it rotates more cleanly. //Doom3: brushDef3 { ( px py pz pd ) ( ( x y o ) ( x y o ) ) texname detailfl surfflags surfvalue } //hexen2's extra surfvalue is completely unused, and should normally be -1 //q3 ignores all contents except detail, as well surfaceflags and surfacevalue //220 ignores rotation, provided only for UI info, scale is still used //we don't care whether the input is planes or points. //if we get a [ instead of an soffset then its brushtex_t *bt; vec3_t d1,d2; vec3_t points[3]; vec4_t texplane[2]; float scale[2], rot; int p; enum { TEXTYPE_AXIAL, //urgh TEXTYPE_PLANES, TEXTYPE_BP, //weird 2d planes } textype = TEXTYPE_AXIAL; memset(points, 0, sizeof(points)); if (patch_tex) { Con_Printf(CON_ERROR "%s: mixed patch+planes\n", mod->name); return false; } for (p = 0; p < 3; p++) { if (token[0] != '(' || token[1] != 0) break; entities = COM_ParseTokenOut(entities, brushpunct, token, sizeof(token), NULL); points[p][0] = atof(token); entities = COM_ParseTokenOut(entities, brushpunct, token, sizeof(token), NULL); points[p][1] = atof(token); entities = COM_ParseTokenOut(entities, brushpunct, token, sizeof(token), NULL); points[p][2] = atof(token); entities = COM_ParseTokenOut(entities, brushpunct, token, sizeof(token), NULL); if (token[0] != ')' || token[1] != 0) { // VectorClear(points[1]); // VectorClear(points[2]); points[1][0] = atof(token); entities = COM_ParseTokenOut(entities, brushpunct, token, sizeof(token), NULL); if (p == 0 && !strcmp(token, ")")) p = 4; //we just managed to read an entire plane instead of 3 points. break; } entities = COM_ParseTokenOut(entities, "()", token, sizeof(token), NULL); } 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; } if (token[0] == '(') { textype = TEXTYPE_BP; entities = COM_ParseTokenOut(entities, brushpunct, token, sizeof(token), NULL); if (token[0] == '(') { entities = COM_ParseTokenOut(entities, brushpunct, token, sizeof(token), NULL); texplane[0][0] = atof(token); entities = COM_ParseTokenOut(entities, brushpunct, token, sizeof(token), NULL); texplane[0][1] = atof(token); entities = COM_ParseTokenOut(entities, brushpunct, token, sizeof(token), NULL); texplane[0][3] = atof(token); entities = COM_ParseTokenOut(entities, brushpunct, token, sizeof(token), NULL); if (token[0] != ')') return false; } entities = COM_ParseTokenOut(entities, brushpunct, token, sizeof(token), NULL); if (token[0] == '(') { entities = COM_ParseTokenOut(entities, brushpunct, token, sizeof(token), NULL); texplane[1][0] = atof(token); entities = COM_ParseTokenOut(entities, brushpunct, token, sizeof(token), NULL); texplane[1][1] = atof(token); entities = COM_ParseTokenOut(entities, brushpunct, token, sizeof(token), NULL); texplane[1][3] = atof(token); entities = COM_ParseTokenOut(entities, brushpunct, token, sizeof(token), NULL); if (token[0] != ')') return false; } entities = COM_ParseTokenOut(entities, brushpunct, token, sizeof(token), NULL); if (token[0] != ')') return false; } bt = Terr_Brush_FindTexture(subhm, token); if (textype != TEXTYPE_BP) { //halflife/valve220 format has the entire [x y z dist] plane specified. entities = COM_ParseTokenOut(entities, brushpunct, token, sizeof(token), NULL); if (*token == '[') { textype = TEXTYPE_PLANES; entities = COM_ParseTokenOut(entities, brushpunct, token, sizeof(token), NULL); texplane[0][0] = atof(token); entities = COM_ParseTokenOut(entities, brushpunct, token, sizeof(token), NULL); texplane[0][1] = atof(token); entities = COM_ParseTokenOut(entities, brushpunct, token, sizeof(token), NULL); texplane[0][2] = atof(token); entities = COM_ParseTokenOut(entities, brushpunct, token, sizeof(token), NULL); texplane[0][3] = atof(token); entities = COM_ParseTokenOut(entities, brushpunct, token, sizeof(token), NULL); //] entities = COM_ParseTokenOut(entities, brushpunct, token, sizeof(token), NULL); //[ entities = COM_ParseTokenOut(entities, brushpunct, token, sizeof(token), NULL); texplane[1][0] = atof(token); entities = COM_ParseTokenOut(entities, brushpunct, token, sizeof(token), NULL); texplane[1][1] = atof(token); entities = COM_ParseTokenOut(entities, brushpunct, token, sizeof(token), NULL); texplane[1][2] = atof(token); entities = COM_ParseTokenOut(entities, brushpunct, token, sizeof(token), NULL); texplane[1][3] = atof(token); entities = COM_ParseTokenOut(entities, brushpunct, token, sizeof(token), NULL); //] } else { //vanilla quake VectorClear(texplane[0]); VectorClear(texplane[1]); texplane[0][3] = atof(token); //aka soffset entities = COM_ParseTokenOut(entities, brushpunct, token, sizeof(token), NULL); texplane[1][3] = atof(token); //aka toffset } entities = COM_ParseTokenOut(entities, brushpunct, token, sizeof(token), NULL); rot = atof(token); entities = COM_ParseTokenOut(entities, brushpunct, token, sizeof(token), NULL); scale[0] = atof(token); entities = COM_ParseTokenOut(entities, brushpunct, token, sizeof(token), NULL); scale[1] = atof(token); } else rot = 0, scale[0] = 1, scale[1] = 1; //hexen2 has some extra junk that is useless - some 'light' value, but its never used and should normally be -1. //quake2/3 on the other hand has 3 different args. Contents SurfaceFlags SurfaceValue. //the SurfaceFlags and SurfaceVales are no longer used in q3 (shaders do it all), but contents is still partially used. //The contents conveys only CONTENTS_DETAIL. which is awkward as it varies somewhat by game, but we assume q2/q3. faces[numplanes].surfaceflags = 0; faces[numplanes].surfacevalue = 0; while (*entities == ' ' || *entities == '\t') entities++; if (*entities == '-' || (*entities >= '0' && *entities <= '9')) { int ex1, ex2 = 0, ex3 = 0; entities = COM_ParseTokenOut(entities, brushpunct, token, sizeof(token), NULL); ex1 = atoi(token); while (*entities == ' ' || *entities == '\t') entities++; if (*entities == '-' || (*entities >= '0' && *entities <= '9')) { entities = COM_ParseTokenOut(entities, brushpunct, token, sizeof(token), NULL); ex2 = atoi(token); } while (*entities == ' ' || *entities == '\t') entities++; if (*entities == '-' || (*entities >= '0' && *entities <= '9')) { entities = COM_ParseTokenOut(entities, brushpunct, token, sizeof(token), NULL); ex3 = atoi(token); //if we got this far, then its q3 format. //q3 is weird. the first extra arg is contents. but only the detail contents is used. //ex1 &= Q3CONTENTS_DETAIL; brushcontents |= ex1; //propagate these, in case someone tries editing a q2bsp. faces[numplanes].surfaceflags = ex2; faces[numplanes].surfacevalue = ex3; } } //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; /* shader_t *shader = R_RegisterCustom(NULL, bt->shadername, SUF_LIGHTMAP, NULL, NULL); if (shader) { brushcontents &= Q3CONTENTS_DETAIL; brushcontents |= shader->contentbits&~Q3CONTENTS_DETAIL; faces[numplanes].surfaceflags = shader->surfacebits; } else */ if (bt && !numplanes) { if (*bt->shadername == '*') { if (!Q_strncasecmp(bt->shadername, "*lava", 5)) brushcontents |= FTECONTENTS_LAVA; else if (!Q_strncasecmp(bt->shadername, "*slime", 5)) brushcontents |= FTECONTENTS_SLIME; else brushcontents |= FTECONTENTS_WATER; } else if (!Q_strncasecmp(bt->shadername, "*sky", 4)) brushcontents |= FTECONTENTS_SKY; else if (!Q_strcasecmp(bt->shadername, "clip")) brushcontents |= FTECONTENTS_PLAYERCLIP|FTECONTENTS_MONSTERCLIP; else if (!Q_strcasecmp(bt->shadername, "hint")) brushcontents |= 0; else if (!Q_strcasecmp(bt->shadername, "skip")) //skip should not force content values if paired with lava etc. ;//brushcontents = 0; else brushcontents |= FTECONTENTS_SOLID; } if (textype == TEXTYPE_BP) { float *norm = planes[numplanes]; float RotY = -atan2(norm[2], sqrt(norm[1]*norm[1] + norm[0]*norm[0])); float RotZ = atan2(norm[1], norm[0]); vec3_t tx = {-sin(RotZ), cos(RotZ), 0}; //tangent vec3_t ty = {-sin(RotY)*cos(RotZ), -sin(RotY)*sin(RotZ), -cos(RotY)}; //bitangent vec2_t tms = {texplane[0][0],texplane[0][1]}, tmt = {texplane[1][0],texplane[1][1]}; //bah, locals reuse suck texplane[0][0] = (tx[0] * tms[0]) + (ty[0] * tms[1]); //multiply out some matricies texplane[0][1] = (tx[1] * tms[0]) + (ty[1] * tms[1]); texplane[0][2] = (tx[2] * tms[0]) + (ty[2] * tms[1]); texplane[1][0] = (tx[0] * tmt[0]) + (ty[0] * tmt[1]); texplane[1][1] = (tx[1] * tmt[0]) + (ty[1] * tmt[1]); texplane[1][2] = (tx[2] * tmt[0]) + (ty[2] * tmt[1]); //scale is part of the matrix. scale[0] = 1; scale[1] = 1; //FIXME: these faces should NOT be scaled by the texture's size! } else if (textype == TEXTYPE_PLANES) ;//texture planes were properly loaded above (the scaling below is still needed though). else if (textype == TEXTYPE_AXIAL) { //quake's .maps use the normal to decide which texture directions to use in some lame axially-aligned way. 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_ParseTokenOut(entities, brushpunct, token, sizeof(token), NULL); else*/ entities = COM_ParseTokenOut(entities, brushpunct, token, sizeof(token), NULL); } 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 legacyterrain; heightmap_t *hm; char token[MAX_QPATH]; int sectsize = 0; char *src; src = COM_ParseOut(buffer, token, sizeof(token)); if (!strcmp(token, "terrain")) { legacyterrain = true; buffer = src; } else if (!strcmp(token, "{")) legacyterrain = false; 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 (legacyterrain) { 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->legacyterrain = legacyterrain; if (legacyterrain) hm->exteriorcontents = FTECONTENTS_SOLID; //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; #ifdef HAVE_SERVER 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->pvsbytes = sizeof(hmpvs_t); 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; } #ifdef HAVE_CLIENT #if 0 //not yet ready struct ted_import_s { size_t x, y; size_t width; size_t height; unsigned short *data; }; //static void ted_itterate(heightmap_t *hm, int distribution, float *pos, float radius, float strength, int steps, static void ted_import_heights_r16(void *vctx, hmsection_t *s, int idx, float wx, float wy, float strength) { struct ted_import_s *ctx = vctx; unsigned int y = idx/SECTHEIGHTSIZE; unsigned int x = idx%SECTHEIGHTSIZE; x += s->sx*(SECTHEIGHTSIZE-1) - ctx->x; y += s->sy*(SECTHEIGHTSIZE-1) - ctx->y; if (x >= ctx->width || y >= ctx->height) return; s->flags |= TSF_NOTIFY|TSF_EDITED|TSF_DIRTY|TSF_RELIGHT; s->heights[idx] = ctx->data[x + y*ctx->width] * (8192.0/(1<<16)); } static void Mod_Terrain_Import_f(void) { model_t *mod; struct ted_import_s ctx; const char *mapname = Cmd_Argv(1); const char *filename; size_t fsize; heightmap_t *hm; vec3_t pos = {0}; if (Cmd_IsInsecure()) { Con_Printf("Please use this command via the console\n"); return; } if (*mapname) mod = NULL;//Mod_FindName(va("maps/%s", mapname)); else mod = cl.worldmodel; if (!mod || mod->type == mod_dummy) return; hm = mod->terrain; if (!hm) return; fsize = 0; filename = va("maps/%s.r16", mapname); ctx.data = (void*)FS_LoadMallocFile(filename, &fsize); if (!ctx.data) { Con_Printf("Unable to read %s\n", filename); return; } ctx.width = ctx.height = sqrt(fsize/2); ctx.x = 0; ctx.y = 0; pos[0] += hm->sectionsize * CHUNKBIAS; pos[1] += hm->sectionsize * CHUNKBIAS; if (fsize == ctx.width*ctx.height*2) ted_itterate(hm, tid_flat, pos, max(ctx.width, ctx.height), 1, SECTHEIGHTSIZE, ted_import_heights_r16, &ctx); FS_FreeFile(ctx.data); } static void Mod_Terrain_Export_f(void) { model_t *mod; struct ted_import_s ctx; char mapname[MAX_QPATH]; const char *filename; heightmap_t *hm; size_t w, h; size_t tx, ty; size_t sx, sy; unsigned int outtilex=0,outtiley=0; qboolean populated; if (Cmd_IsInsecure()) { Con_Printf("Please use this command via the console\n"); return; } if (*Cmd_Argv(1)) mod = NULL;//Mod_FindName(va("maps/%s", mapname)); else mod = cl.worldmodel; if (!mod || mod->type == mod_dummy) return; hm = mod->terrain; if (!hm) return; COM_StripExtension(mod->name, mapname, sizeof(mapname)); ctx.x = hm->firstsegx * (SECTHEIGHTSIZE-1); w = (hm->maxsegx-hm->firstsegx) * (SECTHEIGHTSIZE-1) + 1; while(w) { ctx.width = w; if (ctx.width > 2048+1) ctx.width = 2048; outtiley = 0; ctx.y = hm->firstsegy * (SECTHEIGHTSIZE-1); h = (hm->maxsegy-hm->firstsegy) * (SECTHEIGHTSIZE-1) + 1; while(h) { ctx.height = h; if (ctx.height > 2048+1) ctx.height = 2048; populated = false; ctx.data = Z_Malloc(ctx.width*ctx.height*2); for (sy = ctx.y/(SECTHEIGHTSIZE-1); sy < (ctx.y+ctx.height + SECTHEIGHTSIZE-3)/(SECTHEIGHTSIZE-1); sy++) for (sx = ctx.x/(SECTHEIGHTSIZE-1); sx < (ctx.x+ctx.width + SECTHEIGHTSIZE-3)/(SECTHEIGHTSIZE-1); sx++) { hmsection_t *s = Terr_GetSection(hm, sx, sy, TGS_WAITLOAD|TGS_ANYSTATE); if (s->loadstate == TSLS_FAILED) { //we're doing this weirdly so we can destroy sections as we go. Terr_DestroySection(hm, s, true); s = NULL; } if (s) { populated = true; for (ty = 0; ty < SECTHEIGHTSIZE; ty++) { size_t y = sy*(SECTHEIGHTSIZE-1)+ty - ctx.y; if (y >= ctx.height) continue; for (tx = 0; tx < SECTHEIGHTSIZE; tx++) { size_t x = sx*(SECTHEIGHTSIZE-1)+tx - ctx.x; if (x >= ctx.width) continue; ctx.data[x + y*ctx.width] = s->heights[tx+y*SECTHEIGHTSIZE] / (8192.0/(1<<16)); } } if (!(s->flags & TSF_EDITED)) Terr_DestroySection(hm, s, true); } else { for (ty = 0; ty < SECTHEIGHTSIZE; ty++) { size_t y = sy*(SECTHEIGHTSIZE-1)+ty - ctx.y; if (y >= ctx.height) continue; for (tx = 0; tx < SECTHEIGHTSIZE; tx++) { size_t x = sx*(SECTHEIGHTSIZE-1)+tx - ctx.x; if (x >= ctx.width) continue; ctx.data[x + y*ctx.width] = hm->defaultgroundheight / (8192.0/(1<<16)); } } } } filename = va("%s/x%u_y%u.r16", mapname, outtilex, outtiley); if (populated) { if (FS_WriteFile(filename, ctx.data, ctx.width*ctx.height*2, FS_GAMEONLY)) { char sysname[1024]; FS_NativePath(filename, FS_GAMEONLY, sysname, sizeof(sysname)); Con_Printf("Wrote %s\n", sysname); } else Con_Printf("Unable to write %s\n", filename); } else Con_Printf("Skipping unpopulated %s\n", filename); Z_Free(ctx.data); outtiley++; ctx.y += ctx.height; h -= ctx.height; } outtilex++; ctx.x += ctx.width; w -= ctx.width; } } #endif 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; char *seed; 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 seed\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; } mapdesc = Cmd_Argv(2); if (!*mapdesc) mapdesc = Cmd_Argv(1); skyname = Cmd_Argv(3); groundname = Cmd_Argv(4); groundheight = Cmd_Argv(5); watername = Cmd_Argv(6); waterheight = Cmd_Argv(7); seed = Cmd_Argv(7); Mod_SetEntitiesString(&mod, va( "{\n" "classname \"worldspawn\"\n" "message \"%s\"\n" "_sky \"%s\"\n" "_fog 0.02\n" "_maxdrawdist 0 /*overrides fog distance (if greater)*/\n" "_segmentsize 1024 /*how big each section is. this affects texturing and resolutions*/\n" "_minxsegment -2048\n" "_minysegment -2048\n" "_maxxsegment 2048\n" "_maxysegment 2048\n" "_seed \"%s\" /*for auto-gen plugins*/\n" "_exterior solid\n" "_defaultgroundtexture \"%s\"\n" "_defaultgroundheight \"%s\"\n" "_defaultwatertexture \"%s\"\n" "_defaultwaterheight \"%s\"\n" //hurrah, sea level. // "_tiles 64 64 8 8\n" "}\n" "{\n" "classname info_player_start\n" "origin \"0 0 1024\" /*EDITME*/\n" "}\n" "/*ADD EXTRA ENTITIES!*/\n" , mapdesc ,*skyname?skyname:"terrsky1", seed ,*groundname?groundname:"ground1_1" ,*groundheight?groundheight:"-1024" ,*watername?watername:"*water2" ,*waterheight?waterheight:"0" ), 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); } } mname = va("maps/%s.hmp", Cmd_Argv(1)); 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_TPrintf("unable to open %s\n", mname); else { Terr_WriteMapFile(file, &mod); VFS_CLOSE(file); Con_TPrintf("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... #ifdef HAVE_CLIENT 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))); #ifdef HAVE_CLIENT 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); } plugterrainfuncs_t *Terr_GetTerrainFuncs(size_t structsize) { if (structsize != sizeof(plugterrainfuncs_t)) return NULL; #ifdef HAVE_CLIENT return &terrainfuncs; #else return NULL; //dedicated server builds have all the visual stuff stripped, which makes APIs too inconsistent. Generate then save. Or fix up the API... #endif } 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); #ifdef HAVE_CLIENT // Cmd_AddCommandD("mod_terrain_export", Mod_Terrain_Export_f, "Export a raw heightmap"); // Cmd_AddCommandD("mod_terrain_import", Mod_Terrain_Import_f, "Import a raw heightmap"); 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."); Cvar_Register(&mod_terrain_sundir, "Terrain"); Cvar_Register(&mod_terrain_ambient, "Terrain"); Cvar_Register(&mod_terrain_shadows, "Terrain"); Cvar_Register(&mod_terrain_shadow_dist, "Terrain"); Cvar_Register(&mod_terrain_brushlights, "Terrain"); Cvar_Register(&mod_terrain_brushtexscale, "Terrain"); #endif Mod_RegisterModelFormatText(NULL, "FTE Heightmap Map (hmp)", "terrain", Terr_LoadTerrainModel); Mod_RegisterModelFormatText(NULL, "Quake Map Format (map)", "{", Terr_LoadTerrainModel); } #endif