Example #1
0
/**
 * @brief Add a callback of a function into a node event. There can be more than on listener.
 * @param[in,out] node The node to add the listener to.
 * @param[in] property The property of the node to add the listener to.
 * @param[in] functionNode The node of the listener callback.
 */
void UI_AddListener (uiNode_t *node, const value_t *property, const uiNode_t *functionNode)
{
	uiAction_t *lastAction;
	uiAction_t *action;
	uiAction_t *value;

	if (node->dynamic) {
		Com_Printf("UI_AddListener: '%s' is a dynamic node. We can't listen it.\n", UI_GetPath(node));
		return;
	}
	if (functionNode->dynamic) {
		Com_Printf("UI_AddListener: '%s' is a dynamic node. It can't be a listener callback.\n", UI_GetPath(functionNode));
		return;
	}

	/* create the call action */
	action = (uiAction_t*) Mem_PoolAlloc(sizeof(*action), ui_sysPool, 0);
	value = (uiAction_t*) Mem_PoolAlloc(sizeof(*action), ui_sysPool, 0);
	value->d.terminal.d1.constString = Mem_PoolStrDup(UI_GetPath(functionNode), ui_sysPool, 0);
	value->next = NULL;
	action->type = EA_LISTENER;
	action->d.nonTerminal.left = value;
	/** @todo It is a hack, we should remove that */
	action->d.terminal.d2.constData = &functionNode->onClick;
	action->next = NULL;

	/* insert the action */
	lastAction = *(uiAction_t**)((char*)node + property->ofs);
	if (lastAction) {
		while (lastAction->next)
			lastAction = lastAction->next;
		lastAction->next = action;
	} else
		*(uiAction_t**)((char*)node + property->ofs) = action;
}
Example #2
0
/**
 * @sa R_ModBeginLoading
 * @sa R_EndBuildingLightmaps
 */
void R_BeginBuildingLightmaps (void)
{
	static qboolean gotAllocatedLightmaps = qfalse;

	if (gotAllocatedLightmaps)
		R_DisposeLightmaps();

	gotAllocatedLightmaps = qtrue;

	/* users can tune lightmap size for their card */
	r_lightmaps.size = r_maxlightmap->integer;

	r_lightmaps.allocated = (unsigned *)Mem_PoolAlloc(r_lightmaps.size *
		sizeof(unsigned), vid_lightPool, 0);

	r_lightmaps.sample_buffer = (byte *)Mem_PoolAlloc(
		r_lightmaps.size * r_lightmaps.size * sizeof(unsigned), vid_lightPool, 0);

	r_lightmaps.direction_buffer = (byte *)Mem_PoolAlloc(
		r_lightmaps.size * r_lightmaps.size * sizeof(unsigned), vid_lightPool, 0);

	r_lightmaps.lightmap_count = 0;
	r_lightmaps.deluxemap_count = 0;
	r_lightmaps.incomplete_atlas = qfalse;
}
Example #3
0
/**
 * @brief Parse all language definitions from the script files
 */
void CL_ParseLanguages (const char *name, const char **text)
{
	const char *errhead = "CL_ParseLanguages: unexpected end of file (language ";
	const char	*token;
	language_t *language = NULL;
	localeMapping_t *mapping = NULL;

	if (!*text) {
		Com_Printf("CL_ParseLanguages: language without body ignored (%s)\n", name);
		return;
	}

	token = Com_EParse(text, errhead, name);
	if (!*text || *token != '{') {
		Com_Printf("CL_ParseLanguages: language without body ignored (%s)\n", name);
		return;
	}

	do {
		/* get the name type */
		token = Com_EParse(text, errhead, name);
		if (!*text || *token == '}')
			break;
		/* inner locale id definition */
		if (*token == '{') {
			if (!language) {
				Com_Printf("CL_ParseLanguages: language: '%s' - found mappings with language string - ignore it\n", name);
				return;
			}
			do {
				/* get the locale mappings now type */
				token = Com_EParse(text, errhead, name);
				/* end of locale mappings reached */
				if (!*text || *token == '}')
					break;
				mapping = (localeMapping_t *)Mem_PoolAlloc(sizeof(*mapping), cl_genericPool, 0);
				mapping->localeMapping = Mem_PoolStrDup(token, cl_genericPool, 0);
				/* link it in */
				mapping->next = language->localeMapping;
				language->localeMapping = mapping;
			} while (*text);
			language = NULL;
		} else {
			if (*token != '_') {
				Com_Printf("CL_ParseLanguages: language: '%s' - not marked translatable (%s) - ignore it\n", name, token);
				continue;
			}
			language = (language_t *)Mem_PoolAlloc(sizeof(*language), cl_genericPool, 0);
			language->localeID = Mem_PoolStrDup(name, cl_genericPool, 0);
			language->localeString = Mem_PoolStrDup(token + 1, cl_genericPool, 0);
			language->localeMapping = NULL;
			language->next = languageList;
			languageList = language;
			languageCount++;
		}
	} while (*text);
}
Example #4
0
/**
 * @brief Reorders all surfaces arrays for the specified model, grouping the surface
 * pointers by texture.  This dramatically reduces glBindTexture calls.
 */
