/* ** Command & Conquer Generals Zero Hour(tm) ** Copyright 2025 Electronic Arts Inc. ** ** This program is free software: you can redistribute it and/or modify ** it under the terms of the GNU General Public License as published by ** the Free Software Foundation, either version 3 of the License, or ** (at your option) any later version. ** ** This program is distributed in the hope that it will be useful, ** but WITHOUT ANY WARRANTY; without even the implied warranty of ** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ** GNU General Public License for more details. ** ** You should have received a copy of the GNU General Public License ** along with this program. If not, see . */ /*********************************************************************************************** *** C O N F I D E N T I A L --- W E S T W O O D S T U D I O S *** *********************************************************************************************** * * * Project Name : ww3d * * * * $Archive:: /Commando/Code/ww3d2/sortingrenderer.cpp $* * * * Original Author:: Greg Hjelstrom * * * * Author : Kenny Mitchell * * * * $Modtime:: 06/27/02 1:27p $* * * * $Revision:: 2 $* * * * 06/26/02 KM Matrix name change to avoid MAX conflicts * * 06/27/02 KM Changes to max texture stage caps * *---------------------------------------------------------------------------------------------* * Functions: * * - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ #include "sortingrenderer.h" #include "dx8vertexbuffer.h" #include "dx8indexbuffer.h" #include "dx8wrapper.h" #include "vertmaterial.h" #include "texture.h" #include "d3d8.h" #include "D3dx8math.h" #include "statistics.h" #include #include #ifdef _INTERNAL // for occasional debugging... // #pragma optimize("", off) // #pragma MESSAGE("************************************** WARNING, optimization disabled for debugging purposes") #endif bool SortingRendererClass::_EnableTriangleDraw=true; static unsigned DEFAULT_SORTING_POLY_COUNT = 16384; // (count * 3) must be less than 65536 static unsigned DEFAULT_SORTING_VERTEX_COUNT = 32768; // count must be less than 65536 void SortingRendererClass::SetMinVertexBufferSize( unsigned val ) { DEFAULT_SORTING_VERTEX_COUNT = val; DEFAULT_SORTING_POLY_COUNT = val/2; //typically have 2:1 vertex:triangle ratio. } struct ShortVectorIStruct { unsigned short i; unsigned short j; unsigned short k; }; struct TempIndexStruct { ShortVectorIStruct tri; unsigned short idx; float z; }; bool operator <(const TempIndexStruct &l, const TempIndexStruct &r) { return l.z < r.z; } bool operator <=(const TempIndexStruct &l, const TempIndexStruct &r) { return l.z <= r.z; } bool operator >(const TempIndexStruct &l, const TempIndexStruct &r) { return l.z > r.z; } bool operator >=(const TempIndexStruct &l, const TempIndexStruct &r) { return l.z >= r.z; } bool operator ==(const TempIndexStruct &l, const TempIndexStruct &r) { return l.z == r.z; } // ---------------------------------------------------------------------------- static void InsertionSort(TempIndexStruct *begin, TempIndexStruct *end) { for (TempIndexStruct *iter = begin + 1; iter < end; ++iter) { TempIndexStruct val = iter[0]; TempIndexStruct *insert = iter; while (insert != begin && insert[-1] > val) { insert[0] = insert[-1]; insert -= 1; } insert[0] = val; } } // ---------------------------------------------------------------------------- static void Sort(TempIndexStruct *begin, TempIndexStruct *end) { const int diff = end - begin; if (diff <= 16) { // Insertion sort has less overhead for small arrays InsertionSort(begin, end); } else { // Choose the median of begin, mid, and (end - 1) as the partitioning element. // Rearrange so that *(begin + 1) <= *begin <= *(end - 1). These will be guard // elements. TempIndexStruct *mid = begin + diff/2; std::swap(mid[0], begin[1]); if (begin[1] > end[-1]) { std::swap(begin[1], end[-1]); } if (begin[0] > end[-1]) { std::swap(begin[0], end[-1]); } // end[-1] has the largest element if (begin[1] > begin[0]) { std::swap(begin[1], begin[0]); } // begin[0] has the middle element and begin[1] has the smallest element // *begin is now the partitioning element TempIndexStruct *begin1 = begin + 1; // TODO: Temp fix until I find out who is passing me NaN TempIndexStruct *end1 = end - 1; // TODO: Temp fix until I find out who is passing me NaN TempIndexStruct *left = begin + 1; TempIndexStruct *right = end - 1; for (;;) { #if 0 // TODO: Temp fix until I find out who is passing me NaN. do ++left; while (left[0] < begin[0]); // Scan up to find element >= than partition do --right; while (right[0] > begin[0]); // Scan down to find element <= than partition #else do ++left; while (left < end1 && left[0] < begin[0]); // Scan up to find element >= than partition do --right; while (right > begin1 && right[0] > begin[0]); // Scan down to find element <= than partition #endif if (right < left) break; // Pointers crossed. Partitioning completed. std::swap(left[0], right[0]); // Exchange elements. } std::swap(begin[0], right[0]); // Insert partition element // Sort the smaller subarray first then the larger if (right - begin > end - (right + 1)) { Sort(right + 1, end); Sort(begin, right); } else { Sort(begin, right); Sort(right + 1, end); } } } // ---------------------------------------------------------------------------- class SortingNodeStruct : public DLNodeClass { W3DMPO_GLUE(SortingNodeStruct) public: RenderStateStruct sorting_state; SphereClass bounding_sphere; Vector3 transformed_center; unsigned short start_index; // First index used in the ib unsigned short polygon_count; // Polygon count to process (3 indices = one polygon) unsigned short min_vertex_index; // First index used in the vb unsigned short vertex_count; // Number of vertices used in vb }; static DLListClass sorted_list; static DLListClass clean_list; static unsigned total_sorting_vertices; static SortingNodeStruct* Get_Sorting_Struct() { SortingNodeStruct* state=clean_list.Head(); if (state) { state->Remove(); return state; } state=W3DNEW SortingNodeStruct(); return state; } // ---------------------------------------------------------------------------- // // Temporary arrays for the sorting system // // ---------------------------------------------------------------------------- static TempIndexStruct* temp_index_array; static unsigned temp_index_array_count; static TempIndexStruct* Get_Temp_Index_Array(unsigned count) { if (count < DEFAULT_SORTING_POLY_COUNT) count = DEFAULT_SORTING_POLY_COUNT; if (count>temp_index_array_count) { delete[] temp_index_array; temp_index_array=W3DNEWARRAY TempIndexStruct[count]; temp_index_array_count=count; } return temp_index_array; } // ---------------------------------------------------------------------------- // // Insert triangles to the sorting system. // // ---------------------------------------------------------------------------- void SortingRendererClass::Insert_Triangles( const SphereClass& bounding_sphere, unsigned short start_index, unsigned short polygon_count, unsigned short min_vertex_index, unsigned short vertex_count) { if (!WW3D::Is_Sorting_Enabled()) { DX8Wrapper::Draw_Triangles(start_index,polygon_count,min_vertex_index,vertex_count); return; } SNAPSHOT_SAY(("SortingRenderer::Insert(start_i: %d, polygons : %d, min_vi: %d, vertex_count: %d)\n", start_index,polygon_count,min_vertex_index,vertex_count)); DX8_RECORD_SORTING_RENDER(polygon_count,vertex_count); SortingNodeStruct* state=Get_Sorting_Struct(); DX8Wrapper::Get_Render_State(state->sorting_state); WWASSERT( ((state->sorting_state.index_buffer_type==BUFFER_TYPE_SORTING || state->sorting_state.index_buffer_type==BUFFER_TYPE_DYNAMIC_SORTING) && (state->sorting_state.vertex_buffer_types[0]==BUFFER_TYPE_SORTING || state->sorting_state.vertex_buffer_types[0]==BUFFER_TYPE_DYNAMIC_SORTING))); state->bounding_sphere=bounding_sphere; state->start_index=start_index; state->polygon_count=polygon_count; state->min_vertex_index=min_vertex_index; state->vertex_count=vertex_count; SortingVertexBufferClass* vertex_buffer=static_cast(state->sorting_state.vertex_buffers[0]); WWASSERT(vertex_buffer); WWASSERT(state->vertex_count<=vertex_buffer->Get_Vertex_Count()); D3DXMATRIX mtx=(D3DXMATRIX&)state->sorting_state.world*(D3DXMATRIX&)state->sorting_state.view; D3DXVECTOR3 vec=(D3DXVECTOR3&)state->bounding_sphere.Center; D3DXVECTOR4 transformed_vec; D3DXVec3Transform( &transformed_vec, &vec, &mtx); state->transformed_center=Vector3(transformed_vec[0],transformed_vec[1],transformed_vec[2]); /// @todo lorenzen sez use a bucket sort here... and stop copying so much data so many times SortingNodeStruct* node=sorted_list.Head(); while (node) { if (state->transformed_center.Z>node->transformed_center.Z) { if (sorted_list.Head()==sorted_list.Tail()) sorted_list.Add_Head(state); else state->Insert_Before(node); break; } node=node->Succ(); } if (!node) sorted_list.Add_Tail(state); #ifdef WWDEBUG unsigned short* indices=NULL; SortingIndexBufferClass* index_buffer=static_cast(state->sorting_state.index_buffer); WWASSERT(index_buffer); indices=index_buffer->index_buffer; WWASSERT(indices); indices+=state->start_index; indices+=state->sorting_state.iba_offset; for (int i=0;ipolygon_count;++i) { unsigned short idx1=indices[i*3]-state->min_vertex_index; unsigned short idx2=indices[i*3+1]-state->min_vertex_index; unsigned short idx3=indices[i*3+2]-state->min_vertex_index; WWASSERT(idx1vertex_count); WWASSERT(idx2vertex_count); WWASSERT(idx3vertex_count); } #endif // WWDEBUG } // ---------------------------------------------------------------------------- // // Insert triangles to the sorting system, with no bounding information. // // ---------------------------------------------------------------------------- void SortingRendererClass::Insert_Triangles( unsigned short start_index, unsigned short polygon_count, unsigned short min_vertex_index, unsigned short vertex_count) { SphereClass sphere(Vector3(0.0f,0.0f,0.0f),0.0f); Insert_Triangles(sphere,start_index,polygon_count,min_vertex_index,vertex_count); } // ---------------------------------------------------------------------------- // // Flush all sorting polygons. // // ---------------------------------------------------------------------------- void Release_Refs(SortingNodeStruct* state) { int i; for (i=0;isorting_state.vertex_buffers[i]); } REF_PTR_RELEASE(state->sorting_state.index_buffer); REF_PTR_RELEASE(state->sorting_state.material); for (i=0;iGet_Max_Textures_Per_Pass();++i) { REF_PTR_RELEASE(state->sorting_state.Textures[i]); } } static unsigned overlapping_node_count; static unsigned overlapping_polygon_count; static unsigned overlapping_vertex_count; static const unsigned MAX_OVERLAPPING_NODES=4096; static SortingNodeStruct* overlapping_nodes[MAX_OVERLAPPING_NODES]; // ---------------------------------------------------------------------------- void SortingRendererClass::Insert_To_Sorting_Pool(SortingNodeStruct* state) { if (overlapping_node_count>=MAX_OVERLAPPING_NODES) { Release_Refs(state); WWASSERT(0); return; } overlapping_nodes[overlapping_node_count]=state; overlapping_vertex_count+=state->vertex_count; overlapping_polygon_count+=state->polygon_count; overlapping_node_count++; } // ---------------------------------------------------------------------------- //static unsigned prevLight = 0xffffffff; static void Apply_Render_State(RenderStateStruct& render_state) { DX8Wrapper::Set_Shader(render_state.shader); DX8Wrapper::Set_Material(render_state.material); for (int i=0;iGet_Max_Textures_Per_Pass();++i) { DX8Wrapper::Set_Texture(i,render_state.Textures[i]); } DX8Wrapper::_Set_DX8_Transform(D3DTS_WORLD,render_state.world); DX8Wrapper::_Set_DX8_Transform(D3DTS_VIEW,render_state.view); if (!render_state.material->Get_Lighting()) return; //prevLight = render_state.lightsHash; if (render_state.LightEnable[0]) { DX8Wrapper::Set_DX8_Light(0,&render_state.Lights[0]); if (render_state.LightEnable[1]) { DX8Wrapper::Set_DX8_Light(1,&render_state.Lights[1]); if (render_state.LightEnable[2]) { DX8Wrapper::Set_DX8_Light(2,&render_state.Lights[2]); if (render_state.LightEnable[3]) DX8Wrapper::Set_DX8_Light(3,&render_state.Lights[3]); else DX8Wrapper::Set_DX8_Light(3,NULL); } else DX8Wrapper::Set_DX8_Light(2,NULL); } else DX8Wrapper::Set_DX8_Light(1,NULL); } else DX8Wrapper::Set_DX8_Light(0,NULL); } // ---------------------------------------------------------------------------- void SortingRendererClass::Flush_Sorting_Pool() { if (!overlapping_node_count) return; SNAPSHOT_SAY(("SortingSystem - Flush \n")); // Fill dynamic index buffer with sorting index buffer vertices TempIndexStruct* tis=Get_Temp_Index_Array(overlapping_polygon_count); unsigned vertexAllocCount = overlapping_vertex_count; if (DynamicVBAccessClass::Get_Default_Vertex_Count() < DEFAULT_SORTING_VERTEX_COUNT) vertexAllocCount = DEFAULT_SORTING_VERTEX_COUNT; //make sure that we force the DX8 dynamic vertex buffer to maximum size if (overlapping_vertex_count > vertexAllocCount) vertexAllocCount = overlapping_vertex_count; WWASSERT(DEFAULT_SORTING_VERTEX_COUNT == 1 || vertexAllocCount <= DEFAULT_SORTING_VERTEX_COUNT); DynamicVBAccessClass dyn_vb_access(BUFFER_TYPE_DYNAMIC_DX8,dynamic_fvf_type,vertexAllocCount/*overlapping_vertex_count*/); { DynamicVBAccessClass::WriteLockClass lock(&dyn_vb_access); VertexFormatXYZNDUV2* dest_verts=(VertexFormatXYZNDUV2 *)lock.Get_Formatted_Vertex_Array(); unsigned polygon_array_offset=0; unsigned vertex_array_offset=0; for (unsigned node_id=0;node_id(state->sorting_state.vertex_buffers[0]); WWASSERT(vertex_buffer); src_verts=vertex_buffer->VertexBuffer; WWASSERT(src_verts); src_verts+=state->sorting_state.vba_offset; src_verts+=state->sorting_state.index_base_offset; src_verts+=state->min_vertex_index; // If you have a crash in here and "dest_verts" points to illegal memory area, // it is because D3D is in illegal state, and the only known cure is rebooting. // This illegal state is usually caused by Quake3-engine powered games such as MOHAA. memcpy(dest_verts, src_verts, sizeof(VertexFormatXYZNDUV2)*state->vertex_count); dest_verts += state->vertex_count; D3DXMATRIX d3d_mtx=(D3DXMATRIX&)state->sorting_state.world*(D3DXMATRIX&)state->sorting_state.view; const Matrix4x4& mtx=(const Matrix4x4&)d3d_mtx; unsigned short* indices=NULL; SortingIndexBufferClass* index_buffer=static_cast(state->sorting_state.index_buffer); WWASSERT(index_buffer); indices=index_buffer->index_buffer; WWASSERT(indices); indices+=state->start_index; indices+=state->sorting_state.iba_offset; if (mtx[0][2] == 0.0f && mtx[1][2] == 0.0f && mtx[3][2] == 0.0f && mtx[2][2] == 1.0f) { // The common case for particle systems. for (int i=0;ipolygon_count;++i) { unsigned short idx1=indices[i*3]-state->min_vertex_index; unsigned short idx2=indices[i*3+1]-state->min_vertex_index; unsigned short idx3=indices[i*3+2]-state->min_vertex_index; WWASSERT(idx1vertex_count); WWASSERT(idx2vertex_count); WWASSERT(idx3vertex_count); const VertexFormatXYZNDUV2 *v1 = src_verts + idx1; const VertexFormatXYZNDUV2 *v2 = src_verts + idx2; const VertexFormatXYZNDUV2 *v3 = src_verts + idx3; unsigned array_index=i+polygon_array_offset; WWASSERT(array_indextri.i = idx1 + vertex_array_offset; tis_ptr->tri.j = idx2 + vertex_array_offset; tis_ptr->tri.k = idx3 + vertex_array_offset; tis_ptr->idx = node_id; tis_ptr->z = (v1->z + v2->z + v3->z)/3.0f; DEBUG_ASSERTCRASH((! _isnan(tis_ptr->z) && _finite(tis_ptr->z)), ("Triangle has invalid center")); } } else { for (int i=0;ipolygon_count;++i) { unsigned short idx1=indices[i*3]-state->min_vertex_index; unsigned short idx2=indices[i*3+1]-state->min_vertex_index; unsigned short idx3=indices[i*3+2]-state->min_vertex_index; WWASSERT(idx1vertex_count); WWASSERT(idx2vertex_count); WWASSERT(idx3vertex_count); const VertexFormatXYZNDUV2 *v1 = src_verts + idx1; const VertexFormatXYZNDUV2 *v2 = src_verts + idx2; const VertexFormatXYZNDUV2 *v3 = src_verts + idx3; unsigned array_index=i+polygon_array_offset; WWASSERT(array_indextri.i = idx1 + vertex_array_offset; tis_ptr->tri.j = idx2 + vertex_array_offset; tis_ptr->tri.k = idx3 + vertex_array_offset; tis_ptr->idx = node_id; tis_ptr->z = (mtx[0][2]*(v1->x + v2->x + v3->x) + mtx[1][2]*(v1->y + v2->y + v3->y) + mtx[2][2]*(v1->z + v2->z + v3->z))/3.0f + mtx[3][2]; DEBUG_ASSERTCRASH((! _isnan(tis_ptr->z) && _finite(tis_ptr->z)), ("Triangle has invalid center")); } } state->min_vertex_index=vertex_array_offset; polygon_array_offset+=state->polygon_count; vertex_array_offset+=state->vertex_count; } } Sort(tis, tis + overlapping_polygon_count); /* ///@todo: Add code to break up rendering into multiple index buffer fills to allow more than 65536/3 triangles. -MW int total_overlapping_polygon_count = overlapping_polygon_count; while ( > 0) { if ((total_overlapping_polygon_count*3) > 65535) { //overflowed the index buffer, must break into multiple batches overlapping_polygon_count = 65535/3; } else overlapping_polygon_count = total_overlapping_polygon_count; //insert rendering code here!! total_overlapping_polygon_count -= overlapping_polygon_count; } */ unsigned polygonAllocCount = overlapping_polygon_count; if ((unsigned)(DynamicIBAccessClass::Get_Default_Index_Count()/3) < DEFAULT_SORTING_POLY_COUNT) polygonAllocCount = DEFAULT_SORTING_POLY_COUNT; //make sure that we force the DX8 index buffer to maximum size if (overlapping_polygon_count > polygonAllocCount) polygonAllocCount = overlapping_polygon_count; WWASSERT(DEFAULT_SORTING_POLY_COUNT <= 1 || polygonAllocCount <= DEFAULT_SORTING_POLY_COUNT); DynamicIBAccessClass dyn_ib_access(BUFFER_TYPE_DYNAMIC_DX8,polygonAllocCount*3); { DynamicIBAccessClass::WriteLockClass lock(&dyn_ib_access); ShortVectorIStruct* sorted_polygon_index_array=(ShortVectorIStruct*)lock.Get_Index_Array(); try { for (unsigned a=0;asorting_state); DX8Wrapper::Draw_Triangles( start_index*3, count_to_render, state->min_vertex_index, state->vertex_count); count_to_render=0; start_index=i; node_id=tis[i].idx; } count_to_render++; //keep track of number of polygons of same kind } // Render any remaining polygons... if (count_to_render) { SortingNodeStruct* state=overlapping_nodes[node_id]; Apply_Render_State(state->sorting_state); DX8Wrapper::Draw_Triangles( start_index*3, count_to_render, state->min_vertex_index, state->vertex_count); } // Release all references and return nodes back to the clean list for the frame... for (node_id=0;node_idRemove(); if ((state->sorting_state.index_buffer_type==BUFFER_TYPE_SORTING || state->sorting_state.index_buffer_type==BUFFER_TYPE_DYNAMIC_SORTING) && (state->sorting_state.vertex_buffer_types[0]==BUFFER_TYPE_SORTING || state->sorting_state.vertex_buffer_types[0]==BUFFER_TYPE_DYNAMIC_SORTING)) { Insert_To_Sorting_Pool(state); } else { DX8Wrapper::Set_Render_State(state->sorting_state); DX8Wrapper::Draw_Triangles(state->start_index,state->polygon_count,state->min_vertex_index,state->vertex_count); DX8Wrapper::Release_Render_State(); Release_Refs(state); clean_list.Add_Head(state); } } bool old_enable=DX8Wrapper::_Is_Triangle_Draw_Enabled(); DX8Wrapper::_Enable_Triangle_Draw(_EnableTriangleDraw); Flush_Sorting_Pool(); DX8Wrapper::_Enable_Triangle_Draw(old_enable); DX8Wrapper::Set_Index_Buffer(0,0); DX8Wrapper::Set_Vertex_Buffer(0); total_sorting_vertices=0; DynamicIBAccessClass::_Reset(false); DynamicVBAccessClass::_Reset(false); DX8Wrapper::Set_Transform(D3DTS_VIEW,old_view); DX8Wrapper::Set_Transform(D3DTS_WORLD,old_world); } // ---------------------------------------------------------------------------- void SortingRendererClass::Deinit() { SortingNodeStruct *head = NULL; // // Flush the sorted list // while ((head = sorted_list.Head ()) != NULL) { sorted_list.Remove_Head (); delete head; } // // Flush the clean list // while ((head = clean_list.Head ()) != NULL) { clean_list.Remove_Head (); delete head; } delete[] temp_index_array; temp_index_array=NULL; temp_index_array_count=0; } // ---------------------------------------------------------------------------- // // Insert a VolumeParticle triangle into the sorting system. // // ---------------------------------------------------------------------------- void SortingRendererClass::Insert_VolumeParticle( const SphereClass& bounding_sphere, unsigned short start_index, unsigned short polygon_count, unsigned short min_vertex_index, unsigned short vertex_count, unsigned short layerCount) { if (!WW3D::Is_Sorting_Enabled()) { DX8Wrapper::Draw_Triangles(start_index,polygon_count,min_vertex_index,vertex_count); return; } //FOR VOLUME_PARTICLE LOGIC: // WE MUST MULTIPLY THE VERTCOUNT AND POLYCOUNT BY THE VOLUME_PARTICLE DEPTH DX8_RECORD_SORTING_RENDER( polygon_count * layerCount,vertex_count * layerCount);//THIS IS VOLUME_PARTICLE SPECIFIC SortingNodeStruct* state=Get_Sorting_Struct(); DX8Wrapper::Get_Render_State(state->sorting_state); WWASSERT( ((state->sorting_state.index_buffer_type==BUFFER_TYPE_SORTING || state->sorting_state.index_buffer_type==BUFFER_TYPE_DYNAMIC_SORTING) && (state->sorting_state.vertex_buffer_types[0]==BUFFER_TYPE_SORTING || state->sorting_state.vertex_buffer_types[0]==BUFFER_TYPE_DYNAMIC_SORTING))); state->bounding_sphere=bounding_sphere; state->start_index=start_index; state->min_vertex_index=min_vertex_index; state->polygon_count=polygon_count * layerCount;//THIS IS VOLUME_PARTICLE SPECIFIC state->vertex_count=vertex_count * layerCount;//THIS IS VOLUME_PARTICLE SPECIFIC SortingVertexBufferClass* vertex_buffer=static_cast(state->sorting_state.vertex_buffers[0]); WWASSERT(vertex_buffer); WWASSERT(state->vertex_count<=vertex_buffer->Get_Vertex_Count()); // Transform the center point to view space for sorting D3DXMATRIX mtx=(D3DXMATRIX&)state->sorting_state.world*(D3DXMATRIX&)state->sorting_state.view; D3DXVECTOR3 vec=(D3DXVECTOR3&)state->bounding_sphere.Center; D3DXVECTOR4 transformed_vec; D3DXVec3Transform( &transformed_vec, &vec, &mtx); state->transformed_center=Vector3(transformed_vec[0],transformed_vec[1],transformed_vec[2]); // BUT WHAT IS THE DEAL WITH THE VERTCOUNT AND POLYCOUNT BEING N BUT TRANSFORMED CENTER COUNT == 1 //THE TRANSFORMED CENTER[2] IS THE ZBUFFER DEPTH /// @todo lorenzen sez use a bucket sort here... and stop copying so much data so many times SortingNodeStruct* node=sorted_list.Head(); while (node) { if (state->transformed_center.Z>node->transformed_center.Z) { if (sorted_list.Head()==sorted_list.Tail()) sorted_list.Add_Head(state); else state->Insert_Before(node); break; } node=node->Succ(); } if (!node) sorted_list.Add_Tail(state); }