bool SceneCullingState::createCullingVolume( const Point3F* vertices, U32 numVertices, SceneCullingVolume::Type type, SceneCullingVolume& outVolume ) { const Point3F& viewPos = getCameraState().getViewPosition(); const Point3F& viewDir = getCameraState().getViewDirection(); const bool isOrtho = getCullingFrustum().isOrtho(); //TODO: check if we need to handle penetration of the near plane for occluders specially // Allocate space for the clipping planes we generate. Assume the worst case // of every edge generating a plane and, for includers, all edges meeting at // steep angles so we need to insert extra planes (the latter is not possible, // of course, but it makes things less complicated here). For occluders, add // an extra plane for the near cap. const U32 maxPlanes = ( type == SceneCullingVolume::Occluder ? numVertices + 1 : numVertices * 2 ); PlaneF* planes = allocateData< PlaneF >( maxPlanes ); // Keep track of the world-space bounds of the polygon. We use this later // to derive some metrics. Box3F wsPolyBounds; wsPolyBounds.minExtents = Point3F( TypeTraits< F32 >::MAX, TypeTraits< F32 >::MAX, TypeTraits< F32 >::MAX ); wsPolyBounds.maxExtents = Point3F( TypeTraits< F32 >::MIN, TypeTraits< F32 >::MIN, TypeTraits< F32 >::MIN ); // For occluders, also keep track of the nearest, and two farthest silhouette points. We use // this later to construct a near capping plane. F32 minVertexDistanceSquared = TypeTraits< F32 >::MAX; U32 leastDistantVert = 0; F32 maxVertexDistancesSquared[ 2 ] = { TypeTraits< F32 >::MIN, TypeTraits< F32 >::MIN }; U32 mostDistantVertices[ 2 ] = { 0, 0 }; // Generate the extrusion volume. For orthographic projections, extrude // parallel to the view direction whereas for parallel projections, extrude // from the viewpoint. U32 numPlanes = 0; U32 lastVertex = numVertices - 1; bool invert = false; for( U32 i = 0; i < numVertices; lastVertex = i, ++ i ) { AssertFatal( numPlanes < maxPlanes, "SceneCullingState::createCullingVolume - Did not allocate enough planes!" ); const Point3F& v1 = vertices[ i ]; const Point3F& v2 = vertices[ lastVertex ]; // Keep track of bounds. wsPolyBounds.minExtents.setMin( v1 ); wsPolyBounds.maxExtents.setMax( v1 ); // Skip the edge if it's length is really short. const Point3F edgeVector = v2 - v1; const F32 edgeVectorLenSquared = edgeVector.lenSquared(); if( edgeVectorLenSquared < 0.025f ) continue; //TODO: might need to do additional checks here for non-planar polygons used by occluders //TODO: test for colinearity of edge vector with view vector (occluders only) // Create a plane for the edge. if( isOrtho ) { // Compute a plane through the two edge vertices and one // of the vertices extended along the view direction. if( !invert ) planes[ numPlanes ] = PlaneF( v1, v1 + viewDir, v2 ); else planes[ numPlanes ] = PlaneF( v2, v1 + viewDir, v1 ); } else { // Compute a plane going through the viewpoint and the two // edge vertices. if( !invert ) planes[ numPlanes ] = PlaneF( v1, viewPos, v2 ); else planes[ numPlanes ] = PlaneF( v2, viewPos, v1 ); } numPlanes ++; // If this is the first plane that we have created, find out whether // the vertex ordering is giving us the plane orientations that we want // (facing inside). If not, invert vertex order from now on. if( numPlanes == 1 ) { Point3F center( 0, 0, 0 ); for( U32 n = 0; n < numVertices; ++ n ) center += vertices[n]; center /= numVertices; if( planes[numPlanes - 1].whichSide( center ) == PlaneF::Back ) { invert = true; planes[ numPlanes - 1 ].invert(); } } // For occluders, keep tabs of the nearest, and two farthest vertices. if( type == SceneCullingVolume::Occluder ) { const F32 distSquared = ( v1 - viewPos ).lenSquared(); if( distSquared < minVertexDistanceSquared ) { minVertexDistanceSquared = distSquared; leastDistantVert = i; } if( distSquared > maxVertexDistancesSquared[ 0 ] ) { // Move 0 to 1. maxVertexDistancesSquared[ 1 ] = maxVertexDistancesSquared[ 0 ]; mostDistantVertices[ 1 ] = mostDistantVertices[ 0 ]; // Replace 0. maxVertexDistancesSquared[ 0 ] = distSquared; mostDistantVertices[ 0 ] = i; } else if( distSquared > maxVertexDistancesSquared[ 1 ] ) { // Replace 1. maxVertexDistancesSquared[ 1 ] = distSquared; mostDistantVertices[ 1 ] = i; } } } // If the extrusion produced no useful result, abort. if( numPlanes < 3 ) return false; // For includers, test the angle of the edges at the current vertex. // If too steep, add an extra plane to improve culling efficiency. if( false )//type == SceneCullingVolume::Includer ) { const U32 numOriginalPlanes = numPlanes; U32 lastPlaneIndex = numPlanes - 1; for( U32 i = 0; i < numOriginalPlanes; lastPlaneIndex = i, ++ i ) { const PlaneF& currentPlane = planes[ i ]; const PlaneF& lastPlane = planes[ lastPlaneIndex ]; // Compute the cosine of the angle between the two plane normals. const F32 cosAngle = mFabs( mDot( currentPlane, lastPlane ) ); // The planes meet at increasingly steep angles the more they point // in opposite directions, i.e the closer the angle of their normals // is to 180 degrees. Skip any two planes that don't get near that. if( cosAngle > 0.1f ) continue; //TODO const Point3F addNormals = currentPlane + lastPlane; const Point3F crossNormals = mCross( currentPlane, lastPlane ); Point3F newNormal = currentPlane + lastPlane;//addNormals - mDot( addNormals, crossNormals ) * crossNormals; // planes[ numPlanes ] = PlaneF( currentPlane.getPosition(), newNormal ); numPlanes ++; } } // Compute the metrics of the culling volume in relation to the view frustum. // // For this, we are short-circuiting things slightly. The correct way (other than doing // full screen projections) would be to transform all the polygon points into camera // space, lay an AABB around those points, and then find the X and Z extents on the near plane. // // However, while not as accurate, a faster way is to just project the axial vectors // of the bounding box onto both the camera right and up vector. This gives us a rough // estimate of the camera-space size of the polygon we're looking at. const MatrixF& cameraTransform = getCameraState().getViewWorldMatrix(); const Point3F cameraRight = cameraTransform.getRightVector(); const Point3F cameraUp = cameraTransform.getUpVector(); const Point3F wsPolyBoundsExtents = wsPolyBounds.getExtents(); F32 widthEstimate = getMax( mFabs( wsPolyBoundsExtents.x * cameraRight.x ), getMax( mFabs( wsPolyBoundsExtents.y * cameraRight.y ), mFabs( wsPolyBoundsExtents.z * cameraRight.z ) ) ); F32 heightEstimate = getMax( mFabs( wsPolyBoundsExtents.x * cameraUp.x ), getMax( mFabs( wsPolyBoundsExtents.y * cameraUp.y ), mFabs( wsPolyBoundsExtents.z * cameraUp.z ) ) ); // If the current camera is a perspective one, divide the two estimates // by the distance of the nearest bounding box vertex to the camera // to account for perspective distortion. if( !isOrtho ) { const Point3F nearestVertex = wsPolyBounds.computeVertex( Box3F::getPointIndexFromOctant( - viewDir ) ); const F32 distance = ( nearestVertex - viewPos ).len(); widthEstimate /= distance; heightEstimate /= distance; } // If we are creating an occluder, check to see if the estimates fit // our minimum requirements. if( type == SceneCullingVolume::Occluder ) { const F32 widthEstimatePercentage = widthEstimate / getCullingFrustum().getWidth(); const F32 heightEstimatePercentage = heightEstimate / getCullingFrustum().getHeight(); if( widthEstimatePercentage < smOccluderMinWidthPercentage || heightEstimatePercentage < smOccluderMinHeightPercentage ) return false; // Reject. } // Use the area estimate as the volume's sort point. const F32 sortPoint = widthEstimate * heightEstimate; // Finally, if it's an occluder, compute a near cap. The near cap prevents objects // in front of the occluder from testing positive. The same could be achieved by // manually comparing distances before testing objects but since that would amount // to the same checks the plane/AABB tests do, it's easier to just add another plane. // Additionally, it gives the benefit of being able to create more precise culling // results by angling the plane. //NOTE: Could consider adding a near cap for includers too when generating a volume // for the outdoor zone as that may prevent quite a bit of space from being included. // However, given that this space will most likely just be filled with interior // stuff anyway, it's probably not worth it. if( type == SceneCullingVolume::Occluder ) { const U32 nearCapIndex = numPlanes; planes[ nearCapIndex ] = PlaneF( vertices[ mostDistantVertices[ 0 ] ], vertices[ mostDistantVertices[ 1 ] ], vertices[ leastDistantVert ] ); // Invert the plane, if necessary. if( planes[ nearCapIndex ].whichSide( viewPos ) == PlaneF::Front ) planes[ nearCapIndex ].invert(); numPlanes ++; } // Create the volume from the planes. outVolume = SceneCullingVolume( type, PlaneSetF( planes, numPlanes ) ); outVolume.setSortPoint( sortPoint ); // Done. return true; }
void BrushAdjustHeightAction::process(Selection * sel, const Gui3DMouseEvent & event, bool, Type type) { if(type == Process) return; TerrainBlock *terrBlock = mTerrainEditor->getActiveTerrain(); if ( !terrBlock ) return; if(type == Begin) { mTerrainEditor->lockSelection(true); mTerrainEditor->getRoot()->mouseLock(mTerrainEditor); // the way this works is: // construct a plane that goes through the collision point // with one axis up the terrain Z, and horizontally parallel to the // plane of projection // the cross of the camera ffdv and the terrain up vector produces // the cross plane vector. // all subsequent mouse actions are collided against the plane and the deltaZ // from the previous position is used to delta the selection up and down. Point3F cameraDir; EditTSCtrl::smCamMatrix.getColumn(1, &cameraDir); terrBlock->getTransform().getColumn(2, &mTerrainUpVector); // ok, get the cross vector for the plane: Point3F planeCross; mCross(cameraDir, mTerrainUpVector, &planeCross); planeCross.normalize(); Point3F planeNormal; Point3F intersectPoint; mTerrainEditor->collide(event, intersectPoint); mCross(mTerrainUpVector, planeCross, &planeNormal); mIntersectionPlane.set(intersectPoint, planeNormal); // ok, we have the intersection point... // project the collision point onto the up vector of the terrain mPreviousZ = mDot(mTerrainUpVector, intersectPoint); // add to undo // and record the starting heights for(U32 i = 0; i < sel->size(); i++) { mTerrainEditor->getUndoSel()->add((*sel)[i]); (*sel)[i].mStartHeight = (*sel)[i].mHeight; } } else if(type == Update) { // ok, collide the ray from the event with the intersection plane: Point3F intersectPoint; Point3F start = event.pos; Point3F end = start + event.vec * 1000; F32 t = mIntersectionPlane.intersect(start, end); m_point3F_interpolate( start, end, t, intersectPoint); F32 currentZ = mDot(mTerrainUpVector, intersectPoint); F32 diff = currentZ - mPreviousZ; for(U32 i = 0; i < sel->size(); i++) { (*sel)[i].mHeight = (*sel)[i].mStartHeight + diff * (*sel)[i].mWeight; // clamp it if((*sel)[i].mHeight < 0.f) (*sel)[i].mHeight = 0.f; if((*sel)[i].mHeight > 2047.f) (*sel)[i].mHeight = 2047.f; mTerrainEditor->setGridInfoHeight((*sel)[i]); } mTerrainEditor->scheduleGridUpdate(); } else if(type == End) { mTerrainEditor->getRoot()->mouseUnlock(mTerrainEditor); } }
void CloudLayer::_initBuffers() { // Vertex Buffer... Point3F vertScale( 16.0f, 16.0f, mHeight ); F32 zOffset = -( mCos( mSqrt( 1.0f ) ) + 0.01f ); mVB.set( GFX, smVertCount, GFXBufferTypeStatic ); GFXCloudVertex *pVert = mVB.lock(); if(!pVert) return; for ( U32 y = 0; y < smVertStride; y++ ) { F32 v = ( (F32)y / (F32)smStrideMinusOne - 0.5f ) * 2.0f; for ( U32 x = 0; x < smVertStride; x++ ) { F32 u = ( (F32)x / (F32)smStrideMinusOne - 0.5f ) * 2.0f; F32 sx = u; F32 sy = v; F32 sz = mCos( mSqrt( sx*sx + sy*sy ) ) + zOffset; //F32 sz = 1.0f; pVert->point.set( sx, sy, sz ); pVert->point *= vertScale; // The vert to our right. Point3F rpnt; F32 ru = ( (F32)( x + 1 ) / (F32)smStrideMinusOne - 0.5f ) * 2.0f; F32 rv = v; rpnt.x = ru; rpnt.y = rv; rpnt.z = mCos( mSqrt( rpnt.x*rpnt.x + rpnt.y*rpnt.y ) ) + zOffset; rpnt *= vertScale; // The vert to our front. Point3F fpnt; F32 fu = u; F32 fv = ( (F32)( y + 1 ) / (F32)smStrideMinusOne - 0.5f ) * 2.0f; fpnt.x = fu; fpnt.y = fv; fpnt.z = mCos( mSqrt( fpnt.x*fpnt.x + fpnt.y*fpnt.y ) ) + zOffset; fpnt *= vertScale; Point3F fvec = fpnt - pVert->point; fvec.normalize(); Point3F rvec = rpnt - pVert->point; rvec.normalize(); pVert->normal = mCross( fvec, rvec ); pVert->normal.normalize(); pVert->binormal = fvec; pVert->tangent = rvec; pVert->texCoord.set( u, v ); pVert++; } } mVB.unlock(); // Primitive Buffer... mPB.set( GFX, smTriangleCount * 3, smTriangleCount, GFXBufferTypeStatic ); U16 *pIdx = NULL; mPB.lock(&pIdx); U32 curIdx = 0; for ( U32 y = 0; y < smStrideMinusOne; y++ ) { for ( U32 x = 0; x < smStrideMinusOne; x++ ) { U32 offset = x + y * smVertStride; pIdx[curIdx] = offset; curIdx++; pIdx[curIdx] = offset + 1; curIdx++; pIdx[curIdx] = offset + smVertStride + 1; curIdx++; pIdx[curIdx] = offset; curIdx++; pIdx[curIdx] = offset + smVertStride + 1; curIdx++; pIdx[curIdx] = offset + smVertStride; curIdx++; } } mPB.unlock(); }
bool DecalManager::clipDecal( DecalInstance *decal, Vector<Point3F> *edgeVerts, const Point2F *clipDepth ) { PROFILE_SCOPE( DecalManager_clipDecal ); // Free old verts and indices. _freeBuffers( decal ); ClippedPolyList clipper; clipper.mNormal.set( Point3F( 0, 0, 0 ) ); clipper.mPlaneList.setSize(6); F32 halfSize = decal->mSize * 0.5f; // Ugly hack for ProjectedShadow! F32 halfSizeZ = clipDepth ? clipDepth->x : halfSize; F32 negHalfSize = clipDepth ? clipDepth->y : halfSize; Point3F decalHalfSize( halfSize, halfSize, halfSize ); Point3F decalHalfSizeZ( halfSizeZ, halfSizeZ, halfSizeZ ); MatrixF projMat( true ); decal->getWorldMatrix( &projMat ); const VectorF &crossVec = decal->mNormal; const Point3F &decalPos = decal->mPosition; VectorF newFwd, newRight; projMat.getColumn( 0, &newRight ); projMat.getColumn( 1, &newFwd ); VectorF objRight( 1.0f, 0, 0 ); VectorF objFwd( 0, 1.0f, 0 ); VectorF objUp( 0, 0, 1.0f ); // See above re: decalHalfSizeZ hack. clipper.mPlaneList[0].set( ( decalPos + ( -newRight * halfSize ) ), -newRight ); clipper.mPlaneList[1].set( ( decalPos + ( -newFwd * halfSize ) ), -newFwd ); clipper.mPlaneList[2].set( ( decalPos + ( -crossVec * decalHalfSizeZ ) ), -crossVec ); clipper.mPlaneList[3].set( ( decalPos + ( newRight * halfSize ) ), newRight ); clipper.mPlaneList[4].set( ( decalPos + ( newFwd * halfSize ) ), newFwd ); clipper.mPlaneList[5].set( ( decalPos + ( crossVec * negHalfSize ) ), crossVec ); clipper.mNormal = -decal->mNormal; Box3F box( -decalHalfSizeZ, decalHalfSizeZ ); projMat.mul( box ); DecalData *decalData = decal->mDataBlock; PROFILE_START( DecalManager_clipDecal_buildPolyList ); getContainer()->buildPolyList( box, decalData->clippingMasks, &clipper ); PROFILE_END(); clipper.cullUnusedVerts(); clipper.triangulate(); clipper.generateNormals(); if ( clipper.mVertexList.empty() ) return false; #ifdef DECALMANAGER_DEBUG mDebugPlanes.clear(); mDebugPlanes.merge( clipper.mPlaneList ); #endif decal->mVertCount = clipper.mVertexList.size(); decal->mIndxCount = clipper.mIndexList.size(); Vector<Point3F> tmpPoints; tmpPoints.push_back(( objFwd * decalHalfSize ) + ( objRight * decalHalfSize )); tmpPoints.push_back(( objFwd * decalHalfSize ) + ( -objRight * decalHalfSize )); tmpPoints.push_back(( -objFwd * decalHalfSize ) + ( -objRight * decalHalfSize )); Point3F lowerLeft(( -objFwd * decalHalfSize ) + ( objRight * decalHalfSize )); projMat.inverse(); _generateWindingOrder( lowerLeft, &tmpPoints ); BiQuadToSqr quadToSquare( Point2F( lowerLeft.x, lowerLeft.y ), Point2F( tmpPoints[0].x, tmpPoints[0].y ), Point2F( tmpPoints[1].x, tmpPoints[1].y ), Point2F( tmpPoints[2].x, tmpPoints[2].y ) ); Point2F uv( 0, 0 ); Point3F vecX(0.0f, 0.0f, 0.0f); // Allocate memory for vert and index arrays _allocBuffers( decal ); Point3F vertPoint( 0, 0, 0 ); for ( U32 i = 0; i < clipper.mVertexList.size(); i++ ) { const ClippedPolyList::Vertex &vert = clipper.mVertexList[i]; vertPoint = vert.point; // Transform this point to // object space to look up the // UV coordinate for this vertex. projMat.mulP( vertPoint ); // Clamp the point to be within the quad. vertPoint.x = mClampF( vertPoint.x, -decalHalfSize.x, decalHalfSize.x ); vertPoint.y = mClampF( vertPoint.y, -decalHalfSize.y, decalHalfSize.y ); // Get our UV. uv = quadToSquare.transform( Point2F( vertPoint.x, vertPoint.y ) ); const RectF &rect = decal->mDataBlock->texRect[decal->mTextureRectIdx]; uv *= rect.extent; uv += rect.point; // Set the world space vertex position. decal->mVerts[i].point = vert.point; decal->mVerts[i].texCoord.set( uv.x, uv.y ); decal->mVerts[i].normal = clipper.mNormalList[i]; decal->mVerts[i].normal.normalize(); if( mFabs( decal->mVerts[i].normal.z ) > 0.8f ) mCross( decal->mVerts[i].normal, Point3F( 1.0f, 0.0f, 0.0f ), &vecX ); else if ( mFabs( decal->mVerts[i].normal.x ) > 0.8f ) mCross( decal->mVerts[i].normal, Point3F( 0.0f, 1.0f, 0.0f ), &vecX ); else if ( mFabs( decal->mVerts[i].normal.y ) > 0.8f ) mCross( decal->mVerts[i].normal, Point3F( 0.0f, 0.0f, 1.0f ), &vecX ); decal->mVerts[i].tangent = mCross( decal->mVerts[i].normal, vecX ); } U32 curIdx = 0; for ( U32 j = 0; j < clipper.mPolyList.size(); j++ ) { // Write indices for each Poly ClippedPolyList::Poly *poly = &clipper.mPolyList[j]; AssertFatal( poly->vertexCount == 3, "Got non-triangle poly!" ); decal->mIndices[curIdx] = clipper.mIndexList[poly->vertexStart]; curIdx++; decal->mIndices[curIdx] = clipper.mIndexList[poly->vertexStart + 1]; curIdx++; decal->mIndices[curIdx] = clipper.mIndexList[poly->vertexStart + 2]; curIdx++; } if ( !edgeVerts ) return true; Point3F tmpHullPt( 0, 0, 0 ); Vector<Point3F> tmpHullPts; for ( U32 i = 0; i < clipper.mVertexList.size(); i++ ) { const ClippedPolyList::Vertex &vert = clipper.mVertexList[i]; tmpHullPt = vert.point; projMat.mulP( tmpHullPt ); tmpHullPts.push_back( tmpHullPt ); } edgeVerts->clear(); U32 verts = _generateConvexHull( tmpHullPts, edgeVerts ); edgeVerts->setSize( verts ); projMat.inverse(); for ( U32 i = 0; i < edgeVerts->size(); i++ ) projMat.mulP( (*edgeVerts)[i] ); return true; }
void DecalRoad::_generateEdges() { PROFILE_SCOPE( DecalRoad_generateEdges ); //Con::warnf( "%s - generateEdges", isServerObject() ? "server" : "client" ); if ( mNodes.size() > 0 ) { // Set our object position to the first node. const Point3F &nodePt = mNodes.first().point; MatrixF mat( true ); mat.setPosition( nodePt ); Parent::setTransform( mat ); // The server object has global bounds, which Parent::setTransform // messes up so we must reset it. if ( isServerObject() ) { mObjBox.minExtents.set(-1e10, -1e10, -1e10); mObjBox.maxExtents.set( 1e10, 1e10, 1e10); } } if ( mNodes.size() < 2 ) return; // Ensure nodes are above the terrain height at their xy position for ( U32 i = 0; i < mNodes.size(); i++ ) { _getTerrainHeight( mNodes[i].point ); } // Now start generating edges... U32 nodeCount = mNodes.size(); Point3F *positions = new Point3F[nodeCount]; for ( U32 i = 0; i < nodeCount; i++ ) { const RoadNode &node = mNodes[i]; positions[i].set( node.point.x, node.point.y, node.width ); } CatmullRom<Point3F> spline; spline.initialize( nodeCount, positions ); delete [] positions; mEdges.clear(); Point3F lastBreakVector(0,0,0); RoadEdge slice; Point3F lastBreakNode; lastBreakNode = spline.evaluate(0.0f); for ( U32 i = 1; i < mNodes.size(); i++ ) { F32 t1 = spline.getTime(i); F32 t0 = spline.getTime(i-1); F32 segLength = spline.arcLength( t0, t1 ); U32 numSegments = mCeil( segLength / MIN_METERS_PER_SEGMENT ); numSegments = getMax( numSegments, (U32)1 ); F32 tstep = ( t1 - t0 ) / numSegments; U32 startIdx = 0; U32 endIdx = ( i == nodeCount - 1 ) ? numSegments + 1 : numSegments; for ( U32 j = startIdx; j < endIdx; j++ ) { F32 t = t0 + tstep * j; Point3F splineNode = spline.evaluate(t); F32 width = splineNode.z; _getTerrainHeight( splineNode ); Point3F toNodeVec = splineNode - lastBreakNode; toNodeVec.normalizeSafe(); if ( lastBreakVector.isZero() ) lastBreakVector = toNodeVec; F32 angle = mRadToDeg( mAcos( mDot( toNodeVec, lastBreakVector ) ) ); if ( j == startIdx || ( j == endIdx - 1 && i == mNodes.size() - 1 ) || angle > mBreakAngle ) { // Push back a spline node //slice.p1.set( splineNode.x, splineNode.y, 0.0f ); //_getTerrainHeight( slice.p1 ); slice.p1 = splineNode; slice.uvec.set(0,0,1); slice.width = width; slice.parentNodeIdx = i-1; mEdges.push_back( slice ); lastBreakVector = splineNode - lastBreakNode; lastBreakVector.normalizeSafe(); lastBreakNode = splineNode; } } } /* for ( U32 i = 1; i < nodeCount; i++ ) { F32 t0 = spline.getTime( i-1 ); F32 t1 = spline.getTime( i ); F32 segLength = spline.arcLength( t0, t1 ); U32 numSegments = mCeil( segLength / mBreakAngle ); numSegments = getMax( numSegments, (U32)1 ); F32 tstep = ( t1 - t0 ) / numSegments; AssertFatal( numSegments > 0, "DecalRoad::_generateEdges, got zero segments!" ); U32 startIdx = 0; U32 endIdx = ( i == nodeCount - 1 ) ? numSegments + 1 : numSegments; for ( U32 j = startIdx; j < endIdx; j++ ) { F32 t = t0 + tstep * j; Point3F val = spline.evaluate(t); RoadEdge edge; edge.p1.set( val.x, val.y, 0.0f ); _getTerrainHeight( val.x, val.y, edge.p1.z ); edge.uvec.set(0,0,1); edge.width = val.z; edge.parentNodeIdx = i-1; mEdges.push_back( edge ); } } */ // // Calculate fvec and rvec for all edges // RoadEdge *edge = NULL; RoadEdge *nextEdge = NULL; for ( U32 i = 0; i < mEdges.size() - 1; i++ ) { edge = &mEdges[i]; nextEdge = &mEdges[i+1]; edge->fvec = nextEdge->p1 - edge->p1; edge->fvec.normalize(); edge->rvec = mCross( edge->fvec, edge->uvec ); edge->rvec.normalize(); } // Must do the last edge outside the loop RoadEdge *lastEdge = &mEdges[mEdges.size()-1]; RoadEdge *prevEdge = &mEdges[mEdges.size()-2]; lastEdge->fvec = prevEdge->fvec; lastEdge->rvec = prevEdge->rvec; // // Calculate p0/p2 for all edges // for ( U32 i = 0; i < mEdges.size(); i++ ) { RoadEdge *edge = &mEdges[i]; edge->p0 = edge->p1 - edge->rvec * edge->width * 0.5f; edge->p2 = edge->p1 + edge->rvec * edge->width * 0.5f; _getTerrainHeight( edge->p0 ); _getTerrainHeight( edge->p2 ); } }
void HoverVehicle::updateForces(F32 /*dt*/) { PROFILE_SCOPE( HoverVehicle_UpdateForces ); Point3F gravForce(0, 0, sHoverVehicleGravity * mRigid.mass * mGravityMod); MatrixF currTransform; mRigid.getTransform(&currTransform); mRigid.atRest = false; mThrustLevel = (mForwardThrust * mDataBlock->mainThrustForce + mReverseThrust * mDataBlock->reverseThrustForce + mLeftThrust * mDataBlock->strafeThrustForce + mRightThrust * mDataBlock->strafeThrustForce); Point3F thrustForce = ((Point3F( 0, 1, 0) * (mForwardThrust * mDataBlock->mainThrustForce)) + (Point3F( 0, -1, 0) * (mReverseThrust * mDataBlock->reverseThrustForce)) + (Point3F(-1, 0, 0) * (mLeftThrust * mDataBlock->strafeThrustForce)) + (Point3F( 1, 0, 0) * (mRightThrust * mDataBlock->strafeThrustForce))); currTransform.mulV(thrustForce); if (mJetting) thrustForce *= mDataBlock->turboFactor; Point3F torque(0, 0, 0); Point3F force(0, 0, 0); Point3F vel = mRigid.linVelocity; F32 baseStabLen = getBaseStabilizerLength(); Point3F stabExtend(0, 0, -baseStabLen); currTransform.mulV(stabExtend); StabPoint stabPoints[2]; stabPoints[0].osPoint = Point3F((mObjBox.minExtents.x + mObjBox.maxExtents.x) * 0.5, mObjBox.maxExtents.y, (mObjBox.minExtents.z + mObjBox.maxExtents.z) * 0.5); stabPoints[1].osPoint = Point3F((mObjBox.minExtents.x + mObjBox.maxExtents.x) * 0.5, mObjBox.minExtents.y, (mObjBox.minExtents.z + mObjBox.maxExtents.z) * 0.5); U32 j, i; for (i = 0; i < 2; i++) { currTransform.mulP(stabPoints[i].osPoint, &stabPoints[i].wsPoint); stabPoints[i].wsExtension = stabExtend; stabPoints[i].extension = baseStabLen; stabPoints[i].wsVelocity = mRigid.linVelocity; } RayInfo rinfo; mFloating = true; bool reallyFloating = true; F32 compression[2] = { 0.0f, 0.0f }; F32 normalMod[2] = { 0.0f, 0.0f }; bool normalSet[2] = { false, false }; Point3F normal[2]; for (j = 0; j < 2; j++) { if (getContainer()->castRay(stabPoints[j].wsPoint, stabPoints[j].wsPoint + stabPoints[j].wsExtension * 2.0, TerrainObjectType | WaterObjectType, &rinfo)) { reallyFloating = false; if (rinfo.t <= 0.5) { // Ok, stab is in contact with the ground, let's calc the forces... compression[j] = (1.0 - (rinfo.t * 2.0)) * baseStabLen; } normalSet[j] = true; normalMod[j] = rinfo.t < 0.5 ? 1.0 : (1.0 - ((rinfo.t - 0.5) * 2.0)); normal[j] = rinfo.normal; } if ( pointInWater( stabPoints[j].wsPoint ) ) compression[j] = baseStabLen; } for (j = 0; j < 2; j++) { if (compression[j] != 0.0) { mFloating = false; // Spring force and damping Point3F springForce = -stabPoints[j].wsExtension; springForce.normalize(); springForce *= compression[j] * mDataBlock->stabSpringConstant; Point3F springDamping = -stabPoints[j].wsExtension; springDamping.normalize(); springDamping *= -getMin(mDot(springDamping, stabPoints[j].wsVelocity), 0.7f) * mDataBlock->stabDampingConstant; force += springForce + springDamping; } } // Gravity if (reallyFloating == false) force += gravForce; else force += gravForce * mDataBlock->floatingGravMag; // Braking F32 vellen = mRigid.linVelocity.len(); if (mThrottle == 0.0f && mLeftThrust == 0.0f && mRightThrust == 0.0f && vellen != 0.0f && vellen < mDataBlock->brakingActivationSpeed) { Point3F dir = mRigid.linVelocity; dir.normalize(); dir.neg(); force += dir * mDataBlock->brakingForce; } // Gyro Drag torque = -mRigid.angMomentum * mDataBlock->gyroDrag; // Move to proper normal Point3F sn, r; currTransform.getColumn(2, &sn); if (normalSet[0] || normalSet[1]) { if (normalSet[0] && normalSet[1]) { F32 dot = mDot(normal[0], normal[1]); if (dot > 0.999) { // Just pick the first normal. They're too close to call if ((sn - normal[0]).lenSquared() > 0.00001) { mCross(sn, normal[0], &r); torque += r * mDataBlock->normalForce * normalMod[0]; } } else { Point3F rotAxis; mCross(normal[0], normal[1], &rotAxis); rotAxis.normalize(); F32 angle = mAcos(dot) * (normalMod[0] / (normalMod[0] + normalMod[1])); AngAxisF aa(rotAxis, angle); QuatF q(aa); MatrixF tempMat(true); q.setMatrix(&tempMat); Point3F newNormal; tempMat.mulV(normal[1], &newNormal); if ((sn - newNormal).lenSquared() > 0.00001) { mCross(sn, newNormal, &r); torque += r * (mDataBlock->normalForce * ((normalMod[0] + normalMod[1]) * 0.5)); } } } else { Point3F useNormal; F32 useMod; if (normalSet[0]) { useNormal = normal[0]; useMod = normalMod[0]; } else { useNormal = normal[1]; useMod = normalMod[1]; } if ((sn - useNormal).lenSquared() > 0.00001) { mCross(sn, useNormal, &r); torque += r * mDataBlock->normalForce * useMod; } } } else { if ((sn - Point3F(0, 0, 1)).lenSquared() > 0.00001) { mCross(sn, Point3F(0, 0, 1), &r); torque += r * mDataBlock->restorativeForce; } } Point3F sn2; currTransform.getColumn(0, &sn); currTransform.getColumn(1, &sn2); mCross(sn, sn2, &r); r.normalize(); torque -= r * (mSteering.x * mDataBlock->steeringForce); currTransform.getColumn(0, &sn); currTransform.getColumn(2, &sn2); mCross(sn, sn2, &r); r.normalize(); torque -= r * (mSteering.x * mDataBlock->rollForce); currTransform.getColumn(1, &sn); currTransform.getColumn(2, &sn2); mCross(sn, sn2, &r); r.normalize(); torque -= r * (mSteering.y * mDataBlock->pitchForce); // Apply drag Point3F vDrag = mRigid.linVelocity; if (!mFloating) { vDrag.convolve(Point3F(1, 1, mDataBlock->vertFactor)); } else { vDrag.convolve(Point3F(0.25, 0.25, mDataBlock->vertFactor)); } force -= vDrag * mDataBlock->dragForce; force += mFloating ? thrustForce * mDataBlock->floatingThrustFactor : thrustForce; // Add in physical zone force force += mAppliedForce; // Container buoyancy & drag force += Point3F(0, 0,-mBuoyancy * sHoverVehicleGravity * mRigid.mass * mGravityMod); force -= mRigid.linVelocity * mDrag; torque -= mRigid.angMomentum * mDrag; mRigid.force = force; mRigid.torque = torque; }
void TerrainBlock::buildConvex(const Box3F& box,Convex* convex) { sTerrainConvexList.collectGarbage(); // if (box.maxExtents.z < -TerrainThickness || box.minExtents.z > fixedToFloat(gridMap[BlockShift]->maxHeight)) return; // Transform the bounding sphere into the object's coord space. Note that this // not really optimal. Box3F osBox = box; mWorldToObj.mul(osBox); AssertWarn(mObjScale == Point3F(1, 1, 1), "Error, handle the scale transform on the terrain"); S32 xStart = (S32)mFloor( osBox.minExtents.x / mSquareSize ); S32 xEnd = (S32)mCeil ( osBox.maxExtents.x / mSquareSize ); S32 yStart = (S32)mFloor( osBox.minExtents.y / mSquareSize ); S32 yEnd = (S32)mCeil ( osBox.maxExtents.y / mSquareSize ); S32 xExt = xEnd - xStart; if (xExt > MaxExtent) xExt = MaxExtent; mHeightMax = floatToFixed(osBox.maxExtents.z); mHeightMin = (osBox.minExtents.z < 0)? 0: floatToFixed(osBox.minExtents.z); for (S32 y = yStart; y < yEnd; y++) { S32 yi = y & BlockMask; // for (S32 x = xStart; x < xEnd; x++) { S32 xi = x & BlockMask; GridSquare *gs = findSquare(0, Point2I(xi, yi)); // If we disable repeat, then skip non-primary if(!mTile && (x!=xi || y!=yi)) continue; // holes only in the primary terrain block if (((gs->flags & GridSquare::Empty) && x == xi && y == yi) || gs->minHeight > mHeightMax || gs->maxHeight < mHeightMin) continue; U32 sid = (x << 16) + (y & ((1 << 16) - 1)); Convex* cc = 0; // See if the square already exists as part of the working set. CollisionWorkingList& wl = convex->getWorkingList(); for (CollisionWorkingList* itr = wl.wLink.mNext; itr != &wl; itr = itr->wLink.mNext) if (itr->mConvex->getType() == TerrainConvexType && static_cast<TerrainConvex*>(itr->mConvex)->squareId == sid) { cc = itr->mConvex; break; } if (cc) continue; // Create a new convex. TerrainConvex* cp = new TerrainConvex; sTerrainConvexList.registerObject(cp); convex->addToWorkingList(cp); cp->halfA = true; cp->square = 0; cp->mObject = this; cp->squareId = sid; cp->material = getMaterial(xi,yi)->index;//RDTODO cp->box.minExtents.set((F32)(x * mSquareSize), (F32)(y * mSquareSize), fixedToFloat(gs->minHeight)); cp->box.maxExtents.x = cp->box.minExtents.x + mSquareSize; cp->box.maxExtents.y = cp->box.minExtents.y + mSquareSize; cp->box.maxExtents.z = fixedToFloat(gs->maxHeight); mObjToWorld.mul(cp->box); // Build points Point3F* pos = cp->point; for (int i = 0; i < 4 ; i++,pos++) { S32 dx = i >> 1; S32 dy = dx ^ (i & 1); pos->x = (F32)((x + dx) * mSquareSize); pos->y = (F32)((y + dy) * mSquareSize); pos->z = fixedToFloat(getHeight(xi + dx, yi + dy)); } // Build normals, then split into two Convex objects if the // square is concave if ((cp->split45 = gs->flags & GridSquare::Split45) == true) { VectorF *vp = cp->point; mCross(vp[0] - vp[1],vp[2] - vp[1],&cp->normal[0]); cp->normal[0].normalize(); mCross(vp[2] - vp[3],vp[0] - vp[3],&cp->normal[1]); cp->normal[1].normalize(); if (mDot(vp[3] - vp[1],cp->normal[0]) > 0) { TerrainConvex* nc = new TerrainConvex(*cp); sTerrainConvexList.registerObject(nc); convex->addToWorkingList(nc); nc->halfA = false; nc->square = cp; cp->square = nc; } } else { VectorF *vp = cp->point; mCross(vp[3] - vp[0],vp[1] - vp[0],&cp->normal[0]); cp->normal[0].normalize(); mCross(vp[1] - vp[2],vp[3] - vp[2],&cp->normal[1]); cp->normal[1].normalize(); if (mDot(vp[2] - vp[0],cp->normal[0]) > 0) { TerrainConvex* nc = new TerrainConvex(*cp); sTerrainConvexList.registerObject(nc); convex->addToWorkingList(nc); nc->halfA = false; nc->square = cp; cp->square = nc; } } } } }
bool AtlasGeomChunkTracer::castRayTriangle(Point3F orig, Point3F dir, Point3F vert0, Point3F vert1, Point3F vert2, F32 &t, Point2F &bary) { Point3F tvec, qvec; // Find vectors for two edges sharing vert0 const Point3F edge1 = vert1 - vert0; const Point3F edge2 = vert2 - vert0; // Begin calculating determinant - also used to calculate U parameter. const Point3F pvec = mCross(dir, edge2); // If determinant is near zero, ray lies in plane of triangle. const F32 det = mDot(edge1, pvec); if (det > 0.00001) { // calculate distance from vert0 to ray origin tvec = orig - vert0; // calculate U parameter and test bounds bary.x = mDot(tvec, pvec); // bary.x is really bary.u... if (bary.x < 0.0 || bary.x > det) return false; // prepare to test V parameter qvec = mCross(tvec, edge1); // calculate V parameter and test bounds bary.y = mDot(dir, qvec); // bary.y is really bary.v if (bary.y < 0.0 || (bary.x + bary.y) > det) return false; } else if(det < -0.00001) { // calculate distance from vert0 to ray origin tvec = orig - vert0; // calculate U parameter and test bounds bary.x = mDot(tvec, pvec); if (bary.x > 0.0 || bary.x < det) return false; // prepare to test V parameter qvec = mCross(tvec, edge1); // calculate V parameter and test bounds bary.y = mDot(dir, qvec); if (bary.y > 0.0 || (bary.x + bary.y) < det) return false; } else return false; // ray is parallel to the plane of the triangle. const F32 inv_det = 1.0 / det; // calculate t, ray intersects triangle t = mDot(edge2, qvec) * inv_det; bary *= inv_det; //AssertFatal((t >= 0.f && t <=1.f), "AtlasGeomTracer::castRayTriangle - invalid t!"); // Hack, check the math here! return (t >= 0.f && t <=1.f); }
void Rigid::getVelocity(const Point3F& r, Point3F* v) { mCross(angVelocity, r, v); *v += linVelocity; }
void WaterPlane::innerRender( SceneRenderState *state ) { GFXDEBUGEVENT_SCOPE( WaterPlane_innerRender, ColorI( 255, 0, 0 ) ); const Point3F &camPosition = state->getCameraPosition(); Point3F rvec, fvec, uvec, pos; const MatrixF &objMat = getTransform(); //getRenderTransform(); const MatrixF &camMat = state->getCameraTransform(); MatrixF renderMat( true ); camMat.getColumn( 1, &fvec ); uvec.set( 0, 0, 1 ); rvec = mCross( fvec, uvec ); rvec.normalize(); fvec = mCross( uvec, rvec ); pos = camPosition; pos.z = objMat.getPosition().z; renderMat.setColumn( 0, rvec ); renderMat.setColumn( 1, fvec ); renderMat.setColumn( 2, uvec ); renderMat.setColumn( 3, pos ); setRenderTransform( renderMat ); // Setup SceneData SceneData sgData = setupSceneGraphInfo( state ); // set the material S32 matIdx = getMaterialIndex( camPosition ); if ( !initMaterial( matIdx ) ) return; BaseMatInstance *mat = mMatInstances[matIdx]; WaterMatParams matParams = mMatParamHandles[matIdx]; // render the geometry if ( mat ) { // setup proj/world transform mMatrixSet->restoreSceneViewProjection(); mMatrixSet->setWorld(getRenderTransform()); setShaderParams( state, mat, matParams ); while( mat->setupPass( state, sgData ) ) { mat->setSceneInfo(state, sgData); mat->setTransforms(*mMatrixSet, state, sgData); setCustomTextures( matIdx, mat->getCurPass(), matParams ); // set vert/prim buffer GFX->setVertexBuffer( mVertBuff ); GFX->setPrimitiveBuffer( mPrimBuff ); GFX->drawIndexedPrimitive( GFXTriangleList, 0, 0, mVertCount, 0, mPrimCount ); } } }
void TurretShape::getCameraTransform(F32* pos,MatrixF* mat) { // Returns camera to world space transform // Handles first person / third person camera position if (isServerObject() && mShapeInstance) mShapeInstance->animateNodeSubtrees(true); if (*pos == 0) { getRenderEyeTransform(mat); return; } // Get the shape's camera parameters. F32 min,max; MatrixF rot; Point3F offset; getCameraParameters(&min,&max,&offset,&rot); // Start with the current eye position MatrixF eye; getRenderEyeTransform(&eye); // Build a transform that points along the eye axis // but where the Z axis is always up. { MatrixF cam(1); VectorF x,y,z(0,0,1); eye.getColumn(1, &y); mCross(y, z, &x); x.normalize(); mCross(x, y, &z); z.normalize(); cam.setColumn(0,x); cam.setColumn(1,y); cam.setColumn(2,z); mat->mul(cam,rot); } // Camera is positioned straight back along the eye's -Y axis. // A ray is cast to make sure the camera doesn't go through // anything solid. VectorF vp,vec; vp.x = vp.z = 0; vp.y = -(max - min) * *pos; eye.mulV(vp,&vec); // Use the camera node as the starting position if it exists. Point3F osp,sp; if (mDataBlock->cameraNode != -1) { mShapeInstance->mNodeTransforms[mDataBlock->cameraNode].getColumn(3,&osp); getRenderTransform().mulP(osp,&sp); } else eye.getColumn(3,&sp); // Make sure we don't hit ourself... disableCollision(); if (isMounted()) getObjectMount()->disableCollision(); // Cast the ray into the container database to see if we're going // to hit anything. RayInfo collision; Point3F ep = sp + vec + offset; if (mContainer->castRay(sp, ep, ~(WaterObjectType | GameBaseObjectType | DefaultObjectType | sTriggerMask), &collision) == true) { // Shift the collision point back a little to try and // avoid clipping against the front camera plane. F32 t = collision.t - (-mDot(vec, collision.normal) / vec.len()) * 0.1; if (t > 0.0f) ep = sp + offset + (vec * t); else eye.getColumn(3,&ep); } mat->setColumn(3,ep); // Re-enable our collision. if (isMounted()) getObjectMount()->enableCollision(); enableCollision(); // Apply Camera FX. mat->mul( gCamFXMgr.getTrans() ); }
bool ProjectedShadow::_updateDecal( const SceneRenderState *state ) { PROFILE_SCOPE( ProjectedShadow_UpdateDecal ); if ( !LIGHTMGR ) return false; // Get the position of the decal first. const Box3F &objBox = mParentObject->getObjBox(); const Point3F boxCenter = objBox.getCenter(); Point3F decalPos = boxCenter; const MatrixF &renderTransform = mParentObject->getRenderTransform(); { // Set up the decal position. // We use the object space box center // multiplied by the render transform // of the object to ensure we benefit // from interpolation. MatrixF t( renderTransform ); t.setColumn(2,Point3F::UnitZ); t.mulP( decalPos ); } if ( mDecalInstance ) { mDecalInstance->mPosition = decalPos; if ( !shouldRender( state ) ) return false; } // Get the sunlight for the shadow projection. // We want the LightManager to return NULL if it can't // get the "real" sun, so we specify false for the useDefault parameter. LightInfo *lights[4] = {0}; LightQuery query; query.init( mParentObject->getWorldSphere() ); query.getLights( lights, 4 ); Point3F pos = renderTransform.getPosition(); Point3F lightDir( 0, 0, 0 ); Point3F tmp( 0, 0, 0 ); F32 weight = 0; F32 range = 0; U32 lightCount = 0; F32 dist = 0; F32 fade = 0; for ( U32 i = 0; i < 4; i++ ) { // If we got a NULL light, // we're at the end of the list. if ( !lights[i] ) break; if ( !lights[i]->getCastShadows() ) continue; if ( lights[i]->getType() != LightInfo::Point ) tmp = lights[i]->getDirection(); else tmp = pos - lights[i]->getPosition(); range = lights[i]->getRange().x; dist = ( (tmp.lenSquared()) / ((range * range) * 0.5f)); weight = mClampF( 1.0f - ( tmp.lenSquared() / (range * range)), 0.00001f, 1.0f ); if ( lights[i]->getType() == LightInfo::Vector ) fade = getMax( fade, 1.0f ); else fade = getMax( fade, mClampF( 1.0f - dist, 0.00001f, 1.0f ) ); lightDir += tmp * weight; lightCount++; } lightDir.normalize(); // No light... no shadow. if ( !lights[0] ) return false; // Has the light direction // changed since last update? bool lightDirChanged = !mLastLightDir.equal( lightDir ); // Has the parent object moved // or scaled since the last update? bool hasMoved = !mLastObjectPosition.equal( mParentObject->getRenderPosition() ); bool hasScaled = !mLastObjectScale.equal( mParentObject->getScale() ); // Set the last light direction // to the current light direction. mLastLightDir = lightDir; mLastObjectPosition = mParentObject->getRenderPosition(); mLastObjectScale = mParentObject->getScale(); // Temps used to generate // tangent vector for DecalInstance below. VectorF right( 0, 0, 0 ); VectorF fwd( 0, 0, 0 ); VectorF tmpFwd( 0, 0, 0 ); U32 idx = lightDir.getLeastComponentIndex(); tmpFwd[idx] = 1.0f; right = mCross( tmpFwd, lightDir ); fwd = mCross( lightDir, right ); right = mCross( fwd, lightDir ); right.normalize(); // Set up the world to light space // matrix, along with proper position // and rotation to be used as the world // matrix for the render to texture later on. static MatrixF sRotMat(EulerF( 0.0f, -(M_PI_F/2.0f), 0.0f)); mWorldToLight.identity(); MathUtils::getMatrixFromForwardVector( lightDir, &mWorldToLight ); mWorldToLight.setPosition( ( pos + boxCenter ) - ( ( (mRadius * smDepthAdjust) + 0.001f ) * lightDir ) ); mWorldToLight.mul( sRotMat ); mWorldToLight.inverse(); // Get the shapebase datablock if we have one. ShapeBaseData *data = NULL; if ( mShapeBase ) data = static_cast<ShapeBaseData*>( mShapeBase->getDataBlock() ); // We use the object box's extents multiplied // by the object's scale divided by 2 for the radius // because the object's worldsphere radius is not // rotationally invariant. mRadius = (objBox.getExtents() * mParentObject->getScale()).len() * 0.5f; if ( data ) mRadius *= data->shadowSphereAdjust; // Create the decal if we don't have one yet. if ( !mDecalInstance ) mDecalInstance = gDecalManager->addDecal( decalPos, lightDir, right, mDecalData, 1.0f, 0, PermanentDecal | ClipDecal | CustomDecal ); if ( !mDecalInstance ) return false; mDecalInstance->mVisibility = fade; // Setup decal parameters. mDecalInstance->mSize = mRadius * 2.0f; mDecalInstance->mNormal = -lightDir; mDecalInstance->mTangent = -right; mDecalInstance->mRotAroundNormal = 0; mDecalInstance->mPosition = decalPos; mDecalInstance->mDataBlock = mDecalData; // If the position of the world // space box center is the same // as the decal's position, and // the light direction has not // changed, we don't need to clip. bool shouldClip = lightDirChanged || hasMoved || hasScaled; // Now, check and see if the object is visible. const Frustum &frust = state->getCullingFrustum(); if ( frust.isCulled( SphereF( mDecalInstance->mPosition, mDecalInstance->mSize * mDecalInstance->mSize ) ) && !shouldClip ) return false; F32 shadowLen = 10.0f; if ( data ) shadowLen = data->shadowProjectionDistance; const Point3F &boxExtents = objBox.getExtents(); mShadowLength = shadowLen * mParentObject->getScale().z; // Set up clip depth, and box half // offset for decal clipping. Point2F clipParams( mShadowLength, (boxExtents.x + boxExtents.y) * 0.25f ); bool render = false; bool clipSucceeded = true; // Clip! if ( shouldClip ) { clipSucceeded = gDecalManager->clipDecal( mDecalInstance, NULL, &clipParams ); } // If the clip failed, // we'll return false in // order to keep from // unnecessarily rendering // into the texture. If // there was no reason to clip // on this update, we'll assume we // should update the texture. render = clipSucceeded; // Tell the DecalManager we've changed this decal. gDecalManager->notifyDecalModified( mDecalInstance ); return render; }
Point3F Etherform::_move( const F32 travelTime, Collision *outCol ) { // Try and move to new pos F32 totalMotion = 0.0f; // TODO: not used? //F32 initialSpeed = mVelocity.len(); Point3F start; Point3F initialPosition; getTransform().getColumn(3,&start); initialPosition = start; static CollisionList collisionList; static CollisionList physZoneCollisionList; collisionList.clear(); physZoneCollisionList.clear(); MatrixF collisionMatrix(true); collisionMatrix.setColumn(3, start); VectorF firstNormal(0.0f, 0.0f, 0.0f); F32 time = travelTime; U32 count = 0; static Polyhedron sBoxPolyhedron; static ExtrudedPolyList sExtrudedPolyList; static ExtrudedPolyList sPhysZonePolyList; for (; count < sMoveRetryCount; count++) { F32 speed = mVelocity.len(); if(!speed) break; Point3F end = start + mVelocity * time; Point3F distance = end - start; if (mFabs(distance.x) < mObjBox.len_x() && mFabs(distance.y) < mObjBox.len_y() && mFabs(distance.z) < mObjBox.len_z()) { // We can potentially early out of this. If there are no polys in the clipped polylist at our // end position, then we can bail, and just set start = end; Box3F wBox = mScaledBox; wBox.minExtents += end; wBox.maxExtents += end; static EarlyOutPolyList eaPolyList; eaPolyList.clear(); eaPolyList.mNormal.set(0.0f, 0.0f, 0.0f); eaPolyList.mPlaneList.clear(); eaPolyList.mPlaneList.setSize(6); eaPolyList.mPlaneList[0].set(wBox.minExtents,VectorF(-1.0f, 0.0f, 0.0f)); eaPolyList.mPlaneList[1].set(wBox.maxExtents,VectorF(0.0f, 1.0f, 0.0f)); eaPolyList.mPlaneList[2].set(wBox.maxExtents,VectorF(1.0f, 0.0f, 0.0f)); eaPolyList.mPlaneList[3].set(wBox.minExtents,VectorF(0.0f, -1.0f, 0.0f)); eaPolyList.mPlaneList[4].set(wBox.minExtents,VectorF(0.0f, 0.0f, -1.0f)); eaPolyList.mPlaneList[5].set(wBox.maxExtents,VectorF(0.0f, 0.0f, 1.0f)); // Build list from convex states here... CollisionWorkingList& rList = mConvex.getWorkingList(); CollisionWorkingList* pList = rList.wLink.mNext; while (pList != &rList) { Convex* pConvex = pList->mConvex; if (pConvex->getObject()->getTypeMask() & sCollisionMoveMask) { Box3F convexBox = pConvex->getBoundingBox(); if (wBox.isOverlapped(convexBox)) { // No need to separate out the physical zones here, we want those // to cause a fallthrough as well... pConvex->getPolyList(&eaPolyList); } } pList = pList->wLink.mNext; } if (eaPolyList.isEmpty()) { totalMotion += (end - start).len(); start = end; break; } } collisionMatrix.setColumn(3, start); sBoxPolyhedron.buildBox(collisionMatrix, mScaledBox, true); // Setup the bounding box for the extrudedPolyList Box3F plistBox = mScaledBox; collisionMatrix.mul(plistBox); Point3F oldMin = plistBox.minExtents; Point3F oldMax = plistBox.maxExtents; plistBox.minExtents.setMin(oldMin + (mVelocity * time) - Point3F(0.1f, 0.1f, 0.1f)); plistBox.maxExtents.setMax(oldMax + (mVelocity * time) + Point3F(0.1f, 0.1f, 0.1f)); // Build extruded polyList... VectorF vector = end - start; sExtrudedPolyList.extrude(sBoxPolyhedron,vector); sExtrudedPolyList.setVelocity(mVelocity); sExtrudedPolyList.setCollisionList(&collisionList); sPhysZonePolyList.extrude(sBoxPolyhedron,vector); sPhysZonePolyList.setVelocity(mVelocity); sPhysZonePolyList.setCollisionList(&physZoneCollisionList); // Build list from convex states here... CollisionWorkingList& rList = mConvex.getWorkingList(); CollisionWorkingList* pList = rList.wLink.mNext; while (pList != &rList) { Convex* pConvex = pList->mConvex; if (pConvex->getObject()->getTypeMask() & sCollisionMoveMask) { Box3F convexBox = pConvex->getBoundingBox(); if (plistBox.isOverlapped(convexBox)) { if (pConvex->getObject()->getTypeMask() & PhysicalZoneObjectType) pConvex->getPolyList(&sPhysZonePolyList); else pConvex->getPolyList(&sExtrudedPolyList); } } pList = pList->wLink.mNext; } // Take into account any physical zones... for (U32 j = 0; j < physZoneCollisionList.getCount(); j++) { AssertFatal(dynamic_cast<PhysicalZone*>(physZoneCollisionList[j].object), "Bad phys zone!"); const PhysicalZone* pZone = (PhysicalZone*)physZoneCollisionList[j].object; if (pZone->isActive()) mVelocity *= pZone->getVelocityMod(); } if (collisionList.getCount() != 0 && collisionList.getTime() < 1.0f) { // Set to collision point F32 velLen = mVelocity.len(); F32 dt = time * getMin(collisionList.getTime(), 1.0f); start += mVelocity * dt; time -= dt; totalMotion += velLen * dt; // Back off... if ( velLen > 0.f ) { F32 newT = getMin(0.01f / velLen, dt); start -= mVelocity * newT; totalMotion -= velLen * newT; } // Pick the surface most parallel to the face that was hit. const Collision *collision = &collisionList[0]; const Collision *cp = collision + 1; const Collision *ep = collision + collisionList.getCount(); for (; cp != ep; cp++) { if (cp->faceDot > collision->faceDot) collision = cp; } F32 bd = -mDot(mVelocity, collision->normal); // Copy this collision out so // we can use it to do impacts // and query collision. *outCol = *collision; // Subtract out velocity VectorF dv = collision->normal * (bd + sNormalElasticity); mVelocity += dv; if (count == 0) { firstNormal = collision->normal; } else { if (count == 1) { // Re-orient velocity along the crease. if (mDot(dv,firstNormal) < 0.0f && mDot(collision->normal,firstNormal) < 0.0f) { VectorF nv; mCross(collision->normal,firstNormal,&nv); F32 nvl = nv.len(); if (nvl) { if (mDot(nv,mVelocity) < 0.0f) nvl = -nvl; nv *= mVelocity.len() / nvl; mVelocity = nv; } } } } } else { totalMotion += (end - start).len(); start = end; break; } } if (count == sMoveRetryCount) { // Failed to move start = initialPosition; mVelocity.set(0.0f, 0.0f, 0.0f); } return start; }
F32 GameBase::getUpdatePriority(CameraScopeQuery *camInfo, U32 updateMask, S32 updateSkips) { TORQUE_UNUSED(updateMask); // Calculate a priority used to decide if this object // will be updated on the client. All the weights // are calculated 0 -> 1 Then weighted together at the // end to produce a priority. Point3F pos; getWorldBox().getCenter(&pos); pos -= camInfo->pos; F32 dist = pos.len(); if (dist == 0.0f) dist = 0.001f; pos *= 1.0f / dist; // Weight based on linear distance, the basic stuff. F32 wDistance = (dist < camInfo->visibleDistance)? 1.0f - (dist / camInfo->visibleDistance): 0.0f; // Weight by field of view, objects directly in front // will be weighted 1, objects behind will be 0 F32 dot = mDot(pos,camInfo->orientation); //Winterleaf Modification //bool inFov = dot > camInfo->cosFov; bool inFov = dot > (cos( (camInfo->fov + 40) >360?360:camInfo->fov + 40)/2); //Winterleaf Modification F32 wFov = inFov? 1.0f: 0; // Weight by linear velocity parallel to the viewing plane // (if it's the field of view, 0 if it's not). F32 wVelocity = 0.0f; if (inFov) { Point3F vec; mCross(camInfo->orientation,getVelocity(),&vec); wVelocity = (vec.len() * camInfo->fov) / (camInfo->fov * camInfo->visibleDistance); if (wVelocity > 1.0f) wVelocity = 1.0f; } // Weight by interest. F32 wInterest; if (getTypeMask() & PlayerObjectType) wInterest = 0.75f; else if (getTypeMask() & ProjectileObjectType) { // Projectiles are more interesting if they // are heading for us. wInterest = 0.30f; F32 dot = -mDot(pos,getVelocity()); if (dot > 0.0f) wInterest += 0.20 * dot; } else { if (getTypeMask() & ItemObjectType) wInterest = 0.25f; else // Everything else is less interesting. wInterest = 0.0f; } // Weight by updateSkips F32 wSkips = updateSkips * 0.5; // Calculate final priority, should total to about 1.0f // return wFov * sUpFov + wDistance * sUpDistance + wVelocity * sUpVelocity + wSkips * sUpSkips + wInterest * sUpInterest; }
void ConvexShape::Geometry::generate( const Vector< PlaneF > &planes, const Vector< Point3F > &tangents ) { PROFILE_SCOPE( Geometry_generate ); points.clear(); faces.clear(); AssertFatal( planes.size() == tangents.size(), "ConvexShape - incorrect plane/tangent count." ); #ifdef TORQUE_ENABLE_ASSERTS for ( S32 i = 0; i < planes.size(); i++ ) { F32 dt = mDot( planes[i], tangents[i] ); AssertFatal( mIsZero( dt, 0.0001f ), "ConvexShape - non perpendicular input vectors." ); AssertFatal( planes[i].isUnitLength() && tangents[i].isUnitLength(), "ConvexShape - non unit length input vector." ); } #endif const U32 planeCount = planes.size(); Point3F linePt, lineDir; for ( S32 i = 0; i < planeCount; i++ ) { Vector< MathUtils::Line > collideLines; // Find the lines defined by the intersection of this plane with all others. for ( S32 j = 0; j < planeCount; j++ ) { if ( i == j ) continue; if ( planes[i].intersect( planes[j], linePt, lineDir ) ) { collideLines.increment(); MathUtils::Line &line = collideLines.last(); line.origin = linePt; line.direction = lineDir; } } if ( collideLines.empty() ) continue; // Find edges and points defined by the intersection of these lines. // As we find them we fill them into our working ConvexShape::Face // structure. Face newFace; for ( S32 j = 0; j < collideLines.size(); j++ ) { Vector< Point3F > collidePoints; for ( S32 k = 0; k < collideLines.size(); k++ ) { if ( j == k ) continue; MathUtils::LineSegment segment; MathUtils::mShortestSegmentBetweenLines( collideLines[j], collideLines[k], &segment ); F32 dist = ( segment.p0 - segment.p1 ).len(); if ( dist < 0.0005f ) { S32 l = 0; for ( ; l < planeCount; l++ ) { if ( planes[l].whichSide( segment.p0 ) == PlaneF::Front ) break; } if ( l == planeCount ) collidePoints.push_back( segment.p0 ); } } //AssertFatal( collidePoints.size() <= 2, "A line can't collide with more than 2 other lines in a convex shape..." ); if ( collidePoints.size() != 2 ) continue; // Push back collision points into our points vector // if they are not duplicates and determine the id // index for those points to be used by Edge(s). const Point3F &pnt0 = collidePoints[0]; const Point3F &pnt1 = collidePoints[1]; S32 idx0 = -1; S32 idx1 = -1; for ( S32 k = 0; k < points.size(); k++ ) { if ( pnt0.equal( points[k] ) ) { idx0 = k; break; } } for ( S32 k = 0; k < points.size(); k++ ) { if ( pnt1.equal( points[k] ) ) { idx1 = k; break; } } if ( idx0 == -1 ) { points.push_back( pnt0 ); idx0 = points.size() - 1; } if ( idx1 == -1 ) { points.push_back( pnt1 ); idx1 = points.size() - 1; } // Construct the Face::Edge defined by this collision. S32 localIdx0 = newFace.points.push_back_unique( idx0 ); S32 localIdx1 = newFace.points.push_back_unique( idx1 ); newFace.edges.increment(); ConvexShape::Edge &newEdge = newFace.edges.last(); newEdge.p0 = localIdx0; newEdge.p1 = localIdx1; } if ( newFace.points.size() < 3 ) continue; //AssertFatal( newFace.points.size() == newFace.edges.size(), "ConvexShape - face point count does not equal edge count." ); // Fill in some basic Face information. newFace.id = i; newFace.normal = planes[i]; newFace.tangent = tangents[i]; // Make a working array of Point3Fs on this face. U32 pntCount = newFace.points.size(); Point3F *workPoints = new Point3F[ pntCount ]; for ( S32 j = 0; j < pntCount; j++ ) workPoints[j] = points[ newFace.points[j] ]; // Calculate the average point for calculating winding order. Point3F averagePnt = Point3F::Zero; for ( S32 j = 0; j < pntCount; j++ ) averagePnt += workPoints[j]; averagePnt /= pntCount; // Sort points in correct winding order. U32 *vertMap = new U32[pntCount]; MatrixF quadMat( true ); quadMat.setPosition( averagePnt ); quadMat.setColumn( 0, newFace.tangent ); quadMat.setColumn( 1, mCross( newFace.normal, newFace.tangent ) ); quadMat.setColumn( 2, newFace.normal ); quadMat.inverse(); // Transform working points into quad space // so we can work with them as 2D points. for ( S32 j = 0; j < pntCount; j++ ) quadMat.mulP( workPoints[j] ); MathUtils::sortQuadWindingOrder( true, workPoints, vertMap, pntCount ); // Save points in winding order. for ( S32 j = 0; j < pntCount; j++ ) newFace.winding.push_back( vertMap[j] ); // Calculate the area and centroid of the face. newFace.area = 0.0f; for ( S32 j = 0; j < pntCount; j++ ) { S32 k = ( j + 1 ) % pntCount; const Point3F &p0 = workPoints[ vertMap[j] ]; const Point3F &p1 = workPoints[ vertMap[k] ]; // Note that this calculation returns positive area for clockwise winding // and negative area for counterclockwise winding. newFace.area += p0.y * p1.x; newFace.area -= p0.x * p1.y; } //AssertFatal( newFace.area > 0.0f, "ConvexShape - face area was not positive." ); if ( newFace.area > 0.0f ) newFace.area /= 2.0f; F32 factor; F32 cx = 0.0f, cy = 0.0f; for ( S32 j = 0; j < pntCount; j++ ) { S32 k = ( j + 1 ) % pntCount; const Point3F &p0 = workPoints[ vertMap[j] ]; const Point3F &p1 = workPoints[ vertMap[k] ]; factor = p0.x * p1.y - p1.x * p0.y; cx += ( p0.x + p1.x ) * factor; cy += ( p0.y + p1.y ) * factor; } factor = 1.0f / ( newFace.area * 6.0f ); newFace.centroid.set( cx * factor, cy * factor, 0.0f ); quadMat.inverse(); quadMat.mulP( newFace.centroid ); delete [] workPoints; workPoints = NULL; // Make polygons / triangles for this face. const U32 polyCount = pntCount - 2; newFace.triangles.setSize( polyCount ); for ( S32 j = 0; j < polyCount; j++ ) { ConvexShape::Triangle &poly = newFace.triangles[j]; poly.p0 = vertMap[0]; if ( j == 0 ) { poly.p1 = vertMap[ 1 ]; poly.p2 = vertMap[ 2 ]; } else { poly.p1 = vertMap[ 1 + j ]; poly.p2 = vertMap[ 2 + j ]; } } delete [] vertMap; // Calculate texture coordinates for each point in this face. const Point3F binormal = mCross( newFace.normal, newFace.tangent ); PlaneF planey( newFace.centroid - 0.5f * binormal, binormal ); PlaneF planex( newFace.centroid - 0.5f * newFace.tangent, newFace.tangent ); newFace.texcoords.setSize( newFace.points.size() ); for ( S32 j = 0; j < newFace.points.size(); j++ ) { F32 x = planex.distToPlane( points[ newFace.points[ j ] ] ); F32 y = planey.distToPlane( points[ newFace.points[ j ] ] ); newFace.texcoords[j].set( -x, -y ); } // Data verification tests. #ifdef TORQUE_ENABLE_ASSERTS //S32 triCount = newFace.triangles.size(); //S32 edgeCount = newFace.edges.size(); //AssertFatal( triCount == edgeCount - 2, "ConvexShape - triangle/edge count do not match." ); /* for ( S32 j = 0; j < triCount; j++ ) { F32 area = MathUtils::mTriangleArea( points[ newFace.points[ newFace.triangles[j][0] ] ], points[ newFace.points[ newFace.triangles[j][1] ] ], points[ newFace.points[ newFace.triangles[j][2] ] ] ); AssertFatal( area > 0.0f, "ConvexShape - triangle winding bad." ); }*/ #endif // Done with this Face. faces.push_back( newFace ); } }
bool AtlasGeomChunkTracer::castLeafRay(const Point2I pos, const Point3F &start, const Point3F &end, const F32 &startT, const F32 &endT, RayInfo *info) { if(AtlasInstance::smRayCollisionDebugLevel == AtlasInstance::RayCollisionDebugToColTree) { const F32 invSize = 1.f / F32(BIT(mTreeDepth-1)); // This is a bit of a hack. But good for testing. // Do collision against the collision tree leaf bounding box and return the result... F32 t; Point3F n; Box3F box; box.minExtents.set(Point3F(F32(pos.x ) * invSize, F32(pos.y ) * invSize, getSquareMin(0, pos))); box.maxExtents.set(Point3F(F32(pos.x+1) * invSize, F32(pos.y+1) * invSize, getSquareMax(0, pos))); //Con::printf(" checking at xy = {%f, %f}->{%f, %f}, [%d, %d]", start.x, start.y, end.x, end.y, pos.x, pos.y); if(box.collideLine(start, end, &t, &n) && t >= startT && t <= endT) { info->t = t; info->normal = n; return true; } return false; } else if( AtlasInstance::smRayCollisionDebugLevel == AtlasInstance::RayCollisionDebugToMesh ) { bool haveHit = false; U32 currentIdx = 0; U32 numIdx = mChunk->mIndexCount; F32 bestT = F32_MAX; U32 bestTri = -1; Point2F bestBary; while( !haveHit && currentIdx < numIdx ) { const Point3F& a = mChunk->mVert[ mChunk->mIndex[ currentIdx ] ].point; const Point3F& b = mChunk->mVert[ mChunk->mIndex[ currentIdx + 1 ] ].point; const Point3F& c = mChunk->mVert[ mChunk->mIndex[ currentIdx + 2 ] ].point; F32 localT; Point2F localBary; // Do the cast, using our conveniently precalculated ray delta... if(castRayTriangle(mRayStart, mRayDelta, a,b,c, localT, localBary)) { if(localT < bestT) { // And it hit before anything else we've seen. bestTri = currentIdx; bestT = localT; bestBary = localBary; haveHit = true; } } currentIdx += 3; } // Fill in extra info for the hit. if(!haveHit) return false; // Calculate the normal, we skip that for the initial check. Point3F norm; // Hi norm! const Point3F &a = mChunk->mVert[mChunk->mIndex[bestTri+0]].point; const Point3F &b = mChunk->mVert[mChunk->mIndex[bestTri+1]].point; const Point3F &c = mChunk->mVert[mChunk->mIndex[bestTri+2]].point; const Point2F &aTC = mChunk->mVert[mChunk->mIndex[bestTri+0]].texCoord; const Point2F &bTC = mChunk->mVert[mChunk->mIndex[bestTri+1]].texCoord; const Point2F &cTC = mChunk->mVert[mChunk->mIndex[bestTri+2]].texCoord; // Store everything relevant into the info structure. info->t = bestT; const Point3F e0 = b-a; const Point3F e1 = c-a; info->normal = mCross(e1, e0); info->normal.normalize(); // Calculate and store the texture coords. const Point2F e0TC = bTC-aTC; const Point2F e1TC = cTC-aTC; info->texCoord = e0TC * bestBary.x + e1TC * bestBary.y + aTC; // Return true, we hit something! return true; } else { // Get the triangle list... U16 *triOffset = mChunk->mColIndicesBuffer + mChunk->mColIndicesOffsets[pos.x * BIT(mChunk->mColTreeDepth-1) + pos.y]; // Store best hit results... bool gotHit = false; F32 bestT = F32_MAX; U16 bestTri = -1, offset; Point2F bestBary; while((offset = *triOffset) != 0xFFFF) { // Advance to the next triangle.. triOffset++; // Get each triangle, and do a raycast against it. Point3F a,b,c; AssertFatal(offset < mChunk->mIndexCount, "AtlasGeomTracer2::castLeafRay - offset past end of index list."); a = mChunk->mVert[mChunk->mIndex[offset+0]].point; b = mChunk->mVert[mChunk->mIndex[offset+1]].point; c = mChunk->mVert[mChunk->mIndex[offset+2]].point; /*Con::printf(" o testing triangle %d ({%f,%f,%f},{%f,%f,%f},{%f,%f,%f})", offset, a.x, a.y, a.z, b.x, b.y, b.z, c.x, c.y, c.z); */ F32 localT; Point2F localBary; // Do the cast, using our conveniently precalculated ray delta... if(castRayTriangle(mRayStart, mRayDelta, a,b,c, localT, localBary)) { //Con::printf(" - hit triangle %d at t=%f", offset, localT); // The ray intersected, but we have to make sure we hit actually on // the line segment. (ie, a ray isn't a ray, Ray.) // BJGTODO - This should prevent some nasty edge cases, but we // seem to be calculating the wrong start and end T's. // So I've disabled this for now but it will cause // problems later! //if(localT < startT || localT > endT) // continue; // It really, really hit, wow! if(localT < bestT) { // And it hit before anything else we've seen. bestTri = offset; bestT = localT; bestBary = localBary; gotHit = true; } } else { //Con::printf(" - didn't hit triangle %d at t=%f", offset, localT); } } // Fill in extra info for the hit. if(!gotHit) return false; // Calculate the normal, we skip that for the initial check. Point3F norm; // Hi norm! const Point3F &a = mChunk->mVert[mChunk->mIndex[bestTri+0]].point; const Point3F &b = mChunk->mVert[mChunk->mIndex[bestTri+1]].point; const Point3F &c = mChunk->mVert[mChunk->mIndex[bestTri+2]].point; const Point2F &aTC = mChunk->mVert[mChunk->mIndex[bestTri+0]].texCoord; const Point2F &bTC = mChunk->mVert[mChunk->mIndex[bestTri+1]].texCoord; const Point2F &cTC = mChunk->mVert[mChunk->mIndex[bestTri+2]].texCoord; // Store everything relevant into the info structure. info->t = bestT; const Point3F e0 = b-a; const Point3F e1 = c-a; info->normal = mCross(e1, e0); info->normal.normalize(); // Calculate and store the texture coords. const Point2F e0TC = bTC-aTC; const Point2F e1TC = cTC-aTC; info->texCoord = e0TC * bestBary.x + e1TC * bestBary.y + aTC; // Return true, we hit something! return true; } }
void ConvexShape::_updateGeometry( bool updateCollision ) { mPlanes.clear(); for ( S32 i = 0; i < mSurfaces.size(); i++ ) mPlanes.push_back( PlaneF( mSurfaces[i].getPosition(), mSurfaces[i].getUpVector() ) ); Vector< Point3F > tangents; for ( S32 i = 0; i < mSurfaces.size(); i++ ) tangents.push_back( mSurfaces[i].getRightVector() ); mGeometry.generate( mPlanes, tangents ); AssertFatal( mGeometry.faces.size() <= mSurfaces.size(), "Got more faces than planes?" ); const Vector< ConvexShape::Face > &faceList = mGeometry.faces; const Vector< Point3F > &pointList = mGeometry.points; // Reset our surface center points. for ( S32 i = 0; i < faceList.size(); i++ ) mSurfaces[ faceList[i].id ].setPosition( faceList[i].centroid ); mPlanes.clear(); for ( S32 i = 0; i < mSurfaces.size(); i++ ) mPlanes.push_back( PlaneF( mSurfaces[i].getPosition(), mSurfaces[i].getUpVector() ) ); // Update bounding box. updateBounds( false ); mVertexBuffer = NULL; mPrimitiveBuffer = NULL; mVertCount = 0; mPrimCount = 0; if ( updateCollision ) _updateCollision(); // Server does not need to generate vertex/prim buffers. if ( isServerObject() ) return; if ( faceList.empty() ) return; // Get total vert and prim count. for ( S32 i = 0; i < faceList.size(); i++ ) { U32 count = faceList[i].triangles.size(); mPrimCount += count; mVertCount += count * 3; } // Allocate VB and copy in data. mVertexBuffer.set( GFX, mVertCount, GFXBufferTypeStatic ); VertexType *pVert = mVertexBuffer.lock(); for ( S32 i = 0; i < faceList.size(); i++ ) { const ConvexShape::Face &face = faceList[i]; const Vector< U32 > &facePntMap = face.points; const Vector< ConvexShape::Triangle > &triangles = face.triangles; const ColorI &faceColor = sgConvexFaceColors[ i % sgConvexFaceColorCount ]; const Point3F binormal = mCross( face.normal, face.tangent ); for ( S32 j = 0; j < triangles.size(); j++ ) { for ( S32 k = 0; k < 3; k++ ) { pVert->normal = face.normal; pVert->tangent = face.tangent; pVert->color = faceColor; pVert->point = pointList[ facePntMap[ triangles[j][k] ] ]; pVert->texCoord = face.texcoords[ triangles[j][k] ]; pVert++; } } } mVertexBuffer.unlock(); // Allocate PB mPrimitiveBuffer.set( GFX, mPrimCount * 3, mPrimCount, GFXBufferTypeStatic ); U16 *pIndex; mPrimitiveBuffer.lock( &pIndex ); for ( U16 i = 0; i < mPrimCount * 3; i++ ) { *pIndex = i; pIndex++; } mPrimitiveBuffer.unlock(); }
bool AITurretShape::_testTargetLineOfSight(Point3F& aimPoint, ShapeBase* target, Point3F& sightPoint) { Point3F targetCenter = target->getBoxCenter(); RayInfo ri; bool hit = false; target->disableCollision(); // First check for a clear line of sight to the target's center Point3F testPoint = targetCenter; hit = gServerContainer.castRay(aimPoint, testPoint, sAimTypeMask, &ri); if (hit) { // No clear line of sight to center, so try to the target's right. Players holding // a gun in their right hand will tend to stick their right shoulder out first if // they're peering around some cover to shoot, like a wall. Box3F targetBounds = target->getObjBox(); F32 radius = targetBounds.len_x() > targetBounds.len_y() ? targetBounds.len_x() : targetBounds.len_y(); radius *= 0.5; VectorF toTurret = aimPoint - targetCenter; toTurret.normalizeSafe(); VectorF toTurretRight = mCross(toTurret, Point3F::UnitZ); testPoint = targetCenter + toTurretRight * radius; hit = gServerContainer.castRay(aimPoint, testPoint, sAimTypeMask, &ri); if (hit) { // No clear line of sight to right, so try the target's left VectorF toTurretLeft = toTurretRight * -1.0f; testPoint = targetCenter + toTurretLeft * radius; hit = gServerContainer.castRay(aimPoint, testPoint, sAimTypeMask, &ri); } if (hit) { // No clear line of sight to left, so try the target's top testPoint = targetCenter; testPoint.z += targetBounds.len_z() * 0.5f; hit = gServerContainer.castRay(aimPoint, testPoint, sAimTypeMask, &ri); } if (hit) { // No clear line of sight to top, so try the target's bottom testPoint = targetCenter; testPoint.z -= targetBounds.len_z() * 0.5f; hit = gServerContainer.castRay(aimPoint, testPoint, sAimTypeMask, &ri); } } target->enableCollision(); if (!hit) { // Line of sight point is that last one we tested sightPoint = testPoint; } return !hit; }
void CubeReflector::updateFace( const ReflectParams ¶ms, U32 faceidx ) { GFXDEBUGEVENT_SCOPE( CubeReflector_UpdateFace, ColorI::WHITE ); // store current matrices GFXTransformSaver saver; // set projection to 90 degrees vertical and horizontal F32 left, right, top, bottom; MathUtils::makeFrustum( &left, &right, &top, &bottom, M_HALFPI_F, 1.0f, mDesc->nearDist ); GFX->setFrustum( left, right, bottom, top, mDesc->nearDist, mDesc->farDist ); // We don't use a special clipping projection, but still need to initialize // this for objects like SkyBox which will use it during a reflect pass. gClientSceneGraph->setNonClipProjection( GFX->getProjectionMatrix() ); // Standard view that will be overridden below. VectorF vLookatPt(0.0f, 0.0f, 0.0f), vUpVec(0.0f, 0.0f, 0.0f), vRight(0.0f, 0.0f, 0.0f); switch( faceidx ) { case 0 : // D3DCUBEMAP_FACE_POSITIVE_X: vLookatPt = VectorF( 1.0f, 0.0f, 0.0f ); vUpVec = VectorF( 0.0f, 1.0f, 0.0f ); break; case 1 : // D3DCUBEMAP_FACE_NEGATIVE_X: vLookatPt = VectorF( -1.0f, 0.0f, 0.0f ); vUpVec = VectorF( 0.0f, 1.0f, 0.0f ); break; case 2 : // D3DCUBEMAP_FACE_POSITIVE_Y: vLookatPt = VectorF( 0.0f, 1.0f, 0.0f ); vUpVec = VectorF( 0.0f, 0.0f,-1.0f ); break; case 3 : // D3DCUBEMAP_FACE_NEGATIVE_Y: vLookatPt = VectorF( 0.0f, -1.0f, 0.0f ); vUpVec = VectorF( 0.0f, 0.0f, 1.0f ); break; case 4 : // D3DCUBEMAP_FACE_POSITIVE_Z: vLookatPt = VectorF( 0.0f, 0.0f, 1.0f ); vUpVec = VectorF( 0.0f, 1.0f, 0.0f ); break; case 5: // D3DCUBEMAP_FACE_NEGATIVE_Z: vLookatPt = VectorF( 0.0f, 0.0f, -1.0f ); vUpVec = VectorF( 0.0f, 1.0f, 0.0f ); break; } // create camera matrix VectorF cross = mCross( vUpVec, vLookatPt ); cross.normalizeSafe(); MatrixF matView(true); matView.setColumn( 0, cross ); matView.setColumn( 1, vLookatPt ); matView.setColumn( 2, vUpVec ); matView.setPosition( mObject->getPosition() ); matView.inverse(); GFX->setWorldMatrix(matView); renderTarget->attachTexture( GFXTextureTarget::Color0, cubemap, faceidx ); GFX->setActiveRenderTarget( renderTarget ); GFX->clear( GFXClearStencil | GFXClearTarget | GFXClearZBuffer, gCanvasClearColor, 1.0f, 0 ); SceneRenderState reflectRenderState ( gClientSceneGraph, SPT_Reflect, SceneCameraState::fromGFX() ); reflectRenderState.getMaterialDelegate().bind( REFLECTMGR, &ReflectionManager::getReflectionMaterial ); reflectRenderState.setDiffuseCameraTransform( params.query->cameraMatrix ); reflectRenderState.disableAdvancedLightingBins(true); // render scene LIGHTMGR->registerGlobalLights( &reflectRenderState.getCullingFrustum(), false ); gClientSceneGraph->renderSceneNoLights( &reflectRenderState, mDesc->objectTypeMask ); LIGHTMGR->unregisterAllLights(); // Clean up. renderTarget->resolve(); }
void DecalRoad::_captureVerts() { PROFILE_SCOPE( DecalRoad_captureVerts ); //Con::warnf( "%s - captureVerts", isServerObject() ? "server" : "client" ); if ( isServerObject() ) { //Con::errorf( "DecalRoad::_captureVerts - called on the server side!" ); return; } if ( mEdges.size() == 0 ) return; // // Construct ClippedPolyList objects for each pair // of roadEdges. // Use them to capture Terrain verts. // SphereF sphere; RoadEdge *edge = NULL; RoadEdge *nextEdge = NULL; mTriangleCount = 0; mVertCount = 0; Vector<ClippedPolyList> clipperList; for ( U32 i = 0; i < mEdges.size() - 1; i++ ) { Box3F box; edge = &mEdges[i]; nextEdge = &mEdges[i+1]; box.minExtents = edge->p1; box.maxExtents = edge->p1; box.extend( edge->p0 ); box.extend( edge->p2 ); box.extend( nextEdge->p0 ); box.extend( nextEdge->p1 ); box.extend( nextEdge->p2 ); box.minExtents.z -= 5.0f; box.maxExtents.z += 5.0f; sphere.center = ( nextEdge->p1 + edge->p1 ) * 0.5f; sphere.radius = 100.0f; // NOTE: no idea how to calculate this ClippedPolyList clipper; clipper.mNormal.set(0.0f, 0.0f, 0.0f); VectorF n; PlaneF plane0, plane1; // Construct Back Plane n = edge->p2 - edge->p0; n.normalize(); n = mCross( n, edge->uvec ); plane0.set( edge->p0, n ); clipper.mPlaneList.push_back( plane0 ); // Construct Front Plane n = nextEdge->p2 - nextEdge->p0; n.normalize(); n = -mCross( edge->uvec, n ); plane1.set( nextEdge->p0, -n ); //clipper.mPlaneList.push_back( plane1 ); // Test if / where the planes intersect. bool discardLeft = false; bool discardRight = false; Point3F iPos; VectorF iDir; if ( plane0.intersect( plane1, iPos, iDir ) ) { Point2F iPos2F( iPos.x, iPos.y ); Point2F cPos2F( edge->p1.x, edge->p1.y ); Point2F rVec2F( edge->rvec.x, edge->rvec.y ); Point2F iVec2F = iPos2F - cPos2F; F32 iLen = iVec2F.len(); iVec2F.normalize(); if ( iLen < edge->width * 0.5f ) { F32 dot = mDot( rVec2F, iVec2F ); // The clipping planes intersected on the right side, // discard the right side clipping plane. if ( dot > 0.0f ) discardRight = true; // The clipping planes intersected on the left side, // discard the left side clipping plane. else discardLeft = true; } } // Left Plane if ( !discardLeft ) { n = ( nextEdge->p0 - edge->p0 ); n.normalize(); n = mCross( edge->uvec, n ); clipper.mPlaneList.push_back( PlaneF(edge->p0, n) ); } else { nextEdge->p0 = edge->p0; } // Right Plane if ( !discardRight ) { n = ( nextEdge->p2 - edge->p2 ); n.normalize(); n = -mCross( n, edge->uvec ); clipper.mPlaneList.push_back( PlaneF(edge->p2, -n) ); } else { nextEdge->p2 = edge->p2; } n = nextEdge->p2 - nextEdge->p0; n.normalize(); n = -mCross( edge->uvec, n ); plane1.set( nextEdge->p0, -n ); clipper.mPlaneList.push_back( plane1 ); // We have constructed the clipping planes, // now grab/clip the terrain geometry getContainer()->buildPolyList( PLC_Decal, box, TerrainObjectType, &clipper ); clipper.cullUnusedVerts(); clipper.triangulate(); clipper.generateNormals(); // If we got something, add it to the ClippedPolyList Vector if ( !clipper.isEmpty() && !( smDiscardAll && ( discardRight || discardLeft ) ) ) { clipperList.push_back( clipper ); mVertCount += clipper.mVertexList.size(); mTriangleCount += clipper.mPolyList.size(); } } // // Set the roadEdge height to be flush with terrain // This is not really necessary but makes the debug spline rendering better. // for ( U32 i = 0; i < mEdges.size() - 1; i++ ) { edge = &mEdges[i]; _getTerrainHeight( edge->p0.x, edge->p0.y, edge->p0.z ); _getTerrainHeight( edge->p2.x, edge->p2.y, edge->p2.z ); } // // Allocate the RoadBatch(s) // // If we captured no verts, then we can return here without // allocating any RoadBatches or the Vert/Index Buffers. // PreprenderImage will not allocate a render instance while // mBatches.size() is zero. U32 numClippers = clipperList.size(); if ( numClippers == 0 ) return; mBatches.clear(); // Allocate the VertexBuffer and PrimitiveBuffer mVB.set( GFX, mVertCount, GFXBufferTypeStatic ); mPB.set( GFX, mTriangleCount * 3, 0, GFXBufferTypeStatic ); // Lock the VertexBuffer GFXVertexPNTBT *vertPtr = mVB.lock(); if(!vertPtr) return; U32 vertIdx = 0; // // Fill the VertexBuffer and vertex data for the RoadBatches // Loop through the ClippedPolyList Vector // RoadBatch *batch = NULL; F32 texStart = 0.0f; F32 texEnd; for ( U32 i = 0; i < clipperList.size(); i++ ) { ClippedPolyList *clipper = &clipperList[i]; RoadEdge &edge = mEdges[i]; RoadEdge &nextEdge = mEdges[i+1]; VectorF segFvec = nextEdge.p1 - edge.p1; F32 segLen = segFvec.len(); segFvec.normalize(); F32 texLen = segLen / mTextureLength; texEnd = texStart + texLen; BiQuadToSqr quadToSquare( Point2F( edge.p0.x, edge.p0.y ), Point2F( edge.p2.x, edge.p2.y ), Point2F( nextEdge.p2.x, nextEdge.p2.y ), Point2F( nextEdge.p0.x, nextEdge.p0.y ) ); // if ( i % mSegmentsPerBatch == 0 ) { mBatches.increment(); batch = &mBatches.last(); batch->bounds.minExtents = clipper->mVertexList[0].point; batch->bounds.maxExtents = clipper->mVertexList[0].point; batch->startVert = vertIdx; } // Loop through each ClippedPolyList for ( U32 j = 0; j < clipper->mVertexList.size(); j++ ) { // Add each vert to the VertexBuffer Point3F pos = clipper->mVertexList[j].point; vertPtr[vertIdx].point = pos; vertPtr[vertIdx].normal = clipper->mNormalList[j]; Point2F uv = quadToSquare.transform( Point2F(pos.x,pos.y) ); vertPtr[vertIdx].texCoord.x = uv.x; vertPtr[vertIdx].texCoord.y = -(( texEnd - texStart ) * uv.y + texStart); vertPtr[vertIdx].tangent = mCross( segFvec, clipper->mNormalList[j] ); vertPtr[vertIdx].binormal = segFvec; vertIdx++; // Expand the RoadBatch bounds to contain this vertex batch->bounds.extend( pos ); } batch->endVert = vertIdx - 1; texStart = texEnd; } // Unlock the VertexBuffer, we are done filling it. mVB.unlock(); // Lock the PrimitiveBuffer U16 *idxBuff; mPB.lock(&idxBuff); U32 curIdx = 0; U16 vertOffset = 0; batch = NULL; S32 batchIdx = -1; // Fill the PrimitiveBuffer // Loop through each ClippedPolyList in the Vector for ( U32 i = 0; i < clipperList.size(); i++ ) { ClippedPolyList *clipper = &clipperList[i]; if ( i % mSegmentsPerBatch == 0 ) { batchIdx++; batch = &mBatches[batchIdx]; batch->startIndex = curIdx; } for ( U32 j = 0; j < clipper->mPolyList.size(); j++ ) { // Write indices for each Poly ClippedPolyList::Poly *poly = &clipper->mPolyList[j]; AssertFatal( poly->vertexCount == 3, "Got non-triangle poly!" ); idxBuff[curIdx] = clipper->mIndexList[poly->vertexStart] + vertOffset; curIdx++; idxBuff[curIdx] = clipper->mIndexList[poly->vertexStart + 1] + vertOffset; curIdx++; idxBuff[curIdx] = clipper->mIndexList[poly->vertexStart + 2] + vertOffset; curIdx++; } batch->endIndex = curIdx - 1; vertOffset += clipper->mVertexList.size(); } // Unlock the PrimitiveBuffer, we are done filling it. mPB.unlock(); // Generate the object/world bounds // Is the union of all batch bounding boxes. Box3F box; for ( U32 i = 0; i < mBatches.size(); i++ ) { const RoadBatch &batch = mBatches[i]; if ( i == 0 ) box = batch.bounds; else box.intersect( batch.bounds ); } Point3F pos = getPosition(); mWorldBox = box; resetObjectBox(); // Make sure we are in the correct bins given our world box. if( getSceneManager() != NULL ) getSceneManager()->notifyObjectDirty( this ); }
inline bool isInside(const Point3F& p, const Point3F& a, const Point3F& b, const VectorF& n) { VectorF v; mCross(n,b - a,&v); return mDot(v,p - a) < 0.0f; }