static void R_SortSurfacesArrays (const model_t *mod)
{
	const mBspSurface_t *surf, *s;
	int i, ns;

	/* resolve the start surface and total surface count */
	if (mod->type == mod_bsp) {  /*  world model */
		s = mod->bsp.surfaces;
		ns = mod->bsp.numsurfaces;
	} else {  /* submodels */
		s = &mod->bsp.surfaces[mod->bsp.firstmodelsurface];
		ns = mod->bsp.nummodelsurfaces;
	}

	OBJZERO(r_sorted_surfaces);

	/* allocate the per-texture surfaces arrays and determine counts */
	for (i = 0, surf = s; i < ns; i++, surf++) {
		mBspSurfaces_t *surfs = r_sorted_surfaces[surf->texinfo->image->texnum];
		if (!surfs) {  /* allocate it */
			surfs = (mBspSurfaces_t *)Mem_PoolAlloc(sizeof(*surfs), vid_modelPool, 0);
			r_sorted_surfaces[surf->texinfo->image->texnum] = surfs;
		}

		surfs->count++;
	}

	/* allocate the surfaces pointers based on counts */
	for (i = 0; i < r_numImages; i++) {
		mBspSurfaces_t *surfs = r_sorted_surfaces[r_images[i].texnum];
		if (surfs) {
			surfs->surfaces = (mBspSurface_t **)Mem_PoolAlloc(sizeof(mBspSurface_t *) * surfs->count, vid_modelPool, 0);
			surfs->count = 0;
		}
	}

	/* sort the model's surfaces arrays into the per-texture arrays */
	for (i = 0; i < NUM_SURFACES_ARRAYS; i++) {
		if (mod->bsp.sorted_surfaces[i]->count) {
			R_SortSurfacesArrays_(mod->bsp.sorted_surfaces[i]);
			Com_DPrintf(DEBUG_RENDERER, "%i: #%i surfaces\n", i, mod->bsp.sorted_surfaces[i]->count);
		}
	}

	/* free the per-texture surfaces arrays */
	for (i = 0; i < r_numImages; i++) {
		mBspSurfaces_t *surfs = r_sorted_surfaces[r_images[i].texnum];
		if (surfs) {
			if (surfs->surfaces)
				Mem_Free(surfs->surfaces);
			Mem_Free(surfs);
		}
	}
}
Example #5
0
static void* _hash_alloc (memPool_t* pool, int n, size_t s) {
	#ifdef __DEBUG__
	_num_allocs++;
	#endif // __DEBUG__
	if (pool) return Mem_PoolAlloc (n * s, pool, 0);
	return Mem_Alloc(n * s);
}
Example #6
0
void UI_Init (void)
{
	cvar_t *ui_hunkSize = Cvar_Get("ui_hunksize", "2", CVAR_ARCHIVE, "UI memory hunk size in megabytes");

#ifdef DEBUG
	ui_debug = Cvar_Get("debug_ui", "0", CVAR_DEVELOPER, "Prints node names for debugging purposes - valid values are 1 and 2");
#endif

	/* reset global UI structures */
	OBJZERO(ui_global);

	ui_sounds = Cvar_Get("ui_sounds", "1", CVAR_ARCHIVE, "Activates UI sounds");

#ifdef DEBUG
	Cmd_AddCommand("debug_uimemory", UI_Memory_f, "Display info about UI memory allocation");
#endif

	ui_sysPool = Mem_CreatePool("Client: UI");
	ui_dynStringPool = Mem_CreatePool("Client: Dynamic string for UI");
	ui_dynPool = Mem_CreatePool("Client: Dynamic memory for UI");

	/* 256kb */
	ui_global.adataize = ui_hunkSize->integer * 1024 * 1024;
	ui_global.adata = (byte*)Mem_PoolAlloc(ui_global.adataize, ui_sysPool, 0);
	ui_global.curadata = ui_global.adata;

	UI_InitData();
	UI_InitNodes();
	UI_InitWindows();
	UI_InitDraw();
	UI_InitActions();
}
Example #7
0
/**
 * @sa free_element
 */
static struct dbuffer_element * allocate_element (void)
{
	struct dbuffer_element *e;

	TH_MutexLock(dbuf_lock);

	if (free_elements == 0) {
		struct dbuffer_element *newBuf = (struct dbuffer_element *)Mem_PoolAlloc(sizeof(struct dbuffer_element), com_genericPool, 0);
		newBuf->next = free_element_list;
		free_element_list = newBuf;
		free_elements++;
		allocated_elements++;
	}
	assert(free_element_list);

	e = free_element_list;
	free_element_list = free_element_list->next;
	assert(free_elements > 0);
	free_elements--;
	e->space = DBUFFER_ELEMENT_SIZE;
	e->len = 0;
	e->next = NULL;

	TH_MutexUnlock(dbuf_lock);

	return e;
}
Example #8
0
/**
 * @brief Allocate a dbuffer
 * @return the newly allocated buffer
 * Allocates a new dbuffer and initialises it to be empty
 */
struct dbuffer * new_dbuffer (void)
{
	struct dbuffer *buf;

	TH_MutexLock(dbuf_lock);

	if (free_dbuffers == 0) {
		struct dbuffer *newBuf = (struct dbuffer *)Mem_PoolAlloc(sizeof(struct dbuffer), com_genericPool, 0);
		newBuf->next_free = free_dbuffer_list;
		free_dbuffer_list = newBuf;
		free_dbuffers++;
		allocated_dbuffers++;
	}
	assert(free_dbuffer_list);

	buf = free_dbuffer_list;
	free_dbuffer_list = free_dbuffer_list->next_free;
	assert(free_dbuffers > 0);
	free_dbuffers--;

	TH_MutexUnlock(dbuf_lock);

	buf->len = 0;
	buf->space = DBUFFER_ELEMENT_SIZE;
	buf->head = buf->tail = allocate_element();
	buf->start = buf->end = &buf->head->data[0];

	return buf;
}
static void UI_MaterialEditorNewStage_f (void)
{
	material_t *m;
	materialStage_t *s;
	int id;

	if (Cmd_Argc() < 2) {
		Com_Printf("Usage: %s <image index>\n", Cmd_Argv(0));
		return;
	}

	id = atoi(Cmd_Argv(1));
	if (id < 0 || id >= r_numImages) {
		Com_Printf("Given image index (%i) is out of bounds\n", id);
		return;
	}

	m = &R_GetImageAtIndex(id)->material;
	s = (materialStage_t *)Mem_PoolAlloc(sizeof(*s), vid_imagePool, 0);
	m->num_stages++;

	/* append the stage to the chain */
	if (!m->stages)
		m->stages = s;
	else {
		materialStage_t *ss = m->stages;
		while (ss->next)
			ss = ss->next;
		ss->next = s;
	}

	UI_MaterialEditorUpdate(R_GetImageAtIndex(id), s);
}
/**
 * @brief Register a property to a behaviour.
 * It should not be used in the code
 * @param behaviour Target behaviour
 * @param name Name of the property
 * @param type Type of the property
 * @param pos position of the attribute (which store property memory) into the node structure
 * @param size size of the attribute (which store property memory) into the node structure
 * @see UI_RegisterNodeProperty
 * @see UI_RegisterExtradataNodeProperty
 * @return A link to the node property
 */
const struct value_s *UI_RegisterNodePropertyPosSize_ (struct uiBehaviour_s *behaviour, const char* name, int type, size_t pos, size_t size)
{
	value_t *property = UI_AllocHunkMemory(sizeof(value_t), STRUCT_MEMORY_ALIGN, qfalse);
	if (property == NULL)
		Com_Error(ERR_FATAL, "UI_RegisterNodePropertyPosSize_: UI memory hunk exceeded - increase the size");

	if (type == V_STRING || type == V_LONGSTRING || type == V_CVAR_OR_LONGSTRING || V_CVAR_OR_STRING) {
		size = 0;
	}

	property->string = name;
	property->type = type;
	property->ofs = pos;
	property->size = size;

	if (behaviour->localProperties == NULL) {
		/* temporary memory allocation */
		behaviour->localProperties = (const value_t **)Mem_PoolAlloc(sizeof(*behaviour->localProperties) * LOCAL_PROPERTY_SIZE, ui_sysPool, 0);
	}
	if (behaviour->propertyCount >= LOCAL_PROPERTY_SIZE-1) {
		Com_Error(ERR_FATAL, "UI_RegisterNodePropertyPosSize_: Property memory of behaviour %s is full.", behaviour->name);
	}
	behaviour->localProperties[behaviour->propertyCount++] = property;
	behaviour->localProperties[behaviour->propertyCount] = NULL;

	return property;
}
Example #11
0
/**
 * @brief Loads brush entities like func_door and func_breakable
 * @sa CMod_LoadSubmodels
 */
