/** * @brief RE_AddCoronaToScene * @param[in] org * @param[in] r * @param[in] g * @param[in] b * @param[in] scale * @param[in] id * @param[in] visible */ void RE_AddCoronaToScene(const vec3_t org, float r, float g, float b, float scale, int id, qboolean visible) { corona_t *cor; if (!tr.registered) { return; } if (!visible) { return; } if (r_numcoronas >= MAX_CORONAS) { Ren_Developer("WARNING RE_AddCoronaToScene: Dropping corona, reached MAX_CORONAS\n"); return; } cor = &backEndData->coronas[r_numcoronas++]; VectorCopy(org, cor->origin); cor->color[0] = r; cor->color[1] = g; cor->color[2] = b; cor->scale = scale; cor->id = id; cor->visible = visible; }
/** * @brief GetMDVSurfaceShader * @param[in] ent * @param[in] mdvSurface * @return */ static shader_t *GetMDVSurfaceShader(const trRefEntity_t *ent, mdvSurface_t *mdvSurface) { shader_t *shader = 0; if (ent->e.customShader) { shader = R_GetShaderByHandle(ent->e.customShader); } else if (ent->e.customSkin > 0 && ent->e.customSkin < tr.numSkins) { skin_t *skin; int j; skin = R_GetSkinByHandle(ent->e.customSkin); // match the surface name to something in the skin file shader = tr.defaultShader; for (j = 0; j < skin->numSurfaces; j++) { // the names have both been lowercased if (!strcmp(skin->surfaces[j].name, mdvSurface->name)) { shader = skin->surfaces[j].shader; break; } } if (shader == tr.defaultShader) { Ren_Developer("WARNING: no shader for surface %s in skin %s\n", mdvSurface->name, skin->name); } else if (shader->defaultShader) { Ren_Developer("WARNING: shader %s in skin %s not found\n", shader->name, skin->name); } } else { shader = mdvSurface->shader; } return shader; }
void QDECL Com_DPrintf(const char *msg, ...) { va_list argptr; char text[1024]; va_start(argptr, msg); Q_vsnprintf(text, sizeof(text), msg, argptr); va_end(argptr); Ren_Developer("%s", text); }
void printBits(size_t const size, void const *const ptr) { unsigned char *b = (unsigned char *) ptr; unsigned char byte; int i, j; for (i = size - 1; i >= 0; i--) { for (j = 7; j >= 0; j--) { byte = b[i] & (1 << j); byte >>= j; Ren_Developer("%u", byte); } } }
void R_ShutdownFBOs(void) { int i, j; FBO_t *fbo; Ren_Developer("------- R_ShutdownFBOs -------\n"); if (!glConfig2.framebufferObjectAvailable) { return; } R_BindNullFBO(); for (i = 0; i < tr.numFBOs; i++) { fbo = tr.fbos[i]; for (j = 0; j < glConfig2.maxColorAttachments; j++) { if (fbo->colorBuffers[j].buffer) { glDeleteRenderbuffers(1, &fbo->colorBuffers[j].buffer); } } if (fbo->depthBuffer.buffer) { glDeleteRenderbuffers(1, &fbo->depthBuffer.buffer); } if (fbo->stencilBuffer.buffer) { glDeleteRenderbuffers(1, &fbo->stencilBuffer.buffer); } if (fbo->frameBuffer) { glDeleteFramebuffers(1, &fbo->frameBuffer); } } }
void R_InitFBOs(void) { int i; int width, height; Ren_Developer("------- R_InitFBOs -------\n"); if (!glConfig2.framebufferObjectAvailable) { return; } R_CheckDefaultBuffer(); tr.numFBOs = 0; GL_CheckErrors(); // make sure the render thread is stopped R_IssuePendingRenderCommands(); { // forward shading if (glConfig2.textureNPOTAvailable) { width = glConfig.vidWidth; height = glConfig.vidHeight; } else { width = NearestPowerOfTwo(glConfig.vidWidth); height = NearestPowerOfTwo(glConfig.vidHeight); } // deferredRender FBO for the HDR or LDR context tr.deferredRenderFBO = R_CreateFBO("_deferredRender", width, height); R_BindFBO(tr.deferredRenderFBO); if (r_hdrRendering->integer && glConfig2.textureFloatAvailable) { R_CreateFBOColorBuffer(tr.deferredRenderFBO, GL_RGBA16F_ARB, 0); } else { R_CreateFBOColorBuffer(tr.deferredRenderFBO, GL_RGBA, 0); } R_AttachFBOTexture2D(GL_TEXTURE_2D, tr.deferredRenderFBOImage->texnum, 0); R_CreateFBODepthBuffer(tr.deferredRenderFBO, GL_DEPTH_COMPONENT24_ARB); R_AttachFBOTextureDepth(tr.depthRenderImage->texnum); R_CheckFBO(tr.deferredRenderFBO); } if (glConfig2.framebufferBlitAvailable) { if (glConfig2.textureNPOTAvailable) { width = glConfig.vidWidth; height = glConfig.vidHeight; } else { width = NearestPowerOfTwo(glConfig.vidWidth); height = NearestPowerOfTwo(glConfig.vidHeight); } tr.occlusionRenderFBO = R_CreateFBO("_occlusionRender", width, height); R_BindFBO(tr.occlusionRenderFBO); #if 0 if (glConfig2.framebufferPackedDepthStencilAvailable) { //R_CreateFBOColorBuffer(tr.occlusionRenderFBO, GL_ALPHA32F_ARB, 0); R_CreateFBOPackedDepthStencilBuffer(tr.occlusionRenderFBO, GL_DEPTH24_STENCIL8); } else { //R_CreateFBOColorBuffer(tr.occlusionRenderFBO, GL_RGBA, 0); R_CreateFBODepthBuffer(tr.occlusionRenderFBO, GL_DEPTH_COMPONENT24); } #else R_CreateFBODepthBuffer(tr.occlusionRenderFBO, GL_DEPTH_COMPONENT24); #endif R_CreateFBOColorBuffer(tr.occlusionRenderFBO, GL_RGBA, 0); R_AttachFBOTexture2D(GL_TEXTURE_2D, tr.occlusionRenderFBOImage->texnum, 0); R_CheckFBO(tr.occlusionRenderFBO); } if (r_shadows->integer >= SHADOWING_ESM16 && glConfig2.textureFloatAvailable) { // shadowMap FBOs for shadow mapping offscreen rendering for (i = 0; i < MAX_SHADOWMAPS; i++) { width = height = shadowMapResolutions[i]; tr.shadowMapFBO[i] = R_CreateFBO(va("_shadowMap%d", i), width, height); R_BindFBO(tr.shadowMapFBO[i]); if (r_shadows->integer == SHADOWING_ESM32) { R_CreateFBOColorBuffer(tr.shadowMapFBO[i], GL_ALPHA32F_ARB, 0); } else if (r_shadows->integer == SHADOWING_VSM32) { R_CreateFBOColorBuffer(tr.shadowMapFBO[i], GL_LUMINANCE_ALPHA32F_ARB, 0); } else if (r_shadows->integer == SHADOWING_EVSM32) { if (r_evsmPostProcess->integer) { R_CreateFBOColorBuffer(tr.shadowMapFBO[i], GL_ALPHA32F_ARB, 0); } else { R_CreateFBOColorBuffer(tr.shadowMapFBO[i], GL_RGBA32F_ARB, 0); } } else { R_CreateFBOColorBuffer(tr.shadowMapFBO[i], GL_RGBA16F_ARB, 0); } R_CreateFBODepthBuffer(tr.shadowMapFBO[i], GL_DEPTH_COMPONENT24_ARB); R_CheckFBO(tr.shadowMapFBO[i]); } // sun requires different resolutions for (i = 0; i < MAX_SHADOWMAPS; i++) { width = height = sunShadowMapResolutions[i]; tr.sunShadowMapFBO[i] = R_CreateFBO(va("_sunShadowMap%d", i), width, height); R_BindFBO(tr.sunShadowMapFBO[i]); if (r_shadows->integer == SHADOWING_ESM32) { R_CreateFBOColorBuffer(tr.sunShadowMapFBO[i], GL_ALPHA32F_ARB, 0); } else if (r_shadows->integer == SHADOWING_VSM32) { R_CreateFBOColorBuffer(tr.sunShadowMapFBO[i], GL_LUMINANCE_ALPHA32F_ARB, 0); } else if (r_shadows->integer == SHADOWING_EVSM32) { if (!r_evsmPostProcess->integer) { R_CreateFBOColorBuffer(tr.sunShadowMapFBO[i], GL_RGBA32F_ARB, 0); } } else { R_CreateFBOColorBuffer(tr.sunShadowMapFBO[i], GL_RGBA16F_ARB, 0); } R_CreateFBODepthBuffer(tr.sunShadowMapFBO[i], GL_DEPTH_COMPONENT24_ARB); if (r_shadows->integer == SHADOWING_EVSM32 && r_evsmPostProcess->integer) { R_AttachFBOTextureDepth(tr.sunShadowMapFBOImage[i]->texnum); /* Since we don't have a color attachment the framebuffer will be considered incomplete. Consequently, we must inform the driver that we do not wish to render to the color buffer. We do this with a call to set the draw-buffer and read-buffer to GL_NONE: */ glDrawBuffer(GL_NONE); glReadBuffer(GL_NONE); } R_CheckFBO(tr.sunShadowMapFBO[i]); } } { if (glConfig2.textureNPOTAvailable) { width = glConfig.vidWidth; height = glConfig.vidHeight; } else { width = NearestPowerOfTwo(glConfig.vidWidth); height = NearestPowerOfTwo(glConfig.vidHeight); } // portalRender FBO for portals, mirrors, water, cameras et cetera tr.portalRenderFBO = R_CreateFBO("_portalRender", width, height); R_BindFBO(tr.portalRenderFBO); if (r_hdrRendering->integer && glConfig2.textureFloatAvailable) { R_CreateFBOColorBuffer(tr.portalRenderFBO, GL_RGBA16F_ARB, 0); } else { R_CreateFBOColorBuffer(tr.portalRenderFBO, GL_RGBA, 0); } R_AttachFBOTexture2D(GL_TEXTURE_2D, tr.portalRenderImage->texnum, 0); R_CheckFBO(tr.portalRenderFBO); } { if (glConfig2.textureNPOTAvailable) { width = glConfig.vidWidth * 0.25f; height = glConfig.vidHeight * 0.25f; } else { width = NearestPowerOfTwo(glConfig.vidWidth * 0.25f); height = NearestPowerOfTwo(glConfig.vidHeight * 0.25f); } tr.downScaleFBO_quarter = R_CreateFBO("_downScale_quarter", width, height); R_BindFBO(tr.downScaleFBO_quarter); if (r_hdrRendering->integer && glConfig2.textureFloatAvailable) { R_CreateFBOColorBuffer(tr.downScaleFBO_quarter, GL_RGBA16F_ARB, 0); } else { R_CreateFBOColorBuffer(tr.downScaleFBO_quarter, GL_RGBA, 0); } R_AttachFBOTexture2D(GL_TEXTURE_2D, tr.downScaleFBOImage_quarter->texnum, 0); R_CheckFBO(tr.downScaleFBO_quarter); tr.downScaleFBO_64x64 = R_CreateFBO("_downScale_64x64", 64, 64); R_BindFBO(tr.downScaleFBO_64x64); if (r_hdrRendering->integer && glConfig2.textureFloatAvailable) { R_CreateFBOColorBuffer(tr.downScaleFBO_64x64, GL_RGBA16F_ARB, 0); } else { R_CreateFBOColorBuffer(tr.downScaleFBO_64x64, GL_RGBA, 0); } R_AttachFBOTexture2D(GL_TEXTURE_2D, tr.downScaleFBOImage_64x64->texnum, 0); R_CheckFBO(tr.downScaleFBO_64x64); #if 0 tr.downScaleFBO_16x16 = R_CreateFBO("_downScale_16x16", 16, 16); R_BindFBO(tr.downScaleFBO_16x16); if (r_hdrRendering->integer && glConfig2.textureFloatAvailable) { R_CreateFBOColorBuffer(tr.downScaleFBO_16x16, GL_RGBA16F_ARB, 0); } else { R_CreateFBOColorBuffer(tr.downScaleFBO_16x16, GL_RGBA, 0); } R_AttachFBOTexture2D(GL_TEXTURE_2D, tr.downScaleFBOImage_16x16->texnum, 0); R_CheckFBO(tr.downScaleFBO_16x16); tr.downScaleFBO_4x4 = R_CreateFBO("_downScale_4x4", 4, 4); R_BindFBO(tr.downScaleFBO_4x4); if (r_hdrRendering->integer && glConfig2.textureFloatAvailable) { R_CreateFBOColorBuffer(tr.downScaleFBO_4x4, GL_RGBA16F_ARB, 0); } else { R_CreateFBOColorBuffer(tr.downScaleFBO_4x4, GL_RGBA, 0); } R_AttachFBOTexture2D(GL_TEXTURE_2D, tr.downScaleFBOImage_4x4->texnum, 0); R_CheckFBO(tr.downScaleFBO_4x4); tr.downScaleFBO_1x1 = R_CreateFBO("_downScale_1x1", 1, 1); R_BindFBO(tr.downScaleFBO_1x1); if (r_hdrRendering->integer && glConfig2.textureFloatAvailable) { R_CreateFBOColorBuffer(tr.downScaleFBO_1x1, GL_RGBA16F_ARB, 0); } else { R_CreateFBOColorBuffer(tr.downScaleFBO_1x1, GL_RGBA, 0); } R_AttachFBOTexture2D(GL_TEXTURE_2D, tr.downScaleFBOImage_1x1->texnum, 0); R_CheckFBO(tr.downScaleFBO_1x1); #endif if (glConfig2.textureNPOTAvailable) { width = glConfig.vidWidth * 0.25f; height = glConfig.vidHeight * 0.25f; } else { width = NearestPowerOfTwo(glConfig.vidWidth * 0.25f); height = NearestPowerOfTwo(glConfig.vidHeight * 0.25f); } tr.contrastRenderFBO = R_CreateFBO("_contrastRender", width, height); R_BindFBO(tr.contrastRenderFBO); if (r_hdrRendering->integer && glConfig2.textureFloatAvailable) { R_CreateFBOColorBuffer(tr.contrastRenderFBO, GL_RGBA16F_ARB, 0); } else { R_CreateFBOColorBuffer(tr.contrastRenderFBO, GL_RGBA, 0); } R_AttachFBOTexture2D(GL_TEXTURE_2D, tr.contrastRenderFBOImage->texnum, 0); R_CheckFBO(tr.contrastRenderFBO); for (i = 0; i < 2; i++) { tr.bloomRenderFBO[i] = R_CreateFBO(va("_bloomRender%d", i), width, height); R_BindFBO(tr.bloomRenderFBO[i]); if (r_hdrRendering->integer && glConfig2.textureFloatAvailable) { R_CreateFBOColorBuffer(tr.bloomRenderFBO[i], GL_RGBA16F_ARB, 0); } else { R_CreateFBOColorBuffer(tr.bloomRenderFBO[i], GL_RGBA, 0); } R_AttachFBOTexture2D(GL_TEXTURE_2D, tr.bloomRenderFBOImage[i]->texnum, 0); R_CheckFBO(tr.bloomRenderFBO[i]); } } GL_CheckErrors(); R_BindNullFBO(); }
/* ============== R_AddAnimSurfaces ============== */ void R_AddAnimSurfaces(trRefEntity_t *ent) { mdsHeader_t *header = tr.currentModel->model.mds; mdsSurface_t *surface; shader_t *shader = 0; int i, fogNum, cull; qboolean personalModel = (ent->e.renderfx & RF_THIRD_PERSON) && !tr.viewParms.isPortal; // don't add third_person objects if not in a portal // cull the entire model if merged bounding box of both frames // is outside the view frustum. cull = R_CullModel(header, ent); if (cull == CULL_OUT) { return; } // set up lighting now that we know we aren't culled if (!personalModel || r_shadows->integer > 1) { R_SetupEntityLighting(&tr.refdef, ent); } // see if we are in a fog volume fogNum = R_ComputeFogNum(header, ent); surface = ( mdsSurface_t * )((byte *)header + header->ofsSurfaces); for (i = 0 ; i < header->numSurfaces ; i++) { if (ent->e.customShader) { shader = R_GetShaderByHandle(ent->e.customShader); } else if (ent->e.customSkin > 0 && ent->e.customSkin < tr.numSkins) { skin_t *skin; int hash; int j; skin = R_GetSkinByHandle(ent->e.customSkin); // match the surface name to something in the skin file shader = tr.defaultShader; if (ent->e.renderfx & RF_BLINK) { char *s = va("%s_b", surface->name); // append '_b' for 'blink' hash = Com_HashKey(s, strlen(s)); for (j = 0 ; j < skin->numSurfaces ; j++) { if (hash != skin->surfaces[j]->hash) { continue; } if (!strcmp(skin->surfaces[j]->name, s)) { shader = skin->surfaces[j]->shader; break; } } } if (shader == tr.defaultShader) // blink reference in skin was not found { hash = Com_HashKey(surface->name, sizeof(surface->name)); for (j = 0 ; j < skin->numSurfaces ; j++) { // the names have both been lowercased if (hash != skin->surfaces[j]->hash) { continue; } if (!strcmp(skin->surfaces[j]->name, surface->name)) { shader = skin->surfaces[j]->shader; break; } } } if (shader == tr.defaultShader) { Ren_Developer("WARNING: no shader for surface %s in skin %s\n", surface->name, skin->name); } else if (shader->defaultShader) { Ren_Developer("WARNING: shader %s in skin %s not found\n", shader->name, skin->name); } } else { shader = R_GetShaderByHandle(surface->shaderIndex); } // don't add third_person objects if not viewing through a portal if (!personalModel) { R_AddDrawSurf((void *)surface, shader, fogNum, 0, 0); } surface = ( mdsSurface_t * )((byte *)surface + surface->ofsEnd); } }
/* ===================== R_AddPolysToScene ===================== */ static void R_AddPolysToScene(qhandle_t hShader, int numVerts, const polyVert_t *verts, int numPolys) { srfPoly_t *poly; int i, j; int fogIndex; fog_t *fog; vec3_t bounds[2]; if (!tr.registered) { return; } if (!r_drawpolies->integer) { return; } if (!hShader) { Ren_Developer("WARNING: RE_AddPolyToScene: NULL poly shader\n"); return; } for (j = 0; j < numPolys; j++) { if (r_numPolyVerts + numVerts >= r_maxpolyverts->integer || r_numPolys >= r_maxpolys->integer) { /* NOTE TTimo this was initially a PRINT_WARNING but it happens a lot with high fighting scenes and particles since we don't plan on changing the const and making for room for those effects simply cut this message to developer only */ Ren_Developer("WARNING: RE_AddPolyToScene: r_maxPolyVerts or r_maxPolys reached\n"); return; } poly = &backEndData->polys[r_numPolys]; poly->surfaceType = SF_POLY; poly->hShader = hShader; poly->numVerts = numVerts; poly->verts = &backEndData->polyVerts[r_numPolyVerts]; Com_Memcpy(poly->verts, &verts[numVerts * j], numVerts * sizeof(*verts)); // done. r_numPolys++; r_numPolyVerts += numVerts; // if no world is loaded if (tr.world == NULL) { fogIndex = 0; } // see if it is in a fog volume else if (tr.world->numFogs == 1) { fogIndex = 0; } else { // find which fog volume the poly is in VectorCopy(poly->verts[0].xyz, bounds[0]); VectorCopy(poly->verts[0].xyz, bounds[1]); for (i = 1; i < poly->numVerts; i++) { AddPointToBounds(poly->verts[i].xyz, bounds[0], bounds[1]); } for (fogIndex = 1; fogIndex < tr.world->numFogs; fogIndex++) { fog = &tr.world->fogs[fogIndex]; if (BoundsIntersect(bounds[0], bounds[1], fog->bounds[0], fog->bounds[1])) { break; } } if (fogIndex == tr.world->numFogs) { fogIndex = 0; } } poly->fogIndex = fogIndex; } }
/** * @brief Loads in a model for the given name * @param[in] name * @return Zero will be returned if the model fails to load. * An entry will be retained for failed models as an * optimization to prevent disk rescanning if they are * asked for again. */ qhandle_t RE_RegisterModel(const char *name) { model_t *mod; byte *buffer; int bufferLen = 0; int lod; int ident; qboolean loaded = qfalse; qhandle_t hModel; int numLoaded; if (!name || !name[0]) { Ren_Developer("RE_RegisterModel: NULL name\n"); return 0; } else { Ren_Developer("RE_RegisterModel model: %s\n", name); } if (strlen(name) >= MAX_QPATH) { Ren_Print("Model name exceeds MAX_QPATH\n"); return 0; } // search the currently loaded models for (hModel = 1; hModel < tr.numModels; hModel++) { mod = tr.models[hModel]; if (!strcmp(mod->name, name)) { if (mod->type == MOD_BAD) { Ren_Warning("RE_RegisterModel: bad model '%s' - already registered but in bad condition - returning 0\n", name); return 0; } return hModel; } } // allocate a new model_t if ((mod = R_AllocModel()) == NULL) { Ren_Warning("RE_RegisterModel: R_AllocModel() failed for '%s'\n", name); return 0; } // only set the name after the model has been successfully loaded Q_strncpyz(mod->name, name, sizeof(mod->name)); // make sure the render thread is stopped R_IssuePendingRenderCommands(); mod->numLods = 0; // load the files numLoaded = 0; if (strstr(name, ".mds") || strstr(name, ".mdm") || strstr(name, ".mdx") || strstr(name, ".md5mesh") || strstr(name, ".psk")) { // try loading skeletal file loaded = qfalse; bufferLen = ri.FS_ReadFile(name, (void **)&buffer); if (buffer) { ident = LittleLong(*(unsigned *)buffer); #if 0 if (ident == MDS_IDENT) { loaded = R_LoadMDS(mod, buffer, name); } else #endif if (ident == MDM_IDENT) { loaded = R_LoadMDM(mod, buffer, name); } else if (ident == MDX_IDENT) { loaded = R_LoadMDX(mod, buffer, name); } #if defined(USE_REFENTITY_ANIMATIONSYSTEM) if (!Q_stricmpn((const char *)buffer, "MD5Version", 10)) { loaded = R_LoadMD5(mod, buffer, bufferLen, name); } else if (!Q_stricmpn((const char *)buffer, PSK_IDENTSTRING, PSK_IDENTLEN)) { loaded = R_LoadPSK(mod, buffer, bufferLen, name); } #endif ri.FS_FreeFile(buffer); } if (loaded) { // make sure the VBO glState entries are save R_BindNullVBO(); R_BindNullIBO(); return mod->index; } } for (lod = MD3_MAX_LODS - 1; lod >= 0; lod--) { char filename[1024]; buffer = NULL; strcpy(filename, name); if (lod != 0) { char namebuf[80]; if (strrchr(filename, '.')) { *strrchr(filename, '.') = 0; } sprintf(namebuf, "_%d.md3", lod); strcat(filename, namebuf); } filename[strlen(filename) - 1] = '3'; // try MD3 first (changed order for 2.76) if (ri.FS_FOpenFileRead(filename, NULL, qfalse) > 0) { ri.FS_ReadFile(filename, (void **)&buffer); } if (!buffer) { filename[strlen(filename) - 1] = 'c'; // try MDC second if (ri.FS_FOpenFileRead(filename, NULL, qfalse) > 0) { ri.FS_ReadFile(filename, (void **)&buffer); } if (!buffer) { continue; } } ident = LittleLong(*(unsigned *)buffer); if (ident != MD3_IDENT && ident != MDC_IDENT) { Ren_Warning("RE_RegisterModel: unknown fileid for %s\n", name); ri.FS_FreeFile(buffer); goto fail; } if (ident == MD3_IDENT) { loaded = R_LoadMD3(mod, lod, buffer, bufferLen, name); } else if (ident == MDC_IDENT) { loaded = R_LoadMDC(mod, lod, buffer, bufferLen, name); } ri.FS_FreeFile(buffer); if (!loaded) { if (lod == 0) { goto fail; } else { break; } } else { // make sure the VBO glState entries are save R_BindNullVBO(); R_BindNullIBO(); mod->numLods++; numLoaded++; // if we have a valid model and are biased // so that we won't see any higher detail ones, // stop loading them //if ( lod <= r_lodbias->integer ) { //break; //} } } // make sure the VBO glState entries are save R_BindNullVBO(); R_BindNullIBO(); if (numLoaded) { // duplicate into higher lod spots that weren't // loaded, in case the user changes r_lodbias on the fly for (lod--; lod >= 0; lod--) { mod->numLods++; mod->mdv[lod] = mod->mdv[lod + 1]; } return mod->index; } #ifdef LEGACY_DEBUG else { Ren_Warning("couldn't load '%s'\n", name); } #endif fail: // we still keep the model_t around, so if the model name is asked for // again, we won't bother scanning the filesystem mod->type = MOD_BAD; // make sure the VBO glState entries are save R_BindNullVBO(); R_BindNullIBO(); return 0; }
/* ===================== RE_AddPolysToScene ===================== */ void RE_AddPolysToScene(qhandle_t hShader, int numVerts, const polyVert_t *verts, int numPolys) { srfPoly_t *poly; int i; int fogIndex; fog_t *fog; vec3_t bounds[2]; int j; if (!tr.registered) { return; } if (!hShader) { Ren_Warning("WARNING RE_AddPolysToScene: NULL poly shader\n"); return; } for (j = 0; j < numPolys; j++) { if (r_numpolyverts + numVerts >= r_maxpolyverts->integer) { Ren_Developer("WARNING RE_AddPolysToScene: r_maxpolyverts[%i] reached. r_numpolyverts: %i - numVerts: %i - numPolys %i - shader %i\n", r_maxpolyverts->integer, r_numpolyverts, numVerts, numPolys, hShader); return; } if (r_numpolys >= r_maxpolys->integer) { Ren_Developer("WARNING RE_AddPolysToScene: r_maxpolys[%i] reached. r_numpolys: %i\n", r_maxpolys->integer, r_numpolys); return; } poly = &backEndData->polys[r_numpolys]; poly->surfaceType = SF_POLY; poly->hShader = hShader; poly->numVerts = numVerts; poly->verts = &backEndData->polyVerts[r_numpolyverts]; memcpy(poly->verts, &verts[numVerts * j], numVerts * sizeof(*verts)); r_numpolys++; r_numpolyverts += numVerts; // if no world is loaded if (tr.world == NULL) { fogIndex = 0; } // see if it is in a fog volume else if (tr.world->numfogs == 1) { fogIndex = 0; } else { // find which fog volume the poly is in VectorCopy(poly->verts[0].xyz, bounds[0]); VectorCopy(poly->verts[0].xyz, bounds[1]); for (i = 1 ; i < poly->numVerts ; i++) { AddPointToBounds(poly->verts[i].xyz, bounds[0], bounds[1]); } for (fogIndex = 1 ; fogIndex < tr.world->numfogs ; fogIndex++) { fog = &tr.world->fogs[fogIndex]; if (bounds[1][0] >= fog->bounds[0][0] && bounds[1][1] >= fog->bounds[0][1] && bounds[1][2] >= fog->bounds[0][2] && bounds[0][0] <= fog->bounds[1][0] && bounds[0][1] <= fog->bounds[1][1] && bounds[0][2] <= fog->bounds[1][2]) { break; } } if (fogIndex == tr.world->numfogs) { fogIndex = 0; } } poly->fogIndex = fogIndex; } }
/** * @brief Finds and loads all .shader files, combining them into * a single large text block that can be scanned for shader names */ int ScanAndLoadShaderFilesR1() { char **shaderFiles; char *buffers[MAX_SHADER_FILES]; char *p; int numShaderFiles, i; char *oldp, *token, *textEnd; char **hashMem; int shaderTextHashTableSizes[MAX_SHADERTEXT_HASH], hash; unsigned int size; char filename[MAX_QPATH]; long sum = 0, summand; Com_Memset(buffers, 0, MAX_SHADER_FILES); Com_Memset(shaderTextHashTableSizes, 0, MAX_SHADER_FILES); // scan for shader files shaderFiles = ri.FS_ListFiles("scripts", ".shader", &numShaderFiles); if (!shaderFiles || !numShaderFiles) { Ren_Print("----- ScanAndLoadShaderFilesR1 (no files)-----\n"); return 0; } Ren_Print("----- ScanAndLoadShaderFilesR1 (%i files)-----\n", numShaderFiles); if (numShaderFiles >= MAX_SHADER_FILES) { Ren_Drop("MAX_SHADER_FILES limit is reached!"); } // load and parse shader files for (i = 0; i < numShaderFiles; i++) { Com_sprintf(filename, sizeof(filename), "scripts/%s", shaderFiles[i]); COM_BeginParseSession(filename); Ren_Developer("...loading '%s'\n", filename); summand = ri.FS_ReadFile(filename, (void **)&buffers[i]); if (!buffers[i]) { Ren_Drop("Couldn't load %s", filename); // in this case shader file is cought/listed but the file can't be read - drop! } p = buffers[i]; while (1) { token = COM_ParseExt(&p, qtrue); if (!*token) { break; } // Step over the "table"/"guide" and the name if (!Q_stricmp(token, "table") || !Q_stricmp(token, "guide")) { token = COM_ParseExt2(&p, qtrue); if (!*token) { break; } } oldp = p; token = COM_ParseExt2(&p, qtrue); if (token[0] != '{' && token[1] != '\0') { Ren_Warning("WARNING: Bad shader file %s has incorrect syntax near token '%s' line %i\n", filename, token, COM_GetCurrentParseLine()); ri.FS_FreeFile(buffers[i]); buffers[i] = NULL; break; } SkipBracedSection(&oldp); p = oldp; } if (buffers[i]) { sum += summand; } } // build single large buffer s_shaderTextR1 = (char *)ri.Hunk_Alloc(sum + numShaderFiles * 2, h_low); s_shaderTextR1[0] = '\0'; textEnd = s_shaderTextR1; // free in reverse order, so the temp files are all dumped for (i = numShaderFiles - 1; i >= 0 ; i--) { if (!buffers[i]) { continue; } strcat(textEnd, buffers[i]); strcat(textEnd, "\n"); textEnd += strlen(textEnd); ri.FS_FreeFile(buffers[i]); } COM_Compress(s_shaderTextR1); // free up memory ri.FS_FreeFileList(shaderFiles); Com_Memset(shaderTextHashTableSizes, 0, sizeof(shaderTextHashTableSizes)); size = 0; p = s_shaderTextR1; // look for shader names while (1) { token = COM_ParseExt(&p, qtrue); if (token[0] == 0) { break; } // skip shader tables if (!Q_stricmp(token, "table")) { // skip table name (void) COM_ParseExt2(&p, qtrue); SkipBracedSection(&p); } // support shader templates else if (!Q_stricmp(token, "guide")) { // parse shader name token = COM_ParseExt2(&p, qtrue); //Ren_Print("...guided '%s'\n", token); hash = generateHashValue(token, MAX_SHADERTEXT_HASH); shaderTextHashTableSizes[hash]++; size++; // skip guide name token = COM_ParseExt2(&p, qtrue); // skip parameters token = COM_ParseExt2(&p, qtrue); if (Q_stricmp(token, "(")) { Ren_Warning("expected ( found '%s'\n", token); break; } while (1) { token = COM_ParseExt2(&p, qtrue); if (!token[0]) { break; } if (!Q_stricmp(token, ")")) { break; } } if (Q_stricmp(token, ")")) { Ren_Warning("expected ( found '%s'\n", token); break; } } else { hash = generateHashValue(token, MAX_SHADERTEXT_HASH); shaderTextHashTableSizes[hash]++; size++; SkipBracedSection(&p); } } //Ren_Print("Shader hash table size %i\n", size); size += MAX_SHADERTEXT_HASH; hashMem = (char **)ri.Hunk_Alloc(size * sizeof(char *), h_low); for (i = 0; i < MAX_SHADERTEXT_HASH; i++) { shaderTextHashTableR1[i] = hashMem; hashMem += shaderTextHashTableSizes[i] + 1; } Com_Memset(shaderTextHashTableSizes, 0, sizeof(shaderTextHashTableSizes)); p = s_shaderTextR1; // look for shader names while (1) { oldp = p; token = COM_ParseExt(&p, qtrue); if (token[0] == 0) { break; } // parse shader tables if (!Q_stricmp(token, "table")) { int depth; float values[FUNCTABLE_SIZE]; int numValues; shaderTable_t *tb; qboolean alreadyCreated; Com_Memset(&values, 0, sizeof(values)); Com_Memset(&table, 0, sizeof(table)); token = COM_ParseExt2(&p, qtrue); Q_strncpyz(table.name, token, sizeof(table.name)); // check if already created alreadyCreated = qfalse; hash = generateHashValue(table.name, MAX_SHADERTABLE_HASH); for (tb = shaderTableHashTable[hash]; tb; tb = tb->next) { if (Q_stricmp(tb->name, table.name) == 0) { // match found alreadyCreated = qtrue; break; } } depth = 0; numValues = 0; do { token = COM_ParseExt2(&p, qtrue); if (!Q_stricmp(token, "snap")) { table.snap = qtrue; } else if (!Q_stricmp(token, "clamp")) { table.clamp = qtrue; } else if (token[0] == '{') { depth++; } else if (token[0] == '}') { depth--; } else if (token[0] == ',') { continue; } else { if (numValues == FUNCTABLE_SIZE) { Ren_Warning("WARNING: FUNCTABLE_SIZE hit\n"); break; } values[numValues++] = atof(token); } } while (depth && p); if (!alreadyCreated) { Ren_Developer("...generating '%s'\n", table.name); GeneratePermanentShaderTable(values, numValues); } } // support shader templates else if (!Q_stricmp(token, "guide")) { // parse shader name oldp = p; token = COM_ParseExt2(&p, qtrue); //Ren_Print("...guided '%s'\n", token); hash = generateHashValue(token, MAX_SHADERTEXT_HASH); shaderTextHashTableR1[hash][shaderTextHashTableSizes[hash]++] = oldp; // skip guide name token = COM_ParseExt2(&p, qtrue); // skip parameters token = COM_ParseExt2(&p, qtrue); if (Q_stricmp(token, "(")) { Ren_Warning("expected ( found '%s'\n", token); break; } while (1) { token = COM_ParseExt2(&p, qtrue); if (!token[0]) { break; } if (!Q_stricmp(token, ")")) { break; } } if (Q_stricmp(token, ")")) { Ren_Warning("expected ( found '%s'\n", token); break; } } else { hash = generateHashValue(token, MAX_SHADERTEXT_HASH); shaderTextHashTableR1[hash][shaderTextHashTableSizes[hash]++] = oldp; SkipBracedSection(&p); } } return numShaderFiles; }
/* ============== R_AddMD5Surfaces ============== */ void R_AddMD5Surfaces(trRefEntity_t *ent) { md5Model_t *model = tr.currentModel->md5; md5Surface_t *surface; shader_t *shader; int i; qboolean personalModel = (ent->e.renderfx & RF_THIRD_PERSON) && !tr.viewParms.isPortal; // don't add third_person objects if not in a portal int fogNum; // cull the entire model if merged bounding box of both frames // is outside the view frustum R_CullMD5(ent); if (ent->cull == CULL_OUT) { return; } // set up world bounds for light intersection tests R_SetupEntityWorldBounds(ent); // set up lighting now that we know we aren't culled if (!personalModel || r_shadows->integer > SHADOWING_BLOB) { R_SetupEntityLighting(&tr.refdef, ent, NULL); } // see if we are in a fog volume fogNum = R_FogWorldBox(ent->worldBounds); if (!r_vboModels->integer || !model->numVBOSurfaces || (!glConfig2.vboVertexSkinningAvailable && ent->e.skeleton.type == SK_ABSOLUTE)) { // finally add surfaces for (i = 0, surface = model->surfaces; i < model->numSurfaces; i++, surface++) { if (ent->e.customShader) { shader = R_GetShaderByHandle(ent->e.customShader); } else if (ent->e.customSkin > 0 && ent->e.customSkin < tr.numSkins) { skin_t *skin; skin = R_GetSkinByHandle(ent->e.customSkin); // match the surface name to something in the skin file shader = tr.defaultShader; // FIXME: replace MD3_MAX_SURFACES for skin_t::surfaces if (i >= 0 && i < skin->numSurfaces && skin->surfaces[i]) { shader = skin->surfaces[i]->shader; } if (shader == tr.defaultShader) { Ren_Developer("WARNING: no shader for surface %i in skin %s\n", i, skin->name); } else if (shader->defaultShader) { Ren_Developer("WARNING: shader %s in skin %s not found\n", shader->name, skin->name); } } else { shader = R_GetShaderByHandle(surface->shaderIndex); } // we will add shadows even if the main object isn't visible in the view // don't add third_person objects if not viewing through a portal if (!personalModel) { R_AddDrawSurf((surfaceType_t *)surface, shader, -1, fogNum); } } } else { int i; srfVBOMD5Mesh_t *vboSurface; shader_t *shader; for (i = 0; i < model->numVBOSurfaces; i++) { vboSurface = model->vboSurfaces[i]; if (ent->e.customShader) { shader = R_GetShaderByHandle(ent->e.customShader); } else if (ent->e.customSkin > 0 && ent->e.customSkin < tr.numSkins) { skin_t *skin; skin = R_GetSkinByHandle(ent->e.customSkin); // match the surface name to something in the skin file shader = tr.defaultShader; // FIXME: replace MD3_MAX_SURFACES for skin_t::surfaces //if(i >= 0 && i < skin->numSurfaces && skin->surfaces[i]) if (vboSurface->skinIndex >= 0 && vboSurface->skinIndex < skin->numSurfaces && skin->surfaces[vboSurface->skinIndex]) { shader = skin->surfaces[vboSurface->skinIndex]->shader; } if (shader == tr.defaultShader) { Ren_Developer("WARNING: no shader for surface %i in skin %s\n", i, skin->name); } else if (shader->defaultShader) { Ren_Developer("WARNING: shader %s in skin %s not found\n", shader->name, skin->name); } } else { shader = vboSurface->shader; } // don't add third_person objects if not viewing through a portal if (!personalModel) { R_AddDrawSurf((surfaceType_t *)vboSurface, shader, -1, fogNum); } } } }
/* ================= R_AddMD5Interactions ================= */ void R_AddMD5Interactions(trRefEntity_t *ent, trRefLight_t *light) { int i; md5Model_t *model; md5Surface_t *surface; shader_t *shader = 0; qboolean personalModel; byte cubeSideBits = CUBESIDE_CLIPALL; interactionType_t iaType = IA_DEFAULT; // cull the entire model if merged bounding box of both frames // is outside the view frustum and we don't care about proper shadowing if (ent->cull == CULL_OUT) { if (r_shadows->integer <= SHADOWING_BLOB || light->l.noShadows) { return; } else { iaType = IA_SHADOWONLY; } } // avoid drawing of certain objects #if defined(USE_REFENTITY_NOSHADOWID) if (light->l.inverseShadows) { if (iaType != IA_LIGHTONLY && (light->l.noShadowID && (light->l.noShadowID != ent->e.noShadowID))) { return; } } else { if (iaType != IA_LIGHTONLY && (light->l.noShadowID && (light->l.noShadowID == ent->e.noShadowID))) { return; } } #endif // don't add third_person objects if not in a portal personalModel = (ent->e.renderfx & RF_THIRD_PERSON) && !tr.viewParms.isPortal; model = tr.currentModel->md5; // do a quick AABB cull if (!BoundsIntersect(light->worldBounds[0], light->worldBounds[1], ent->worldBounds[0], ent->worldBounds[1])) { tr.pc.c_dlightSurfacesCulled += model->numSurfaces; return; } // do a more expensive and precise light frustum cull if (!r_noLightFrustums->integer) { if (R_CullLightWorldBounds(light, ent->worldBounds) == CULL_OUT) { tr.pc.c_dlightSurfacesCulled += model->numSurfaces; return; } } cubeSideBits = R_CalcLightCubeSideBits(light, ent->worldBounds); if (!r_vboModels->integer || !model->numVBOSurfaces || (!glConfig2.vboVertexSkinningAvailable && ent->e.skeleton.type == SK_ABSOLUTE)) { // generate interactions with all surfaces for (i = 0, surface = model->surfaces; i < model->numSurfaces; i++, surface++) { if (ent->e.customShader) { shader = R_GetShaderByHandle(ent->e.customShader); } else if (ent->e.customSkin > 0 && ent->e.customSkin < tr.numSkins) { skin_t *skin; skin = R_GetSkinByHandle(ent->e.customSkin); // match the surface name to something in the skin file shader = tr.defaultShader; // FIXME: replace MD3_MAX_SURFACES for skin_t::surfaces if (i >= 0 && i < skin->numSurfaces && skin->surfaces[i]) { shader = skin->surfaces[i]->shader; } if (shader == tr.defaultShader) { Ren_Developer("R_AddMD5Interactions WARNING: no shader for surface %i in skin %s\n", i, skin->name); } else if (shader->defaultShader) { Ren_Developer("R_AddMD5Interactions WARNING: shader %s in skin %s not found\n", shader->name, skin->name); } } else { shader = R_GetShaderByHandle(surface->shaderIndex); } // skip all surfaces that don't matter for lighting only pass if (shader->isSky || (!shader->interactLight && shader->noShadows)) { continue; } // we will add shadows even if the main object isn't visible in the view // don't add third_person objects if not viewing through a portal if (!personalModel) { R_AddLightInteraction(light, (surfaceType_t *)surface, shader, cubeSideBits, iaType); tr.pc.c_dlightSurfaces++; } } } else { int i; srfVBOMD5Mesh_t *vboSurface; shader_t *shader; for (i = 0; i < model->numVBOSurfaces; i++) { vboSurface = model->vboSurfaces[i]; if (ent->e.customShader) { shader = R_GetShaderByHandle(ent->e.customShader); } else if (ent->e.customSkin > 0 && ent->e.customSkin < tr.numSkins) { skin_t *skin; skin = R_GetSkinByHandle(ent->e.customSkin); // match the surface name to something in the skin file shader = tr.defaultShader; // FIXME: replace MD3_MAX_SURFACES for skin_t::surfaces if (i >= 0 && i < skin->numSurfaces && skin->surfaces[i]) { shader = skin->surfaces[i]->shader; } if (shader == tr.defaultShader) { Ren_Developer("R_AddMD5Interactions WARNING: no shader for surface %i in skin %s\n", i, skin->name); } else if (shader->defaultShader) { Ren_Developer("R_AddMD5Interactions WARNING: shader %s in skin %s not found\n", shader->name, skin->name); } } else { shader = vboSurface->shader; } // skip all surfaces that don't matter for lighting only pass if (shader->isSky || (!shader->interactLight && shader->noShadows)) { continue; } // don't add third_person objects if not viewing through a portal if (!personalModel) { R_AddLightInteraction(light, (surfaceType_t *)vboSurface, shader, cubeSideBits, iaType); tr.pc.c_dlightSurfaces++; } } } }
/** * @brief RE_AddPolyToScene * @param[in] hShader * @param[in] numVerts * @param[in] verts */ void RE_AddPolyToScene(qhandle_t hShader, int numVerts, const polyVert_t *verts) { srfPoly_t *poly; int fogIndex; fog_t *fog; vec3_t bounds[2]; if (!tr.registered) { return; } if (!hShader) { Ren_Warning("WARNING RE_AddPolyToScene: NULL poly shader\n"); return; } if (((r_numpolyverts + numVerts) >= r_maxPolyVerts->integer) || (r_numpolys >= r_maxPolys->integer)) { Ren_Developer("WARNING RE_AddPolyToScene: r_maxpolyverts or r_maxpolys reached\n"); return; } poly = &backEndData->polys[r_numpolys]; poly->surfaceType = SF_POLY; poly->hShader = hShader; poly->numVerts = numVerts; poly->verts = &backEndData->polyVerts[r_numpolyverts]; Com_Memcpy(poly->verts, verts, numVerts * sizeof(*verts)); r_numpolys++; r_numpolyverts += numVerts; // see if it is in a fog volume if (tr.world->numfogs == 1) { fogIndex = 0; } else { int i; // find which fog volume the poly is in VectorCopy(poly->verts[0].xyz, bounds[0]); VectorCopy(poly->verts[0].xyz, bounds[1]); for (i = 1 ; i < poly->numVerts ; i++) { AddPointToBounds(poly->verts[i].xyz, bounds[0], bounds[1]); } for (fogIndex = 1 ; fogIndex < tr.world->numfogs ; fogIndex++) { fog = &tr.world->fogs[fogIndex]; if (bounds[1][0] >= fog->bounds[0][0] && bounds[1][1] >= fog->bounds[0][1] && bounds[1][2] >= fog->bounds[0][2] && bounds[0][0] <= fog->bounds[1][0] && bounds[0][1] <= fog->bounds[1][1] && bounds[0][2] <= fog->bounds[1][2]) { break; } } if (fogIndex == tr.world->numfogs) { fogIndex = 0; } } poly->fogIndex = fogIndex; }
/** * @brief R_AddMDVSurfaces * @param[in,out] ent */ void R_AddMDVSurfaces(trRefEntity_t *ent) { int i; mdvModel_t *model = 0; mdvSurface_t *mdvSurface = 0; shader_t *shader = 0; int lod; qboolean personalModel; int fogNum; // don't add third_person objects if not in a portal personalModel = (ent->e.renderfx & RF_THIRD_PERSON) && !tr.viewParms.isPortal; if (ent->e.renderfx & RF_WRAP_FRAMES) { ent->e.frame %= tr.currentModel->mdv[0]->numFrames; ent->e.oldframe %= tr.currentModel->mdv[0]->numFrames; } // compute LOD if (ent->e.renderfx & RF_FORCENOLOD) { lod = 0; } else { lod = R_ComputeLOD(ent); } // Validate the frames so there is no chance of a crash. // This will write directly into the entity structure, so // when the surfaces are rendered, they don't need to be // range checked again. if ((ent->e.frame >= tr.currentModel->mdv[lod]->numFrames) || (ent->e.frame < 0) || (ent->e.oldframe >= tr.currentModel->mdv[lod]->numFrames) || (ent->e.oldframe < 0)) { //Only spam if the lod level is 0 (lods usually don't have animation frames as they are only seen from a far) if (lod == 0) { Ren_Developer("R_AddMDVSurfaces: no such frame %d to %d for '%s' (%d)\n", ent->e.oldframe, ent->e.frame, tr.currentModel->name, tr.currentModel->mdv[lod]->numFrames); } ent->e.frame = 0; ent->e.oldframe = 0; } model = tr.currentModel->mdv[lod]; // cull the entire model if merged bounding box of both frames // is outside the view frustum. R_CullMDV(model, ent); if (ent->cull == CULL_OUT) { return; } // set up lighting now that we know we aren't culled if (!personalModel || r_shadows->integer > SHADOWING_BLOB) { R_SetupEntityLighting(&tr.refdef, ent, NULL); } // see if we are in a fog volume fogNum = R_FogWorldBox(ent->worldBounds); // draw all surfaces if (r_vboModels->integer && model->numVBOSurfaces) { int i; srfVBOMDVMesh_t *vboSurface; shader_t *shader; for (i = 0; i < model->numVBOSurfaces; i++) { vboSurface = model->vboSurfaces[i]; mdvSurface = vboSurface->mdvSurface; shader = GetMDVSurfaceShader(ent, mdvSurface); // don't add third_person objects if not viewing through a portal if (!personalModel) { R_AddDrawSurf((surfaceType_t *)vboSurface, shader, LIGHTMAP_NONE, fogNum); } } } else { for (i = 0, mdvSurface = model->surfaces; i < model->numSurfaces; i++, mdvSurface++) { shader = GetMDVSurfaceShader(ent, mdvSurface); // we will add shadows even if the main object isn't visible in the view // don't add third_person objects if not viewing through a portal if (!personalModel) { R_AddDrawSurf((surfaceType_t *)mdvSurface, shader, LIGHTMAP_NONE, fogNum); } } } }
qboolean R_LoadPSK(model_t *mod, void *buffer, int bufferSize, const char *modName) { int i, j, k; memStream_t *stream = NULL; axChunkHeader_t chunkHeader; int numPoints; axPoint_t *point; axPoint_t *points = NULL; int numVertexes; axVertex_t *vertex; axVertex_t *vertexes = NULL; //int numSmoothGroups; int numTriangles; axTriangle_t *triangle; axTriangle_t *triangles = NULL; int numMaterials; axMaterial_t *material; axMaterial_t *materials = NULL; int numReferenceBones; axReferenceBone_t *refBone; axReferenceBone_t *refBones = NULL; int numWeights; axBoneWeight_t *axWeight; axBoneWeight_t *axWeights = NULL; md5Model_t *md5; md5Bone_t *md5Bone; md5Weight_t *weight; vec3_t boneOrigin; quat_t boneQuat; //mat4_t boneMat; int materialIndex, oldMaterialIndex; int numRemaining; growList_t sortedTriangles; growList_t vboVertexes; growList_t vboTriangles; growList_t vboSurfaces; int numBoneReferences; int boneReferences[MAX_BONES]; mat4_t unrealToQuake; #define DeallocAll() Com_Dealloc(materials); \ Com_Dealloc(points); \ Com_Dealloc(vertexes); \ Com_Dealloc(triangles); \ Com_Dealloc(refBones); \ Com_Dealloc(axWeights); \ FreeMemStream(stream); //MatrixSetupScale(unrealToQuake, 1, -1, 1); mat4_from_angles(unrealToQuake, 0, 90, 0); stream = AllocMemStream(buffer, bufferSize); GetChunkHeader(stream, &chunkHeader); // check indent again if (Q_stricmpn(chunkHeader.ident, "ACTRHEAD", 8)) { Ren_Warning("R_LoadPSK: '%s' has wrong chunk indent ('%s' should be '%s')\n", modName, chunkHeader.ident, "ACTRHEAD"); DeallocAll(); return qfalse; } PrintChunkHeader(&chunkHeader); mod->type = MOD_MD5; mod->dataSize += sizeof(md5Model_t); md5 = mod->md5 = ri.Hunk_Alloc(sizeof(md5Model_t), h_low); // read points GetChunkHeader(stream, &chunkHeader); if (Q_stricmpn(chunkHeader.ident, "PNTS0000", 8)) { Ren_Warning("R_LoadPSK: '%s' has wrong chunk indent ('%s' should be '%s')\n", modName, chunkHeader.ident, "PNTS0000"); DeallocAll(); return qfalse; } if (chunkHeader.dataSize != sizeof(axPoint_t)) { Ren_Warning("R_LoadPSK: '%s' has wrong chunk dataSize ('%i' should be '%i')\n", modName, chunkHeader.dataSize, ( int ) sizeof(axPoint_t)); DeallocAll(); return qfalse; } PrintChunkHeader(&chunkHeader); numPoints = chunkHeader.numData; points = Com_Allocate(numPoints * sizeof(axPoint_t)); for (i = 0, point = points; i < numPoints; i++, point++) { point->point[0] = MemStreamGetFloat(stream); point->point[1] = MemStreamGetFloat(stream); point->point[2] = MemStreamGetFloat(stream); #if 0 // HACK convert from Unreal coordinate system to the Quake one MatrixTransformPoint2(unrealToQuake, point->point); #endif } // read vertices GetChunkHeader(stream, &chunkHeader); if (Q_stricmpn(chunkHeader.ident, "VTXW0000", 8)) { Ren_Warning("R_LoadPSK: '%s' has wrong chunk indent ('%s' should be '%s')\n", modName, chunkHeader.ident, "VTXW0000"); DeallocAll(); return qfalse; } if (chunkHeader.dataSize != sizeof(axVertex_t)) { Ren_Warning("R_LoadPSK: '%s' has wrong chunk dataSize ('%i' should be '%i')\n", modName, chunkHeader.dataSize, ( int ) sizeof(axVertex_t)); DeallocAll(); return qfalse; } PrintChunkHeader(&chunkHeader); numVertexes = chunkHeader.numData; vertexes = Com_Allocate(numVertexes * sizeof(axVertex_t)); { int tmpVertexInt = -1; // tmp vertex member values - MemStreamGet functions return -1 if they fail // now we print a warning if they do or abort if pointIndex is invalid for (i = 0, vertex = vertexes; i < numVertexes; i++, vertex++) { tmpVertexInt = MemStreamGetShort(stream); if (tmpVertexInt < 0 || tmpVertexInt >= numPoints) { ri.Printf(PRINT_ERROR, "R_LoadPSK: '%s' has vertex with point index out of range (%i while max %i)\n", modName, tmpVertexInt, numPoints); DeallocAll(); return qfalse; } vertex->pointIndex = tmpVertexInt; tmpVertexInt = MemStreamGetShort(stream); if (tmpVertexInt < 0) { Ren_Warning("R_LoadPSK: MemStream NULL or empty (vertex->unknownA)\n"); } vertex->unknownA = tmpVertexInt; vertex->st[0] = MemStreamGetFloat(stream); if (vertex->st[0] == -1) { Ren_Warning("R_LoadPSK: MemStream possibly NULL or empty (vertex->st[0])\n"); } vertex->st[1] = MemStreamGetFloat(stream); if (vertex->st[1] == -1) { Ren_Warning("R_LoadPSK: MemStream possibly NULL or empty (vertex->st[1])\n"); } tmpVertexInt = MemStreamGetC(stream); if (tmpVertexInt < 0) { Ren_Warning("R_LoadPSK: MemStream NULL or empty (vertex->materialIndex)\n"); } vertex->materialIndex = tmpVertexInt; tmpVertexInt = MemStreamGetC(stream); if (tmpVertexInt < 0) { Ren_Warning("R_LoadPSK: MemStream NULL or empty (vertex->materialIndex)\n"); } vertex->reserved = tmpVertexInt; tmpVertexInt = MemStreamGetShort(stream); if (tmpVertexInt < 0) { Ren_Warning("R_LoadPSK: MemStream NULL or empty (vertex->materialIndex)\n"); } vertex->unknownB = tmpVertexInt; #if 0 Ren_Print("R_LoadPSK: axVertex_t(%i):\n" "axVertex:pointIndex: %i\n" "axVertex:unknownA: %i\n" "axVertex::st: %f %f\n" "axVertex:materialIndex: %i\n" "axVertex:reserved: %d\n" "axVertex:unknownB: %d\n", i, vertex->pointIndex, vertex->unknownA, vertex->st[0], vertex->st[1], vertex->materialIndex, vertex->reserved, vertex->unknownB); #endif } // read triangles GetChunkHeader(stream, &chunkHeader); if (Q_stricmpn(chunkHeader.ident, "FACE0000", 8)) { Ren_Warning("R_LoadPSK: '%s' has wrong chunk indent ('%s' should be '%s')\n", modName, chunkHeader.ident, "FACE0000"); DeallocAll(); return qfalse; } if (chunkHeader.dataSize != sizeof(axTriangle_t)) { Ren_Warning("R_LoadPSK: '%s' has wrong chunk dataSize ('%i' should be '%i')\n", modName, chunkHeader.dataSize, ( int ) sizeof(axTriangle_t)); DeallocAll(); return qfalse; } PrintChunkHeader(&chunkHeader); numTriangles = chunkHeader.numData; triangles = Com_Allocate(numTriangles * sizeof(axTriangle_t)); for (i = 0, triangle = triangles; i < numTriangles; i++, triangle++) { for (j = 0; j < 3; j++) //for(j = 2; j >= 0; j--) { tmpVertexInt = MemStreamGetShort(stream); if (tmpVertexInt < 0) { Ren_Warning("R_LoadPSK: '%s' MemStream NULL or empty (triangle->indexes[%i])\n", modName, j); DeallocAll(); return qfalse; } if (tmpVertexInt >= numVertexes) { Ren_Warning("R_LoadPSK: '%s' has triangle with vertex index out of range (%i while max %i)\n", modName, tmpVertexInt, numVertexes); DeallocAll(); return qfalse; } triangle->indexes[j] = tmpVertexInt; } triangle->materialIndex = MemStreamGetC(stream); triangle->materialIndex2 = MemStreamGetC(stream); triangle->smoothingGroups = MemStreamGetLong(stream); } } // read materials GetChunkHeader(stream, &chunkHeader); if (Q_stricmpn(chunkHeader.ident, "MATT0000", 8)) { Ren_Warning("R_LoadPSK: '%s' has wrong chunk indent ('%s' should be '%s')\n", modName, chunkHeader.ident, "MATT0000"); DeallocAll(); return qfalse; } if (chunkHeader.dataSize != sizeof(axMaterial_t)) { Ren_Warning("R_LoadPSK: '%s' has wrong chunk dataSize ('%i' should be '%i')\n", modName, chunkHeader.dataSize, ( int ) sizeof(axMaterial_t)); DeallocAll(); return qfalse; } PrintChunkHeader(&chunkHeader); numMaterials = chunkHeader.numData; materials = Com_Allocate(numMaterials * sizeof(axMaterial_t)); for (i = 0, material = materials; i < numMaterials; i++, material++) { MemStreamRead(stream, material->name, sizeof(material->name)); Ren_Print("R_LoadPSK: material name: '%s'\n", material->name); material->shaderIndex = MemStreamGetLong(stream); material->polyFlags = MemStreamGetLong(stream); material->auxMaterial = MemStreamGetLong(stream); material->auxFlags = MemStreamGetLong(stream); material->lodBias = MemStreamGetLong(stream); material->lodStyle = MemStreamGetLong(stream); } for (i = 0, vertex = vertexes; i < numVertexes; i++, vertex++) { if (vertex->materialIndex < 0 || vertex->materialIndex >= numMaterials) { Ren_Warning("R_LoadPSK: '%s' has vertex with material index out of range (%i while max %i)\n", modName, vertex->materialIndex, numMaterials); DeallocAll(); return qfalse; } } for (i = 0, triangle = triangles; i < numTriangles; i++, triangle++) { if (triangle->materialIndex < 0 || triangle->materialIndex >= numMaterials) { Ren_Warning("R_LoadPSK: '%s' has triangle with material index out of range (%i while max %i)\n", modName, triangle->materialIndex, numMaterials); DeallocAll(); return qfalse; } } // read reference bones GetChunkHeader(stream, &chunkHeader); if (Q_stricmpn(chunkHeader.ident, "REFSKELT", 8)) { Ren_Warning("R_LoadPSK: '%s' has wrong chunk indent ('%s' should be '%s')\n", modName, chunkHeader.ident, "REFSKELT"); DeallocAll(); return qfalse; } if (chunkHeader.dataSize != sizeof(axReferenceBone_t)) { Ren_Warning("R_LoadPSK: '%s' has wrong chunk dataSize ('%i' should be '%i')\n", modName, chunkHeader.dataSize, ( int ) sizeof(axReferenceBone_t)); DeallocAll(); return qfalse; } PrintChunkHeader(&chunkHeader); numReferenceBones = chunkHeader.numData; refBones = Com_Allocate(numReferenceBones * sizeof(axReferenceBone_t)); for (i = 0, refBone = refBones; i < numReferenceBones; i++, refBone++) { MemStreamRead(stream, refBone->name, sizeof(refBone->name)); //Ren_Print("R_LoadPSK: reference bone name: '%s'\n", refBone->name); refBone->flags = MemStreamGetLong(stream); refBone->numChildren = MemStreamGetLong(stream); refBone->parentIndex = MemStreamGetLong(stream); GetBone(stream, &refBone->bone); #if 0 Ren_Print("R_LoadPSK: axReferenceBone_t(%i):\n" "axReferenceBone_t::name: '%s'\n" "axReferenceBone_t::flags: %i\n" "axReferenceBone_t::numChildren %i\n" "axReferenceBone_t::parentIndex: %i\n" "axReferenceBone_t::quat: %f %f %f %f\n" "axReferenceBone_t::position: %f %f %f\n" "axReferenceBone_t::length: %f\n" "axReferenceBone_t::xSize: %f\n" "axReferenceBone_t::ySize: %f\n" "axReferenceBone_t::zSize: %f\n", i, refBone->name, refBone->flags, refBone->numChildren, refBone->parentIndex, refBone->bone.quat[0], refBone->bone.quat[1], refBone->bone.quat[2], refBone->bone.quat[3], refBone->bone.position[0], refBone->bone.position[1], refBone->bone.position[2], refBone->bone.length, refBone->bone.xSize, refBone->bone.ySize, refBone->bone.zSize); #endif } // read bone weights GetChunkHeader(stream, &chunkHeader); if (Q_stricmpn(chunkHeader.ident, "RAWWEIGHTS", 10)) { Ren_Warning("R_LoadPSK: '%s' has wrong chunk indent ('%s' should be '%s')\n", modName, chunkHeader.ident, "RAWWEIGHTS"); DeallocAll(); return qfalse; } if (chunkHeader.dataSize != sizeof(axBoneWeight_t)) { Ren_Warning("R_LoadPSK: '%s' has wrong chunk dataSize ('%i' should be '%i')\n", modName, chunkHeader.dataSize, ( int ) sizeof(axBoneWeight_t)); DeallocAll(); return qfalse; } PrintChunkHeader(&chunkHeader); numWeights = chunkHeader.numData; axWeights = Com_Allocate(numWeights * sizeof(axBoneWeight_t)); for (i = 0, axWeight = axWeights; i < numWeights; i++, axWeight++) { axWeight->weight = MemStreamGetFloat(stream); axWeight->pointIndex = MemStreamGetLong(stream); axWeight->boneIndex = MemStreamGetLong(stream); #if 0 Ren_Print("R_LoadPSK: axBoneWeight_t(%i):\n" "axBoneWeight_t::weight: %f\n" "axBoneWeight_t::pointIndex %i\n" "axBoneWeight_t::boneIndex: %i\n", i, axWeight->weight, axWeight->pointIndex, axWeight->boneIndex); #endif } // // convert the model to an internal MD5 representation // md5->numBones = numReferenceBones; // calc numMeshes <number> /* numSmoothGroups = 0; for(i = 0, triangle = triangles; i < numTriangles; i++, triangle++) { if(triangle->smoothingGroups) { } } */ if (md5->numBones < 1) { Ren_Warning("R_LoadPSK: '%s' has no bones\n", modName); DeallocAll(); return qfalse; } if (md5->numBones > MAX_BONES) { Ren_Warning("R_LoadPSK: '%s' has more than %i bones (%i)\n", modName, MAX_BONES, md5->numBones); DeallocAll(); return qfalse; } //Ren_Print("R_LoadPSK: '%s' has %i bones\n", modName, md5->numBones); // copy all reference bones md5->bones = ri.Hunk_Alloc(sizeof(*md5Bone) * md5->numBones, h_low); for (i = 0, md5Bone = md5->bones, refBone = refBones; i < md5->numBones; i++, md5Bone++, refBone++) { Q_strncpyz(md5Bone->name, refBone->name, sizeof(md5Bone->name)); if (i == 0) { md5Bone->parentIndex = refBone->parentIndex - 1; } else { md5Bone->parentIndex = refBone->parentIndex; } //Ren_Print("R_LoadPSK: '%s' has bone '%s' with parent index %i\n", modName, md5Bone->name, md5Bone->parentIndex); if (md5Bone->parentIndex >= md5->numBones) { DeallocAll(); Ren_Drop("R_LoadPSK: '%s' has bone '%s' with bad parent index %i while numBones is %i", modName, md5Bone->name, md5Bone->parentIndex, md5->numBones); } for (j = 0; j < 3; j++) { boneOrigin[j] = refBone->bone.position[j]; } // I have really no idea why the .psk format stores the first quaternion with inverted quats. // Furthermore only the X and Z components of the first quat are inverted ?!?! if (i == 0) { boneQuat[0] = refBone->bone.quat[0]; boneQuat[1] = -refBone->bone.quat[1]; boneQuat[2] = refBone->bone.quat[2]; boneQuat[3] = refBone->bone.quat[3]; } else { boneQuat[0] = -refBone->bone.quat[0]; boneQuat[1] = -refBone->bone.quat[1]; boneQuat[2] = -refBone->bone.quat[2]; boneQuat[3] = refBone->bone.quat[3]; } VectorCopy(boneOrigin, md5Bone->origin); //MatrixTransformPoint(unrealToQuake, boneOrigin, md5Bone->origin); quat_copy(boneQuat, md5Bone->rotation); //QuatClear(md5Bone->rotation); #if 0 Ren_Print("R_LoadPSK: md5Bone_t(%i):\n" "md5Bone_t::name: '%s'\n" "md5Bone_t::parentIndex: %i\n" "md5Bone_t::quat: %f %f %f %f\n" "md5bone_t::position: %f %f %f\n", i, md5Bone->name, md5Bone->parentIndex, md5Bone->rotation[0], md5Bone->rotation[1], md5Bone->rotation[2], md5Bone->rotation[3], md5Bone->origin[0], md5Bone->origin[1], md5Bone->origin[2]); #endif if (md5Bone->parentIndex >= 0) { vec3_t rotated; quat_t quat; md5Bone_t *parent; parent = &md5->bones[md5Bone->parentIndex]; QuatTransformVector(parent->rotation, md5Bone->origin, rotated); //QuatTransformVector(md5Bone->rotation, md5Bone->origin, rotated); VectorAdd(parent->origin, rotated, md5Bone->origin); QuatMultiply1(parent->rotation, md5Bone->rotation, quat); quat_copy(quat, md5Bone->rotation); } MatrixSetupTransformFromQuat(md5Bone->inverseTransform, md5Bone->rotation, md5Bone->origin); mat4_inverse_self(md5Bone->inverseTransform); #if 0 Ren_Print("R_LoadPSK: md5Bone_t(%i):\n" "md5Bone_t::name: '%s'\n" "md5Bone_t::parentIndex: %i\n" "md5Bone_t::quat: %f %f %f %f\n" "md5bone_t::position: %f %f %f\n", i, md5Bone->name, md5Bone->parentIndex, md5Bone->rotation[0], md5Bone->rotation[1], md5Bone->rotation[2], md5Bone->rotation[3], md5Bone->origin[0], md5Bone->origin[1], md5Bone->origin[2]); #endif } Com_InitGrowList(&vboVertexes, 10000); for (i = 0, vertex = vertexes; i < numVertexes; i++, vertex++) { md5Vertex_t *vboVert = Com_Allocate(sizeof(*vboVert)); for (j = 0; j < 3; j++) { vboVert->position[j] = points[vertex->pointIndex].point[j]; } vboVert->texCoords[0] = vertex->st[0]; vboVert->texCoords[1] = vertex->st[1]; // find number of associated weights vboVert->numWeights = 0; for (j = 0, axWeight = axWeights; j < numWeights; j++, axWeight++) { if (axWeight->pointIndex == vertex->pointIndex && axWeight->weight > 0.0f) { vboVert->numWeights++; } } if (vboVert->numWeights > MAX_WEIGHTS) { DeallocAll(); Ren_Drop("R_LoadPSK: vertex %i requires more weights %i than the maximum of %i in model '%s'", i, vboVert->numWeights, MAX_WEIGHTS, modName); //Ren_Warning( "R_LoadPSK: vertex %i requires more weights %i than the maximum of %i in model '%s'\n", i, vboVert->numWeights, MAX_WEIGHTS, modName); } vboVert->weights = ri.Hunk_Alloc(sizeof(*vboVert->weights) * vboVert->numWeights, h_low); for (j = 0, axWeight = axWeights, k = 0; j < numWeights; j++, axWeight++) { if (axWeight->pointIndex == vertex->pointIndex && axWeight->weight > 0.0f) { weight = ri.Hunk_Alloc(sizeof(*weight), h_low); weight->boneIndex = axWeight->boneIndex; weight->boneWeight = axWeight->weight; // FIXME? weight->offset[0] = refBones[axWeight->boneIndex].bone.xSize; weight->offset[1] = refBones[axWeight->boneIndex].bone.ySize; weight->offset[2] = refBones[axWeight->boneIndex].bone.zSize; vboVert->weights[k++] = weight; } } Com_AddToGrowList(&vboVertexes, vboVert); } ClearBounds(md5->bounds[0], md5->bounds[1]); for (i = 0, vertex = vertexes; i < numVertexes; i++, vertex++) { AddPointToBounds(points[vertex->pointIndex].point, md5->bounds[0], md5->bounds[1]); } #if 0 Ren_Print("R_LoadPSK: AABB (%i %i %i) (%i %i %i)\n", ( int ) md5->bounds[0][0], ( int ) md5->bounds[0][1], ( int ) md5->bounds[0][2], ( int ) md5->bounds[1][0], ( int ) md5->bounds[1][1], ( int ) md5->bounds[1][2]); #endif // sort triangles qsort(triangles, numTriangles, sizeof(axTriangle_t), CompareTrianglesByMaterialIndex); Com_InitGrowList(&sortedTriangles, 1000); for (i = 0, triangle = triangles; i < numTriangles; i++, triangle++) { skelTriangle_t *sortTri = Com_Allocate(sizeof(*sortTri)); for (j = 0; j < 3; j++) { sortTri->indexes[j] = triangle->indexes[j]; sortTri->vertexes[j] = Com_GrowListElement(&vboVertexes, triangle->indexes[j]); } sortTri->referenced = qfalse; Com_AddToGrowList(&sortedTriangles, sortTri); } // calc tangent spaces #if 1 { md5Vertex_t *v0, *v1, *v2; const float *p0, *p1, *p2; const float *t0, *t1, *t2; vec3_t tangent = { 0, 0, 0 }; vec3_t binormal; vec3_t normal; for (j = 0; j < vboVertexes.currentElements; j++) { v0 = Com_GrowListElement(&vboVertexes, j); VectorClear(v0->tangent); VectorClear(v0->binormal); VectorClear(v0->normal); } for (j = 0; j < sortedTriangles.currentElements; j++) { skelTriangle_t *tri = Com_GrowListElement(&sortedTriangles, j); v0 = Com_GrowListElement(&vboVertexes, tri->indexes[0]); v1 = Com_GrowListElement(&vboVertexes, tri->indexes[1]); v2 = Com_GrowListElement(&vboVertexes, tri->indexes[2]); p0 = v0->position; p1 = v1->position; p2 = v2->position; t0 = v0->texCoords; t1 = v1->texCoords; t2 = v2->texCoords; #if 1 R_CalcTangentSpace(tangent, binormal, normal, p0, p1, p2, t0, t1, t2); #else R_CalcNormalForTriangle(normal, p0, p1, p2); R_CalcTangentsForTriangle(tangent, binormal, p0, p1, p2, t0, t1, t2); #endif for (k = 0; k < 3; k++) { float *v; v0 = Com_GrowListElement(&vboVertexes, tri->indexes[k]); v = v0->tangent; VectorAdd(v, tangent, v); v = v0->binormal; VectorAdd(v, binormal, v); v = v0->normal; VectorAdd(v, normal, v); } } for (j = 0; j < vboVertexes.currentElements; j++) { v0 = Com_GrowListElement(&vboVertexes, j); VectorNormalize(v0->tangent); VectorNormalize(v0->binormal); VectorNormalize(v0->normal); } } #else { float bb, s, t; vec3_t bary; vec3_t faceNormal; md5Vertex_t *dv[3]; for (j = 0; j < sortedTriangles.currentElements; j++) { skelTriangle_t *tri = Com_GrowListElement(&sortedTriangles, j); dv[0] = Com_GrowListElement(&vboVertexes, tri->indexes[0]); dv[1] = Com_GrowListElement(&vboVertexes, tri->indexes[1]); dv[2] = Com_GrowListElement(&vboVertexes, tri->indexes[2]); R_CalcNormalForTriangle(faceNormal, dv[0]->position, dv[1]->position, dv[2]->position); // calculate barycentric basis for the triangle bb = (dv[1]->texCoords[0] - dv[0]->texCoords[0]) * (dv[2]->texCoords[1] - dv[0]->texCoords[1]) - (dv[2]->texCoords[0] - dv[0]->texCoords[0]) * (dv[1]->texCoords[1] - dv[0]->texCoords[1]); if (fabs(bb) < 0.00000001f) { continue; } // do each vertex for (k = 0; k < 3; k++) { // calculate s tangent vector s = dv[k]->texCoords[0] + 10.0f; t = dv[k]->texCoords[1]; bary[0] = ((dv[1]->texCoords[0] - s) * (dv[2]->texCoords[1] - t) - (dv[2]->texCoords[0] - s) * (dv[1]->texCoords[1] - t)) / bb; bary[1] = ((dv[2]->texCoords[0] - s) * (dv[0]->texCoords[1] - t) - (dv[0]->texCoords[0] - s) * (dv[2]->texCoords[1] - t)) / bb; bary[2] = ((dv[0]->texCoords[0] - s) * (dv[1]->texCoords[1] - t) - (dv[1]->texCoords[0] - s) * (dv[0]->texCoords[1] - t)) / bb; dv[k]->tangent[0] = bary[0] * dv[0]->position[0] + bary[1] * dv[1]->position[0] + bary[2] * dv[2]->position[0]; dv[k]->tangent[1] = bary[0] * dv[0]->position[1] + bary[1] * dv[1]->position[1] + bary[2] * dv[2]->position[1]; dv[k]->tangent[2] = bary[0] * dv[0]->position[2] + bary[1] * dv[1]->position[2] + bary[2] * dv[2]->position[2]; VectorSubtract(dv[k]->tangent, dv[k]->position, dv[k]->tangent); VectorNormalize(dv[k]->tangent); // calculate t tangent vector (binormal) s = dv[k]->texCoords[0]; t = dv[k]->texCoords[1] + 10.0f; bary[0] = ((dv[1]->texCoords[0] - s) * (dv[2]->texCoords[1] - t) - (dv[2]->texCoords[0] - s) * (dv[1]->texCoords[1] - t)) / bb; bary[1] = ((dv[2]->texCoords[0] - s) * (dv[0]->texCoords[1] - t) - (dv[0]->texCoords[0] - s) * (dv[2]->texCoords[1] - t)) / bb; bary[2] = ((dv[0]->texCoords[0] - s) * (dv[1]->texCoords[1] - t) - (dv[1]->texCoords[0] - s) * (dv[0]->texCoords[1] - t)) / bb; dv[k]->binormal[0] = bary[0] * dv[0]->position[0] + bary[1] * dv[1]->position[0] + bary[2] * dv[2]->position[0]; dv[k]->binormal[1] = bary[0] * dv[0]->position[1] + bary[1] * dv[1]->position[1] + bary[2] * dv[2]->position[1]; dv[k]->binormal[2] = bary[0] * dv[0]->position[2] + bary[1] * dv[1]->position[2] + bary[2] * dv[2]->position[2]; VectorSubtract(dv[k]->binormal, dv[k]->position, dv[k]->binormal); VectorNormalize(dv[k]->binormal); // calculate the normal as cross product N=TxB #if 0 CrossProduct(dv[k]->tangent, dv[k]->binormal, dv[k]->normal); VectorNormalize(dv[k]->normal); // Gram-Schmidt orthogonalization process for B // compute the cross product B=NxT to obtain // an orthogonal basis CrossProduct(dv[k]->normal, dv[k]->tangent, dv[k]->binormal); if (DotProduct(dv[k]->normal, faceNormal) < 0) { VectorInverse(dv[k]->normal); //VectorInverse(dv[k]->tangent); //VectorInverse(dv[k]->binormal); } #else VectorAdd(dv[k]->normal, faceNormal, dv[k]->normal); #endif } } #if 1 for (j = 0; j < vboVertexes.currentElements; j++) { dv[0] = Com_GrowListElement(&vboVertexes, j); //VectorNormalize(dv[0]->tangent); //VectorNormalize(dv[0]->binormal); VectorNormalize(dv[0]->normal); } #endif } #endif #if 0 { md5Vertex_t *v0, *v1; // do another extra smoothing for normals to avoid flat shading for (j = 0; j < vboVertexes.currentElements; j++) { v0 = Com_GrowListElement(&vboVertexes, j); for (k = 0; k < vboVertexes.currentElements; k++) { if (j == k) { continue; } v1 = Com_GrowListElement(&vboVertexes, k); if (VectorCompare(v0->position, v1->position)) { VectorAdd(v0->position, v1->normal, v0->normal); } } VectorNormalize(v0->normal); } } #endif // split the surfaces into VBO surfaces by the maximum number of GPU vertex skinning bones Com_InitGrowList(&vboSurfaces, 10); materialIndex = oldMaterialIndex = -1; for (i = 0; i < numTriangles; i++) { triangle = &triangles[i]; materialIndex = triangle->materialIndex; if (materialIndex != oldMaterialIndex) { oldMaterialIndex = materialIndex; numRemaining = sortedTriangles.currentElements - i; while (numRemaining) { numBoneReferences = 0; Com_Memset(boneReferences, 0, sizeof(boneReferences)); Com_InitGrowList(&vboTriangles, 1000); for (j = i; j < sortedTriangles.currentElements; j++) { skelTriangle_t *sortTri; triangle = &triangles[j]; materialIndex = triangle->materialIndex; if (materialIndex != oldMaterialIndex) { continue; } sortTri = Com_GrowListElement(&sortedTriangles, j); if (sortTri->referenced) { continue; } if (AddTriangleToVBOTriangleList(&vboTriangles, sortTri, &numBoneReferences, boneReferences)) { sortTri->referenced = qtrue; } } for (j = 0; j < MAX_BONES; j++) { if (boneReferences[j] > 0) { Ren_Print("R_LoadPSK: referenced bone: '%s'\n", (j < numReferenceBones) ? refBones[j].name : NULL); } } if (!vboTriangles.currentElements) { Ren_Warning("R_LoadPSK: could not add triangles to a remaining VBO surface for model '%s'\n", modName); break; } // FIXME skinIndex AddSurfaceToVBOSurfacesList2(&vboSurfaces, &vboTriangles, &vboVertexes, md5, vboSurfaces.currentElements, materials[oldMaterialIndex].name, numBoneReferences, boneReferences); numRemaining -= vboTriangles.currentElements; Com_DestroyGrowList(&vboTriangles); } } } for (j = 0; j < sortedTriangles.currentElements; j++) { skelTriangle_t *sortTri = Com_GrowListElement(&sortedTriangles, j); Com_Dealloc(sortTri); } Com_DestroyGrowList(&sortedTriangles); for (j = 0; j < vboVertexes.currentElements; j++) { md5Vertex_t *v = Com_GrowListElement(&vboVertexes, j); Com_Dealloc(v); } Com_DestroyGrowList(&vboVertexes); // move VBO surfaces list to hunk md5->numVBOSurfaces = vboSurfaces.currentElements; md5->vboSurfaces = ri.Hunk_Alloc(md5->numVBOSurfaces * sizeof(*md5->vboSurfaces), h_low); for (i = 0; i < md5->numVBOSurfaces; i++) { md5->vboSurfaces[i] = ( srfVBOMD5Mesh_t * ) Com_GrowListElement(&vboSurfaces, i); } Com_DestroyGrowList(&vboSurfaces); FreeMemStream(stream); Com_Dealloc(points); Com_Dealloc(vertexes); Com_Dealloc(triangles); Com_Dealloc(materials); Ren_Developer("%i VBO surfaces created for PSK model '%s'\n", md5->numVBOSurfaces, modName); return qtrue; }
/** * @brief Creates a new decal projector from a triangle. * * Projected polygons should be 3 or 4 points. * * If a single point is passed in (numPoints == 1) then the decal will be omnidirectional * omnidirectional decals use points[ 0 ] as center and projection[ 3 ] as radius * pass in lifeTime < 0 for a temporary mark. * * @param[in] hShader * @param[in] numPoints * @param[in] points * @param[in] projection * @param[in] color * @param[in] lifeTime * @param[in] fadeTime */ void RE_ProjectDecal(qhandle_t hShader, int numPoints, vec3_t *points, vec4_t projection, vec4_t color, int lifeTime, int fadeTime) { static int totalProjectors = 0; vec3_t xyz; decalVert_t dv[4]; int i; decalProjector_t *dp, temp; if (r_numDecalProjectors >= MAX_DECAL_PROJECTORS) { Ren_Print("WARNING: RE_ProjectDecal() Max decal projectors reached (%d)\n", MAX_DECAL_PROJECTORS); return; } // dummy check if (numPoints != 1 && numPoints != 3 && numPoints != 4) { Ren_Print("WARNING: RE_ProjectDecal() Invalid number of decal points (%d)\n", numPoints); return; } // early outs if (lifeTime == 0) { Ren_Developer("WARNING: RE_ProjectDecal() lifeTime == 0\n"); // modders should have a look at this - vanilla does these calls return; } if (projection[3] <= 0.0f) { Ren_Print("WARNING: RE_ProjectDecal() projection[3] <= 0.0f\n"); return; } // set times properly if (lifeTime < 0 || fadeTime < 0) { lifeTime = 0; fadeTime = 0; } // basic setup temp.shader = R_GetShaderByHandle(hShader); temp.color[0] = (byte)(color[0] * 255); temp.color[1] = (byte)(color[1] * 255); temp.color[2] = (byte)(color[2] * 255); temp.color[3] = (byte)(color[3] * 255); temp.numPlanes = numPoints + 2; temp.fadeStartTime = tr.refdef.time + lifeTime - fadeTime; // FIXME: stale refdef time temp.fadeEndTime = temp.fadeStartTime + fadeTime; temp.projectorNum = 0; // set up decal texcoords (FIXME: support arbitrary projector st coordinates in trapcall) dv[0].st[0] = 0.0f; dv[0].st[1] = 0.0f; dv[1].st[0] = 0.0f; dv[1].st[1] = 1.0f; dv[2].st[0] = 1.0f; dv[2].st[1] = 1.0f; dv[3].st[0] = 1.0f; dv[3].st[1] = 0.0f; // omnidirectional? if (numPoints == 1) { float radius; float iDist; // set up omnidirectional numPoints = 4; temp.numPlanes = 6; temp.omnidirectional = qtrue; radius = projection[3]; Vector4Set(projection, 0.0f, 0.0f, -1.0f, radius * 2.0f); iDist = 1.0f / (radius * 2.0f); // set corner VectorSet(xyz, points[0][0] - radius, points[0][1] - radius, points[0][2] + radius); // make x axis texture matrix (yz) VectorSet(temp.texMat[0][0], 0.0f, iDist, 0.0f); temp.texMat[0][0][3] = -DotProduct(temp.texMat[0][0], xyz); VectorSet(temp.texMat[0][1], 0.0f, 0.0f, iDist); temp.texMat[0][1][3] = -DotProduct(temp.texMat[0][1], xyz); // make y axis texture matrix (xz) VectorSet(temp.texMat[1][0], iDist, 0.0f, 0.0f); temp.texMat[1][0][3] = -DotProduct(temp.texMat[1][0], xyz); VectorSet(temp.texMat[1][1], 0.0f, 0.0f, iDist); temp.texMat[1][1][3] = -DotProduct(temp.texMat[1][1], xyz); // make z axis texture matrix (xy) VectorSet(temp.texMat[2][0], iDist, 0.0f, 0.0f); temp.texMat[2][0][3] = -DotProduct(temp.texMat[2][0], xyz); VectorSet(temp.texMat[2][1], 0.0f, iDist, 0.0f); temp.texMat[2][1][3] = -DotProduct(temp.texMat[2][1], xyz); // setup decal points VectorSet(dv[0].xyz, points[0][0] - radius, points[0][1] - radius, points[0][2] + radius); VectorSet(dv[1].xyz, points[0][0] - radius, points[0][1] + radius, points[0][2] + radius); VectorSet(dv[2].xyz, points[0][0] + radius, points[0][1] + radius, points[0][2] + radius); VectorSet(dv[3].xyz, points[0][0] + radius, points[0][1] - radius, points[0][2] + radius); } else { // set up unidirectional temp.omnidirectional = qfalse; // set up decal points VectorCopy(points[0], dv[0].xyz); VectorCopy(points[1], dv[1].xyz); VectorCopy(points[2], dv[2].xyz); VectorCopy(points[3], dv[3].xyz); // make texture matrix if (!MakeTextureMatrix(temp.texMat[0], projection, &dv[0], &dv[1], &dv[2])) { Ren_Print("WARNING: RE_ProjectDecal() MakeTextureMatrix returns NULL\n"); return; } } // bound the projector ClearBounds(temp.mins, temp.maxs); for (i = 0; i < numPoints; i++) { AddPointToBounds(dv[i].xyz, temp.mins, temp.maxs); VectorMA(dv[i].xyz, projection[3], projection, xyz); AddPointToBounds(xyz, temp.mins, temp.maxs); } // make bounding sphere VectorAdd(temp.mins, temp.maxs, temp.center); VectorScale(temp.center, 0.5f, temp.center); VectorSubtract(temp.maxs, temp.center, xyz); temp.radius = VectorLength(xyz); temp.radius2 = temp.radius * temp.radius; // make the front plane if (!PlaneFromPoints(temp.planes[0], dv[0].xyz, dv[1].xyz, dv[2].xyz)) { Ren_Developer("WARNING: RE_ProjectDecal() PlaneFromPoints is NULL\n"); // occurs on UJE_fueldump return; } // make the back plane VectorSubtract(vec3_origin, temp.planes[0], temp.planes[1]); VectorMA(dv[0].xyz, projection[3], projection, xyz); temp.planes[1][3] = DotProduct(xyz, temp.planes[1]); // make the side planes for (i = 0; i < numPoints; i++) { VectorMA(dv[i].xyz, projection[3], projection, xyz); if (!PlaneFromPoints(temp.planes[i + 2], dv[(i + 1) % numPoints].xyz, dv[i].xyz, xyz)) { Ren_Developer("WARNING: RE_ProjectDecal() a side plane is NULL\n"); // occurs on map venice return; } } // create a new projector dp = &backEndData->decalProjectors[r_numDecalProjectors]; Com_Memcpy(dp, &temp, sizeof(*dp)); dp->projectorNum = totalProjectors++; // we have a winner r_numDecalProjectors++; }
/** * @brief Logs OpenGL commands when com_developer cvar is enabled */ void GLimp_LogComment(const char *comment) { Ren_Developer("%s", comment); }
static int GLimp_SetMode(int mode, qboolean fullscreen, qboolean noborder) { int perChannelColorBits; int colorBits, depthBits, stencilBits; int samples; int i = 0; SDL_Surface *icon = NULL; SDL_DisplayMode desktopMode; int display = 0; int x = SDL_WINDOWPOS_UNDEFINED, y = SDL_WINDOWPOS_UNDEFINED; Uint32 flags = SDL_WINDOW_OPENGL | SDL_WINDOW_INPUT_GRABBED; #ifndef FEATURE_RENDERER_GLES GLenum glewResult; #endif Ren_Print("Initializing OpenGL display\n"); if (r_allowResize->integer && !fullscreen) { flags |= SDL_WINDOW_RESIZABLE; } icon = SDL_CreateRGBSurfaceFrom( (void *)CLIENT_WINDOW_ICON.pixel_data, CLIENT_WINDOW_ICON.width, CLIENT_WINDOW_ICON.height, CLIENT_WINDOW_ICON.bytes_per_pixel * 8, CLIENT_WINDOW_ICON.bytes_per_pixel * CLIENT_WINDOW_ICON.width, #ifdef Q3_LITTLE_ENDIAN 0x000000FF, 0x0000FF00, 0x00FF0000, 0xFF000000 #else 0xFF000000, 0x00FF0000, 0x0000FF00, 0x000000FF #endif ); // If a window exists, note its display index if (main_window != NULL) { display = SDL_GetWindowDisplayIndex(main_window); } if (SDL_GetDesktopDisplayMode(display, &desktopMode) == 0) { displayAspect = (float)desktopMode.w / (float)desktopMode.h; Ren_Print("Estimated display aspect: %.3f\n", displayAspect); } else { Com_Memset(&desktopMode, 0, sizeof(SDL_DisplayMode)); Ren_Print("Cannot estimate display aspect, assuming 1.333\n"); } Ren_Print("...setting mode %d: ", mode); if (mode == -2) { // use desktop video resolution if (desktopMode.h > 0) { glConfig.vidWidth = desktopMode.w; glConfig.vidHeight = desktopMode.h; } else { glConfig.vidWidth = 640; glConfig.vidHeight = 480; Ren_Print("Cannot determine display resolution, assuming 640x480\n"); } glConfig.windowAspect = (float)glConfig.vidWidth / (float)glConfig.vidHeight; } else if (!R_GetModeInfo(&glConfig.vidWidth, &glConfig.vidHeight, &glConfig.windowAspect, mode)) { Ren_Print("invalid mode\n"); return RSERR_INVALID_MODE; } Ren_Print("%dx%d\n", glConfig.vidWidth, glConfig.vidHeight); // Center window if (r_centerWindow->integer && !fullscreen) { x = (desktopMode.w / 2) - (glConfig.vidWidth / 2); y = (desktopMode.h / 2) - (glConfig.vidHeight / 2); } // Destroy existing state if it exists if (SDL_glContext != NULL) { SDL_GL_DeleteContext(SDL_glContext); SDL_glContext = NULL; } if (main_window != NULL) { SDL_GetWindowPosition(main_window, &x, &y); Ren_Developer("Existing window at %dx%d before being destroyed\n", x, y); SDL_DestroyWindow(main_window); main_window = NULL; } if (fullscreen) { if (r_mode->integer == -2) { flags |= SDL_WINDOW_FULLSCREEN_DESKTOP; } else { flags |= SDL_WINDOW_FULLSCREEN; } glConfig.isFullscreen = qtrue; } else { if (noborder) { flags |= SDL_WINDOW_BORDERLESS; } glConfig.isFullscreen = qfalse; } colorBits = r_colorbits->value; if ((!colorBits) || (colorBits >= 32)) { colorBits = 24; } if (!r_depthbits->value) { depthBits = 24; } else { depthBits = r_depthbits->value; } stencilBits = r_stencilbits->value; samples = r_ext_multisample->value; for (i = 0; i < 16; i++) { int testColorBits, testDepthBits, testStencilBits; // 0 - default // 1 - minus colorBits // 2 - minus depthBits // 3 - minus stencil if ((i % 4) == 0 && i) { // one pass, reduce switch (i / 4) { case 2: if (colorBits == 24) { colorBits = 16; } break; case 1: if (depthBits == 32) { depthBits = 24; } else if (depthBits == 24) { depthBits = 16; } else if (depthBits == 16) { depthBits = 8; } case 3: // fall through if (stencilBits == 24) { stencilBits = 16; } else if (stencilBits == 16) { stencilBits = 8; } } } testColorBits = colorBits; testDepthBits = depthBits; testStencilBits = stencilBits; if ((i % 4) == 3) // reduce colorbits { if (testColorBits == 24) { testColorBits = 16; } } if ((i % 4) == 2) // reduce depthbits { if (testDepthBits == 24) { testDepthBits = 16; } else if (testDepthBits == 16) { testDepthBits = 8; } } if ((i % 4) == 1) // reduce stencilbits { if (testStencilBits == 24) { testStencilBits = 16; } else if (testStencilBits == 16) { testStencilBits = 8; } else { testStencilBits = 0; } } if (testColorBits == 24) { perChannelColorBits = 8; } else { perChannelColorBits = 4; } #ifdef __sgi // Fix for SGIs grabbing too many bits of color if (perChannelColorBits == 4) { perChannelColorBits = 0; /* Use minimum size for 16-bit color */ } // Need alpha or else SGIs choose 36+ bit RGB mode SDL_GL_SetAttribute(SDL_GL_ALPHA_SIZE, 1); #endif SDL_GL_SetAttribute(SDL_GL_RED_SIZE, perChannelColorBits); SDL_GL_SetAttribute(SDL_GL_GREEN_SIZE, perChannelColorBits); SDL_GL_SetAttribute(SDL_GL_BLUE_SIZE, perChannelColorBits); SDL_GL_SetAttribute(SDL_GL_DEPTH_SIZE, testDepthBits); SDL_GL_SetAttribute(SDL_GL_STENCIL_SIZE, testStencilBits); SDL_GL_SetAttribute(SDL_GL_MULTISAMPLEBUFFERS, samples ? 1 : 0); SDL_GL_SetAttribute(SDL_GL_MULTISAMPLESAMPLES, samples); // SDL2 uses opengl by default, if we want opengl es we need to set this attribute //SDL_GL_SetAttribute(SDL_GL_CONTEXT_EGL, 1); if (r_stereoEnabled->integer) { glConfig.stereoEnabled = qtrue; SDL_GL_SetAttribute(SDL_GL_STEREO, 1); } else { glConfig.stereoEnabled = qfalse; SDL_GL_SetAttribute(SDL_GL_STEREO, 0); } SDL_GL_SetAttribute(SDL_GL_DOUBLEBUFFER, 1); // If not allowing software GL, demand accelerated if (!r_allowSoftwareGL->integer) { SDL_GL_SetAttribute(SDL_GL_ACCELERATED_VISUAL, 1); } main_window = SDL_CreateWindow(CLIENT_WINDOW_TITLE, x, y, glConfig.vidWidth, glConfig.vidHeight, flags | SDL_WINDOW_SHOWN); if (!main_window) { Ren_Developer("SDL_CreateWindow failed: %s\n", SDL_GetError()); continue; } //This is disabled since at least now we have no use for this /* if (!Glimp_Create2DRenderer(main_window)) { continue; } */ if (fullscreen) { SDL_DisplayMode mode; switch (testColorBits) { case 16: mode.format = SDL_PIXELFORMAT_RGB565; break; case 24: mode.format = SDL_PIXELFORMAT_RGB24; break; default: Ren_Developer("testColorBits is %d, can't fullscreen\n", testColorBits); continue; } mode.w = glConfig.vidWidth; mode.h = glConfig.vidHeight; mode.refresh_rate = glConfig.displayFrequency = ri.Cvar_VariableIntegerValue("r_displayRefresh"); mode.driverdata = NULL; if (SDL_SetWindowDisplayMode(main_window, &mode) < 0) { Ren_Developer("SDL_SetWindowDisplayMode failed: %s\n", SDL_GetError()); continue; } } SDL_SetWindowIcon(main_window, icon); #if defined(FEATURE_RENDERER2) glewExperimental = GL_TRUE; SDL_GL_SetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION, 3); SDL_GL_SetAttribute(SDL_GL_CONTEXT_MINOR_VERSION, 2); SDL_GL_SetAttribute(SDL_GL_CONTEXT_PROFILE_MASK, SDL_GL_CONTEXT_PROFILE_CORE); SDL_GL_SetAttribute(SDL_GL_CONTEXT_FLAGS, SDL_GL_CONTEXT_FORWARD_COMPATIBLE_FLAG); #endif if ((SDL_glContext = SDL_GL_CreateContext(main_window)) == NULL) { Ren_Developer("SDL_GL_CreateContext failed: %s\n", SDL_GetError()); continue; } SDL_GL_MakeCurrent(main_window, SDL_glContext); SDL_GL_SetSwapInterval(r_swapInterval->integer); glConfig.colorBits = testColorBits; glConfig.depthBits = testDepthBits; glConfig.stencilBits = testStencilBits; ri.Printf(PRINT_ALL, "Using %d color bits, %d depth, %d stencil display.\n", glConfig.colorBits, glConfig.depthBits, glConfig.stencilBits); break; } GLimp_DetectAvailableModes(); #if !defined(FEATURE_RENDERER_GLES) glewResult = glewInit(); if (GLEW_OK != glewResult) { // glewInit failed, something is seriously wrong Ren_Fatal("GLW_StartOpenGL() - could not load OpenGL subsystem: %s", glewGetErrorString(glewResult)); } else { Ren_Print("Using GLEW %s\n", glewGetString(GLEW_VERSION)); } #endif if (!GLimp_InitOpenGLContext()) { return RSERR_OLD_GL; } if (!main_window) //|| !main_renderer) { Ren_Print("Couldn't get a visual\n"); return RSERR_INVALID_MODE; } SDL_FreeSurface(icon); return RSERR_OK; }
/* ================= R_AddMDCSurfaces ================= */ void R_AddMDCSurfaces(trRefEntity_t *ent) { int i; mdcHeader_t *header = 0; mdcSurface_t *surface = 0; md3Shader_t *md3Shader = 0; shader_t *shader = 0; int cull; int lod; int fogNum; qboolean personalModel = (ent->e.renderfx & RF_THIRD_PERSON) && !tr.viewParms.isPortal; // don't add third_person objects if not in a portal if (ent->e.renderfx & RF_WRAP_FRAMES) { ent->e.frame %= tr.currentModel->model.mdc[0]->numFrames; ent->e.oldframe %= tr.currentModel->model.mdc[0]->numFrames; } // Validate the frames so there is no chance of a crash. // This will write directly into the entity structure, so // when the surfaces are rendered, they don't need to be // range checked again. if ((ent->e.frame >= tr.currentModel->model.mdc[0]->numFrames) || (ent->e.frame < 0) || (ent->e.oldframe >= tr.currentModel->model.mdc[0]->numFrames) || (ent->e.oldframe < 0)) { Ren_Developer("R_AddMDCSurfaces: no such frame %d to %d for '%s'\n", ent->e.oldframe, ent->e.frame, tr.currentModel->name); ent->e.frame = 0; ent->e.oldframe = 0; } // compute LOD if (ent->e.renderfx & RF_FORCENOLOD) { lod = 0; } else { lod = R_ComputeLOD(ent); } header = tr.currentModel->model.mdc[lod]; // cull the entire model if merged bounding box of both frames // is outside the view frustum. cull = R_CullModel(header, ent); if (cull == CULL_OUT) { return; } // set up lighting now that we know we aren't culled if (!personalModel || r_shadows->integer > 1) { R_SetupEntityLighting(&tr.refdef, ent); } // see if we are in a fog volume fogNum = R_ComputeFogNum(header, ent); // draw all surfaces surface = ( mdcSurface_t * )((byte *)header + header->ofsSurfaces); for (i = 0 ; i < header->numSurfaces ; i++) { if (ent->e.customShader) { shader = R_GetShaderByHandle(ent->e.customShader); } else if (ent->e.customSkin > 0 && ent->e.customSkin < tr.numSkins) { skin_t *skin; int hash; int j; skin = R_GetSkinByHandle(ent->e.customSkin); // match the surface name to something in the skin file shader = tr.defaultShader; // added blink if (ent->e.renderfx & RF_BLINK) { char *s = va("%s_b", surface->name); // append '_b' for 'blink' hash = Com_HashKey(s, strlen(s)); for (j = 0 ; j < skin->numSurfaces ; j++) { if (hash != skin->surfaces[j]->hash) { continue; } if (!strcmp(skin->surfaces[j]->name, s)) { shader = skin->surfaces[j]->shader; break; } } } if (shader == tr.defaultShader) // blink reference in skin was not found { hash = Com_HashKey(surface->name, sizeof(surface->name)); for (j = 0 ; j < skin->numSurfaces ; j++) { // the names have both been lowercased if (hash != skin->surfaces[j]->hash) { continue; } if (!strcmp(skin->surfaces[j]->name, surface->name)) { shader = skin->surfaces[j]->shader; break; } } } } else if (surface->numShaders <= 0) { shader = tr.defaultShader; } else { md3Shader = ( md3Shader_t * )((byte *)surface + surface->ofsShaders); md3Shader += ent->e.skinNum % surface->numShaders; shader = tr.shaders[md3Shader->shaderIndex]; } // we will add shadows even if the main object isn't visible in the view // stencil shadows can't do personal models unless I polyhedron clip if (!personalModel && r_shadows->integer == 2 && fogNum == 0 && !(ent->e.renderfx & (RF_NOSHADOW | RF_DEPTHHACK)) && shader->sort == SS_OPAQUE) { R_AddDrawSurf((void *)surface, tr.shadowShader, 0, 0, 0); } // projection shadows work fine with personal models if (r_shadows->integer == 3 && fogNum == 0 && (ent->e.renderfx & RF_SHADOW_PLANE) && shader->sort == SS_OPAQUE) { R_AddDrawSurf((void *)surface, tr.projectionShadowShader, 0, 0, 0); } // for testing polygon shadows (on /all/ models) if (r_shadows->integer == 4) { R_AddDrawSurf((void *)surface, tr.projectionShadowShader, 0, 0, 0); } // don't add third_person objects if not viewing through a portal if (!personalModel) { R_AddDrawSurf((void *)surface, shader, fogNum, 0, 0); } surface = ( mdcSurface_t * )((byte *)surface + surface->ofsEnd); } }