//========= Copyright Valve Corporation, All rights reserved. ============// // $Id:$ #ifndef RAYTRACE_H #define RAYTRACE_H #include #include #include #include #include #include #include #include // fast SSE-ONLY ray tracing module. Based upon various "real time ray tracing" // research. //#define DEBUG_RAYTRACE 1 class FourRays { public: FourVectors origin; FourVectors direction; inline void Check(void) const { // in order to be valid to trace as a group, all four rays must have the // same signs in all of their direction components #ifndef NDEBUG for (int c = 1; c < 4; c++) { Assert(direction.X(0) * direction.X(c) >= 0); Assert(direction.Y(0) * direction.Y(c) >= 0); Assert(direction.Z(0) * direction.Z(c) >= 0); } #endif } // returns direction sign mask for 4 rays. returns -1 if the rays can not be // traced as a bundle. int CalculateDirectionSignMask(void) const; }; /// The format a triangle is stored in for intersections. size of this structure /// is important. This structure can be in one of two forms. Before the ray /// tracing environment is set up, the ProjectedEdgeEquations hold the /// coordinates of the 3 vertices, for facilitating bounding box checks needed /// while building the tree. afterwards, they are changed into the projected ege /// equations for intersection purposes. enum triangleflags { FCACHETRI_TRANSPARENT = 0x01, FCACHETRI_NEGATIVE_NORMAL = 0x02, }; struct TriIntersectData_t { // this structure is 16longs=64 bytes for cache line packing. float m_flNx, m_flNy, m_flNz; // plane equation float m_flD; int32 m_nTriangleID; // id of the triangle. float m_ProjectedEdgeEquations[6]; // A,B,C for each edge equation. a // point is inside the triangle if // a*c1+b*c2+c is negative for all 3 // edges. uint8 m_nCoordSelect0, m_nCoordSelect1; // the triangle is projected onto a // 2d plane for edge testing. These // are the indices (0..2) of the // coordinates preserved in the // projection uint8 m_nFlags; // triangle flags uint8 m_unused0; // no longer used }; struct TriGeometryData_t { int32 m_nTriangleID; // id of the triangle. float m_VertexCoordData[9]; // can't use a vector in a union uint8 m_nFlags; // triangle flags signed char m_nTmpData0; // used by kd-tree builder signed char m_nTmpData1; // used by kd-tree builder // accessors to get around union annoyance FORCEINLINE Vector &Vertex(int idx) { return *(reinterpret_cast(m_VertexCoordData + 3 * idx)); } }; struct CacheOptimizedTriangle { union { TriIntersectData_t m_IntersectData; TriGeometryData_t m_GeometryData; } m_Data; // accessors to get around union annoyance FORCEINLINE Vector &Vertex(int idx) { return *(reinterpret_cast( m_Data.m_GeometryData.m_VertexCoordData + 3 * idx)); } FORCEINLINE const Vector &Vertex(int idx) const { return *(reinterpret_cast( m_Data.m_GeometryData.m_VertexCoordData + 3 * idx)); } void ChangeIntoIntersectionFormat( void); // change information storage format for // computing intersections. int ClassifyAgainstAxisSplit(int split_plane, float split_value); // PLANECHECK_xxx below }; #define PLANECHECK_POSITIVE 1 #define PLANECHECK_NEGATIVE -1 #define PLANECHECK_STRADDLING 0 #define KDNODE_STATE_XSPLIT 0 // this node is an x split #define KDNODE_STATE_YSPLIT 1 // this node is a ysplit #define KDNODE_STATE_ZSPLIT 2 // this node is a zsplit #define KDNODE_STATE_LEAF 3 // this node is a leaf struct CacheOptimizedKDNode { // this is the cache intensive data structure. "Tricks" are used to fit it // into 8 bytes: // // A) the right child is always stored after the left child, which means we // only need one pointer B) The type of node (KDNODE_xx) is stored in the // lower 2 bits of the pointer. C) for leaf nodes, we store the number of // triangles in the leaf in the same place as the floating // point splitting parameter is stored in a non-leaf node int32 Children; // child idx, or'ed with flags above float SplittingPlaneValue; // for non-leaf nodes, the nodes on the // "high" side of the splitting plane // are on the right #ifdef DEBUG_RAYTRACE Vector vecMins; Vector vecMaxs; #endif inline int NodeType(void) const { return Children & 3; } inline int32 TriangleIndexStart(void) const { assert(NodeType() == KDNODE_STATE_LEAF); return Children >> 2; } inline int LeftChild(void) const { assert(NodeType() != KDNODE_STATE_LEAF); return Children >> 2; } inline int RightChild(void) const { return LeftChild() + 1; } inline int NumberOfTrianglesInLeaf(void) const { assert(NodeType() == KDNODE_STATE_LEAF); return *((int32 *)&SplittingPlaneValue); } inline void SetNumberOfTrianglesInLeafNode(int n) { *((int32 *)&SplittingPlaneValue) = n; } protected: }; struct RayTracingSingleResult { Vector surface_normal; // surface normal at intersection int32 HitID; // -1=no hit. otherwise, triangle index float HitDistance; // distance to intersection float ray_length; // leng of initial ray }; struct RayTracingResult { FourVectors surface_normal; // surface normal at intersection ALIGN16 int32 HitIds[4] ALIGN16_POST; // -1=no hit. otherwise, triangle index fltx4 HitDistance; // distance to intersection }; class RayTraceLight { public: FourVectors Position; FourVectors Intensity; }; #define RTE_FLAGS_FAST_TREE_GENERATION 1 #define RTE_FLAGS_DONT_STORE_TRIANGLE_COLORS 2 // saves memory if not needed #define RTE_FLAGS_DONT_STORE_TRIANGLE_MATERIALS 4 enum RayTraceLightingMode_t { DIRECT_LIGHTING, // just dot product lighting DIRECT_LIGHTING_WITH_SHADOWS, // with shadows GLOBAL_LIGHTING // global light w/ shadows }; class RayStream { friend class RayTracingEnvironment; RayTracingSingleResult *PendingStreamOutputs[8][4]; int n_in_stream[8]; FourRays PendingRays[8]; public: RayStream(void) { memset(n_in_stream, 0, sizeof(n_in_stream)); } }; // When transparent triangles are in the list, the caller can provide a callback // that will get called at each triangle allowing the callback to stop // processing if desired. UNDONE: This is not currently SIMD - it really only // supports single rays Also for efficiency FourRays really needs some kind of // active mask for the cases where rays get unbundled class ITransparentTriangleCallback { public: virtual bool VisitTriangle_ShouldContinue( const TriIntersectData_t &triangle, const FourRays &rays, fltx4 *hitMask, fltx4 *b0, fltx4 *b1, fltx4 *b2, int32 hitID) = 0; }; class RayTracingEnvironment { public: uint32 Flags; // RTE_FLAGS_xxx above Vector m_MinBound; Vector m_MaxBound; FourVectors BackgroundColor; //< color where no intersection CUtlVector OptimizedKDTree; //< the packed kdtree. root is 0 CUtlBlockVector OptimizedTriangleList; //< the packed triangles CUtlVector TriangleIndexList; //< the list of triangle indices. CUtlVector LightList; //< the list of lights CUtlVector TriangleColors; //< color of tries CUtlVector TriangleMaterials; //< material index of tries public: RayTracingEnvironment() : OptimizedTriangleList(1024) { BackgroundColor.DuplicateVector(Vector(1, 0, 0)); // red Flags = 0; } // call AddTriangle to set up the world void AddTriangle(int32 id, const Vector &v1, const Vector &v2, const Vector &v3, const Vector &color); // Adds a triangle with flags & material void AddTriangle(int32 id, const Vector &v1, const Vector &v2, const Vector &v3, const Vector &color, uint16 flags, int32 materialIndex); void AddQuad(int32 id, const Vector &v1, const Vector &v2, const Vector &v3, const Vector &v4, // specify vertices in cw or ccw order const Vector &color); // for ease of testing. void AddAxisAlignedRectangularSolid(int id, Vector mincoord, Vector Maxcoord, const Vector &color); // SetupAccelerationStructure to prepare for tracing void SetupAccelerationStructure(void); // lowest level intersection routine - fire 4 rays through the scene. all 4 // rays must pass the Check() function, and t extents must be initialized. // skipid can be set to exclude a particular id (such as the origin // surface). This function finds the closest intersection. void Trace4Rays(const FourRays &rays, fltx4 TMin, fltx4 TMax, int DirectionSignMask, RayTracingResult *rslt_out, int32 skip_id = -1, ITransparentTriangleCallback *pCallback = NULL); // higher level intersection routine that handles computing the mask and // handling rays which do not match in direciton sign void Trace4Rays(const FourRays &rays, fltx4 TMin, fltx4 TMax, RayTracingResult *rslt_out, int32 skip_id = -1, ITransparentTriangleCallback *pCallback = NULL); // compute virtual light sources to model inter-reflection void ComputeVirtualLightSources(void); // high level interface - pass viewing parameters, rendering flags, and a // destination frame buffer, and get a ray traced scene in 32-bit rgba // format void RenderScene(int width, int height, // width and height of desired rendering int stride, // actual width in pixels of target buffer uint32 *output_buffer, // pointer to destination Vector CameraOrigin, // eye position Vector ULCorner, // word space coordinates of upper left // monitor corner Vector URCorner, // top right corner Vector LLCorner, // lower left Vector LRCorner, // lower right RayTraceLightingMode_t lightmode = DIRECT_LIGHTING); /// raytracing stream - lets you trace an array of rays by feeding them to /// this function. results will not be returned until FinishStream is /// called. This function handles sorting the rays by direction, tracing /// them 4 at a time, and de-interleaving the results. void AddToRayStream(RayStream &s, Vector const &start, Vector const &end, RayTracingSingleResult *rslt_out); inline void FlushStreamEntry(RayStream &s, int msk); /// call this when you are done. handles all cleanup. After this is called, /// all rslt ptrs previously passed to AddToRaySteam will have been filled /// in. void FinishRayStream(RayStream &s); int MakeLeafNode(int first_tri, int last_tri); float CalculateCostsOfSplit(int split_plane, int32 const *tri_list, int ntris, Vector MinBound, Vector MaxBound, float &split_value, int &nleft, int &nright, int &nboth); void RefineNode(int node_number, int32 const *tri_list, int ntris, Vector MinBound, Vector MaxBound, int depth); void CalculateTriangleListBounds(int32 const *tris, int ntris, Vector &minout, Vector &maxout); void AddInfinitePointLight(Vector position, // light center Vector intensity); // rgb amount // use the global variables set by LoadBSPFile to populated the // RayTracingEnvironment with faces. void InitializeFromLoadedBSP(void); void AddBSPFace(int id, dface_t const &face); // MakeRoomForTriangles - a hint telling it how many triangles we are going // to add so that the utl vectors used can be pre-allocated void MakeRoomForTriangles(int ntriangles); const CacheOptimizedTriangle &GetTriangle(int32 triID) { return OptimizedTriangleList[triID]; } int32 GetTriangleMaterial(int32 triID) { return TriangleMaterials[triID]; } const Vector &GetTriangleColor(int triID) { return TriangleColors[triID]; } }; #endif