static void R_ModLoadSubmodels (const lump_t *l)
{
	const dBspModel_t *in;
	mBspHeader_t *out;
	int i, j, count;

	in = (const dBspModel_t *) (mod_base + l->fileofs);
	if (l->filelen % sizeof(*in))
		Com_Error(ERR_DROP, "R_ModLoadSubmodels: funny lump size in %s", r_worldmodel->name);
	count = l->filelen / sizeof(*in);
	out = (mBspHeader_t *)Mem_PoolAlloc(count * sizeof(*out), vid_modelPool, 0);
	Com_DPrintf(DEBUG_RENDERER, "...submodels: %i\n", count);

	r_worldmodel->bsp.submodels = out;
	r_worldmodel->bsp.numsubmodels = count;

	for (i = 0; i < count; i++, in++, out++) {
		/* spread the mins / maxs by a pixel */
		for (j = 0; j < 3; j++) {
			out->mins[j] = LittleFloat(in->mins[j]) - 1.0f + (float)shift[j];
			out->maxs[j] = LittleFloat(in->maxs[j]) + 1.0f + (float)shift[j];
			out->origin[j] = LittleFloat(in->origin[j]) + (float)shift[j];
		}
		out->radius = R_RadiusFromBounds(out->mins, out->maxs);
		out->headnode = LittleLong(in->headnode);
		out->firstface = LittleLong(in->firstface);
		out->numfaces = LittleLong(in->numfaces);
	}
}
Example #12
0
/**
 * @brief A brand new game has been started
 */
static void SV_InitGame (void)
{
	/* allow next change after map change or restart */
	sv_maxclients->flags |= CVAR_LATCH;

	/* get any latched variable changes (sv_maxclients, etc) */
	Cvar_UpdateLatchedVars();

	if (svs.serverMutex)
		Sys_Error("There is still a server running");

	svs.clients = (client_t *)Mem_PoolAlloc(sizeof(client_t) * sv_maxclients->integer, sv_genericPool, 0);
	svs.serverMutex = TH_MutexCreate("server");

	/* init network stuff */
	if (sv_maxclients->integer > 1) {
		svs.initialized = SV_Start(NULL, port->string, &SV_ReadPacket);
		svs.netDatagramSocket = NET_DatagramSocketNew(NULL, Cvar_Get("port", DOUBLEQUOTE(PORT_SERVER), CVAR_NOSET, NULL)->string, &SV_DiscoveryCallback);
	} else
		svs.initialized = SV_Start(NULL, NULL, &SV_ReadPacket);

	SV_Heartbeat_f();

	/* init game */
	SV_InitGameProgs();

	if (sv_maxclients->integer != 1 && (sv_dedicated->integer || sv_public->integer))
		SV_SetMaster_f();
}
Example #13
0
/**
 * @brief Only called once at startup, not for each game
 */
void SV_Init (void)
{
	Com_Printf("\n------ server initialization -------\n");

	sv_genericPool = Mem_CreatePool("Server: Generic");

	OBJZERO(svs);

	sv = (serverInstanceGame_t *) Mem_PoolAlloc(sizeof(*sv), sv_genericPool, 0);

	SV_InitOperatorCommands();

	rcon_password = Cvar_Get("rcon_password", "", 0, NULL);
	Cvar_Get("sv_cheats", "0", CVAR_SERVERINFO | CVAR_LATCH, NULL);
	Cvar_Get("sv_protocol", DOUBLEQUOTE(PROTOCOL_VERSION), CVAR_SERVERINFO | CVAR_NOSET, NULL);
	/* this cvar will become a latched cvar when you start the server */
	sv_maxclients = Cvar_Get("sv_maxclients", "1", CVAR_SERVERINFO, "Max. connected clients");
	sv_hostname = Cvar_Get("sv_hostname", "noname", CVAR_SERVERINFO | CVAR_ARCHIVE, "The name of the server that is displayed in the serverlist");
	sv_http_downloadserver = Cvar_Get("sv_http_downloadserver", "", CVAR_ARCHIVE, "URL to a location where clients can download game content over HTTP");
	sv_enablemorale = Cvar_Get("sv_enablemorale", "1", CVAR_ARCHIVE | CVAR_SERVERINFO | CVAR_LATCH, "Enable morale changes in multiplayer");
	sv_maxsoldiersperteam = Cvar_Get("sv_maxsoldiersperteam", "4", CVAR_ARCHIVE | CVAR_SERVERINFO, "Max. amount of soldiers per team (see sv_maxsoldiersperplayer and sv_teamplay)");
	sv_maxsoldiersperplayer = Cvar_Get("sv_maxsoldiersperplayer", "8", CVAR_ARCHIVE | CVAR_SERVERINFO, "Max. amount of soldiers each player can controll (see maxsoldiers and sv_teamplay)");
	Cvar_SetCheckFunction("sv_maxsoldiersperplayer", SV_CheckMaxSoldiersPerPlayer);

	sv_dumpmapassembly = Cvar_Get("sv_dumpmapassembly", "0", CVAR_ARCHIVE, "Dump map assembly information to game console");

	sv_threads = Cvar_Get("sv_threads", "1", CVAR_LATCH | CVAR_ARCHIVE, "Run the server threaded");
	sv_public = Cvar_Get("sv_public", "1", 0, "Should heartbeats be send to the masterserver");
	sv_reconnect_limit = Cvar_Get("sv_reconnect_limit", "3", CVAR_ARCHIVE, "Minimum seconds between connect messages");

	SV_MapcycleInit();
}
Example #14
0
/**
 * @brief Loads and registers a sound file for later use
 * @param[in] soundFile The name of the soundfile, relative to the sounds dir
 * @return The index of the loaded sample or 0
 * @sa S_LoadSound
 */
