/* Copyright (C) 1991-2001 and beyond by Bungie Studios, Inc. and the "Aleph One" developers. This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. This license is contained in the file "COPYING", which is included with this source code; it is available online at http://www.gnu.org/licenses/gpl.html 3D-Model Object Storage Functions By Loren Petrich, July 8, 2001 */ #include #include #include "VecOps.h" #include "cseries.h" #include "world.h" #ifdef HAVE_OPENGL #ifdef __WIN32__ #include #endif #include "Model3D.h" #ifdef HAVE_OPENGL # if defined (__APPLE__) && defined (__MACH__) # include # elif defined mac # include # else # include # endif #endif // Bone-stack and transformation-matrix locally-used arrays; // the matrices have dimensions (output coords)(input-coord multipliers + offset for output) static vector BoneMatrices; static vector BoneStack; // Find transform of point (source and dest must be different arrays) inline void TransformPoint(GLfloat *Dest, GLfloat *Src, Model3D_Transform& T) { for (int ic=0; ic<3; ic++) { GLfloat *Row = T.M[ic]; Dest[ic] = ScalarProd(Src,Row) + Row[3]; } } // Like above, but a vector, such as a normal (source and dest must be different arrays) inline void TransformVector(GLfloat *Dest, GLfloat *Src, Model3D_Transform& T) { for (int ic=0; ic<3; ic++) { GLfloat *Row = T.M[ic]; Dest[ic] = ScalarProd(Src,Row); } } // Bone and Frame (positions, angles) -> Transform Matrix static void FindFrameTransform(Model3D_Transform& T, Model3D_Frame& Frame, GLfloat MixFrac, Model3D_Frame& AddlFrame); static void FindBoneTransform(Model3D_Transform& T, Model3D_Bone& Bone, Model3D_Frame& Frame, GLfloat MixFrac, Model3D_Frame& AddlFrame); // Res = A * B, in that order static void TMatMultiply(Model3D_Transform& Res, Model3D_Transform& A, Model3D_Transform& B); // Trig-function conversion: const GLfloat TrigNorm = GLfloat(1)/GLfloat(TRIG_MAGNITUDE); // Erase everything void Model3D::Clear() { Positions.clear(); TxtrCoords.clear(); Normals.clear(); Colors.clear(); VtxSrcIndices.clear(); VtxSources.clear(); NormSources.clear(); InverseVSIndices.clear(); InvVSIPointers.clear(); Bones.clear(); VertIndices.clear(); Frames.clear(); SeqFrames.clear(); SeqFrmPointers.clear(); FindBoundingBox(); } // Normalize an individual normal; return whether the normal had a nonzero length static bool NormalizeNormal(GLfloat *Normal) { GLfloat NormalSqr = Normal[0]*Normal[0] + Normal[1]*Normal[1] + Normal[2]*Normal[2]; if (NormalSqr <= 0) return false; GLfloat NormalRecip = (GLfloat)(1/sqrt(NormalSqr)); Normal[0] *= NormalRecip; Normal[1] *= NormalRecip; Normal[2] *= NormalRecip; return true; } // Flagged vector for per-polygon and per-vertex normals; // the flag is "true" for nonzero vectors and vectors meant to be used struct FlaggedVector { GLfloat Vec[3]; bool Flag; }; // Normalize the normals void Model3D::AdjustNormals(int NormalType, float SmoothThreshold) { // Copy in normal sources for processing if (!NormSources.empty()) { Normals.resize(NormSources.size()); objlist_copy(NormBase(),NormSrcBase(),NormSources.size()); } // Which kind of special processing? switch(NormalType) { case None: Normals.clear(); break; case Original: case Reversed: default: // Normalize for (unsigned k=0; k PerPolygonNormalList(NumPolys); GLushort *IndxPtr = VIBase(); for (unsigned k=0; k PerVertexNormalList(NumVerts); objlist_clear(&PerVertexNormalList[0],NumVerts); IndxPtr = VIBase(); for (unsigned k=0; k Variances(NumVerts); objlist_clear(&Variances[0],NumVerts); vector NumPolysPerVert(NumVerts); objlist_clear(&NumPolysPerVert[0],NumVerts); IndxPtr = VIBase(); for (unsigned k=0; k 0 && PerVertexNormalList[k].Flag) PerVertexNormalList[k].Flag = sqrt(Variances[k]/NumVertPolys) <= SmoothThreshold; } // The vertex flags are now set for whether to use that vertex's normal; // re-count the number of polygons per vertex. // Use NONE for unsplit ones objlist_clear(&NumPolysPerVert[0],NumVerts); IndxPtr = VIBase(); for (unsigned k=0; k IndicesInList(NumVerts); short IndxInList = 0; for (unsigned k=0; k VertexPolygons(NewNumVerts); objlist_clear(&NumPolysPerVert[0],NumVerts); // In creating that list, also remap the triangles' vertices GLushort *VIPtr = VIBase(); for (unsigned k=0; k NewPositions(3*NewNumVerts); vector NewTxtrCoords; vector NewNormals(3*NewNumVerts); vector NewColors; vector NewVtxSrcIndices; bool TCPresent = !TxtrCoords.empty(); if (TCPresent) NewTxtrCoords.resize(2*NewNumVerts); bool ColPresent = !Colors.empty(); if (ColPresent) NewColors.resize(3*NewNumVerts); bool VSPresent = !VtxSrcIndices.empty(); if (VSPresent) NewVtxSrcIndices.resize(NewNumVerts); // Use marching pointers to speed up the copy-over GLfloat *OldP = &Positions[0]; GLfloat *NewP = &NewPositions[0]; GLfloat *OldT = &TxtrCoords[0]; GLfloat *NewT = &NewTxtrCoords[0]; GLfloat *OldC = &Colors[0]; GLfloat *NewC = &NewColors[0]; GLushort *OldS = &VtxSrcIndices[0]; GLushort *NewS = &NewVtxSrcIndices[0]; GLfloat *NewN = &NewNormals[0]; for (unsigned k=0; k 0) { NormSources.resize(NormSize); objlist_copy(NormSrcBase(),NormBase(),NormSize); } else NormSources.clear(); } else NormSources.clear(); } // From the position data void Model3D::FindBoundingBox() { size_t NumVertices = Positions.size()/3; if (NumVertices > 0) { // Find the min and max of the positions: VecCopy(&Positions[0],BoundingBox[0]); VecCopy(&Positions[0],BoundingBox[1]); for (size_t i=1; i::iterator VSI_Iter = VtxSrcIndices.begin(); VSI_Iter < VtxSrcIndices.end(); VSI_Iter++) InvVSIPointers[*VSI_Iter]++; // Find the positions from the counts GLushort PtrSum = 0; for (vector::iterator IVP_Iter = InvVSIPointers.begin(); IVP_Iter < InvVSIPointers.end(); IVP_Iter++) { GLushort NewPtrSum = PtrSum + *IVP_Iter; *IVP_Iter = PtrSum; PtrSum = NewPtrSum; } // Place the inverse indices for (unsigned k = 0; k::iterator IVP_Iter = InvVSIPointers.end()-1; IVP_Iter > InvVSIPointers.begin(); IVP_Iter--) { *IVP_Iter = *(IVP_Iter - 1); } InvVSIPointers[0] = 0; } // Neutral case: returns whether vertex-source data was used (present in animated models) bool Model3D::FindPositions_Neutral(bool UseModelTransform) { // Positions already there if (VtxSrcIndices.empty()) return false; // Straight copy of the vertices: size_t NumVertices = VtxSrcIndices.size(); Positions.resize(3*NumVertices); GLfloat *PP = PosBase(); GLushort *IP = VtxSIBase(); size_t NumVtxSources = VtxSources.size(); if (UseModelTransform) { for (size_t k=0; k= 0 && VSIndex < NumVtxSources) { Model3D_VertexSource& VS = VtxSources[VSIndex]; TransformPoint(PP,VS.Position,TransformPos); } else { GLfloat VP[3] = {0,0,0}; TransformPoint(PP,VP,TransformPos); } } } else { for (size_t k=0; k= 0 && VSIndex < NumVtxSources) { Model3D_VertexSource& VS = VtxSources[VSIndex]; GLfloat *VP = VS.Position; *(PP++) = *(VP++); *(PP++) = *(VP++); *(PP++) = *(VP++); } else { *(PP++) = 0; *(PP++) = 0; *(PP++) = 0; } } } // Copy in the normals Normals.resize(NormSources.size()); if (UseModelTransform) { GLfloat *NormPtr = NormBase(); GLfloat *NormBasePtr = NormSrcBase(); size_t NumNorms = NormSources.size()/3; for (size_t k=0; k= Frames.size()) return false; if (InverseVSIndices.empty()) BuildInverseVSIndices(); size_t NumVertices = VtxSrcIndices.size(); Positions.resize(3*NumVertices); // Set sizes: BoneMatrices.resize(NumBones); BoneStack.resize(NumBones); // Find which frame; remember that frame data comes in [NumBones] sets Model3D_Frame *FramePtr = &Frames[NumBones*FrameIndex]; Model3D_Frame *AddlFramePtr = &Frames[NumBones*AddlFrameIndex]; // Find the individual-bone transformation matrices: for (size_t ib=0; ib= 0) Parent = BoneStack[StackIndx--]; else Parent = UNONE; } if (TEST_FLAG(Bone.Flags,Model3D_Bone::Push)) { StackIndx = MAX(StackIndx,-1); BoneStack[++StackIndx] = Parent; } // Do the transform! if (Parent != UNONE) { Model3D_Transform Res; TMatMultiply(Res,BoneMatrices[Parent],BoneMatrices[ib]); obj_copy(BoneMatrices[ib],Res); } // Default: parent of next bone is current bone Parent = ib; } bool NormalsPresent = !NormSources.empty(); if (NormalsPresent) Normals.resize(NormSources.size()); for (unsigned ivs=0; ivs= 0) { Model3D_Transform& T0 = BoneMatrices[VS.Bone0]; TransformPoint(Position,VS.Position,T0); if (NormalsPresent) { for (int iv=InvVSIPointers[ivs]; iv= 0 && Blend != 0) { Model3D_Transform& T1 = BoneMatrices[VS.Bone1]; GLfloat PosExtra[3]; GLfloat PosDiff[3]; TransformPoint(PosExtra,VS.Position,T1); VecSub(PosExtra,Position,PosDiff); VecScalarMultTo(PosDiff,Blend); VecAddTo(Position,PosDiff); if (NormalsPresent) { for (int iv=InvVSIPointers[ivs]; iv= GLshort(SeqFrmPointers.size()))) return 0; return (SeqFrmPointers[SeqIndex+1] - SeqFrmPointers[SeqIndex]); } bool Model3D::FindPositions_Sequence(bool UseModelTransform, GLshort SeqIndex, GLshort FrameIndex, GLfloat MixFrac, GLshort AddlFrameIndex) { // Bad inputs: do nothing and return false GLshort NumSF = NumSeqFrames(SeqIndex); if (NumSF <= 0) return false; if (FrameIndex < 0 || FrameIndex >= NumSF) return false; Model3D_Transform TSF; Model3D_SeqFrame& SF = SeqFrames[SeqFrmPointers[SeqIndex] + FrameIndex]; if (MixFrac != 0 && AddlFrameIndex != FrameIndex) { if (AddlFrameIndex < 0 || AddlFrameIndex >= NumSF) return false; Model3D_SeqFrame& ASF = SeqFrames[SeqFrmPointers[SeqIndex] + AddlFrameIndex]; FindFrameTransform(TSF,SF,MixFrac,ASF); if (!FindPositions_Frame(false,SF.Frame,MixFrac,ASF.Frame)) return false; } else { if (!FindPositions_Frame(false,SF.Frame)) return false; FindFrameTransform(TSF,SF,0,SF); } Model3D_Transform TTot; if (UseModelTransform) TMatMultiply(TTot,TransformPos,TSF); else obj_copy(TTot,TSF); size_t NumVerts = Positions.size()/3; GLfloat *Pos = PosBase(); for (size_t iv=0; iv= HALF_CIRCLE) AngleDiff -= FULL_CIRCLE; Angle += int16(MixFrac*AngleDiff); } return NORMALIZE_ANGLE(Angle); } // Bone and Frame (positions, angles) -> Transform Matrix static void FindFrameTransform(Model3D_Transform& T, Model3D_Frame& Frame, GLfloat MixFrac, Model3D_Frame& AddlFrame) { T.Identity(); // First, do rotations: short Angle; // Right-to-left; in the right order for Dim3 (and Tomb Raider) // Z: Angle = InterpolateAngle(Frame.Angles[2],MixFrac,AddlFrame.Angles[2]); if (Angle != 0) { GLfloat C = TrigNorm*cosine_table[Angle]; GLfloat S = TrigNorm*sine_table[Angle]; for (int ic=0; ic<3; ic++) { GLfloat X = T.M[0][ic]; GLfloat Y = T.M[1][ic]; GLfloat XR = X*C - Y*S; GLfloat YR = X*S + Y*C; T.M[0][ic] = XR; T.M[1][ic] = YR; } } // X: Angle = InterpolateAngle(Frame.Angles[0],MixFrac,AddlFrame.Angles[0]); if (Angle != 0) { GLfloat C = TrigNorm*cosine_table[Angle]; GLfloat S = TrigNorm*sine_table[Angle]; for (int ic=0; ic<3; ic++) { GLfloat X = T.M[1][ic]; GLfloat Y = T.M[2][ic]; GLfloat XR = X*C - Y*S; GLfloat YR = X*S + Y*C; T.M[1][ic] = XR; T.M[2][ic] = YR; } } // Y: Angle = InterpolateAngle(Frame.Angles[1],MixFrac,AddlFrame.Angles[1]); if (Angle != 0) { GLfloat C = TrigNorm*cosine_table[Angle]; GLfloat S = TrigNorm*sine_table[Angle]; for (int ic=0; ic<3; ic++) { GLfloat X = T.M[2][ic]; GLfloat Y = T.M[0][ic]; GLfloat XR = X*C - Y*S; GLfloat YR = X*S + Y*C; T.M[2][ic] = XR; T.M[0][ic] = YR; } } // Set up overall translate: GLfloat *FrameOfst = Frame.Offset; if (MixFrac != 0) { GLfloat *AddlFrameOfst = AddlFrame.Offset; for (int ic=0; ic<3; ic++) T.M[ic][3] = FrameOfst[ic] + MixFrac*(AddlFrameOfst[ic]-FrameOfst[ic]); } else { for (int ic=0; ic<3; ic++) T.M[ic][3] = FrameOfst[ic]; } } static void FindBoneTransform(Model3D_Transform& T, Model3D_Bone& Bone, Model3D_Frame& Frame, GLfloat MixFrac, Model3D_Frame& AddlFrame) { FindFrameTransform(T,Frame,MixFrac,AddlFrame); // Set up overall translate: GLfloat *BonePos = Bone.Position; for (int ic=0; ic<3; ic++) T.M[ic][3] += BonePos[ic] - ScalarProd(T.M[ic],BonePos); } // Res = A * B, in that order static void TMatMultiply(Model3D_Transform& Res, Model3D_Transform& A, Model3D_Transform& B) { // Multiply the rotation parts for (int i=0; i<3; i++) for (int j=0; j<3; j++) { GLfloat Sum = 0; for (int k=0; k<3; k++) Sum += A.M[i][k]*B.M[k][j]; Res.M[i][j] = Sum; } // Now the translation part for (int i=0; i<3; i++) { GLfloat Sum = 0; for (int k=0; k<3; k++) Sum += A.M[i][k]*B.M[k][3]; Res.M[i][3] = A.M[i][3] + Sum; } } #endif // def HAVE_OPENGL