//given a capsule specified with a transform, two points, and a radius, this will approximate how much is submerged //and distribute that force to the appropriate points on the capsule static bool ApplyCapsuleBuoyancy(const LTVector& vPt1, const LTVector& vPt2, float fLength, float fRadius, const LTPlane& WSPlane, float& fVolume, LTVector& vApplyAt, float& fSurfaceArea) { //convert the capsule to an OBB and apply it //determine information about the main axis LTVector vMainAxis = vPt2 - vPt1; LTASSERT( fLength > 0.0f, "Invalid capsule length." ); LTVector vUnitAxis = vMainAxis / fLength; //we can now build up a rotation given the plane normal and the axis to build our transform LTVector vUp = WSPlane.Normal(); if(fabsf(vUp.Dot(vUnitAxis)) > 0.99f) { //too close to use, built an arbitrary orthonormal vUp = vUnitAxis.BuildOrthonormal(); } LTMatrix3x4 mTemp; LTVector vRight = vUnitAxis.Cross(vUp); vRight.Normalize( ); LTVector vTrueUp = vRight.Cross( vUnitAxis ); mTemp.SetBasisVectors(vRight, vTrueUp, vUnitAxis); LTRotation rRot; rRot.ConvertFromMatrix(mTemp); //now we can form our transform LTRigidTransform tTransform((vPt1 + vPt2) * 0.5f, rRot); LTVector vHalfDims(fRadius, fRadius, fLength * 0.5f + fRadius); return ApplyOBBBuoyancy(tTransform, vHalfDims, WSPlane, fVolume, vApplyAt, fSurfaceArea); }
void CRagDollInPlaneConstraint::Apply(uint32 nPosIndex) { //find the plane pivot point (the connection between both points forming the first plane) LTVector& vPivot = m_pPlaneCenter->m_vPosition[nPosIndex]; //find the vector that goes from the pivot point to the hinge point LTVector vToHinge = m_pHinge->m_vPosition[nPosIndex] - vPivot; LTVector vToOther = m_pPlaneOther->m_vPosition[nPosIndex] - vPivot; //find the plane normal LTVector vPlaneNormal = vToOther.Cross(vToHinge); vPlaneNormal.Normalize(); vPlaneNormal = vToHinge.Cross(vPlaneNormal) * m_fNormalScale; vPlaneNormal.Normalize(); //move the point into the plane LTVector& vConstrain = m_pConstrain->m_vPosition[nPosIndex]; float fDot = vPlaneNormal.Dot(vConstrain - vPivot); if(fDot < -m_fTolerance) { fDot += m_fTolerance; vConstrain -= vPlaneNormal * fDot; } else if(fDot > m_fTolerance) { fDot -= m_fTolerance; vConstrain -= vPlaneNormal * fDot; } //success }
void DebugLineSystem::AddArrow( const LTVector & vStart, const LTVector & vEnd, const DebugLine::Color & color /* = Color::White */, uint8 nAlpha /* = 255 */ ) { const float fHeadSize = 4.0f; LTVector vStartToEnd = vEnd - vStart; float fLen = vStartToEnd.Mag(); if( vStartToEnd != LTVector::GetIdentity() ) { vStartToEnd.Normalize(); } AddLine( vStart, vEnd, color, nAlpha); LTVector vArrow = vStart + ( ( fLen * 0.9f ) * vStartToEnd ); LTVector vUp( 0.f, 1.f, 0.f ); LTVector vNorm; if( vStartToEnd != vUp ) { vNorm = vStartToEnd.Cross( vUp ); } else { vNorm = LTVector( 1.f, 0.f, 0.f ); } vNorm *= ( fHeadSize/2.0f ); AddLine( vArrow - vNorm, vArrow + vNorm, color, nAlpha); AddLine( vArrow + vNorm, vEnd, color, nAlpha); AddLine( vArrow - vNorm, vEnd, color, nAlpha); }
bool FindNearestPointOnLine( const LTVector& l0, const LTVector& l1, const LTVector& vPos, LTVector* pvPosNearest ) { // Sanity check. if( !pvPosNearest ) { return false; } // Find the line's normal. LTVector vUp( 0.f, 1.f, 0.f ); LTVector vDir = l1 - l0; vDir.Normalize(); LTVector vNormal = vDir.Cross( vUp ); vNormal.Normalize(); // Find the nearest intersection point between the point and the line. LTVector vRay0 = vPos + ( vNormal * 100000.f ); LTVector vRay1 = vPos - ( vNormal * 100000.f ); return ( kRayIntersect_Failure != RayIntersectLineSegment( l0, l1, vRay0, vRay1, true, pvPosNearest ) ); }
void COccluder_Frustum::InitFrustum(const ViewParams& Params) { // Clear! Init(); // Get the points of the near plane in order const uint32 k_nNumScreenPts = 4; LTVector aScreenPts[k_nNumScreenPts]; aScreenPts[0] = Params.m_ViewPoints[2]; aScreenPts[1] = Params.m_ViewPoints[3]; aScreenPts[2] = Params.m_ViewPoints[1]; aScreenPts[3] = Params.m_ViewPoints[0]; // Gimmie some room m_aEdgePlanes.reserve(k_nNumScreenPts); m_aWorldEdgePlanes.reserve(k_nNumScreenPts); // Remember the near plane m_cPolyPlane = Params.m_ClipPlanes[CPLANE_NEAR_INDEX]; m_ePolyPlaneCorner = GetAABBPlaneCorner(m_cPolyPlane.m_Normal); // Build the occluder LTVector vPrevWorld = aScreenPts[3]; LTVector vPrevScr; MatVMul_H(&vPrevScr, &Params.m_FullTransform, &vPrevWorld); for (const LTVector *pCurVert = aScreenPts; pCurVert != &aScreenPts[k_nNumScreenPts]; ++pCurVert) { const LTVector &vNextWorld = *pCurVert; LTVector vNextScr; MatVMul_H(&vNextScr, &Params.m_FullTransform, &vNextWorld); float fXDiff = (vNextScr.x - vPrevScr.x); float fYDiff = (vNextScr.y - vPrevScr.y); if ((fXDiff * fXDiff + fYDiff * fYDiff) > 0.001f) { LTPlane cScreenPlane; LTVector vEdgeScr = vNextScr - vPrevScr; cScreenPlane.m_Normal.Init(vEdgeScr.y, -vEdgeScr.x, 0.0f); cScreenPlane.m_Normal.Normalize(); cScreenPlane.m_Dist = cScreenPlane.m_Normal.x * vNextScr.x + cScreenPlane.m_Normal.y * vNextScr.y; LTPlane cWorldPlane; LTVector vEdgeWorld = vNextWorld - vPrevWorld; cWorldPlane.m_Normal = vEdgeWorld.Cross(vNextWorld - Params.m_Pos); cWorldPlane.m_Normal.Normalize(); cWorldPlane.m_Dist = cWorldPlane.m_Normal.Dot(vNextWorld); AddPlane(cScreenPlane, cWorldPlane); } vPrevWorld = vNextWorld; vPrevScr = vNextScr; } }
void CRagDollAbovePlaneOnEdgeConstraint::Apply(uint32 nPosIndex) { const LTVector& vPtInPlane = m_pPt1->m_vPosition[nPosIndex]; LTVector vEdge = m_pPt2->m_vPosition[nPosIndex] - vPtInPlane; //we first off need to caclulate the normal of the plane LTVector vNormal = vEdge.Cross(m_pPt3->m_vPosition[nPosIndex] - vPtInPlane); vNormal.Normalize(); //now we need to take that plane, and find the perpindicular plane that passes through the first edge LTVector vEdgeNormal = vNormal.Cross(vEdge) * m_fNormalScale; vEdgeNormal.Normalize(); //ok, now we see if the point is above the plane LTVector& vConstrain = m_pConstrain->m_vPosition[nPosIndex]; float fDot = vEdgeNormal.Dot(vConstrain - vPtInPlane); if(fDot < 0.0f) { vConstrain -= fDot * vEdgeNormal; } }
// The plane primitive command was called void CRegionView::OnCreatePrimitivePlane() { // The sphere primitive dialog CPlanePrimitiveDlg dlg; //load all the defaults from the registry dlg.m_fWidth = (float)::GetApp()->GetOptions().GetDWordValue("DefaultPlaneWidth", 64); dlg.m_fHeight = (float)::GetApp()->GetOptions().GetDWordValue("DefaultPlaneHeight", 64); dlg.m_nOrientation = ::GetApp()->GetOptions().GetDWordValue("DefaultPlaneOr", 0); dlg.m_nType = ::GetApp()->GetOptions().GetDWordValue("DefaultPlaneType", 0); // Create the sphere primitive dialog if (dlg.DoModal() == IDOK) { //determine our up and right vectors for each orientation LTVector vOrUp[6], vOrNormal[6]; vOrNormal[0].Init(1.0f, 0.0f, 0.0f); vOrUp[0].Init(0.0f, 1.0f, 0.0f); vOrNormal[1].Init(-1.0f, 0.0f, 0.0f); vOrUp[1].Init(0.0f, 1.0f, 0.0f); vOrNormal[2].Init(0.0f, 1.0f, 0.0f); vOrUp[2].Init(0.0f, 0.0f, 1.0f); vOrNormal[3].Init(0.0f, -1.0f, 0.0f); vOrUp[3].Init(0.0f, 0.0f, 1.0f); vOrNormal[4].Init(0.0f, 0.0f, 1.0f); vOrUp[4].Init(0.0f, 1.0f, 0.0f); vOrNormal[5].Init(0.0f, 0.0f, -1.0f); vOrUp[5].Init(0.0f, 1.0f, 0.0f); //form the right vector LTVector vUp = vOrUp[dlg.m_nOrientation]; LTVector vRight = vUp.Cross(vOrNormal[dlg.m_nOrientation]); //now form the basis point LTVector vBasis = GetRegion()->m_vMarker - vRight * dlg.m_fWidth / 2.0f - vUp * dlg.m_fHeight / 2.0f; //determine the base point DoCreatePrimitivePlane(vBasis, vRight, vUp, dlg.m_fWidth, dlg.m_fHeight, (dlg.m_nType == 1)); //save the options back out ::GetApp()->GetOptions().SetDWordValue("DefaultPlaneWidth", (uint32)dlg.m_fWidth); ::GetApp()->GetOptions().SetDWordValue("DefaultPlaneHeight", (uint32)dlg.m_fHeight); ::GetApp()->GetOptions().SetDWordValue("DefaultPlaneOr", dlg.m_nOrientation); ::GetApp()->GetOptions().SetDWordValue("DefaultPlaneType", dlg.m_nType); } }
//determines if this polygon is concave or not bool CEditPoly::IsConcave() { uint32 nNumPts = NumVerts(); if(nNumPts <= 3) { return false; } //get the normal for this polygon LTVector vNormal = Normal(); //now go through every edge uint32 nPrevPt = nNumPts - 1; for(uint32 nCurrPt = 0; nCurrPt < nNumPts; nPrevPt = nCurrPt, nCurrPt++) { //build the edge normal LTVector vEdge = m_pBrush->m_Points[Index(nCurrPt)] - m_pBrush->m_Points[Index(nPrevPt)]; //find the normal LTVector vEdgeNorm = vNormal.Cross(vEdge); vEdgeNorm.Norm(); //now run through all the other points for(uint32 nTestPt = 0; nTestPt < nNumPts; nTestPt++) { //ignore the points on the edge if((nTestPt == nCurrPt) || (nTestPt == nPrevPt)) continue; //see if it is on the correct side if(vEdgeNorm.Dot(m_pBrush->m_Points[Index(nTestPt)] - m_pBrush->m_Points[Index(nCurrPt)]) > 0.001f) { return true; } } } return false; }
bool CLTBModelFX::Init(ILTClient *pClientDE, FX_BASEDATA *pBaseData, const CBaseFXProps *pProps) { // Perform base class initialisation if (!CBaseFX::Init(pClientDE, pBaseData, pProps)) return false; // Use the "target" Normal instead, if one was specified... LTVector vNorm = GetProps()->m_vNorm; if( pBaseData->m_vTargetNorm.LengthSquared() > MATH_EPSILON ) { vNorm = pBaseData->m_vTargetNorm; vNorm.Normalize(); } // Develop the Right and Up vectors based off the Forward... LTVector vR, vU; if( (1.0f == vNorm.y) || (-1.0f == vNorm.y) ) { vR = LTVector( 1.0f, 0.0f, 0.0f ).Cross( vNorm ); } else { vR = LTVector( 0.0f, 1.0f, 0.0f ).Cross( vNorm ); } vU = vNorm.Cross( vR ); m_rNormalRot = LTRotation( vNorm, vU ); ObjectCreateStruct ocs; // Combine the direction we would like to face with our parents rotation... if( m_hParent ) { m_pLTClient->GetObjectRotation( m_hParent, &ocs.m_Rotation ); } else { ocs.m_Rotation = m_rCreateRot; } ocs.m_Rotation = ocs.m_Rotation * m_rNormalRot; ocs.m_ObjectType = OT_MODEL; ocs.m_Flags |= pBaseData->m_dwObjectFlags | (GetProps()->m_bShadow ? FLAG_SHADOW : 0 ); ocs.m_Flags2 |= pBaseData->m_dwObjectFlags2; // Calculate the position with the offset in 'local' coordinate space... LTMatrix mMat; ocs.m_Rotation.ConvertToMatrix( mMat ); m_vPos = ocs.m_Pos = m_vCreatePos + (mMat * GetProps()->m_vOffset); SAFE_STRCPY( ocs.m_Filename, GetProps()->m_szModelName ); SAFE_STRCPY( ocs.m_SkinNames[0], GetProps()->m_szSkinName[0] ); SAFE_STRCPY( ocs.m_SkinNames[1], GetProps()->m_szSkinName[1] ); SAFE_STRCPY( ocs.m_SkinNames[2], GetProps()->m_szSkinName[2] ); SAFE_STRCPY( ocs.m_SkinNames[3], GetProps()->m_szSkinName[3] ); SAFE_STRCPY( ocs.m_SkinNames[4], GetProps()->m_szSkinName[4] ); SAFE_STRCPY( ocs.m_RenderStyleNames[0], GetProps()->m_szRenderStyle[0] ); SAFE_STRCPY( ocs.m_RenderStyleNames[1], GetProps()->m_szRenderStyle[1] ); SAFE_STRCPY( ocs.m_RenderStyleNames[2], GetProps()->m_szRenderStyle[2] ); SAFE_STRCPY( ocs.m_RenderStyleNames[3], GetProps()->m_szRenderStyle[3] ); m_hObject = m_pLTClient->CreateObject(&ocs); if( !m_hObject ) return LTFALSE; ILTModel *pLTModel = m_pLTClient->GetModelLT(); ANIMTRACKERID nTracker; pLTModel->GetMainTracker( m_hObject, nTracker ); //setup the animation if the user specified one if( strlen(GetProps()->m_szAnimName) > 0) { //ok, we need to set this HMODELANIM hAnim = m_pLTClient->GetAnimIndex(m_hObject, GetProps()->m_szAnimName); if(hAnim != INVALID_MODEL_ANIM) { //ok, lets set this animation pLTModel->SetCurAnim(m_hObject, nTracker, hAnim); pLTModel->ResetAnim(m_hObject, nTracker); } } //disable looping on this animation (so we can actually stop!) pLTModel->SetLooping(m_hObject, nTracker, false); // Setup the initial data needed to override the models animation length... if( GetProps()->m_bOverrideAniLength ) { uint32 nAnimLength; pLTModel->GetCurAnimLength( m_hObject, nTracker, nAnimLength ); pLTModel->SetCurAnimTime( m_hObject, nTracker, 0 ); float fAniLength = (GetProps()->m_fAniLength < MATH_EPSILON) ? GetProps()->m_tmLifespan : GetProps()->m_fAniLength; if( fAniLength >= MATH_EPSILON || fAniLength <= -MATH_EPSILON ) m_fAniRate = (nAnimLength * 0.001f) / fAniLength; pLTModel->SetAnimRate( m_hObject, nTracker, m_fAniRate ); } // Success !! return LTTRUE; }
bool CLTBBouncyChunkFX::Init(ILTClient *pClientDE, FX_BASEDATA *pBaseData, const CBaseFXProps *pProps) { // Perform base class initialisation if (!CBaseFX::Init(pClientDE, pBaseData, pProps)) return false; LTVector vChunkDir = GetProps()->m_vChunkDir; if (pBaseData->m_bUseTargetData) { vChunkDir = pBaseData->m_vTargetNorm; } LTVector vPos; LTRotation rRot; if (m_hParent) { m_pLTClient->GetObjectPos(m_hParent, &vPos); m_pLTClient->GetObjectRotation(m_hParent, &rRot); } else { vPos = m_vCreatePos; rRot = m_rCreateRot; } float scale; CalcScale(m_tmElapsed, GetProps()->m_tmLifespan, &scale); LTVector vScale(scale, scale, scale); ObjectCreateStruct ocs; INIT_OBJECTCREATESTRUCT(ocs); ocs.m_ObjectType = OT_MODEL; ocs.m_Flags = FLAG_NOLIGHT | FLAG_VISIBLE; ocs.m_Pos = vPos + GetProps()->m_vOffset; ocs.m_Rotation = rRot; ocs.m_Scale = vScale; strcpy(ocs.m_Filename, GetProps()->m_sModelName); strcpy(ocs.m_SkinName, GetProps()->m_sSkinName); m_hBouncyChunk = m_pLTClient->CreateObject(&ocs); // Setup an initial vector for the velocity LTVector vOther; vOther.x = 1.0f; vOther.y = 0.0f; vOther.z = 1.0f; vOther.Norm(); LTVector vRight = vChunkDir.Cross(vOther); LTVector vUp = vRight.Cross(vOther); m_vVel = vRight * (-GetProps()->m_fChunkSpread + (float)(rand() % (int)(GetProps()->m_fChunkSpread * 2.0f))); m_vVel += vUp * (-GetProps()->m_fChunkSpread + (float)(rand() % (int)(GetProps()->m_fChunkSpread * 2.0f))); m_vVel += vChunkDir * GetProps()->m_fChunkSpeed; m_vVel.Norm(GetProps()->m_fChunkSpeed); // Create the base object CreateDummyObject(); // Success !! return true; }
bool CTrackedNodeMgr::SetNodeConstraints( HTRACKEDNODE ID, const LTVector& vMovConeAxis, const LTVector& vMovConeUp, float fXDiscomfortAngle, float fYDiscomfortAngle, float fXMaxAngle, float fYMaxAngle, float fMaxAngVel ) { //sanity checks if(!CheckValidity(ID)) return false; //ok, we have a valid ID, so let us setup the parameters CTrackedNode* pNode = (CTrackedNode*)ID; //see if the up and forward vectors are valid LTVector vForward = vMovConeAxis; LTVector vUp = vMovConeUp; //ensure proper scale vForward.Normalize(); vUp.Normalize(); //ensure they form a valid space (and not a plane) if(vUp.Dot(vForward) > 0.99f) { //not valid, we need to try a different up, our preference is the world up vUp.Init(0.0f, 1.0f, 0.0f); if(vUp.Dot(vForward) > 0.99f) { //ok, forward is already taking the up....so, tilt us back vUp.Init(0.0f, 0.0f, -1.0f); } } //now generate the right, and ensure orthogonality LTVector vRight = vForward.Cross(vUp); vUp = vRight.Cross(vForward); vRight.Normalize(); vUp.Normalize(); //setup this as the basis space pNode->m_mInvTargetTransform.SetBasisVectors(&vRight, &vUp, &vForward); pNode->m_mInvTargetTransform.Transpose(); //we need to make sure that their angular constraints are valid (meaning that they are positive and //less than 90 deg) fXMaxAngle = LTCLAMP(fXMaxAngle, 0.0f, DEG2RAD(89.0f)); fYMaxAngle = LTCLAMP(fYMaxAngle, 0.0f, DEG2RAD(89.0f)); fXDiscomfortAngle = LTCLAMP(fXDiscomfortAngle, 0.0f, fXMaxAngle); fYDiscomfortAngle = LTCLAMP(fYDiscomfortAngle, 0.0f, fYMaxAngle); //now precompute the tangent of those values (used for finding the height of the cone created which //is used in the threshold determination code) pNode->m_fTanXDiscomfort = (float)tan(fXDiscomfortAngle); pNode->m_fTanYDiscomfort = (float)tan(fYDiscomfortAngle); pNode->m_fTanXThreshold = (float)tan(fXMaxAngle); pNode->m_fTanYThreshold = (float)tan(fYMaxAngle); //handle setting up the maximum angular velocity pNode->m_fMaxAngVel = (float)fabs(fMaxAngVel); //and we are ready for primetime return true; }
//function that handles the custom rendering void CTracerFX::RenderTracer(ILTCustomRenderCallback* pInterface, const LTRigidTransform& tCamera) { //track our performance CTimedSystemBlock TimingBlock(g_tsClientFXTracer); //first determine the length, position, and U range for this tracer (this allows for some //early outs) float fTracerLen = GetTracerLength(); float fTracerStart = m_fRayPosition; float fTracerEnd = fTracerStart - fTracerLen; if((fTracerStart <= 0.0f) || (fTracerEnd >= m_fRayLength)) { //the tracer has fully gone through the ray, don't render return; } //now we need to clip the extents to the range [0..ray length], and handle cropping of the texture float fUMin = 0.0f; float fUMax = 1.0f; if(fTracerEnd < 0.0f) { //adjust the U max if we are cropping if(GetProps()->m_bCropTexture) { fUMax += fTracerEnd / fTracerLen; } fTracerEnd = 0.0f; } if(fTracerStart >= m_fRayLength) { //adjust the U min if we are cropping if(GetProps()->m_bCropTexture) { fUMin += (fTracerStart - m_fRayLength) / fTracerLen; } fTracerStart = m_fRayLength; } //setup our vertex declaration if(pInterface->SetVertexDeclaration(g_ClientFXVertexDecl.GetTexTangentSpaceDecl()) != LT_OK) return; //bind a quad index stream if(pInterface->BindQuadIndexStream() != LT_OK) return; //sanity check to ensure that we can at least render a sprite LTASSERT(QUAD_RENDER_INDEX_STREAM_SIZE >= 6, "Error: Quad index list is too small to render a tracer"); LTASSERT(DYNAMIC_RENDER_VERTEX_STREAM_SIZE / sizeof(STexTangentSpaceVert) > 4, "Error: Dynamic vertex buffer size is too small to render a tracer"); //we need to determine the facing of this tracer. This is formed by creating a plane from the points //Camera, Start, and another point on the ray. The plane normal is then the up, the right is the ray //direction, and the normal is ray cross plane normal. LTVector vStartToCamera = m_vStartPos - tCamera.m_vPos; float fMag = vStartToCamera.Mag( ); if( fMag < 0.00001f ) { vStartToCamera = tCamera.m_rRot.Forward(); } else { vStartToCamera /= fMag; } //determine the up vector LTVector vUp = vStartToCamera.Cross(m_vDirection); vUp.Normalize(); //now determine the actual normal (doesn't need to be normalized since the vectors are //unit length and orthogonal) LTVector vNormal = vUp.Cross(m_vDirection); //lock down our buffer for rendering SDynamicVertexBufferLockRequest LockRequest; if(pInterface->LockDynamicVertexBuffer(4, LockRequest) != LT_OK) return; //fill in our sprite vertices STexTangentSpaceVert* pCurrOut = (STexTangentSpaceVert*)LockRequest.m_pData; //determine the color of this tracer float fUnitLifetime = GetUnitLifetime(); uint32 nColor = CFxProp_Color4f::ToColor(GetProps()->m_cfcColor.GetValue(fUnitLifetime)); //calculate the front of the tracer in world space LTVector vFront = m_vStartPos + m_vDirection * fTracerStart; LTVector vBack = m_vStartPos + m_vDirection * fTracerEnd; //and the thickness vector float fThickness = GetProps()->m_ffcThickness.GetValue(fUnitLifetime); LTVector vThickness = vUp * (fThickness * 0.5f); //fill in the particle vertices pCurrOut[0].m_vPos = vFront + vThickness; pCurrOut[0].m_vUV.Init(fUMin, 0.0f); pCurrOut[1].m_vPos = vBack + vThickness; pCurrOut[1].m_vUV.Init(fUMax, 0.0f); pCurrOut[2].m_vPos = vBack - vThickness; pCurrOut[2].m_vUV.Init(fUMax, 1.0f); pCurrOut[3].m_vPos = vFront - vThickness; pCurrOut[3].m_vUV.Init(fUMin, 1.0f); //setup the remaining vertex components for(uint32 nCurrVert = 0; nCurrVert < 4; nCurrVert++) { pCurrOut[nCurrVert].m_nPackedColor = nColor; pCurrOut[nCurrVert].m_vNormal = vNormal; pCurrOut[nCurrVert].m_vTangent = vUp; pCurrOut[nCurrVert].m_vBinormal = m_vDirection; } //unlock and render the batch pInterface->UnlockAndBindDynamicVertexBuffer(LockRequest); pInterface->RenderIndexed( eCustomRenderPrimType_TriangleList, 0, 6, LockRequest.m_nStartIndex, 0, 4); }
// Wrap the textures, starting at a poly index void CRVTrackerTextureWrap::WrapTexture(CTWPolyInfo *pPoly, const CVector &vWrapDir, CTextExtents &cExtents) const { // Mark this poly as wrapped pPoly->m_bTouched = TRUE; CTexturedPlane& Texture = pPoly->m_pPoly->GetTexture(GetCurrTexture()); // Get the texture space LTVector vWrapO = Texture.GetO(); LTVector vWrapP = Texture.GetP(); LTVector vWrapQ = Texture.GetQ(); // Get the texture offset projections float fWrapOdotP = vWrapO.Dot(vWrapP); float fWrapOdotQ = vWrapO.Dot(vWrapQ); // Update the texturing extents for (uint32 nExtentLoop = 0; nExtentLoop < pPoly->m_aEdges.GetSize(); ++nExtentLoop) { LTVector vEdgePt = pPoly->m_aEdges[nExtentLoop]->m_aPt[0]; float fCurU = vWrapP.Dot(vEdgePt) - fWrapOdotP; float fCurV = vWrapQ.Dot(vEdgePt) - fWrapOdotQ; cExtents.m_fMinU = LTMIN(fCurU, cExtents.m_fMinU); cExtents.m_fMaxU = LTMAX(fCurU, cExtents.m_fMaxU); cExtents.m_fMinV = LTMIN(fCurV, cExtents.m_fMinV); cExtents.m_fMaxV = LTMAX(fCurV, cExtents.m_fMaxV); } CMoArray<uint32> aNeighbors; CMoArray<float> aDots; // Insert the neighbors into a list in dot-product order for (uint32 nNeighborLoop = 0; nNeighborLoop < pPoly->m_aNeighbors.GetSize(); ++nNeighborLoop) { CTWPolyInfo *pNeighbor = pPoly->m_aNeighbors[nNeighborLoop]; // Skip edges that don't have a neighbor if (!pNeighbor) continue; // Skip neighbors that are already wrapped if (pNeighbor->m_bTouched) continue; // Get our dot product float fCurDot = vWrapDir.Dot(pPoly->m_aEdges[nNeighborLoop]->m_Plane.m_Normal); if ((m_bRestrictWalkDir) && (fCurDot < 0.707f)) continue; // Mark this neighbor as touched (to avoid later polygons pushing it onto the stack) pNeighbor->m_bTouched = TRUE; // Insert it into the list for (uint32 nInsertLoop = 0; nInsertLoop < aNeighbors.GetSize(); ++nInsertLoop) { if (fCurDot > aDots[nInsertLoop]) break; } aDots.Insert(nInsertLoop, fCurDot); aNeighbors.Insert(nInsertLoop, nNeighborLoop); } // Recurse through its neighbors for (uint32 nWrapLoop = 0; nWrapLoop < aNeighbors.GetSize(); ++nWrapLoop) { CTWPolyInfo *pNeighbor = pPoly->m_aNeighbors[aNeighbors[nWrapLoop]]; CTWEdgeInfo *pEdge = pPoly->m_aEdges[aNeighbors[nWrapLoop]]; ////////////////////////////////////////////////////////////////////////////// // Wrap this neighbor // Create a matrix representing the basis of the polygon in relation to this edge LTMatrix mPolyBasis; mPolyBasis.SetTranslation(0.0f, 0.0f, 0.0f); mPolyBasis.SetBasisVectors(&pEdge->m_vDir, &pPoly->m_pPoly->m_Plane.m_Normal, &pEdge->m_Plane.m_Normal); // Create a new basis for the neighbor polygon LTMatrix mNeighborBasis; LTVector vNeighborForward; vNeighborForward = pNeighbor->m_pPoly->m_Plane.m_Normal.Cross(pEdge->m_vDir); // Just to be sure.. vNeighborForward.Norm(); mNeighborBasis.SetTranslation(0.0f, 0.0f, 0.0f); mNeighborBasis.SetBasisVectors(&pEdge->m_vDir, &pNeighbor->m_pPoly->m_Plane.m_Normal, &vNeighborForward); // Create a rotation matrix from here to there LTMatrix mRotation; mRotation = mNeighborBasis * ~mPolyBasis; // Rotate the various vectors LTVector vNewP; LTVector vNewQ; LTVector vNewDir; mRotation.Apply3x3(vWrapP, vNewP); mRotation.Apply3x3(vWrapQ, vNewQ); mRotation.Apply3x3(vWrapDir, vNewDir); // Rotate the texture basis if we're following a path if (m_nWrapStyle == k_WrapPath) { LTVector vNeighborEdgeDir; if (GetSimilarEdgeDir(pNeighbor, vNewDir, vNeighborEdgeDir, 0.707f)) { LTMatrix mRotatedNeighbor; LTVector vNeighborRight; vNeighborRight = vNeighborEdgeDir.Cross(pNeighbor->m_pPoly->m_Plane.m_Normal); vNeighborRight.Norm(); // Make sure we're pointing the right way... if (vNeighborRight.Dot(pEdge->m_vDir) < 0.0f) vNeighborRight = -vNeighborRight; mRotatedNeighbor.SetTranslation(0.0f, 0.0f, 0.0f); mRotatedNeighbor.SetBasisVectors(&vNeighborRight, &pNeighbor->m_pPoly->m_Plane.m_Normal, &vNeighborEdgeDir); // Build a basis based on an edge from the current polygon LTVector vBestPolyEdge; GetSimilarEdgeDir(pPoly, vWrapDir, vBestPolyEdge); LTVector vPolyRight = vBestPolyEdge.Cross(pNeighbor->m_pPoly->m_Plane.m_Normal); vPolyRight.Norm(); // Make sure we're pointing the right way... if (vPolyRight.Dot(pEdge->m_vDir) < 0.0f) vPolyRight = -vPolyRight; // Build the poly edge matrix LTMatrix mPolyEdgeBasis; mPolyEdgeBasis.SetTranslation(0.0f, 0.0f, 0.0f); mPolyEdgeBasis.SetBasisVectors(&vPolyRight, &pNeighbor->m_pPoly->m_Plane.m_Normal, &vBestPolyEdge); // Get a matrix from here to there LTMatrix mRotator; mRotator = mRotatedNeighbor * ~mPolyEdgeBasis; // Rotate the texture basis mRotator.Apply3x3(vNewP); mRotator.Apply3x3(vNewQ); // And use the new edge as the new direction vNewDir = vNeighborEdgeDir; } // Remove skew from vNewP/vNewQ if ((float)fabs(vNewP.Dot(vNewQ)) > 0.001f) { float fMagP = vNewP.Mag(); float fMagQ = vNewQ.Mag(); vNewQ *= 1.0f / fMagQ; vNewP -= vNewQ * vNewQ.Dot(vNewP); vNewP.Norm(fMagP); vNewQ *= fMagQ; } } // Get the first edge point.. CVector vEdgePt = pEdge->m_aPt[0]; // Calculate the texture coordinate at this point float fWrapU = vWrapP.Dot(vEdgePt) - fWrapOdotP; float fWrapV = vWrapQ.Dot(vEdgePt) - fWrapOdotQ; // Build the new offset float fNewOdotP = vNewP.Dot(vEdgePt) - fWrapU; float fNewOdotQ = vNewQ.Dot(vEdgePt) - fWrapV; LTVector vNewO; vNewO.Init(); float fNewPMag = vNewP.MagSqr(); if (fNewPMag > 0.0f) vNewO += vNewP * (fNewOdotP / fNewPMag); float fNewQMag = vNewQ.MagSqr(); if (fNewQMag > 0.0f) vNewO += vNewQ * (fNewOdotQ / fNewQMag); pNeighbor->m_pPoly->SetTextureSpace(GetCurrTexture(), vNewO, vNewP, vNewQ); // Recurse into this neighbor WrapTexture(pNeighbor, vNewDir, cExtents); } }
//function that handles the custom rendering void CParticleSystemGroup::RenderParticleSystem(ILTCustomRenderCallback* pInterface, const LTRigidTransform& tCamera) { //track our performance CTimedSystemBlock TimingBlock(g_tsClientFXParticles); //setup our vertex declaration if(pInterface->SetVertexDeclaration(g_ClientFXVertexDecl.GetTexTangentSpaceDecl()) != LT_OK) return; //bind a quad index stream if(pInterface->BindQuadIndexStream() != LT_OK) return; //set the fact that we were visible *m_pVisibleFlag = true; //now determine the largest number of particles that we can render at any time uint32 nMaxParticlesPerBatch = QUAD_RENDER_INDEX_STREAM_SIZE / 6; nMaxParticlesPerBatch = LTMIN(nMaxParticlesPerBatch, DYNAMIC_RENDER_VERTEX_STREAM_SIZE / (sizeof(STexTangentSpaceVert) * 4)); //determine the screen orientation LTRotation rCamera = tCamera.m_rRot; if (m_pProps->m_bObjectSpace) { LTRotation rObjectRotation; g_pLTClient->GetObjectRotation(m_hCustomRender, &rObjectRotation); rCamera = rObjectRotation.Conjugate() * rCamera; } LTVector vUp = rCamera.Up(); LTVector vRight = rCamera.Right(); //create some vectors to offset to each corner (avoids adding for displacement in the inner loop) //Each one can just be scaled by the size of the particle to get the final offset static const float kfHalfRoot2 = 0.5f * MATH_SQRT2; //premultiplied versions of up and right scaled by half the square root of two LTVector vUpHalfRoot2 = vUp * kfHalfRoot2; LTVector vRightHalfRoot2 = vRight * kfHalfRoot2; //precalculate the diagonals for non-rotating particles since these are constant LTVector vDiagonals[4]; vDiagonals[0] = vUpHalfRoot2 - vRightHalfRoot2; vDiagonals[1] = vUpHalfRoot2 + vRightHalfRoot2; vDiagonals[2] = -vUpHalfRoot2 + vRightHalfRoot2; vDiagonals[3] = -vUpHalfRoot2 - vRightHalfRoot2; uint32 nNumParticlesLeft = m_Particles.GetNumParticles(); //precalculate some data for the basis space of the particles LTVector vNormal = -rCamera.Forward(); LTVector vTangent = vRight; LTVector vBinormal = -vUp; //the U scale for particle images float fUImageWidth = 1.0f / (float)m_pProps->m_nNumImages; //variables used within the inner loop float fSize; uint32 nColor; //now run through all the particles and render CParticleIterator itParticles = m_Particles.GetIterator(); while(nNumParticlesLeft > 0) { //determine our batch size uint32 nBatchSize = LTMIN(nNumParticlesLeft, nMaxParticlesPerBatch); //lock down our buffer for rendering SDynamicVertexBufferLockRequest LockRequest; if(pInterface->LockDynamicVertexBuffer(nBatchSize * 4, LockRequest) != LT_OK) return; //fill in a batch of particles STexTangentSpaceVert* pCurrOut = (STexTangentSpaceVert*)LockRequest.m_pData; if(m_pProps->m_bRotate) { //we need to render the particles rotated for(uint32 nBatchParticle = 0; nBatchParticle < nBatchSize; nBatchParticle++) { //sanity check LTASSERT(!itParticles.IsDone(), "Error: Particle count and iterator mismatch"); //get the particle from the iterator SParticle* pParticle = (SParticle*)itParticles.GetParticle(); GetParticleSizeAndColor(pParticle, nColor, fSize); //determine the sin and cosine of this particle angle float fAngle = pParticle->m_fAngle; float fSinAngle = LTSin(fAngle); float fCosAngle = LTCos(fAngle); LTVector vRotRight = (vRightHalfRoot2 * fCosAngle + vUpHalfRoot2 * fSinAngle) * fSize; LTVector vRotUp = vNormal.Cross(vRotRight); LTVector vRotTangent = vTangent * fCosAngle + vBinormal * fSinAngle; LTVector vRotBinormal = vTangent * fSinAngle + vBinormal * fCosAngle; SetupParticle( pCurrOut, pParticle->m_Pos + vRotUp - vRotRight, pParticle->m_Pos + vRotUp + vRotRight, pParticle->m_Pos - vRotUp + vRotRight, pParticle->m_Pos - vRotUp - vRotRight, nColor, vNormal, vRotTangent, vRotBinormal, pParticle->m_nUserData & PARTICLE_IMAGE_MASK, fUImageWidth); //move onto the next set of particles pCurrOut += 4; //move onto the next particle for processing itParticles.Next(); } } else if (m_pProps->m_bStreak) { //the particles are non-rotated but streaked along their velocity for(uint32 nBatchParticle = 0; nBatchParticle < nBatchSize; nBatchParticle++) { //sanity check LTASSERT(!itParticles.IsDone(), "Error: Particle count and iterator mismatch"); //get the particle from the iterator SParticle* pParticle = (SParticle*)itParticles.GetParticle(); GetParticleSizeAndColor(pParticle, nColor, fSize); //in order to render the streak, we determine a line that passes through //the particle and runs in the direction of the velocity of the particle //we need to project the velocity onto the screen LTVector2 vScreen; vScreen.x = -(pParticle->m_Velocity.Dot(vRight)); vScreen.y = -(pParticle->m_Velocity.Dot(vUp)); //we know that the up and right vectors are normalized, so we can save some work by //just doing a 2d normalization float fMag = vScreen.Mag(); if(fMag == 0.0f) vScreen.Init(fSize, 0.0f); else vScreen *= fSize / fMag; //determine our actual screen space velocity LTVector vScreenRight = vUp * vScreen.y + vRight * vScreen.x; LTVector vScreenUp = vUp * -vScreen.x + vRight * vScreen.y; //and now compute the endpoint of the streak LTVector vEndPos = pParticle->m_Pos - pParticle->m_Velocity * m_pProps->m_fStreakScale; SetupParticle( pCurrOut, pParticle->m_Pos - vScreenRight - vScreenUp, vEndPos + vScreenRight - vScreenUp, vEndPos + vScreenRight + vScreenUp, pParticle->m_Pos - vScreenRight + vScreenUp, nColor, vNormal, vScreenRight, vScreenUp, pParticle->m_nUserData & PARTICLE_IMAGE_MASK, fUImageWidth); //move onto the next set of particles pCurrOut += 4; //move onto the next particle for processing itParticles.Next(); } } else { //the particles are non-rotated for(uint32 nBatchParticle = 0; nBatchParticle < nBatchSize; nBatchParticle++) { //sanity check LTASSERT(!itParticles.IsDone(), "Error: Particle count and iterator mismatch"); //get the particle from the iterator SParticle* pParticle = (SParticle*)itParticles.GetParticle(); GetParticleSizeAndColor(pParticle, nColor, fSize); SetupParticle( pCurrOut, pParticle->m_Pos + vDiagonals[0] * fSize, pParticle->m_Pos + vDiagonals[1] * fSize, pParticle->m_Pos + vDiagonals[2] * fSize, pParticle->m_Pos + vDiagonals[3] * fSize, nColor, vNormal, vTangent, vBinormal, pParticle->m_nUserData & PARTICLE_IMAGE_MASK, fUImageWidth); //move onto the next set of particles pCurrOut += 4; //move onto the next particle for processing itParticles.Next(); } } //unlock and render the batch pInterface->UnlockAndBindDynamicVertexBuffer(LockRequest); pInterface->RenderIndexed( eCustomRenderPrimType_TriangleList, 0, nBatchSize * 6, LockRequest.m_nStartIndex, 0, nBatchSize * 4); nNumParticlesLeft -= nBatchSize; } }
bool CAIWeaponAbstract::GetShootPosition( CAI* pAI, AimContext& Context,LTVector& outvShootPos ) { ASSERT(pAI); // Cineractive firing. if( m_eFiringState == kAIFiringState_CineFiring ) { LTVector vDir = pAI->GetWeaponForward( m_pWeapon ); vDir.Normalize(); outvShootPos = pAI->GetPosition() + ( vDir * 5000.f ); return true; } // If perfect accuracy is enabled, we are done. if( pAI->GetAIBlackBoard()->GetBBPerfectAccuracy() ) { HOBJECT hTarget = pAI->GetAIBlackBoard()->GetBBTargetObject(); g_pLTServer->GetObjectPos( hTarget, &outvShootPos ); return true; } // Initially aim for the target's visible position. // If the target is not visible at all, use his actual position. // This is a failsafe for AI shooting at the origin if they have // not yet seen the target ever. LTVector vVisiblePosition = pAI->GetTarget()->GetVisiblePosition(); if( !pAI->GetAIBlackBoard()->GetBBTargetVisibleFromWeapon() ) { vVisiblePosition = pAI->GetAIBlackBoard()->GetBBTargetPosition(); } outvShootPos = vVisiblePosition; // If Target is within the FullAccuracy radius, we are done. if( pAI->GetTarget()->GetTargetDistSqr() < pAI->GetFullAccuracyRadiusSqr() ) { return true; } // The following code forces the AI to intenionally miss every x // number of shots, depending on their accuracy. This gives players // the excitement of getting shot at without killing them too fast. // For example, if accuracy = 0.5 there will be a guaranteed sequence // of HIT, MISS, HIT, MISS, ... // If accuracy = 0.25, then HIT, MISS, MISS, MISS, HIT, MISS, MISS, MISS, etc. // If accuracy = 0.75, then HIT, HIT, HIT, MISS, HIT, HIT, HIT, MISS, etc. // Calculate the ratio of hits to misses based on the current // accuracy. This needs to be recalculated for every shot, // because accuracy may change at any time. float fAccuracy = m_flWeaponContextInaccuracyScalar * pAI->GetAccuracy(); if( fAccuracy <= 0.f ) { Context.m_cMisses = 1; Context.m_cHits = 0; } else if( fAccuracy >= 1.f ) { Context.m_cMisses = 0; Context.m_cHits = 1; } else if( fAccuracy < 0.5f ) { Context.m_cMisses = (uint32)( ( ( 1.f - fAccuracy ) / fAccuracy ) + 0.5f ); Context.m_cHits = 1; } else { Context.m_cMisses = 1; Context.m_cHits = (uint32)( ( fAccuracy / ( 1.f - fAccuracy ) ) + 0.5f ); } // If we have met or exceeded the required number of misses, // reset the counters. if( Context.m_iMiss >= Context.m_cMisses ) { Context.m_iHit = 0; Context.m_iMiss = 0; } // // First take care of hits, then take care of misses. // // Hit. if( Context.m_iHit < Context.m_cHits ) { ++Context.m_iHit; // Blind fire. if( pAI->GetAIBlackBoard()->GetBBBlindFire() ) { GetBlindFirePosition( pAI, outvShootPos, !FIRE_MISS ); return false; } // Suppression fire at last known pos. if( pAI->GetAIBlackBoard()->GetBBSuppressionFire() ) { HOBJECT hTarget = pAI->GetAIBlackBoard()->GetBBTargetObject(); CAIWMFact factQuery; factQuery.SetFactType( kFact_Character ); factQuery.SetTargetObject( hTarget ); CAIWMFact* pFact = pAI->GetAIWorkingMemory()->FindWMFact( factQuery ); if( pFact ) { outvShootPos = pFact->GetPos(); } } // Default fire. // If target has started moving or change directions recently, // factor in some inaccuracy. float fInnaccuracy = LTMAX( 0.f, pAI->GetTarget()->GetCurMovementInaccuracy() ); if( fInnaccuracy > 0.f ) { LTVector vShootOffset = LTVector( GetRandom( -fInnaccuracy, fInnaccuracy ), GetRandom( -fInnaccuracy * 0.5f, fInnaccuracy * 0.5f ), GetRandom( -fInnaccuracy, fInnaccuracy ) ); vShootOffset.Normalize(); outvShootPos += vShootOffset * 100.0f; } return true; } // Miss. else { ++Context.m_iMiss; // Blind fire. if( pAI->GetAIBlackBoard()->GetBBBlindFire() ) { GetBlindFirePosition( pAI, outvShootPos, FIRE_MISS ); return false; } // Default fire. HOBJECT hTarget = pAI->GetAIBlackBoard()->GetBBTargetObject(); if( !IsCharacter( hTarget ) ) { return false; } CCharacter* pChar = (CCharacter*)g_pLTServer->HandleToObject( hTarget ); if( !pChar ) { return false; } // Intentionally shoot a little short of the target. LTVector vPos = pAI->GetAIBlackBoard()->GetBBTargetPosition();; // Suppression fire at last known pos. if( pAI->GetAIBlackBoard()->GetBBSuppressionFire() ) { CAIWMFact factQuery; factQuery.SetFactType( kFact_Character ); factQuery.SetTargetObject( hTarget ); CAIWMFact* pFact = pAI->GetAIWorkingMemory()->FindWMFact( factQuery ); if( pFact ) { vPos = pFact->GetPos(); } } float fDist = sqrt( pAI->GetTarget()->GetTargetDistSqr() ); float fRadius = pChar->GetRadius(); float fRand = GetRandom( 0.f, 1.f ); fDist -= ( fRadius * 2.f ) + ( fRand * pAI->GetAccuracyMissPerturb() ); // Calculate a position to the right or left of the target. LTVector vDir = vPos - pAI->GetPosition(); if( vDir != LTVector::GetIdentity() ) { vDir.Normalize(); } vPos = pAI->GetPosition() + ( vDir * fDist ); LTVector vRight = vDir.Cross( LTVector( 0.f, 1.f, 0.f ) ); fRand = GetRandom( 0.f, 1.f ); float fPerturb = ( ( pAI->GetAccuracyMissPerturb() * 2.f ) * fRand ) - pAI->GetAccuracyMissPerturb(); vRight *= fPerturb; // Apply the offset to miss the target. outvShootPos = vPos + vRight; // Force bullets to land in front of the target, on the floor. if( m_pAIWeaponRecord->bForceMissToFloor ) { float fFloor = pAI->GetAIBlackBoard()->GetBBTargetPosition().y; fFloor -= pAI->GetAIBlackBoard()->GetBBTargetDims().y; outvShootPos.y = fFloor; } return false; } return false; }
//function that handles the custom rendering void CBaseSpriteFX::RenderSprite(ILTCustomRenderCallback* pInterface, const LTRigidTransform& tCamera) { //setup our vertex declaration if(pInterface->SetVertexDeclaration(g_ClientFXVertexDecl.GetTexTangentSpaceDecl()) != LT_OK) return; //bind a quad index stream if(pInterface->BindQuadIndexStream() != LT_OK) return; //determine how many indices we are going to need uint32 nNumIndices = (GetProps()->m_bTwoSided) ? 12 : 6; uint32 nNumVertices = (GetProps()->m_bTwoSided) ? 8 : 4; //sanity check to ensure that we can at least render a sprite LTASSERT(QUAD_RENDER_INDEX_STREAM_SIZE >= nNumIndices, "Error: Quad index list is too small to render a sprite"); LTASSERT(DYNAMIC_RENDER_VERTEX_STREAM_SIZE / sizeof(STexTangentSpaceVert) >= nNumVertices, "Error: Dynamic vertex buffer size is too small to render a sprite"); //determine the up and right vectors for the sprite LTVector vTangent, vBinormal; //get the position of this sprite LTRigidTransform tObjTransform; g_pLTClient->GetObjectTransform(m_hObject, &tObjTransform); //determine the center of this sprite LTVector vCenter = tObjTransform.m_vPos; //determine the orientation of the sprite based upon its facing if(GetProps()->m_bAlignToCamera) { //apply the to camera offset LTVector vToCamera = tCamera.m_vPos - vCenter; float fScale = GetProps()->m_fToCameraOffset / vToCamera.Mag(); vCenter += vToCamera * fScale; //perform the rotation float fCosAng = LTCos(m_fCurrRotationRad); float fSinAng = LTSin(m_fCurrRotationRad); LTVector vRight = tCamera.m_rRot.Right(); LTVector vUp = -tCamera.m_rRot.Up(); vTangent = fCosAng * vRight + fSinAng * vUp; vBinormal = fCosAng * vUp - fSinAng * vRight; } else if(GetProps()->m_bAlignAroundZ) { //we want to orient around the Z axis and align to the camera //we need to determine our U vector, which is always our forward LTVector vU = tObjTransform.m_rRot.Forward(); //and now we want to offset our center so that we are anchored on the right hand //side of the sprite to the point vCenter += vU * (m_fWidth * 0.5f); //determine the axis from our camera to our object LTVector vToCamera = tCamera.m_vPos - vCenter; //and now derive our V vector from the forward and the direction to the camera LTVector vV = vToCamera.Cross(vU); //detect degenerate cases if(vV == LTVector::GetIdentity()) { //degenerate case, any orientation will work fine since the sprite won't //be visible anyway vV.Init(0.0f, 1.0f, 0.0f); } //and normalize our vector vV.Normalize(); //now we can determine our tangent and binormal vectors vTangent = -vU; vBinormal = vV; } else { vTangent = -tObjTransform.m_rRot.Right(); vBinormal = -tObjTransform.m_rRot.Up(); } LTVector vNormal = vBinormal.Cross(vTangent); //scale the right and down values to be the appropriate size LTVector vRight = vTangent * m_fWidth * -0.5f; LTVector vDown = vBinormal * m_fWidth * GetProps()->m_fAspectRatio * 0.5f; //lock down our buffer for rendering SDynamicVertexBufferLockRequest LockRequest; if(pInterface->LockDynamicVertexBuffer(nNumVertices, LockRequest) != LT_OK) return; //fill in our sprite vertices STexTangentSpaceVert* pCurrOut = (STexTangentSpaceVert*)LockRequest.m_pData; uint32 nColor = SETRGBA( (uint8)(m_vColor.x * 255.0f), (uint8)(m_vColor.y * 255.0f), (uint8)(m_vColor.z * 255.0f), (uint8)(m_vColor.w * 255.0f)); //fill in the particle vertices pCurrOut[0].m_vPos = vCenter + vRight - vDown; pCurrOut[0].m_vUV.Init(0.0f, 0.0f); pCurrOut[1].m_vPos = vCenter - vRight - vDown; pCurrOut[1].m_vUV.Init(1.0f, 0.0f); pCurrOut[2].m_vPos = vCenter - vRight + vDown; pCurrOut[2].m_vUV.Init(1.0f, 1.0f); pCurrOut[3].m_vPos = vCenter + vRight + vDown; pCurrOut[3].m_vUV.Init(0.0f, 1.0f); //setup the remaining vertex components for(uint32 nCurrVert = 0; nCurrVert < 4; nCurrVert++) { pCurrOut[nCurrVert].m_nPackedColor = nColor; pCurrOut[nCurrVert].m_vNormal = vNormal; pCurrOut[nCurrVert].m_vTangent = vTangent; pCurrOut[nCurrVert].m_vBinormal = vBinormal; } //and fill in the back side if appropriate if(GetProps()->m_bTwoSided) { pCurrOut[4].m_vPos = vCenter - vRight - vDown; pCurrOut[4].m_vUV.Init(1.0f, 0.0f); pCurrOut[5].m_vPos = vCenter + vRight - vDown; pCurrOut[5].m_vUV.Init(0.0f, 0.0f); pCurrOut[6].m_vPos = vCenter + vRight + vDown; pCurrOut[6].m_vUV.Init(0.0f, 1.0f); pCurrOut[7].m_vPos = vCenter - vRight + vDown; pCurrOut[7].m_vUV.Init(1.0f, 1.0f); //setup the remaining vertex components for(uint32 nCurrVert = 4; nCurrVert < 8; nCurrVert++) { pCurrOut[nCurrVert].m_nPackedColor = nColor; pCurrOut[nCurrVert].m_vNormal = -vNormal; pCurrOut[nCurrVert].m_vTangent = -vTangent; pCurrOut[nCurrVert].m_vBinormal = vBinormal; } } //unlock and render the batch pInterface->UnlockAndBindDynamicVertexBuffer(LockRequest); pInterface->RenderIndexed( eCustomRenderPrimType_TriangleList, 0, nNumIndices, LockRequest.m_nStartIndex, 0, nNumVertices); }