int S_LoadSampleIdx (const char *soundFile)
{
	Mix_Chunk *chunk;
	s_sample_t *sample;
	char name[MAX_QPATH];
	unsigned hash;

	if (!s_env.initialized)
		return 0;

	Com_StripExtension(soundFile, name, sizeof(name));

	sample = S_FindByName(name);
	if (sample)
		return sample->index;

	/* make sure the sound is loaded */
	chunk = S_LoadSampleChunk(name);
	if (!chunk)
		return 0;		/* couldn't load the sound's data */

	hash = Com_HashKey(name, SAMPLE_HASH_SIZE);
	sample = (s_sample_t *)Mem_PoolAlloc(sizeof(*sample), cl_soundSysPool, 0);
	sample->name = Mem_PoolStrDup(name, cl_soundSysPool, 0);
	sample->chunk = chunk;
	sample->hashNext = sampleHash[hash];
	sampleHash[hash] = sample;
	sampleIndex[++sampleIndexLast] = sample;
	sample->index = sampleIndexLast;
	return sample->index;
}
Example #15
0
static void R_ModLoadLeafs (const lump_t *l)
{
	const dBspLeaf_t *in;
	mBspLeaf_t *out;
	int i, j, count;

	in = (const dBspLeaf_t *) (mod_base + l->fileofs);
	if (l->filelen % sizeof(*in))
		Com_Error(ERR_DROP, "R_ModLoadLeafs: funny lump size in %s", r_worldmodel->name);
	count = l->filelen / sizeof(*in);
	out = (mBspLeaf_t *)Mem_PoolAlloc(count * sizeof(*out), vid_modelPool, 0);
	Com_DPrintf(DEBUG_RENDERER, "...leafs: %i\n", count);

	r_worldmodel->bsp.leafs = out;
	r_worldmodel->bsp.numleafs = count;

	for (i = 0; i < count; i++, in++, out++) {
		for (j = 0; j < 3; j++) {
			out->minmaxs[j] = LittleShort(in->mins[j]) + (float)shift[j];
			out->minmaxs[3 + j] = LittleShort(in->maxs[j]) + (float)shift[j];
		}

		out->contents = LittleLong(in->contentFlags);
	}
}
Example #16
0
/**
 * @brief Add a new command to the script interface
 * @param[in] cmd_name The name the command is available via script interface
 * @param[in] function The function pointer
 * @param[in] desc A usually(?) one-line description of what the cmd does
 * @sa Cmd_RemoveCommand
 */
void Cmd_AddCommand (const char *cmd_name, xcommand_t function, const char *desc)
{
	cmd_function_t *cmd;
	unsigned int hash;

	if (!cmd_name || !cmd_name[0])
		return;

	/* fail if the command is a variable name */
	if (Cvar_GetString(cmd_name)[0]) {
		Com_Printf("Cmd_AddCommand: %s already defined as a var\n", cmd_name);
		return;
	}

	/* fail if the command already exists */
	hash = Com_HashKey(cmd_name, CMD_HASH_SIZE);
	for (cmd = cmd_functions_hash[hash]; cmd; cmd = cmd->hash_next) {
		if (Q_streq(cmd_name, cmd->name)) {
			Com_DPrintf(DEBUG_COMMANDS, "Cmd_AddCommand: %s already defined\n", cmd_name);
			return;
		}
	}

	cmd = (cmd_function_t *)Mem_PoolAlloc(sizeof(*cmd), com_cmdSysPool, 0);
	cmd->name = cmd_name;
	cmd->description = desc;
	cmd->function = function;
	cmd->completeParam = NULL;
	HASH_Add(cmd_functions_hash, cmd, hash);
	cmd->next = cmd_functions;
	cmd_functions = cmd;
}
Example #17
0
/**
 * @brief Creates a new command that executes a command string (possibly ; separated)
 */
static void Cmd_Alias_f (void)
{
	cmd_alias_t *a;
	char cmd[MAX_STRING_CHARS];
	size_t len;
	unsigned int hash;
	int i, c;
	const char *s;

	if (Cmd_Argc() == 1) {
		Com_Printf("Current alias commands:\n");
		for (a = cmd_alias; a; a = a->next)
			Com_Printf("%s : %s\n", a->name, a->value);
		return;
	}

	s = Cmd_Argv(1);
	len = strlen(s);
	if (len == 0)
		return;

	if (len >= MAX_ALIAS_NAME) {
		Com_Printf("Alias name is too long\n");
		return;
	}

	/* if the alias already exists, reuse it */
	hash = Com_HashKey(s, ALIAS_HASH_SIZE);
	for (a = cmd_alias_hash[hash]; a; a = a->hash_next) {
		if (Q_streq(s, a->name)) {
			Mem_Free(a->value);
			break;
		}
	}

	if (!a) {
		a = (cmd_alias_t *)Mem_PoolAlloc(sizeof(*a), com_aliasSysPool, 0);
		a->next = cmd_alias;
		/* cmd_alias_hash should be null on the first run */
		a->hash_next = cmd_alias_hash[hash];
		cmd_alias_hash[hash] = a;
		cmd_alias = a;
	}
	Q_strncpyz(a->name, s, sizeof(a->name));

	/* copy the rest of the command line */
	cmd[0] = 0;					/* start out with a null string */
	c = Cmd_Argc();
	for (i = 2; i < c; i++) {
		Q_strcat(cmd, Cmd_Argv(i), sizeof(cmd));
		if (i != (c - 1))
			Q_strcat(cmd, " ", sizeof(cmd));
	}

	if (Q_streq(Cmd_Argv(0), "aliasa"))
		a->archive = qtrue;

	a->value = Mem_PoolStrDup(cmd, com_aliasSysPool, 0);
}
Example #18
0
/**
 * @brief Allocates data arrays for animated models. Only called once at loading time.
 */
