/*********************************************************************************************** * * Copyright © DreamWorks Interactive. 1996 * * Contents: * Implementation of 'FastBump.hpp.' * * To do: * Get rid of DrawBump. Let CDrawPolygon<> do the triangulisation, and CLineBumpMake do * the matrix setup required. * *********************************************************************************************** * * $Log:: /JP2_PC/Source/Lib/Renderer/Primitives/FastBump.cpp $ * * 113 10/02/98 3:02p Mmouni * Added missing brackets to delete. * * 112 98.09.20 12:35a Mmouni * Now clears the erfMIPS_CREATED flag for curved bumps. * * 111 9/11/98 8:48p Agrant * Removed re-calculculation of solid colour since it was causing an assert in TextureManager. * * 110 98.09.10 2:01p Mmouni * Solid colours for curved bump polys are now computed from their raster after curving. * * 109 8/27/98 9:13p Asouth * loop variable moved out of block scope * * 108 98.08.26 6:57p Mmouni * Non-curved bump map maptrices are now hash and kept in a map for quick matching. * Added FastBumpCleanup(). * * 107 98.08.25 4:36p Mmouni * Curved bump-mapped objects no longer allocate unique matrices. * Remove old commented out cruft. * * 106 8/21/98 2:25a Rwyatt * Added a terminal error in VER_TEST builds for textures missing in the swap file. * * 105 8/19/98 1:30p Rwyatt * Removed bump stats and replaced them with mem counters * Bump matricies are now allocated from the load heap. * * 104 98.08.13 4:26p Mmouni * Changes for VC++ 5.0sp3 compatibility. * * 103 98/07/22 14:41 Speter * mx3ObjToTexture now scaled down slightly, to guard against lighting value range errors. * * 102 7/10/98 5:10p Mlange * Poly iterators now take a render context instead of a camera. * * 101 98.07.08 12:12p Mmouni * Moved CPixelFormatBumpMap to the header file. * * 100 7/08/98 12:12p Rwyatt * Adjsuted curved bump hash generation to use integers instead of floats. * * 99 98/06/29 16:25 Speter * Rendering functions now take CInstance*. * * 98 98/06/01 18:43 Speter * Added new include. * * 97 4/27/98 8:54p Pkeet * Made an assert more tolerant. * **********************************************************************************************/ // // Includes. // #include "Common.hpp" #include "Lib/W95/WinInclude.hpp" #include "FastBump.hpp" #include "FastBumpMath.hpp" #include "Lib/Math/FastTrig.hpp" #include "Lib/View/RasterVid.hpp" #include "Lib/Renderer/Primitives/DrawTriangle.hpp" #include "Lib/Renderer/Primitives/LineBumpMake.hpp" #include "Lib/GeomDBase/MeshIterator.hpp" #include "Lib/EntityDBase/MessageLog.hpp" #include "Lib/Loader/TextureManager.hpp" #include "Lib/Loader/ImageLoader.hpp" #include "Lib/Std/Hash.hpp" #include "Lib/Sys/MemoryLog.hpp" #include "Lib/Sys/DebugConsole.hpp" #include #include #include #include #include #include #define bAXIS_SWAP (1) // // Forward declaration of functions used in this module. // //***************************************************************************************** void FatFill(CTexCoord& tc_0, CTexCoord& tc_1, CTexCoord& tc_2, rptr pbump); //***************************************************************************************** CMatrix3<>* pmx3GetSharedMatrix(const CMatrix3<> &mx3_mat); //***************************************************************************************** CMatrix3<>* pmx3GetVerticalAxesSwapMatrix(const CDir3<>& d3_control); //********************************************************************************************* void DrawTriangleBump ( CBumpMap* prasDest, CRenderPolygon& rpoly // Info on polygon (triangle) to render. ); //***************************************************************************************** class CVerticalAxisSwapMatrices { private: CMatrix3<> mx3SwapMatrices[6]; public: //************************************************************************************* CVerticalAxisSwapMatrices(); //************************************************************************************* CMatrix3<>* pmx3GetMatrix ( const CDir3<>& d3_control ); //************************************************************************************* CMatrix3<>* pmx3GetMatrix ( int i_index ); int iNumMatrices() const { return 6; } }; static CVerticalAxisSwapMatrices SwapMatrices; //********************************************************************************************* // // CBumpAnglePair implementation. // //***************************************************************************************** void CBumpAnglePair::SetBump(const CDir3<>& rdir3, bool b_set_light_phi) { uint u_theta; // // Get theta. Note that this function relies on an arctan lookup table. To ensure // precision and reduce the size of the arctan table, the conversion table only // covers a 45 degree slice. // if (Abs(rdir3.tX) >= Abs(rdir3.tY)) { // // If there is no x value, theta must be zero (i.e., the normal points along // the y axis. // if (rdir3.tX == 0.0f) { u_theta = 0; } else { // tY divide by tX will always be less than one. u_theta = BumpAngleConvert.uArcTan45(rdir3.tY / rdir3.tX); // If x is negative, theta must lay 180 degrees opposite of the table's value. if (rdir3.tX < 0.0f) { u_theta -= iTHETA_PI; } } } else { // tX divide by tY will always be less than one. u_theta = iTHETA_PI_2 - BumpAngleConvert.uArcTan45(rdir3.tX / rdir3.tY); // If x is negative, theta must lay 180 degrees opposite of the table's value. if (rdir3.tY < 0.0f) { u_theta -= iTHETA_PI; } } // // Set the Phi angle, which is the arcsine of z. // if (b_set_light_phi) // Set the light phi value. SetPhiL(BumpAngleConvert.iArcSinL(rdir3.tZ)); else // Set the bump phi value (clamped to positive). SetPhiB(BumpAngleConvert.iArcSinB(rdir3.tZ)); // Set the theta angle. SetTheta(u_theta - iTHETA_PI_2); }; //********************************************************************************************* float CBumpAnglePair::fGetPhiB() { return BumpAngleConvert.fGetPhiB(*this); } //********************************************************************************************* float CBumpAnglePair::fGetPhiL() { return BumpAngleConvert.fGetPhiL(*this); } //***************************************************************************************** CDir3<> CBumpAnglePair::dir3MakeNormal() { // Use the transform class to convert the angles in radians to a normal. return BumpAngleConvert.dir3AnglesToNormal(fGetPhiB(), fGetTheta()); } //***************************************************************************************** CDir3<> CBumpAnglePair::dir3MakeNormalFast() { // Use the transform class to convert the angles in radians to a normal. return BumpAngleConvert.dir3AnglesToNormal(*this); } //********************************************************************************************* // // CBumpMap member functions. // //***************************************************************************************** // // CBumpMap constructors. // //***************************************************************************************** CBumpMap::CBumpMap(int i_width, int i_height, const CPal* ppal, TPixel pix_solid) : CRasterMemT(i_width, i_height) { Assert(ppal); // Force the pixel format to be 8-bit (paletted), so that colour conversion functions work. pxf = CPixelFormatBumpmap(); // Store the palette pointer in the bump-map palette. AttachPalette((CPal*)ppal); // // Fill bumpmap memory with an up-facing bump of the solid colour. // CBumpAnglePair bang; bang.SetPhiB(iMAX_PHI_B); bang.SetColour(pix_solid); Clear(bang); } //***************************************************************************************** CBumpMap::CBumpMap(rptr pras_heightmap, rptr pras_texture, float f_bumpiness) : CRasterMemT(pras_texture->iWidth, pras_texture->iHeight) { Assert(pras_texture->iWidth > 0); Assert(pras_texture->iHeight > 0); if (pras_heightmap) { Assert(pras_heightmap->iWidth == pras_texture->iWidth); Assert(pras_heightmap->iHeight == pras_texture->iHeight); } Assert(f_bumpiness >= 0.0f); // Force the pixel format to be 8-bit (paletted), so that colour conversion functions work. pxf = CPixelFormatBumpmap(); // Clear memory for the bumpmap. Clear(); // Add bumps. AddHeightField(pras_heightmap, f_bumpiness); // Add texture. AddTexture(pras_texture); } //***************************************************************************************** CBumpMap::CBumpMap(char* str_height_filename, char* str_texture_filename, float f_bumpiness) { Assert(str_height_filename); Assert(str_texture_filename); Assert(f_bumpiness >= 0.0f); // Load the texturemap associated with the bumpmap. rptr pras_texture = prasReadBMP(str_texture_filename); // Load heightmap. rptr pras_bumpmap = prasReadBMP(str_height_filename); new(this) CBumpMap(pras_texture, pras_bumpmap, f_bumpiness); } //***************************************************************************************** // // CBumpMap member functions. // //***************************************************************************************** // void CBumpMap::AddHeightField ( rptr pras_heightmap, // Height field representing bumpmap. May be rptr0. float f_bumpiness // Value to multiply height by to get vertical value // in pixels. ) // // Converts a heightfield to a bumpmap. // //************************************** { if (pras_heightmap) { Assert(pras_heightmap->pSurface); Assert(iWidth == pras_heightmap->iWidth); Assert(iHeight == pras_heightmap->iHeight); } else f_bumpiness = 0; Assert(f_bumpiness >= 0.0f); int i_x, i_y; // // Fill the centre of the bumpmap with angles. // for (i_y = 1; i_y < iHeight; i_y++) { CBumpAnglePair* pbang = &tPix(1, i_y); for (i_x = 1; i_x < iWidth; i_x++) { if (f_bumpiness) pbang->SetBump(CDir3<> ( (int)pras_heightmap->pixGet(i_x-1, i_y) - (int)pras_heightmap->pixGet(i_x, i_y), (int)pras_heightmap->pixGet(i_x, i_y-1) - (int)pras_heightmap->pixGet(i_x, i_y), 1.0f / f_bumpiness )); else pbang->SetBump(d3ZAxis); pbang++; } } // // Fill the top and left edges by copying from the next inner edges. // This continues the curve in the same direction it was going. // // Fill the top angles. for (i_x = 0; i_x < iWidth; i_x++) { tPix(i_x, 0).SetBump(tPix(i_x, 1)); } // Fill the left angles. for (i_y = 0; i_y < iHeight; i_y++) { tPix(0, i_y).SetBump(tPix(1, i_y)); } } //***************************************************************************************** // void CBumpMap::AddTexture ( rptr pras_texture // Raster representing bitmap. ) // // Adds an 8-bit bitmap to the bumpmap. // //************************************** { Assert(pras_texture->pSurface); Assert(pras_texture->pxf.ppalAttached); Assert(iWidth == pras_texture->iWidth); Assert(iHeight == pras_texture->iHeight); Assert(pras_texture->iPixelBits == 8); // Store the texture palette in the bump-map palette. AttachPalette(pras_texture->pxf.ppalAttached); // Copy the texture to the combined bumpmap. for (int i_y = 0; i_y < iHeight; i_y++) { CBumpAnglePair* pbang = &tPix(0, i_y); uint8* pu1_tex = (uint8*)pras_texture->pAddress(0, i_y); for (int i_x = 0; i_x < iWidth; i_x++) { pbang->SetColour(*pu1_tex++); pbang++; } } } //***************************************************************************************** void CBumpMap::DrawBump(CTexCoord tc_0, CDir3<> dir3_normal_0, CTexCoord tc_1, CDir3<> dir3_normal_1, CTexCoord tc_2, CDir3<> dir3_normal_2, const CMatrix3<>& mx3_reverse) { Assert(bWithin(tc_0.tX, 0.0f, 1.0f)); Assert(bWithin(tc_1.tX, 0.0f, 1.0f)); Assert(bWithin(tc_2.tX, 0.0f, 1.0f)); Assert(pSurface); // Copy over the reverse transform. mx3Reverse = mx3_reverse; // Create a CRenderPolygon for CDrawPolygon. CRenderPolygon rp; rp.cvFace = 0; SRenderVertex arvVertices[3]; SRenderVertex* aprvVertices[3] = { &arvVertices[0], &arvVertices[1], &arvVertices[2] }; // Assign the polygon's vertex pointer array. rp.paprvPolyVertices = PArray(3, aprvVertices); // Get a floating point representation of width and height. float f_width = float(iWidth) /* - fTexEdgeTolerance * 2.0f */; float f_height = float(iHeight) /* - fTexEdgeTolerance * 2.0f */; float f_bias = 0.0f /* + fTexEdgeTolerance */; // // Convert texture coordinates and normals to the SRenderVertex/SRenderTriangle format. // arvVertices[0].v3Screen.tX = tc_0.tX * f_width + f_bias; arvVertices[0].v3Screen.tY = tc_0.tY * f_height + f_bias; arvVertices[0].v3Screen.tZ = 0.001f; arvVertices[0].pdir3Normal = &dir3_normal_0; arvVertices[1].v3Screen.tX = tc_1.tX * f_width + f_bias; arvVertices[1].v3Screen.tY = tc_1.tY * f_height + f_bias; arvVertices[1].v3Screen.tZ = 0.001f; arvVertices[1].pdir3Normal = &dir3_normal_1; arvVertices[2].v3Screen.tX = tc_2.tX * f_width + f_bias; arvVertices[2].v3Screen.tY = tc_2.tY * f_height + f_bias; arvVertices[2].v3Screen.tZ = 0.001f; arvVertices[2].pdir3Normal = &dir3_normal_2; #define fCONSTANT -0.0001f #define fLOW 0.0001f // No out of boundses, please. SetMinMax(arvVertices[0].v3Screen.tX, fLOW, iWidth + fCONSTANT); SetMinMax(arvVertices[1].v3Screen.tX, fLOW, iWidth + fCONSTANT); SetMinMax(arvVertices[2].v3Screen.tX, fLOW, iWidth + fCONSTANT); SetMinMax(arvVertices[0].v3Screen.tY, fLOW, iHeight + fCONSTANT); SetMinMax(arvVertices[1].v3Screen.tY, fLOW, iHeight + fCONSTANT); SetMinMax(arvVertices[2].v3Screen.tY, fLOW, iHeight + fCONSTANT); // // Use a standard rasterizing routine to draw a triangle with interpolated curvature on // the bumpmap surface. // //CDrawPolygonWide< CLineBumpMake >(this, rp); DrawTriangleBump(this , rp); #if (0) // // Check the dimension of the triangle. // float min_x = arvVertices[0].v3Screen.tX; float min_y = arvVertices[0].v3Screen.tY; float max_x = arvVertices[0].v3Screen.tX; float max_y = arvVertices[0].v3Screen.tY; for (int i = 1; i < 3; i++) { if (min_x > arvVertices[i].v3Screen.tX) min_x = arvVertices[i].v3Screen.tX; if (min_y > arvVertices[i].v3Screen.tY) min_y = arvVertices[i].v3Screen.tY; if (max_x < arvVertices[i].v3Screen.tX) max_x = arvVertices[i].v3Screen.tX; if (max_y < arvVertices[i].v3Screen.tY) max_y = arvVertices[i].v3Screen.tY; } if (max_x - min_x < 1.0f || max_y - min_y < 1.0f) { // Fill the whole map in x. for (int y = 0; y < iHeight; y++) { CBumpAnglePair* pbang_map = (CBumpAnglePair*)pSurface + y * iLinePixels; // Find first pixel in x. for (int x = 0; x < iWidth; x++) if (pbang_map[x].bGetCurveFlag()) break; if (x < iWidth) { int start_x = x; for (x = start_x-1; x >= 0; x--) pbang_map[x] = pbang_map[start_x]; } // Find last pixel in x. for (x = iWidth-1; x >= 0; x--) if (pbang_map[x].bGetCurveFlag()) break; if (x >= 0) { int start_x = x; for (x = start_x+1; x < iWidth; x++) pbang_map[x] = pbang_map[start_x]; } } // Fill the whole map in y. for (int x = 0; x < iWidth; x++) { CBumpAnglePair* pbang_map = (CBumpAnglePair*)pSurface + x; // Find first pixel in y. for (int y = 0; y < iHeight; y++) if (pbang_map[y*iLinePixels].bGetCurveFlag()) break; if (y < iHeight) { int start_y = y; for (y = start_y-1; y >= 0; y--) pbang_map[y*iLinePixels] = pbang_map[start_y*iLinePixels]; } // Find last pixel in y. for (y = iHeight-1; y >= 0; y--) if (pbang_map[y*iLinePixels].bGetCurveFlag()) break; if (y >= 0) { int start_y = y; for (y = start_y+1; y < iHeight; y++) pbang_map[y*iLinePixels] = pbang_map[start_y*iLinePixels]; } } // Make sure map is filled. for (y = 0; y < iHeight; y++) { CBumpAnglePair* pbang_map = (CBumpAnglePair*)pSurface + y * iLinePixels; for (int x = 0; x < iWidth; x++) Assert(pbang_map[x].bGetCurveFlag()); } } #endif } //***************************************************************************************** void CBumpMap::DrawBumps(CTexCoord tc_0, CDir3<> dir3_normal_0, CTexCoord tc_1, CDir3<> dir3_normal_1, CTexCoord tc_2, CDir3<> dir3_normal_2, CDir3<> dir3_normal_face, const CMatrix3<>& mx3_reverse) { // Get the coordinates for the newly generated vertex. CTexCoord tc_centre = (tc_0 + tc_1 + tc_2) / 3.0f; // Draw the subtriangles. DrawBump(tc_0, dir3_normal_0, tc_1, dir3_normal_1, tc_centre, dir3_normal_face, mx3_reverse); DrawBump(tc_centre, dir3_normal_face, tc_1, dir3_normal_1, tc_2, dir3_normal_2, mx3_reverse); DrawBump(tc_0, dir3_normal_0, tc_centre, dir3_normal_face, tc_2, dir3_normal_2, mx3_reverse); } // // CBumpMap static variables. // CMatrix3<> CBumpMap::mx3Reverse; // // Global functions. // //***************************************************************************************** inline TReal rSignedArea ( const CVector2<>& v2_0, const CVector2<>& v2_1, const CVector2<>& v2_2 ) { return (v2_1 - v2_0) ^ (v2_2 - v2_0); } // // Fuzzy specialisation for matrix. // //********************************************************************************************** inline TReal Difference(const CMatrix3<>& mx3_a, const CMatrix3<>& mx3_b) // // Specialise the Difference function used by CFuzzy<>. // // Note: this version does not allow ordering comparisions, just [in]equality. // //************************************** { return Difference(mx3_a.v3X, mx3_b.v3X) + Difference(mx3_a.v3Y, mx3_b.v3Y) + Difference(mx3_a.v3Z, mx3_b.v3Z); } //********************************************************************************************** inline CFuzzy, TReal> Fuzzy(const CMatrix3<>& mx3_value, TReal r_tolerance = 0.01) // Specialise the Fuzzy function. { return CFuzzy, TReal>(mx3_value, r_tolerance); } //***************************************************************************************** CMatrix3<> mx3ObjToTexture ( const CVector3<>& v3_0, const CVector3<>& v3_1, const CVector3<>& v3_2, const CDir3<>& d3, const CTexCoord& tc_0, const CTexCoord& tc_1, const CTexCoord& tc_2 ) { // // The direction of the SDirReflectData is in object space. We need to convert // that to texture space. // We need a transformation which converts a frame representing the object triangle // to a frame representing the texture triangle. // Each frame consists of a vector in the plane of the triangle, and a vector // perpendicular to it. // // First construct the inverse object frame matrix, from the triangle edge vector and normal. CMatrix3<> mx3_obj = CMatrix3<> ( // The first direction is the one from point 0 to the midpoint of 1 and 2. (v3_1 + v3_2) * 0.5 - v3_0, // The second direction is the object-space normal of the triangle. d3, // These are perpendicular. true ); // Shrink it a bit to ensure we don't generate normals that are too large. mx3_obj *= CScaleI3<>(0.999f); Assert(Fuzzy(~mx3_obj, 0.1f) == mx3_obj.mx3Transpose()); TReal r_sign = rSignedArea(tc_0, tc_1, tc_2); if (r_sign == 0) // Degenerate texture triangle; just return the object transform. return mx3_obj.mx3Transpose(); // The direction from point 0 to the midpoint of 1 and 2. CDir2<> d2_tex = (tc_1 + tc_2) * 0.5 - tc_0; // // Create a similar matrix representing the texture frame. // Then concatenate the inverse of mx3_obj with the texture matrix, thus creating // a matrix which converts from the object frame to the texture frame. Finally, // if the texture frame's Z axis is below the XY plane (its orientation is clockwise), // reverse the Z axis of the resulting transform. // // We actually do a shortcut version of this process. The texture matrix has d2_tex // as its X, the positive or negative Z axis as its Y, and their cross-product as its Z: // // d2_tex.tX d3_tex.tY 0 // 0 0 S // S*d3_tex.tY -S*d2_tex.tX 0 // // Also, the inverse of mx3_obj is its transpose. Thus, the concatenation of ~mx3_obj // and the texture matrix is given by the code below: // if (r_sign < 0) mx3_obj.v3Z *= -1; return CMatrix3<> ( CVector2<>(mx3_obj.v3X.tX, mx3_obj.v3Z.tX) * d2_tex, CVector2<>(mx3_obj.v3X.tX, mx3_obj.v3Z.tX) ^ d2_tex, mx3_obj.v3Y.tX, CVector2<>(mx3_obj.v3X.tY, mx3_obj.v3Z.tY) * d2_tex, CVector2<>(mx3_obj.v3X.tY, mx3_obj.v3Z.tY) ^ d2_tex, mx3_obj.v3Y.tY, CVector2<>(mx3_obj.v3X.tZ, mx3_obj.v3Z.tZ) * d2_tex, CVector2<>(mx3_obj.v3X.tZ, mx3_obj.v3Z.tZ) ^ d2_tex, mx3_obj.v3Y.tZ ); } //***************************************************************************************** inline float fGetX(CTexCoord tc, rptr pras) { return tc.tX * float(pras->iWidth); } //***************************************************************************************** inline float fGetY(CTexCoord tc, rptr pras) { return tc.tY * float(pras->iHeight); } //***************************************************************************************** uint32 u4GenerateUniqueBumpHashValue ( uint32 u4_old_hash, float f_xmin, float f_ymin, float f_xmax, float f_ymax, const CVector3<>& v3_p1, const CVector3<>& v3_p2, const CVector3<>& v3_p3, uint32 u4_poly, uint32 u4_vertices ) //************************************* { struct SGenHash { uint32 u4HashValue; int32 i4MinX; int32 i4MinY; int32 i4MaxX; int32 i4MaxY; uint32 u4PolyNumber; uint32 u4Verts; int32 i4Vert1p1; int32 i4Vert1p2; int32 i4Vert1p3; int32 i4Vert2p1; int32 i4Vert2p2; int32 i4Vert2p3; int32 i4Vert3p1; int32 i4Vert3p2; int32 i4Vert3p3; }; SGenHash gen_hash; // // The curved bump hash value is calculated with ints because we had problems with debug and release // mode generating different hash values. All I can think of that could cause this is the differences // is code generation generating slightly different float values and therefore a different hash value. // I now just use 2 decimal places of precison, if we are generating floating point values that differ // by more than 0.01 we have a serious problem. // // multiple the poly number by the old hash in an attempt to make the hash value more unique. gen_hash.u4PolyNumber = u4_poly * u4_old_hash; gen_hash.u4Verts = u4_vertices * u4_old_hash; gen_hash.i4MinX = (int32)(f_xmin*100.0f); gen_hash.i4MinY = (int32)(f_ymin*100.0f); gen_hash.i4MaxX = (int32)(f_xmax*100.0f); gen_hash.i4MaxY = (int32)(f_ymax*100.0f); gen_hash.u4HashValue = u4_old_hash; gen_hash.i4Vert1p1 = (int32)(v3_p1.tX*100.0f); gen_hash.i4Vert1p2 = (int32)(v3_p1.tY*100.0f); gen_hash.i4Vert1p3 = (int32)(v3_p1.tZ*100.0f); gen_hash.i4Vert2p1 = (int32)(v3_p2.tX*100.0f); gen_hash.i4Vert2p2 = (int32)(v3_p2.tY*100.0f); gen_hash.i4Vert2p3 = (int32)(v3_p2.tZ*100.0f); gen_hash.i4Vert3p1 = (int32)(v3_p3.tX*100.0f); gen_hash.i4Vert3p2 = (int32)(v3_p3.tY*100.0f); gen_hash.i4Vert3p3 = (int32)(v3_p3.tZ*100.0f); return u4Hash(&gen_hash, sizeof(gen_hash), false); } #pragma optimize("p", on) // Imrove float cosistency for generation of hash values. //***************************************************************************************** void GetUniqueTextureSize ( CMesh::CPolyIterator& pi, // Polygon iterator containing current polygon. CTexCoord& tc_min_src, CTexCoord& tc_max_src ) { for (int i = 0; i < pi.iNumVertices(); i++) { CTexCoord tc = pi.tcTexCoord(i); if (i == 0) tc_min_src = tc_max_src = tc; else { SetMin(tc_min_src.tX, tc.tX); SetMin(tc_min_src.tY, tc.tY); SetMax(tc_max_src.tX, tc.tX); SetMax(tc_max_src.tY, tc.tY); } } } #pragma optimize("p", off) //********************************************************************************************* void AdjustUniqueTextureCoordinates ( CMesh::CPolyIterator& pi, // Polygon iterator containing current polygon. rptr pbump, // original raster rptr pbump_new, // new raster CTexCoord tc_min // minimum texture co-ordinates ) //************************************* { // // Set new texture coordinates. // int i_x_src_0 = iTrunc(fGetX(tc_min, rptr_cast(CRaster, pbump))) - 2; int i_y_src_0 = iTrunc(fGetY(tc_min, rptr_cast(CRaster, pbump))) - 2; float f_x_src_0 = float(i_x_src_0) / float(pbump->iWidth); float f_y_src_0 = float(i_y_src_0) / float(pbump->iHeight); float f_x_src_to_dest = float(pbump->iWidth) / float(pbump_new->iWidth); float f_y_src_to_dest = float(pbump->iHeight) / float(pbump_new->iHeight); for (int i = 0; i < pi.iNumVertices(); i++) { pi.pmvVertex(i)->tcTex.tX = (pi.pmvVertex(i)->tcTex.tX - f_x_src_0) * f_x_src_to_dest; pi.pmvVertex(i)->tcTex.tY = (pi.pmvVertex(i)->tcTex.tY - f_y_src_0) * f_y_src_to_dest; // Check texture coordinate ranges. // Assert(bWithin(pi.pmvVertex(i)->tcTex.tX, 0.0, 1.0)); // Assert(bWithin(pi.pmvVertex(i)->tcTex.tX, 0.0, 1.0)); } } //***************************************************************************************** rptr pbumpCreateUniqueBumpap ( CMesh::CPolyIterator& pi, // Polygon iterator containing current polygon. uint32 u4_new_hash, // hash value of the new texture rptr pbump // The source bumpmap. ) { Assert(pbump); uint32 u4_parent_hash = pi.ptexTexture()->u4HashValue; // add this parent bump map to the list of parents. The parents do not have rasters // attached to them when the image is saved. If the bump map is only a curved bump // parent then it will not be in any other list, but if the parent map is used elsewhere // as a normal bump map then the map will also be in the usual pack image directory. In // this case the item from the parent list should be removed. // A parent map is only added to the list for every curved map that uses but each parent // will only be present once in the list. gtxmTexMan.AddCurvedBumpParent ( rptr_cast(CRaster,pbump), u4_parent_hash, pi.ptexTexture()->seterfFeatures[erfTRANSPARENT] ); // Get the minimum and maximum dimensions for the texture. CTexCoord tc_max_src; CTexCoord tc_min_src; GetUniqueTextureSize(pi,tc_min_src,tc_max_src); // Get the source copy area, rounding up/down and adding another pixel just for good measure. int i_x_src_0 = iTrunc(fGetX(tc_min_src, rptr_cast(CRaster, pbump))) - 2; int i_y_src_0 = iTrunc(fGetY(tc_min_src, rptr_cast(CRaster, pbump))) - 2; int i_x_src_1 = iTrunc(fGetX(tc_max_src, rptr_cast(CRaster, pbump))) + 3; int i_y_src_1 = iTrunc(fGetY(tc_max_src, rptr_cast(CRaster, pbump))) + 3; if (i_y_src_0 > i_y_src_1) Swap(i_y_src_0, i_y_src_1); if (i_x_src_0 > i_x_src_1) Swap(i_x_src_0, i_x_src_1); Assert(i_x_src_0 < i_x_src_1); Assert(i_y_src_0 < i_y_src_1); // if the size of the new map, in any dimension, is equal to the grow amount then // we have generated a curved texture that would have zero height. This will // produce large amounts of blue!. // Assert( (i_x_src_1 - i_x_src_0)>5); // ZERO PIXEL BUMP MAP-IDENTICAL U,V?? // Assert( (i_y_src_1 - i_y_src_0)>5); // ZERO PIXEL BUMP MAP-IDENTICAL U,V?? rptr pbump_new; // if the size of the new bump map is more than 5 bigger than the source bump // map then the UV co-ords of the cuvred section must be tiled and the delta UV // must be above 1.0. Tiled UV co-ords are not illegal, for example 0.4 to 1.4 is // tiled but valid as there is no overlap. A co-ords of 0.4 to 2.0 are illegal as // there is overlap. Overlapping tiled co-ords work perfectly it just makes new // rasters which are huge, the same as the tiling factor larger than the source. // For example, a tiled curved map which goes from 0.0 to 2.0 in both directions // with a source map of 256x256 would make a curved map of 512x512. // Try 8 pixels to allow a little overlap Assert( ((i_x_src_1 - i_x_src_0)-8)<=pbump->iWidth); //TILED CURVED MAP Assert( ((i_y_src_1 - i_y_src_0)-8)<=pbump->iHeight); //TILED CURVED MAP // Create a bumpmap of the width and height of the source rectangle. pbump_new = rptr_new CBumpMap ( i_x_src_1 - i_x_src_0, i_y_src_1 - i_y_src_0, pbump->pxf.ppalAttached ); // // Copy the source bumpmap rectangle to the new raster. // for (int i_y = 0; i_y < pbump_new->iHeight; i_y++) { int i_y_src = i_y + i_y_src_0; if (i_y_src < 0) { i_y_src += (-i_y_src*pbump->iHeight); } CBumpAnglePair* pbang = &pbump_new->tPix(0, i_y); CBumpAnglePair* pbang_src = &pbump->tPix(0, i_y_src % pbump->iHeight); for (int i_x = 0; i_x < pbump_new->iWidth; i_x++) { int i_x_src = i_x + i_x_src_0; if (i_x_src < 0) { // add on some multiple of the width to ensure that the X co-ord // is positive. // The line below will give massive X co-ords but this is OK because // the modulus in the array index will keep the actual co-ords in // the range 0..iWidth i_x_src += (-i_x_src*pbump->iWidth); } if (pbump->iWidth != 0) pbang[i_x].br = pbang_src[i_x_src % pbump->iWidth].br; } } AdjustUniqueTextureCoordinates ( pi, rptr_cast(CRaster,pbump), rptr_cast(CRaster,pbump_new), tc_min_src ); // Copy the polygon's current texture, change its raster to the new one, // and reassign it to the polygon's unique surface. CMesh::SSurface& sf = *pi.pmpPolygon()->pSurface; // Create the curved bump map sf.ptexTexture = rptr_new CTexture ( rptr_cast(CRaster, pbump_new), sf.ptexTexture->ppcePalClut->pmatMaterial, sf.ptexTexture->tpSolid, sf.ptexTexture->seterfFeatures ); // Set the curved flag. sf.ptexTexture->seterfFeatures += erfCURVED; // Clear the mips-created flag. sf.ptexTexture->seterfFeatures -= erfMIPS_CREATED; // // set the new hash value for the texture // sf.ptexTexture->u4HashValue = u4_new_hash; #if VER_DEBUG sf.ptexTexture->Validate(); #endif return pbump_new; } extern rptr ptexImageTexture ( SDirectoryFileChunk* pdfc, // Structure that specified virtual memory location const CMaterial* pmat // Material for clut construction. ); const int iMAX_VERTICES = 200; //***************************************************************************************** void ApplyCurves(rptr pmsh) { CCycleTimer ctmr; bool b_made_unique = false; uint32 u4_poly_count = 0; #if VER_DEBUG pmsh->Validate(); #endif // Iterate through the triangles of the mesh. // OK to use 0 for mesh instance and render context, as it doesn't need them. for (CMesh::CPolyIterator pi(*pmsh, 0, 0); pi.bNext(); ) { // u4_poly_count is only used for an ID so it does not matter that it is 1 based u4_poly_count++; // Act only on bumpmaps. if (pi.ptexTexture()->seterfFeatures[erfBUMP]) { // Get the transformation from world to texture space. CMatrix3<> mx3_obj_to_tex = mx3ObjToTexture ( pi.v3Point(0), pi.v3Point(1), pi.v3Point(2), pi.d3Normal(), pi.tcTexCoord(0), pi.tcTexCoord(1), pi.tcTexCoord(2) ); #if !bAXIS_SWAP // Save the object-to-texture matrix. pi.pmpPolygon()->pmx3ObjToTexture = pmx3GetSharedMatrix(mx3_obj_to_tex); #endif if (!pi.bCurved()) { #if bAXIS_SWAP // Do not apply curves. However, save the object-to-texture matrix. pi.pmpPolygon()->pmx3ObjToTexture = pmx3GetSharedMatrix(mx3_obj_to_tex); #endif continue; } MEMLOG_ADD_COUNTER(emlBumpCurveCount,1); // Ensure that every polygon has unique vertices and surfaces. if (!b_made_unique) { b_made_unique = true; pmsh->MakeVerticesUnique(); pmsh->MakeSurfacesUnique(); } // // Generate the hash value for this section of the curved map and check // if we already have one. // // Get the minimum and maximum dimensions for the texture. CTexCoord tc_max_src; CTexCoord tc_min_src; GetUniqueTextureSize(pi,tc_min_src,tc_max_src); rptr pras = rptr_nonconst(pi.ptexTexture()->prasGetTexture()); uint32 u4_hash = u4GenerateUniqueBumpHashValue(pi.ptexTexture()->u4HashValue, fGetX(tc_min_src, rptr_cast(CRaster, pras )), fGetY(tc_min_src, rptr_cast(CRaster, pras )), fGetX(tc_max_src, rptr_cast(CRaster, pras )), fGetY(tc_max_src, rptr_cast(CRaster, pras )), pi.v3Point(0), pi.v3Point(1), pi.v3Point(2), u4_poly_count, pi.iNumVertices()); // Detect zero area texture polygons. bool bBadTextureCoords = false; if (fabs(rSignedArea(pi.tcTexCoord(0), pi.tcTexCoord(1), pi.tcTexCoord(2))) < 0.001) bBadTextureCoords = true; // // If the polygon we are attempting to curve has zero area, set all the texture // values the same. // if (bBadTextureCoords) { CTexCoord tc_avg = pi.pmvVertex(0)->tcTex; // Compute averages. int i; for (i = 1; i < pi.iNumVertices(); i++) { tc_avg += pi.pmvVertex(i)->tcTex; } tc_avg /= float(pi.iNumVertices()); // Loop through the triangles of this polygon. for (i = 0; i < pi.iNumVertices(); i++) { pi.pmvVertex(i)->tcTex = tc_avg; pi.pmvVertex(i)->d3Normal = pi.d3Normal(); } // Update thest values since we have modified the texture co-ordinates. GetUniqueTextureSize(pi,tc_min_src,tc_max_src); } // is there a valid VM image file open?? if (CLoadImageDirectory::bImageValid()) { // yes there is a valid image, is our map in it SDirectoryFileChunk* pdfc; pdfc = CLoadImageDirectory::plidImageDir->mapChunk[(uint64)u4_hash]; if (pdfc) { CMesh::SSurface& sf = *pi.pmpPolygon()->pSurface; sf.ptexTexture = ptexImageTexture(pdfc, sf.ptexTexture->ppcePalClut->pmatMaterial ); sf.ptexTexture->seterfFeatures += erfCURVED; AdjustUniqueTextureCoordinates ( pi, rptr_cast(CRaster,pras), rptr_cast(CRaster,sf.ptexTexture->prasGetTexture()), tc_min_src ); #if bAXIS_SWAP // Store the matrix necessary to transform a light direction from object to the // axis-swapped texture space. pi.pmpPolygon()->pmx3ObjToTexture = SwapMatrices.pmx3GetMatrix(pi.d3Normal()); #endif continue; } else { #if VER_TEST char str_buffer[1024]; sprintf(str_buffer, "%s\n\nWarning: Curved bump map not in swap file..\n", __FILE__); bTerminalError(ERROR_ASSERTFAIL, true, str_buffer, __LINE__); #endif } } SVirtualImageElement* vie = gtxmTexMan.vieFindInPackedLog(u4_hash, true, pi.ptexTexture()->tpSolid); if (vie) { dprintf("Instancing curved bump map for polygon %d\n", u4_poly_count-1); CMesh::SSurface& sf = *pi.pmpPolygon()->pSurface; sf.ptexTexture = vie->ptexTexture; AdjustUniqueTextureCoordinates ( pi, rptr_cast(CRaster,pras), rptr_cast(CRaster,sf.ptexTexture->prasGetTexture()), tc_min_src ); #if bAXIS_SWAP // Store the matrix necessary to transform a light direction from object to the // axis-swapped texture space. pi.pmpPolygon()->pmx3ObjToTexture = SwapMatrices.pmx3GetMatrix(pi.d3Normal()); #endif continue; } // // If we get to here we did not find the curved map in the virtual image, or there is // no virtual image. // // Get a pointer to the texture surface as a bump map. // This dynamic cast will alwyas fail if the parent is within the virtual image // but the curved section cannot be found. The imaged parent cannot be cast to a // bump. rptr pbump = rptr_dynamic_cast(CBumpMap, pras); // did we get a bump map from the above cast Assert(pbump); if (!pbump) { pi.pmpPolygon()->pmx3ObjToTexture = SwapMatrices.pmx3GetMatrix(pi.d3Normal()); continue; } // Make a unique raster for the segement of the bumpmap to be curved. rptr pbump_new = pbumpCreateUniqueBumpap ( pi, u4_hash, pbump ); //Assert(rptr_cast(CRaster, pbump_new) == pi.ptexTexture->prasTexture); Assert(pbump_new != pbump); // // Store the colour values. // // Create a temporary array for storage. uint u_pixels = pbump_new->iLinePixels * pbump_new->iHeight; uint u_width = pbump_new->iLinePixels; uint u_height = pbump_new->iHeight; Assert(u_pixels); Assert(u_width); Assert(u_height); CBumpAnglePair* pbang_store = new CBumpAnglePair[u_pixels]; CBumpAnglePair* pbang_map = (CBumpAnglePair*)pbump_new->pSurface; Assert(pbang_map); Assert(pbang_store); // Copy. { pbump_new->Lock(); for (uint u = 0; u < u_pixels; ++u) { CBumpAnglePair bang_temp = pbang_map[u]; pbang_map[u].SetColour(0); pbang_store[u] = bang_temp; } pbump_new->Unlock(); } #if (0) // Create a CRenderPolygon for CDrawPolygon. CRenderPolygon rp; Assert(pi.iNumVertices() <= iMAX_VERTICES); SRenderVertex arvVertices[iMAX_VERTICES]; SRenderVertex* aprvVertices[iMAX_VERTICES]; // Assign the polygon's vertex pointer array. rp.paprvPolyVertices = PArray(pi.iNumVertices(), aprvVertices); rp.ptexTexture = rptr_new CTexture(pbump_new); for (int i = 0; i < pi.iNumVertices(); i++) { aprvVertices[i] = &arvVertices[i]; // Extend the texture coordinates to do a "Fat Fill." FatFill(tc_0, tc_1, tc_2, pbump_new); arvVertices[i].tcTex = pi.tcTexCoord(i); arvVertices[i].v3Screen.tZ = 0.001; arvVertices[i].pdir3Normal = &pi.d3Normal(i); } // // Use a standard rasterizing routine to draw a triangle with interpolated curvature on // the bumpmap surface. Assume that there is no Z-buffer. // CDrawPolygon< CLineBumpMake > ( pbump_new, ptr(), rp ); #else // Loop through the triangles of this polygon. for (int i_tri = 0; i_tri < pi.iNumVertices() - 2; i_tri++) { CTexCoord tc_0 = pi.tcTexCoord(0); CTexCoord tc_1 = pi.tcTexCoord(1 + i_tri); CTexCoord tc_2 = pi.tcTexCoord(2 + i_tri); // Get the vertex normals. CDir3<> d3_0 = pi.d3Normal(0); CDir3<> d3_1 = pi.d3Normal(1 + i_tri); CDir3<> d3_2 = pi.d3Normal(2 + i_tri); CDir3<> d3_face = pi.d3Normal(); // Transform the vertex normals to texture space. d3_0 *= mx3_obj_to_tex; d3_1 *= mx3_obj_to_tex; d3_2 *= mx3_obj_to_tex; d3_face *= mx3_obj_to_tex; if (!bBadTextureCoords) { // Extend the texture coordinates to do a "Fat Fill." FatFill(tc_0, tc_1, tc_2, pbump_new); } #if bAXIS_SWAP // // Calculate final transformation matrix. This transforms bumps from flat texture // space into object space, then swaps any axes necessary to get it pointing mostly // vertical. // CMatrix3<> mx3_reverse = mx3_obj_to_tex.mx3Transpose(); SwapAxesVertical(mx3_reverse, pi.d3Normal()); #else // Do not transform bumps. CMatrix3<> mx3_reverse; #endif #if bSUBDIVIDE_BUMPS // Render the curve. if (rSignedArea(tc_0, tc_1, tc_2) < 0) pbump_new->DrawBumps(tc_0, d3_0, tc_1, d3_1, tc_2, d3_2, d3_face, mx3_reverse); else pbump_new->DrawBumps(tc_2, d3_2, tc_1, d3_1, tc_0, d3_0, d3_face, mx3_reverse); #else // Render the curve. if (rSignedArea(tc_0, tc_1, tc_2) < 0) pbump_new->DrawBump(tc_0, d3_0, tc_1, d3_1, tc_2, d3_2, mx3_reverse); else pbump_new->DrawBump(tc_2, d3_2, tc_1, d3_1, tc_0, d3_0, mx3_reverse); #endif // bSUBDIVIDE_BUMPS } #endif pbump_new->Lock(); #if (0) // // Restore the colour values and grow the curved area by 1 pixel in X and Y. // { bool b_col; uint u_offset; // // PASS 1 of the colour restore restores just the curved pixels in the source // map and grows by 1 pixel on the X axis. In doing this the bCurvedFlag gets // destroyed as this flag is stored in place of the colour information. // u_offset = 0; for (uint y = 0; y0) { pbang_map[u_offset-1] = pbang_map[u_offset]; pbang_map[u_offset-1].SetColour(pbang_store[u_offset-1].u1GetColour()); } b_col = true; } } u_offset++; } } // // PASS 2: Scans the bit map vertically growing the pixels out by 1. Angle data // for the new pixels is obtained from the neighbour bur the colour data is // obtained from the same pixel in the source map. // for (uint x = 0; x0) { pbang_map[u_offset-u_width] = pbang_map[u_offset]; pbang_map[u_offset-u_width]. SetColour(pbang_store[u_offset-u_width].u1GetColour()); } b_col = true; } } } } } #else { for (uint u = 0; u < u_pixels; ++u) { if (pbang_map[u].bGetCurveFlag()) { //Assert(pbang_store[u].u1GetColour()); pbang_map[u].SetColour(pbang_store[u].u1GetColour()); } } } #endif pbump_new->Unlock(); delete[] pbang_store; #if bAXIS_SWAP // Store the matrix necessary to transform a light direction from object to the // axis-swapped texture space. pi.pmpPolygon()->pmx3ObjToTexture = SwapMatrices.pmx3GetMatrix(pi.d3Normal()); #endif } } #if VER_DEBUG pmsh->Validate(); #endif } //***************************************************************************************** double dGetRadians(CTexCoord tc_a, CTexCoord tc_b) { float dx = tc_b.tX - tc_a.tX; float dy = tc_b.tY - tc_a.tY; return fmod((atan2(dy, dx) + d2_PI), d2_PI); } //***************************************************************************************** double dAverageAngles(double d_a, double d_b) { if (d_a > d_b) Swap(d_a, d_b); double d_average = (d_a + d_b) / 2.0; if (d_b - d_a > dPI) return fmod(d_average + dPI, d2_PI); return d_average; } //***************************************************************************************** void Extend(CTexCoord& tc_0, CTexCoord tc_1, CTexCoord tc_2, double d_radius = 2.0) { double d_ang = dAverageAngles ( dGetRadians(tc_1, tc_0), dGetRadians(tc_2, tc_0) ); tc_0.tX += float(cos(d_ang) * d_radius); tc_0.tY += float(sin(d_ang) * d_radius); } //***************************************************************************************** void FatFill(CTexCoord& tc_0, CTexCoord& tc_1, CTexCoord& tc_2, rptr pbump) { CTexCoord tc_d_0, tc_d_1, tc_d_2; // Texture coordinates in destination coordinates. CTexCoord tc_dp_0, tc_dp_1, tc_dp_2; // New texture coordinates in destination coordinates. // Create coordinates using destination dimensions. tc_d_0.tX = fGetX(tc_0, rptr_cast(CRaster, pbump)); tc_d_0.tY = fGetY(tc_0, rptr_cast(CRaster, pbump)); tc_d_1.tX = fGetX(tc_1, rptr_cast(CRaster, pbump)); tc_d_1.tY = fGetY(tc_1, rptr_cast(CRaster, pbump)); tc_d_2.tX = fGetX(tc_2, rptr_cast(CRaster, pbump)); tc_d_2.tY = fGetY(tc_2, rptr_cast(CRaster, pbump)); tc_dp_0 = tc_d_0; tc_dp_1 = tc_d_1; tc_dp_2 = tc_d_2; // Extend coordinates. Extend(tc_dp_0, tc_d_1, tc_d_2); Extend(tc_dp_1, tc_d_0, tc_d_2); Extend(tc_dp_2, tc_d_1, tc_d_0); // Convert texture coordinates back to virtual coordinates. float f_dx = 1.0f / pbump->iWidth; float f_dy = 1.0f / pbump->iHeight; tc_0.tX = tc_dp_0.tX * f_dx; tc_0.tY = tc_dp_0.tY * f_dy; tc_1.tX = tc_dp_1.tX * f_dx; tc_1.tY = tc_dp_1.tY * f_dy; tc_2.tX = tc_dp_2.tX * f_dx; tc_2.tY = tc_dp_2.tY * f_dy; // Make sure texture coordinates are within the bounds of the raster. SetMinMax(tc_0.tX, 0.0f, 1.0f); SetMinMax(tc_0.tY, 0.0f, 1.0f); SetMinMax(tc_1.tX, 0.0f, 1.0f); SetMinMax(tc_1.tY, 0.0f, 1.0f); SetMinMax(tc_2.tX, 0.0f, 1.0f); SetMinMax(tc_2.tY, 0.0f, 1.0f); } //***************************************************************************************** void SwapAxesVertical(CVector3<>& v3, const CDir3<>& d3_control) { if (Abs(d3_control.tX) >= Abs(d3_control.tY)) { if (Abs(d3_control.tX) >= Abs(d3_control.tZ)) { // X is main axis; swap with Z. Swap(v3.tX, v3.tZ); if (d3_control.tX < 0) { // And it's negative, so negate the two. v3.tX = -v3.tX; v3.tZ = -v3.tZ; } } else if (d3_control.tZ < 0) { // Z is main axis, and negative; so negate Z. v3.tZ = -v3.tZ; } } else { if (Abs(d3_control.tY) >= Abs(d3_control.tZ)) { // Y is main axis; swap with Z. Swap(v3.tY, v3.tZ); if (d3_control.tY < 0) { // And it's negative, so negate the two. v3.tY = -v3.tY; v3.tZ = -v3.tZ; } } else if (d3_control.tZ < 0) { // Z is main axis, and negative; so negate Z. v3.tZ = -v3.tZ; } } #if VER_DEBUG static bool b_flag = 0; if (!b_flag) { b_flag = true; CDir3<> d3_test = d3_control; SwapAxesVertical(d3_test, d3_control); Assert(d3_test.tZ >= Abs(d3_test.tX) && d3_test.tZ >= Abs(d3_test.tY)); SwapAxesVertical(d3_test, d3_control); Assert(Fuzzy(d3_test, FLT_EPSILON) == d3_control); b_flag = false; } #endif } //***************************************************************************************** void SwapAxesVertical(CMatrix3<>& mx3, const CDir3<>& d3_control) { SwapAxesVertical(mx3.v3X, d3_control); SwapAxesVertical(mx3.v3Y, d3_control); SwapAxesVertical(mx3.v3Z, d3_control); } //************************************************************************************* CVerticalAxisSwapMatrices::CVerticalAxisSwapMatrices() { for (int i = 0; i < 6; i++) { mx3SwapMatrices[i] = CMatrix3<>(); switch (i) { case 0: // Identity. break; case 1: // Z is main axis, and negative; so negate Z. mx3SwapMatrices[i].v3X.tZ = -mx3SwapMatrices[i].v3X.tZ; mx3SwapMatrices[i].v3Y.tZ = -mx3SwapMatrices[i].v3Y.tZ; mx3SwapMatrices[i].v3Z.tZ = -mx3SwapMatrices[i].v3Z.tZ; break; case 2: // X is main axis; swap with Z. Swap(mx3SwapMatrices[i].v3X.tX, mx3SwapMatrices[i].v3X.tZ); Swap(mx3SwapMatrices[i].v3Y.tX, mx3SwapMatrices[i].v3Y.tZ); Swap(mx3SwapMatrices[i].v3Z.tX, mx3SwapMatrices[i].v3Z.tZ); break; case 3: // X is main axis; swap with Z. Swap(mx3SwapMatrices[i].v3X.tX, mx3SwapMatrices[i].v3X.tZ); Swap(mx3SwapMatrices[i].v3Y.tX, mx3SwapMatrices[i].v3Y.tZ); Swap(mx3SwapMatrices[i].v3Z.tX, mx3SwapMatrices[i].v3Z.tZ); // And it's negative, so negate the two. mx3SwapMatrices[i].v3X.tX = -mx3SwapMatrices[i].v3X.tX; mx3SwapMatrices[i].v3Y.tX = -mx3SwapMatrices[i].v3Y.tX; mx3SwapMatrices[i].v3Z.tX = -mx3SwapMatrices[i].v3Z.tX; mx3SwapMatrices[i].v3X.tZ = -mx3SwapMatrices[i].v3X.tZ; mx3SwapMatrices[i].v3Y.tZ = -mx3SwapMatrices[i].v3Y.tZ; mx3SwapMatrices[i].v3Z.tZ = -mx3SwapMatrices[i].v3Z.tZ; break; case 4: // Y is main axis; swap with Z. Swap(mx3SwapMatrices[i].v3X.tY, mx3SwapMatrices[i].v3X.tZ); Swap(mx3SwapMatrices[i].v3Y.tY, mx3SwapMatrices[i].v3Y.tZ); Swap(mx3SwapMatrices[i].v3Z.tY, mx3SwapMatrices[i].v3Z.tZ); break; case 5: // Y is main axis; swap with Z. Swap(mx3SwapMatrices[i].v3X.tY, mx3SwapMatrices[i].v3X.tZ); Swap(mx3SwapMatrices[i].v3Y.tY, mx3SwapMatrices[i].v3Y.tZ); Swap(mx3SwapMatrices[i].v3Z.tY, mx3SwapMatrices[i].v3Z.tZ); // And it's negative, so negate the two. mx3SwapMatrices[i].v3X.tY = -mx3SwapMatrices[i].v3X.tY; mx3SwapMatrices[i].v3Y.tY = -mx3SwapMatrices[i].v3Y.tY; mx3SwapMatrices[i].v3Z.tY = -mx3SwapMatrices[i].v3Z.tY; mx3SwapMatrices[i].v3X.tZ = -mx3SwapMatrices[i].v3X.tZ; mx3SwapMatrices[i].v3Y.tZ = -mx3SwapMatrices[i].v3Y.tZ; mx3SwapMatrices[i].v3Z.tZ = -mx3SwapMatrices[i].v3Z.tZ; break; } } } //************************************************************************************* CMatrix3<>* CVerticalAxisSwapMatrices::pmx3GetMatrix(const CDir3<>& d3_control) { int i_matrix = 0; if (Abs(d3_control.tX) >= Abs(d3_control.tY)) { if (Abs(d3_control.tX) >= Abs(d3_control.tZ)) { // X is main axis; swap with Z. i_matrix = 2; if (d3_control.tX < 0) { // And it's negative, so negate the two. i_matrix = 3; } } else if (d3_control.tZ < 0) { // Z is main axis, and negative; so negate Z. i_matrix = 1; } } else { if (Abs(d3_control.tY) >= Abs(d3_control.tZ)) { // Y is main axis; swap with Z. i_matrix = 4; if (d3_control.tY < 0) { // And it's negative, so negate the two. i_matrix = 5; } } else if (d3_control.tZ < 0) { // Z is main axis, and negative; so negate Z. i_matrix = 1; } } return &mx3SwapMatrices[i_matrix]; } //************************************************************************************* CMatrix3<>* CVerticalAxisSwapMatrices::pmx3GetMatrix(int i_index) { return &mx3SwapMatrices[i_index]; } //***************************************************************************************** // int iHashMatrix ( const CMatrix3<> &mx3_mat ) // // Compute a unique value for a given matrix. // //************************************** { const float f_radians = 3.14159f*2.0f; const int i_ang_bits = 8; const int i_range = 1 << i_ang_bits; float xrot, yrot, zrot; // Convert to Euler angles. yrot = asin(-mx3_mat.v3X.tZ); if (fabs(cos(yrot)) > FLT_EPSILON) { xrot = atan2(mx3_mat.v3Y.tZ, mx3_mat.v3Z.tZ); zrot = atan2(mx3_mat.v3X.tY, mx3_mat.v3X.tX); } else { xrot = atan2(mx3_mat.v3Y.tX, mx3_mat.v3Y.tY); zrot = 0.0f; } // Make sure they are all positive. if (xrot < 0.0f) xrot += f_radians; if (yrot < 0.0f) yrot += f_radians; if (zrot < 0.0f) zrot += f_radians; Assert(xrot >= 0.0f && xrot <= f_radians) Assert(yrot >= 0.0f && yrot <= f_radians) Assert(zrot >= 0.0f && zrot <= f_radians) // Cut down precision of angles. int i_xrot = iRound(xrot*(i_range/f_radians)) & (i_range-1); int i_yrot = iRound(yrot*(i_range/f_radians)) & (i_range-1); int i_zrot = iRound(zrot*(i_range/f_radians)) & (i_range-1); // Assembly into an integer. return (i_xrot << i_ang_bits*2) | (i_yrot << i_ang_bits) | i_zrot; } //***************************************************************************************** // void MatrixFromHash ( int i_hash, CMatrix3<> &mx3_mat ) // // Re-construct a matrix from a hash identifier. // //************************************** { const float f_radians = 3.14159f*2.0f; const int i_ang_bits = 8; const int i_range = 1 << i_ang_bits; // Get angles from hash. float xrot = ((i_hash >> i_ang_bits*2) & i_range-1) * f_radians/i_range; float yrot = ((i_hash >> i_ang_bits) & i_range-1) * f_radians/i_range; float zrot = (i_hash & i_range-1) * f_radians/i_range; // Compute sines and cosines. float f_sin_x = sin(xrot); float f_cos_x = cos(xrot); float f_sin_y = sin(yrot); float f_cos_y = cos(yrot); float f_sin_z = sin(zrot); float f_cos_z = cos(zrot); // Compute matrix. mx3_mat.v3X.tX = f_cos_y*f_cos_z; mx3_mat.v3X.tY = f_cos_y*f_sin_z; mx3_mat.v3X.tZ = -f_sin_y; mx3_mat.v3Y.tX = f_sin_x*f_sin_y*f_cos_z - f_cos_x*f_sin_z; mx3_mat.v3Y.tY = f_sin_x*f_sin_y*f_sin_z + f_cos_x*f_cos_z; mx3_mat.v3Y.tZ = f_sin_x*f_cos_y; mx3_mat.v3Z.tX = f_cos_x*f_sin_y*f_cos_z + f_sin_x*f_sin_z; mx3_mat.v3Z.tY = f_cos_x*f_sin_y*f_sin_z - f_sin_x*f_cos_z; mx3_mat.v3Z.tZ = f_cos_x*f_cos_y; } // // Set of bump map matrices. // typedef std::map< int, CMatrix3<>*, std::less > TMapMatrixHash; TMapMatrixHash* pmapBumpMatrices = 0; //***************************************************************************************** // void FastBumpCleanup() // // Cleanup anything that has been allocated by bump map creation. // //************************************** { delete pmapBumpMatrices; pmapBumpMatrices = 0; } //***************************************************************************************** // CMatrix3<>* pmx3GetSharedMatrix ( const CMatrix3<> &mx3_mat ) // // Either allocate or find a matrix for bump-mapping. // //************************************** { // Create the bump matrix map if we haven't already. if (!pmapBumpMatrices) { pmapBumpMatrices = new TMapMatrixHash; AlwaysAssert(pmapBumpMatrices); } // Compute the matrix hash. int i_hash = iHashMatrix(mx3_mat); // See if we already have this matrix. CMatrix3<> *pmx3 = (*pmapBumpMatrices)[i_hash]; if (!pmx3) { // Just create the damn thing. void *pv_mat = CLoadImageDirectory::pvAllocate(sizeof(CMatrix3<>)); MEMLOG_ADD_COUNTER(emlBumpMatrix,1); pmx3 = new(pv_mat) CMatrix3<>(mx3_mat); #if (0) // Should we re-construct the matrix from the hash value? MatrixFromHash(i_hash, *pmx3); #endif // Add to map. (*pmapBumpMatrices)[i_hash] = pmx3; } return pmx3; } //********************************************************************************************* // void DrawTriangleBump ( CBumpMap* prasDest, CRenderPolygon& rpoly // Info on polygon (triangle) to render. ) // // Routine to render a polygon so that any pixel that is overlapped by the polygon // is drawn. The bump map is drawn by interpolating normals. // // Note that this routine does not sub-pixel correct the normals. It could compute the // area of intersection of the polygon with the pixel to get correct normal values. In // practice it doesn't seem necessary. // //************************************** { int i, cnt; int topi, li, ri; // Top, current left, and current right vertex indices. int iy; // Current integer y value. float fy; // Current floating point y value. int ly, ry; // End of left and right edges. int ix1, ix2; float recip_x; // Edge values. float x1, xslope1; CVector3<> v1, vslope1; float x2, xslope2; CVector3<> v2, vslope2; cnt = rpoly.paprvPolyVertices.uLen; // We are limited to triangle right now. AlwaysAssert(cnt == 3); // Find top vertex of polygon. topi = 0; float ymin = rpoly.paprvPolyVertices[0]->v3Screen.tY; for (i = 1; i < cnt; i++) { if (rpoly.paprvPolyVertices[i]->v3Screen.tY < ymin) { topi = i; ymin = rpoly.paprvPolyVertices[i]->v3Screen.tY; } } fy = ymin; iy = floor(ymin); li = ri = topi; ly = ry = iy-1; CBumpAnglePair* ptpix_screen = (CBumpAnglePair*)prasDest->pSurface + iy * prasDest->iLinePixels; // Draw. while (cnt > 0) { // Advance left edge? while (ly <= iy && cnt) { cnt--; i = li + 1; if (i >= rpoly.paprvPolyVertices.uLen) i = 0; float recip_y = 1.0f / (rpoly.paprvPolyVertices[i]->v3Screen.tY - rpoly.paprvPolyVertices[li]->v3Screen.tY); x1 = rpoly.paprvPolyVertices[li]->v3Screen.tX; xslope1 = (rpoly.paprvPolyVertices[i]->v3Screen.tX - rpoly.paprvPolyVertices[li]->v3Screen.tX) * recip_y; v1 = *rpoly.paprvPolyVertices[li]->pdir3Normal; vslope1 = ((CVector3<>)*rpoly.paprvPolyVertices[i]->pdir3Normal - (CVector3<>)*rpoly.paprvPolyVertices[li]->pdir3Normal) * recip_y; ly = floor(rpoly.paprvPolyVertices[i]->v3Screen.tY); li = i; } // Advance right edge? while (ry <= iy && cnt) { cnt--; i = ri - 1; if (i < 0) i = rpoly.paprvPolyVertices.uLen - 1; float recip_y = 1.0f / (rpoly.paprvPolyVertices[i]->v3Screen.tY - rpoly.paprvPolyVertices[ri]->v3Screen.tY); x2 = rpoly.paprvPolyVertices[ri]->v3Screen.tX; xslope2 = (rpoly.paprvPolyVertices[i]->v3Screen.tX - rpoly.paprvPolyVertices[ri]->v3Screen.tX) * recip_y; v2 = *rpoly.paprvPolyVertices[ri]->pdir3Normal; vslope2 = ((CVector3<>)*rpoly.paprvPolyVertices[i]->pdir3Normal - (CVector3<>)*rpoly.paprvPolyVertices[ri]->pdir3Normal) * recip_y; ry = floor(rpoly.paprvPolyVertices[i]->v3Screen.tY); ri = i; } // Fill until end of left or right edge. while (iy < ly && iy < ry) { // Determine the x positions where the left and right edges cross into the // next scanline. float y_step = (iy+1) - fy; float next_x1 = x1 + xslope1 * y_step; float next_x2 = x2 + xslope2 * y_step; // Take the min/max of the current x value and the next x values. if (next_x1 < x1) ix1 = floor(next_x1); else ix1 = floor(x1); if (next_x2 > x2) ix2 = floor(next_x2); else ix2 = floor(x2); if (ix1 < ix2) recip_x = 1.0f / float(ix2 - ix1); else recip_x = 0.0f; vec3DeltaX = (v2 - v1) * recip_x; Assert(ix1 >= 0); Assert(ix2 < prasDest->iWidth); DrawLoopBumpMake(ix1 - ix2 - 1, ptpix_screen + ix2 + 1, v1); // Advance edge values by y_step. x1 += xslope1 * y_step; x2 += xslope2 * y_step; v1 += vslope1 * y_step; v2 += vslope2 * y_step; // Integer y and floating point y now match up. iy++; fy = iy; ptpix_screen += prasDest->iLinePixels; } // Check for zero height polygon. if (ri == topi) { // Left edge has consumed all the edges. Assert(cnt == 0); // Find min/max x co-ordinates. x1 = rpoly.paprvPolyVertices[0]->v3Screen.tX; x2 = rpoly.paprvPolyVertices[0]->v3Screen.tX; v1 = *rpoly.paprvPolyVertices[0]->pdir3Normal; v2 = *rpoly.paprvPolyVertices[0]->pdir3Normal; for (i = 1; i < rpoly.paprvPolyVertices.uLen; i++) { if (rpoly.paprvPolyVertices[i]->v3Screen.tX < x1) { x1 = rpoly.paprvPolyVertices[i]->v3Screen.tX; v1 = *rpoly.paprvPolyVertices[i]->pdir3Normal; } if (rpoly.paprvPolyVertices[i]->v3Screen.tX > x2) { x2 = rpoly.paprvPolyVertices[i]->v3Screen.tX; v2 = *rpoly.paprvPolyVertices[i]->pdir3Normal; } } // Draw a line between them. ix1 = floor(x1); ix2 = floor(x2); if (ix1 < ix2) recip_x = 1.0f / float(ix2 - ix1); else recip_x = 0.0f; vec3DeltaX = (v2 - v1) * recip_x; Assert(ix1 >= 0); Assert(ix2 < prasDest->iWidth); DrawLoopBumpMake(ix1 - ix2 - 1, ptpix_screen + ix2 + 1, v1); } else { float alt_x1, alt_x2; // Finish left over part of edge, could also be zero height edge. if (ly == iy && ry == iy) { // End of both edges. // Determine the x positions where the left and right edges end. alt_x1 = rpoly.paprvPolyVertices[li]->v3Screen.tX; alt_x2 = rpoly.paprvPolyVertices[ri]->v3Screen.tX; // This would need to take into account the way "fy" is treated // to work for N-sided polygons since the left and right edges // may end at different points. } else if (ly == iy) { // End of left edge. fy = rpoly.paprvPolyVertices[li]->v3Screen.tY; alt_x1 = rpoly.paprvPolyVertices[li]->v3Screen.tX; // Step other edge to fy. alt_x2 = x2; x2 += xslope2 * (fy - iy); } else if (ry == iy) { // End of right edge. fy = rpoly.paprvPolyVertices[ri]->v3Screen.tY; alt_x2 = rpoly.paprvPolyVertices[ri]->v3Screen.tX; // Step other edge to fy. alt_x1 = x1; x1 += xslope1 * (fy - iy); } // Take the min/max of the current x value and the alternate x values. if (alt_x1 < x1) ix1 = floor(alt_x1); else ix1 = floor(x1); if (alt_x2 > x2) ix2 = floor(alt_x2); else ix2 = floor(x2); if (ix1 < ix2) recip_x = 1.0f / float(ix2 - ix1); else recip_x = 0.0f; vec3DeltaX = (v2 - v1) * recip_x; Assert(ix1 >= 0); Assert(ix2 < prasDest->iWidth); DrawLoopBumpMake(ix1 - ix2 - 1, ptpix_screen + ix2 + 1, v1); } } }