void R_ModLoadArrayData (mAliasModel_t *mod, mAliasMesh_t *mesh, qboolean loadNormals)
{
	const int v = mesh->num_tris * 3 * 3;
	const int t = mesh->num_tris * 3 * 4;
	const int st = mesh->num_tris * 3 * 2;

	assert(mesh->verts == NULL);
	assert(mesh->texcoords == NULL);
	assert(mesh->normals == NULL);
	assert(mesh->tangents == NULL);
	assert(mesh->next_verts == NULL);
	assert(mesh->next_normals == NULL);
	assert(mesh->next_tangents == NULL);

	mesh->verts = (float *)Mem_PoolAlloc(sizeof(float) * v, vid_modelPool, 0);
	mesh->normals = (float *)Mem_PoolAlloc(sizeof(float) * v, vid_modelPool, 0);
	mesh->tangents = (float *)Mem_PoolAlloc(sizeof(float) * t, vid_modelPool, 0);
	mesh->texcoords = (float *)Mem_PoolAlloc(sizeof(float) * st, vid_modelPool, 0);
	if (mod->num_frames == 1) {
		R_FillArrayData(mod, mesh, 0.0, 0, 0, loadNormals);
	} else {
		mesh->next_verts = (float *)Mem_PoolAlloc(sizeof(float) * v, vid_modelPool, 0);
		mesh->next_normals = (float *)Mem_PoolAlloc(sizeof(float) * v, vid_modelPool, 0);
		mesh->next_tangents = (float *)Mem_PoolAlloc(sizeof(float) * t, vid_modelPool, 0);

		mod->curFrame = -1;
		mod->oldFrame = -1;
	}
}
Example #19
0
void R_ModLoadAnims (mAliasModel_t *mod, const char *buffer)
{
	const char *text, *token;
	mAliasAnim_t *anim;
	int n;

	for (n = 0, text = buffer; text; n++)
		Com_Parse(&text);
	n /= 4;
	if (n > MAX_ANIMS)
		n = MAX_ANIMS;

	mod->animdata = (mAliasAnim_t *) Mem_PoolAlloc(n * sizeof(*mod->animdata), vid_modelPool, 0);
	anim = mod->animdata;
	text = buffer;
	mod->num_anims = 0;

	do {
		/* get the name */
		token = Com_Parse(&text);
		if (!text)
			break;
		Q_strncpyz(anim->name, token, sizeof(anim->name));

		/* get the start */
		token = Com_Parse(&text);
		if (!text)
			break;
		anim->from = atoi(token);
		if (anim->from < 0)
			Com_Error(ERR_FATAL, "R_ModLoadAnims: negative start frame for %s", mod->animname);
		else if (anim->from > mod->num_frames)
			Com_Error(ERR_FATAL, "R_ModLoadAnims: start frame is higher than models frame count (%i) (model: %s)",
					mod->num_frames, mod->animname);

		/* get the end */
		token = Com_Parse(&text);
		if (!text)
			break;
		anim->to = atoi(token);
		if (anim->to < 0)
			Com_Error(ERR_FATAL, "R_ModLoadAnims: negative start frame for %s", mod->animname);
		else if (anim->to > mod->num_frames)
			Com_Error(ERR_FATAL, "R_ModLoadAnims: end frame is higher than models frame count (%i) (model: %s)",
					mod->num_frames, mod->animname);

		/* get the fps */
		token = Com_Parse(&text);
		if (!text)
			break;
		anim->time = (atof(token) > 0.01) ? (1000.0 / atof(token)) : (1000.0 / 0.01);

		/* add it */
		mod->num_anims++;
		anim++;
	} while (mod->num_anims < MAX_ANIMS);
}
Example #20
0
/**
 * @brief Load the lightmap data
 */
static void R_ModLoadLighting (const lump_t *l)
{
	/* map has no lightmap */
	if (l->filelen == 0)
		return;

	r_worldmodel->bsp.lightdata = (byte *)Mem_PoolAlloc(l->filelen, vid_lightPool, 0);
	r_worldmodel->bsp.lightquant = *(const byte *) (mod_base + l->fileofs);
	memcpy(r_worldmodel->bsp.lightdata, mod_base + l->fileofs, l->filelen);
}
Example #21
0
/**
 * @sa TR_BuildTracingNode_r
 * @sa R_RecurseSetParent
 */
static void R_ModLoadNodes (const lump_t *l)
{
	int i, j, count;
	const dBspNode_t *in;
	mBspNode_t *out;
	mBspNode_t *parent = NULL;

	in = (const dBspNode_t *) (mod_base + l->fileofs);
	if (l->filelen % sizeof(*in))
		Com_Error(ERR_DROP, "R_ModLoadNodes: funny lump size in %s", r_worldmodel->name);
	count = l->filelen / sizeof(*in);
	out = (mBspNode_t *)Mem_PoolAlloc(count * sizeof(*out), vid_modelPool, 0);
	Com_DPrintf(DEBUG_RENDERER, "...nodes: %i\n", count);

	r_worldmodel->bsp.nodes = out;
	r_worldmodel->bsp.numnodes = count;

	for (i = 0; i < count; i++, in++, out++) {
		const int p = LittleLong(in->planenum);

		/* skip special pathfinding nodes - they have a negative index */
		if (p == PLANENUM_LEAF) {
			/* in case of "special" pathfinding nodes (they don't have a plane)
			 * we have to set this to NULL */
			out->plane = NULL;
			out->contents = CONTENTS_PATHFINDING_NODE;
			parent = NULL;
		} else {
			out->plane = r_worldmodel->bsp.planes + p;
			/* differentiate from leafs */
			out->contents = CONTENTS_NODE;
			parent = out;
		}

		for (j = 0; j < 3; j++) {
			out->minmaxs[j] = LittleShort(in->mins[j]) + (float)shift[j];
			out->minmaxs[3 + j] = LittleShort(in->maxs[j]) + (float)shift[j];
		}

		out->firstsurface = LittleShort(in->firstface);
		out->numsurfaces = LittleShort(in->numfaces);

		for (j = 0; j < 2; j++) {
			const int p2 = LittleLong(in->children[j]);
			if (p2 > LEAFNODE) {
				assert(p2 < r_worldmodel->bsp.numnodes);
				out->children[j] = r_worldmodel->bsp.nodes + p2;
			} else {
				assert((LEAFNODE - p2) < r_worldmodel->bsp.numleafs);
				out->children[j] = (mBspNode_t *) (r_worldmodel->bsp.leafs + (LEAFNODE - p2));
			}
			out->children[j]->parent = parent;
		}
	}
}
Example #22
0
/**
 * @brief Set a new action to a @c uiAction_t pointer
 * @param[in,out] action Allocated action
 * @param[in] type Only @c EA_CMD is supported
 * @param[in] data The data for this action - in case of @c EA_CMD this is the commandline
 * @note You first have to free existing node actions - only free those that are
 * not static in @c ui_global.actions array
 * @todo we should create a function to free the memory. We can use a tag in the Mem_PoolAlloc
 * calls and use use Mem_FreeTag.
 */
void UI_PoolAllocAction (uiAction_t** action, int type, const void *data)
{
	if (*action)
		Com_Error(ERR_FATAL, "There is already an action assigned");
	*action = (uiAction_t *)Mem_PoolAlloc(sizeof(**action), ui_sysPool, 0);
	(*action)->type = type;
	switch (type) {
	case EA_CMD:
		(*action)->d.terminal.d1.constString = Mem_PoolStrDup((const char *)data, ui_sysPool, 0);
		break;
	default:
		Com_Error(ERR_FATAL, "Action type %i is not yet implemented", type);
	}
}
Example #23
0
/**
 * @brief Allocate a node into the UI memory (do notr call behaviour->new)
 * @note It's not a dynamic memory allocation. Please only use it at the loading time
 * @todo Assert out when we are not in parsing/loading stage
 * @param[in] name Name of the new node, else NULL if we dont want to edit it.
 * @param[in] type Name of the node behavior
 * @param[in] isDynamic Allocate a node in static or dynamic memory
 */
static uiNode_t* UI_AllocNodeWithoutNew (const char* name, const char* type, qboolean isDynamic)
{
	uiNode_t* node;
	uiBehaviour_t *behaviour;
	int nodeSize;

	behaviour = UI_GetNodeBehaviour(type);
	if (behaviour == NULL)
		Com_Error(ERR_FATAL, "UI_AllocNodeWithoutNew: Node behaviour '%s' doesn't exist", type);

	nodeSize = sizeof(*node) + behaviour->extraDataSize;

	if (!isDynamic) {
		if (ui_global.curadata + nodeSize > ui_global.adata + ui_global.adataize)
			Com_Error(ERR_FATAL, "UI_AllocNodeWithoutNew: No more memory to allocate a new node");
		/** @todo fix this hard coded '8' value */
		ui_global.curadata = ALIGN_PTR(ui_global.curadata, 8);
		node = (uiNode_t*) ui_global.curadata;
		ui_global.curadata += nodeSize;
		ui_global.numNodes++;
		memset(node, 0, nodeSize);
	} else {
		node = (uiNode_t*)Mem_PoolAlloc(nodeSize, ui_dynPool, 0);
		memset(node, 0, nodeSize);
		node->dynamic = qtrue;
	}

	node->behaviour = behaviour;
#ifdef DEBUG
	node->behaviour->count++;
#endif
	if (node->behaviour->isAbstract)
		Com_Error(ERR_FATAL, "UI_AllocNodeWithoutNew: Node behavior '%s' is abstract. We can't instantiate it.", type);

	if (name != NULL) {
		Q_strncpyz(node->name, name, sizeof(node->name));
		if (strlen(node->name) != strlen(name))
			Com_Printf("UI_AllocNodeWithoutNew: Node name \"%s\" truncated. New name is \"%s\"\n", name, node->name);
	}

	/* initialize default properties */
	if (node->behaviour->loading)
		node->behaviour->loading(node);

	return node;
}
Example #24
0
/**
 * @brief Allocate a node into the UI memory (do not call behaviour->new)
 * @note It's not a dynamic memory allocation. Please only use it at the loading time
 * @todo Assert out when we are not in parsing/loading stage
 * @param[in] name Name of the new node, else nullptr if we don't want to edit it.
 * @param[in] type Name of the node behavior
 * @param[in] isDynamic Allocate a node in static or dynamic memory
 */
static uiNode_t* UI_AllocNodeWithoutNew (const char* name, const char* type, bool isDynamic)
{
	uiNode_t* node;
	uiBehaviour_t *behaviour;
	int nodeSize;

	behaviour = UI_GetNodeBehaviour(type);
	if (behaviour == nullptr)
		Com_Error(ERR_FATAL, "UI_AllocNodeWithoutNew: Node behaviour '%s' doesn't exist", type);

	nodeSize = sizeof(*node) + behaviour->extraDataSize;

	if (!isDynamic) {
		void *memory = UI_AllocHunkMemory(nodeSize, STRUCT_MEMORY_ALIGN, true);
		if (memory == nullptr)
			Com_Error(ERR_FATAL, "UI_AllocNodeWithoutNew: No more memory to allocate a new node - increase the cvar ui_hunksize");
		node = static_cast<uiNode_t*>(memory);
		ui_global.numNodes++;
	} else {
		node = static_cast<uiNode_t*>(Mem_PoolAlloc(nodeSize, ui_dynPool, 0));
		node->dynamic = true;
	}

	node->behaviour = behaviour;
#ifdef DEBUG
	UI_Node_DebugCountWidget(node, 1);
#endif
	if (UI_Node_IsAbstract(node))
		Com_Error(ERR_FATAL, "UI_AllocNodeWithoutNew: Node behavior '%s' is abstract. We can't instantiate it.", type);

	if (name != nullptr) {
		Q_strncpyz(node->name, name, sizeof(node->name));
		if (strlen(node->name) != strlen(name))
			Com_Printf("UI_AllocNodeWithoutNew: Node name \"%s\" truncated. New name is \"%s\"\n", name, node->name);
	}

	/* initialize default properties */
	UI_Node_Loading(node);

	return node;
}
Example #25
0
static void R_ModLoadEdges (const lump_t *l)
{
	const dBspEdge_t *in;
	mBspEdge_t *out;
	int i, count;

	in = (const dBspEdge_t *) (mod_base + l->fileofs);
	if (l->filelen % sizeof(*in))
		Com_Error(ERR_DROP, "R_ModLoadEdges: funny lump size in %s", r_worldmodel->name);
	count = l->filelen / sizeof(*in);
	out = (mBspEdge_t *)Mem_PoolAlloc((count + 1) * sizeof(*out), vid_modelPool, 0);
	Com_DPrintf(DEBUG_RENDERER, "...edges: %i\n", count);

	r_worldmodel->bsp.edges = out;
	r_worldmodel->bsp.numedges = count;

	for (i = 0; i < count; i++, in++, out++) {
		out->v[0] = (unsigned short) LittleShort(in->v[0]);
		out->v[1] = (unsigned short) LittleShort(in->v[1]);
	}
}
Example #26
0
static void R_ModLoadSurfedges (const lump_t *l)
{
	int i, count;
	const int *in;
	int *out;

	in = (const int *) (mod_base + l->fileofs);
	if (l->filelen % sizeof(*in))
		Com_Error(ERR_DROP, "R_ModLoadSurfedges: funny lump size in %s", r_worldmodel->name);
	count = l->filelen / sizeof(*in);
	if (count < 1 || count >= MAX_MAP_SURFEDGES)
		Com_Error(ERR_DROP, "R_ModLoadSurfedges: bad surfedges count in %s: %i", r_worldmodel->name, count);

	out = (int *) Mem_PoolAlloc(count * sizeof(*out), vid_modelPool, 0);
	Com_DPrintf(DEBUG_RENDERER, "...surface edges: %i\n", count);

	r_worldmodel->bsp.surfedges = out;
	r_worldmodel->bsp.numsurfedges = count;

	for (i = 0; i < count; i++)
		out[i] = LittleLong(in[i]);
}
Example #27
0
static void R_ModLoadVertexes (const lump_t *l)
{
	const dBspVertex_t *in;
	mBspVertex_t *out;
	int i, count;

	in = (const dBspVertex_t *) (mod_base + l->fileofs);
	if (l->filelen % sizeof(*in))
		Com_Error(ERR_DROP, "R_ModLoadVertexes: funny lump size in %s", r_worldmodel->name);
	count = l->filelen / sizeof(*in);
	out = (mBspVertex_t *)Mem_PoolAlloc(count * sizeof(*out), vid_modelPool, 0);
	Com_DPrintf(DEBUG_RENDERER, "...verts: %i\n", count);

	r_worldmodel->bsp.vertexes = out;
	r_worldmodel->bsp.numvertexes = count;

	for (i = 0; i < count; i++, in++, out++) {
		out->position[0] = LittleFloat(in->point[0]);
		out->position[1] = LittleFloat(in->point[1]);
		out->position[2] = LittleFloat(in->point[2]);
	}
}
Example #28
0
/**
 * @sa CMod_LoadPlanes
 */
static void R_ModLoadPlanes (const lump_t *l)
{
	int i, j;
	cBspPlane_t *out;
	const dBspPlane_t *in;
	int count;

	in = (const dBspPlane_t *) (mod_base + l->fileofs);
	if (l->filelen % sizeof(*in))
		Com_Error(ERR_DROP, "R_ModLoadPlanes: funny lump size in %s", r_worldmodel->name);
	count = l->filelen / sizeof(*in);
	out = (cBspPlane_t *)Mem_PoolAlloc(count * 2 * sizeof(*out), vid_modelPool, 0);
	Com_DPrintf(DEBUG_RENDERER, "...planes: %i\n", count);

	r_worldmodel->bsp.planes = out;
	r_worldmodel->bsp.numplanes = count;

	for (i = 0; i < count; i++, in++, out++) {
		for (j = 0; j < 3; j++)
			out->normal[j] = LittleFloat(in->normal[j]);
		out->dist = LittleFloat(in->dist);
		out->type = LittleLong(in->type);
	}
}
Example #29
0
/**
 * @sa CP_StartMissionMap
 */
static void R_ModLoadTexinfo (const lump_t *l)
{
	const dBspTexinfo_t *in;
	mBspTexInfo_t *out;
	int i, j, count;
	char name[MAX_QPATH];

	in = (const dBspTexinfo_t *) (mod_base + l->fileofs);
	if (l->filelen % sizeof(*in))
		Com_Error(ERR_DROP, "R_ModLoadTexinfo: funny lump size in %s", r_worldmodel->name);
	count = l->filelen / sizeof(*in);
	out = (mBspTexInfo_t *)Mem_PoolAlloc(count * sizeof(*out), vid_modelPool, 0);
	Com_DPrintf(DEBUG_RENDERER, "...texinfo: %i\n", count);

	r_worldmodel->bsp.texinfo = out;
	r_worldmodel->bsp.numtexinfo = count;

	for (i = 0; i < count; i++, in++, out++) {
		for (j = 0; j < 3; j++) {
			out->uv[j] = LittleFloat(in->vecs[0][j]);
			out->vv[j] = LittleFloat(in->vecs[1][j]);
		}
		out->u_offset = LittleFloat(in->vecs[0][3]);
		out->v_offset = LittleFloat(in->vecs[1][3]);

		out->flags = LittleLong(in->surfaceFlags);

		/* exchange the textures with the ones that are needed for base assembly */
		if (refdef.mapZone && strstr(in->texture, "tex_terrain/dummy"))
			Com_sprintf(name, sizeof(name), "textures/tex_terrain/%s", refdef.mapZone);
		else
			Com_sprintf(name, sizeof(name), "textures/%s", in->texture);

		out->image = R_FindImage(name, it_world);
	}
}
Example #30
0
/**
 * @brief Calculates normals and tangents for all frames and does vertex merging based on smoothness
 * @param mesh The mesh to calculate normals for
 * @param nFrames How many frames the mesh has
 * @param smoothness How aggressively should normals be smoothed; value is compared with dotproduct of vectors to decide if they should be merged
 * @sa R_ModCalcNormalsAndTangents
 */
void R_ModCalcUniqueNormalsAndTangents (mAliasMesh_t *mesh, int nFrames, float smoothness)
{
	int i, j;
	vec3_t triangleNormals[MAX_ALIAS_TRIS];
	vec3_t triangleTangents[MAX_ALIAS_TRIS];
	vec3_t triangleBitangents[MAX_ALIAS_TRIS];
	const mAliasVertex_t *vertexes = mesh->vertexes;
	mAliasCoord_t *stcoords = mesh->stcoords;
	mAliasVertex_t *newVertexes;
	mAliasComplexVertex_t tmpVertexes[MAX_ALIAS_VERTS];
	vec3_t tmpBitangents[MAX_ALIAS_VERTS];
	mAliasCoord_t *newStcoords;
	const int numIndexes = mesh->num_tris * 3;
	const int32_t *indexArray = mesh->indexes;
	int32_t *newIndexArray;
	int indRemap[MAX_ALIAS_VERTS];
	int sharedTris[MAX_ALIAS_VERTS];
	int numVerts = 0;

	newIndexArray = (int32_t *)Mem_PoolAlloc(sizeof(int32_t) * numIndexes, vid_modelPool, 0);

	/* calculate per-triangle surface normals */
	for (i = 0, j = 0; i < numIndexes; i += 3, j++) {
		vec3_t dir1, dir2;
		vec2_t dir1uv, dir2uv;

		/* calculate two mostly perpendicular edge directions */
		VectorSubtract(vertexes[indexArray[i + 0]].point, vertexes[indexArray[i + 1]].point, dir1);
		VectorSubtract(vertexes[indexArray[i + 2]].point, vertexes[indexArray[i + 1]].point, dir2);
		Vector2Subtract(stcoords[indexArray[i + 0]], stcoords[indexArray[i + 1]], dir1uv);
		Vector2Subtract(stcoords[indexArray[i + 2]], stcoords[indexArray[i + 1]], dir2uv);

		/* we have two edge directions, we can calculate a third vector from
		 * them, which is the direction of the surface normal */
		CrossProduct(dir1, dir2, triangleNormals[j]);

		/* then we use the texture coordinates to calculate a tangent space */
		if ((dir1uv[1] * dir2uv[0] - dir1uv[0] * dir2uv[1]) != 0.0) {
			const float frac = 1.0 / (dir1uv[1] * dir2uv[0] - dir1uv[0] * dir2uv[1]);
			vec3_t tmp1, tmp2;

			/* calculate tangent */
			VectorMul(-1.0 * dir2uv[1] * frac, dir1, tmp1);
			VectorMul(dir1uv[1] * frac, dir2, tmp2);
			VectorAdd(tmp1, tmp2, triangleTangents[j]);

			/* calculate bitangent */
			VectorMul(-1.0 * dir2uv[0] * frac, dir1, tmp1);
			VectorMul(dir1uv[0] * frac, dir2, tmp2);
			VectorAdd(tmp1, tmp2, triangleBitangents[j]);
		} else {
			const float frac = 1.0 / (0.00001);
			vec3_t tmp1, tmp2;

			/* calculate tangent */
			VectorMul(-1.0 * dir2uv[1] * frac, dir1, tmp1);
			VectorMul(dir1uv[1] * frac, dir2, tmp2);
			VectorAdd(tmp1, tmp2, triangleTangents[j]);

			/* calculate bitangent */
			VectorMul(-1.0 * dir2uv[0] * frac, dir1, tmp1);
			VectorMul(dir1uv[0] * frac, dir2, tmp2);
			VectorAdd(tmp1, tmp2, triangleBitangents[j]);
		}

		/* normalize */
		VectorNormalizeFast(triangleNormals[j]);
		VectorNormalizeFast(triangleTangents[j]);
		VectorNormalizeFast(triangleBitangents[j]);

		Orthogonalize(triangleTangents[j], triangleBitangents[j]);
	}

	/* do smoothing */
	for (i = 0; i < numIndexes; i++) {
		const int idx = (i - i % 3) / 3;
		VectorCopy(triangleNormals[idx], tmpVertexes[i].normal);
		VectorCopy(triangleTangents[idx], tmpVertexes[i].tangent);
		VectorCopy(triangleBitangents[idx], tmpBitangents[i]);

		for (j = 0; j < numIndexes; j++) {
			const int idx2 = (j - j % 3) / 3;
			/* don't add a vertex with itself */
			if (j == i)
				continue;

			/* only average normals if vertices have the same position
			 * and the normals aren't too far apart to start with */
			if (VectorEqual(vertexes[indexArray[i]].point, vertexes[indexArray[j]].point)
					&& DotProduct(triangleNormals[idx], triangleNormals[idx2]) > smoothness) {
				/* average the normals */
				VectorAdd(tmpVertexes[i].normal, triangleNormals[idx2], tmpVertexes[i].normal);

				/* if the tangents match as well, average them too.
				 * Note that having matching normals without matching tangents happens
				 * when the order of vertices in two triangles sharing the vertex
				 * in question is different.  This happens quite frequently if the
				 * modeler does not go out of their way to avoid it. */

				if (Vector2Equal(stcoords[indexArray[i]], stcoords[indexArray[j]])
						&& DotProduct(triangleTangents[idx], triangleTangents[idx2]) > smoothness
						&& DotProduct(triangleBitangents[idx], triangleBitangents[idx2]) > smoothness) {
					/* average the tangents */
					VectorAdd(tmpVertexes[i].tangent, triangleTangents[idx2], tmpVertexes[i].tangent);
					VectorAdd(tmpBitangents[i], triangleBitangents[idx2], tmpBitangents[i]);
				}
			}
		}

		VectorNormalizeFast(tmpVertexes[i].normal);
		VectorNormalizeFast(tmpVertexes[i].tangent);
		VectorNormalizeFast(tmpBitangents[i]);
	}

	/* assume all vertices are unique until proven otherwise */
	for (i = 0; i < numIndexes; i++)
		indRemap[i] = -1;

	/* merge vertices that have become identical */
	for (i = 0; i < numIndexes; i++) {
		vec3_t n, b, t, v;
		if (indRemap[i] != -1)
			continue;

		for (j = i + 1; j < numIndexes; j++) {
			if (Vector2Equal(stcoords[indexArray[i]], stcoords[indexArray[j]])
					&& VectorEqual(vertexes[indexArray[i]].point, vertexes[indexArray[j]].point)
					&& (DotProduct(tmpVertexes[i].normal, tmpVertexes[j].normal) > smoothness)
					&& (DotProduct(tmpVertexes[i].tangent, tmpVertexes[j].tangent) > smoothness)) {
				indRemap[j] = i;
				newIndexArray[j] = numVerts;
			}
		}

		VectorCopy(tmpVertexes[i].normal, n);
		VectorCopy(tmpVertexes[i].tangent, t);
		VectorCopy(tmpBitangents[i], b);

		/* normalization here does shared-vertex smoothing */
		VectorNormalizeFast(n);
		VectorNormalizeFast(t);
		VectorNormalizeFast(b);

		/* Grahm-Schmidt orthogonalization */
		VectorMul(DotProduct(t, n), n, v);
		VectorSubtract(t, v, t);
		VectorNormalizeFast(t);

		/* calculate handedness */
		CrossProduct(n, t, v);
		tmpVertexes[i].tangent[3] = (DotProduct(v, b) < 0.0) ? -1.0 : 1.0;
		VectorCopy(n, tmpVertexes[i].normal);
		VectorCopy(t, tmpVertexes[i].tangent);

		newIndexArray[i] = numVerts++;
		indRemap[i] = i;
	}

	for (i = 0; i < numVerts; i++)
		sharedTris[i] = 0;

	for (i = 0; i < numIndexes; i++)
		sharedTris[newIndexArray[i]]++;

	/* set up reverse-index that maps Vertex objects to a list of triangle verts */
	mesh->revIndexes = (mIndexList_t *)Mem_PoolAlloc(sizeof(mIndexList_t) * numVerts, vid_modelPool, 0);
	for (i = 0; i < numVerts; i++) {
		mesh->revIndexes[i].length = 0;
		mesh->revIndexes[i].list = (int32_t *)Mem_PoolAlloc(sizeof(int32_t) * sharedTris[i], vid_modelPool, 0);
	}

	/* merge identical vertexes, storing only unique ones */
	newVertexes = (mAliasVertex_t *)Mem_PoolAlloc(sizeof(mAliasVertex_t) * numVerts * nFrames, vid_modelPool, 0);
	newStcoords = (mAliasCoord_t *)Mem_PoolAlloc(sizeof(mAliasCoord_t) * numVerts, vid_modelPool, 0);
	for (i = 0; i < numIndexes; i++) {
		const int idx = indexArray[indRemap[i]];
		const int idx2 = newIndexArray[i];

		/* add vertex to new vertex array */
		VectorCopy(vertexes[idx].point, newVertexes[idx2].point);
		Vector2Copy(stcoords[idx], newStcoords[idx2]);
		mesh->revIndexes[idx2].list[mesh->revIndexes[idx2].length++] = i;
	}

	/* copy over the points from successive frames */
	for (i = 1; i < nFrames; i++) {
		for (j = 0; j < numIndexes; j++) {
			const int idx = indexArray[indRemap[j]] + (mesh->num_verts * i);
			const int idx2 = newIndexArray[j] + (numVerts * i);

			VectorCopy(vertexes[idx].point, newVertexes[idx2].point);
		}
	}

	/* copy new arrays back into original mesh */
	Mem_Free(mesh->stcoords);
	Mem_Free(mesh->indexes);
	Mem_Free(mesh->vertexes);

	mesh->num_verts = numVerts;
	mesh->vertexes = newVertexes;
	mesh->stcoords = newStcoords;
	mesh->indexes = newIndexArray;
}