/** * @file llviewerparceloverlay.cpp * @brief LLViewerParcelOverlay class implementation * * Copyright (c) 2002-2007, Linden Research, Inc. * * Second Life Viewer Source Code * The source code in this file ("Source Code") is provided by Linden Lab * to you under the terms of the GNU General Public License, version 2.0 * ("GPL"), unless you have obtained a separate licensing agreement * ("Other License"), formally executed by you and Linden Lab. Terms of * the GPL can be found in doc/GPL-license.txt in this distribution, or * online at http://secondlife.com/developers/opensource/gplv2 * * There are special exceptions to the terms and conditions of the GPL as * it is applied to this Source Code. View the full text of the exception * in the file doc/FLOSS-exception.txt in this software distribution, or * online at http://secondlife.com/developers/opensource/flossexception * * By copying, modifying or distributing this software, you acknowledge * that you have read and understood your obligations described above, * and agree to abide by those obligations. * * ALL LINDEN LAB SOURCE CODE IS PROVIDED "AS IS." LINDEN LAB MAKES NO * WARRANTIES, EXPRESS, IMPLIED OR OTHERWISE, REGARDING ITS ACCURACY, * COMPLETENESS OR PERFORMANCE. */ #include "llviewerprecompiledheaders.h" #include "llviewerparceloverlay.h" // indra includes #include "llparcel.h" #include "llgl.h" #include "v4color.h" #include "v2math.h" // newview includes #include "llviewerimage.h" #include "llviewercontrol.h" #include "llsurface.h" #include "llviewerregion.h" #include "llagent.h" #include "llviewercamera.h" #include "llviewerimagelist.h" #include "llglheaders.h" const U8 OVERLAY_IMG_COMPONENTS = 4; LLViewerParcelOverlay::LLViewerParcelOverlay(LLViewerRegion* region, F32 region_width_meters) : mRegion( region ), mParcelGridsPerEdge( S32( region_width_meters / PARCEL_GRID_STEP_METERS ) ), mDirty( FALSE ), mTimeSinceLastUpdate(), mOverlayTextureIdx(-1), mLineImageID( gViewerArt.getString("propertyline.tga") ), mVertexCount(0), mVertexArray(NULL), mColorArray(NULL) // mTexCoordArray(NULL), { // Create a texture to hold color information. // 4 components // Use mipmaps = FALSE, clamped, NEAREST filter, for sharp edges mTexture = new LLImageGL(FALSE); mImageRaw = new LLImageRaw(mParcelGridsPerEdge, mParcelGridsPerEdge, OVERLAY_IMG_COMPONENTS); mTexture->createGLTexture(0, mImageRaw); glActiveTextureARB(GL_TEXTURE0_ARB); mTexture->bind(0); mTexture->setClamp(TRUE, TRUE); mTexture->setMipFilterNearest(TRUE); // // Initialize the GL texture with empty data. // // Create the base texture. U8 *raw = mImageRaw->getData(); const S32 COUNT = mParcelGridsPerEdge * mParcelGridsPerEdge * OVERLAY_IMG_COMPONENTS; for (S32 i = 0; i < COUNT; i++) { raw[i] = 0; } mTexture->setSubImage(mImageRaw, 0, 0, mParcelGridsPerEdge, mParcelGridsPerEdge); // Create storage for ownership information from simulator // and initialize it. mOwnership = new U8[ mParcelGridsPerEdge * mParcelGridsPerEdge ]; for (S32 i = 0; i < mParcelGridsPerEdge * mParcelGridsPerEdge; i++) { mOwnership[i] = PARCEL_PUBLIC; } // Make sure the texture matches the ownership information. updateOverlayTexture(); } LLViewerParcelOverlay::~LLViewerParcelOverlay() { delete[] mOwnership; mOwnership = NULL; delete[] mVertexArray; mVertexArray = NULL; delete[] mColorArray; mColorArray = NULL; // JC No textures. // delete mTexCoordArray; // mTexCoordArray = NULL; mImageRaw = NULL; } //--------------------------------------------------------------------------- // ACCESSORS //--------------------------------------------------------------------------- BOOL LLViewerParcelOverlay::isOwned(const LLVector3& pos) const { S32 row = S32(pos.mV[VY] / PARCEL_GRID_STEP_METERS); S32 column = S32(pos.mV[VX] / PARCEL_GRID_STEP_METERS); return (PARCEL_PUBLIC != ownership(row, column)); } BOOL LLViewerParcelOverlay::isOwnedSelf(const LLVector3& pos) const { S32 row = S32(pos.mV[VY] / PARCEL_GRID_STEP_METERS); S32 column = S32(pos.mV[VX] / PARCEL_GRID_STEP_METERS); return (PARCEL_SELF == ownership(row, column)); } BOOL LLViewerParcelOverlay::isOwnedGroup(const LLVector3& pos) const { S32 row = S32(pos.mV[VY] / PARCEL_GRID_STEP_METERS); S32 column = S32(pos.mV[VX] / PARCEL_GRID_STEP_METERS); return (PARCEL_GROUP == ownership(row, column)); } BOOL LLViewerParcelOverlay::isOwnedOther(const LLVector3& pos) const { S32 row = S32(pos.mV[VY] / PARCEL_GRID_STEP_METERS); S32 column = S32(pos.mV[VX] / PARCEL_GRID_STEP_METERS); U8 overlay = ownership(row, column); return (PARCEL_OWNED == overlay || PARCEL_FOR_SALE == overlay); } BOOL LLViewerParcelOverlay::isSoundLocal(const LLVector3& pos) const { S32 row = S32(pos.mV[VY] / PARCEL_GRID_STEP_METERS); S32 column = S32(pos.mV[VX] / PARCEL_GRID_STEP_METERS); return PARCEL_SOUND_LOCAL & mOwnership[row * mParcelGridsPerEdge + column]; } U8 LLViewerParcelOverlay::ownership( const LLVector3& pos) const { S32 row = S32(pos.mV[VY] / PARCEL_GRID_STEP_METERS); S32 column = S32(pos.mV[VX] / PARCEL_GRID_STEP_METERS); return ownership(row, column); } F32 LLViewerParcelOverlay::getOwnedRatio() const { S32 size = mParcelGridsPerEdge * mParcelGridsPerEdge; S32 total = 0; for (S32 i = 0; i < size; i++) { if ((mOwnership[i] & PARCEL_COLOR_MASK) != PARCEL_PUBLIC) { total++; } } return (F32)total / (F32)size; } //--------------------------------------------------------------------------- // MANIPULATORS //--------------------------------------------------------------------------- // Color tables for owned land // Available = index 0 // Other = index 1 // Group = index 2 // Self = index 3 // Make sure the texture colors match the ownership data. // Note: Assumes that the ownership array and void LLViewerParcelOverlay::updateOverlayTexture() { if (mOverlayTextureIdx < 0 && mDirty) { mOverlayTextureIdx = 0; } if (mOverlayTextureIdx < 0) { return; } // Can do this because gColors are actually stored as LLColor4U const LLColor4U avail = gColors.getColor4U("PropertyColorAvail"); const LLColor4U owned = gColors.getColor4U("PropertyColorOther"); const LLColor4U group = gColors.getColor4U("PropertyColorGroup"); const LLColor4U self = gColors.getColor4U("PropertyColorSelf"); const LLColor4U for_sale = gColors.getColor4U("PropertyColorForSale"); const LLColor4U auction = gColors.getColor4U("PropertyColorAuction"); // Create the base texture. U8 *raw = mImageRaw->getData(); const S32 COUNT = mParcelGridsPerEdge * mParcelGridsPerEdge; S32 max = mOverlayTextureIdx + mParcelGridsPerEdge; if (max > COUNT) max = COUNT; S32 pixel_index = mOverlayTextureIdx*OVERLAY_IMG_COMPONENTS; S32 i; for (i = mOverlayTextureIdx; i < max; i++) { U8 ownership = mOwnership[i]; U8 r,g,b,a; // Color stored in low three bits switch( ownership & 0x7 ) { case PARCEL_PUBLIC: r = avail.mV[VRED]; g = avail.mV[VGREEN]; b = avail.mV[VBLUE]; a = avail.mV[VALPHA]; break; case PARCEL_OWNED: r = owned.mV[VRED]; g = owned.mV[VGREEN]; b = owned.mV[VBLUE]; a = owned.mV[VALPHA]; break; case PARCEL_GROUP: r = group.mV[VRED]; g = group.mV[VGREEN]; b = group.mV[VBLUE]; a = group.mV[VALPHA]; break; case PARCEL_SELF: r = self.mV[VRED]; g = self.mV[VGREEN]; b = self.mV[VBLUE]; a = self.mV[VALPHA]; break; case PARCEL_FOR_SALE: r = for_sale.mV[VRED]; g = for_sale.mV[VGREEN]; b = for_sale.mV[VBLUE]; a = for_sale.mV[VALPHA]; break; case PARCEL_AUCTION: r = auction.mV[VRED]; g = auction.mV[VGREEN]; b = auction.mV[VBLUE]; a = auction.mV[VALPHA]; break; default: r = self.mV[VRED]; g = self.mV[VGREEN]; b = self.mV[VBLUE]; a = self.mV[VALPHA]; break; } raw[pixel_index + 0] = r; raw[pixel_index + 1] = g; raw[pixel_index + 2] = b; raw[pixel_index + 3] = a; pixel_index += OVERLAY_IMG_COMPONENTS; } // Copy data into GL texture from raw data if (i >= COUNT) { mTexture->setSubImage(mImageRaw, 0, 0, mParcelGridsPerEdge, mParcelGridsPerEdge); mOverlayTextureIdx = -1; } else { mOverlayTextureIdx = i; } } void LLViewerParcelOverlay::uncompressLandOverlay(S32 chunk, U8 *packed_overlay) { // Unpack the message data into the ownership array S32 size = mParcelGridsPerEdge * mParcelGridsPerEdge; S32 chunk_size = size / PARCEL_OVERLAY_CHUNKS; memcpy(mOwnership + chunk*chunk_size, packed_overlay, chunk_size); /*Flawfinder: ignore*/ // Force property lines and overlay texture to update setDirty(); } void LLViewerParcelOverlay::updatePropertyLines() { if (!gSavedSettings.getBOOL("ShowPropertyLines")) return; S32 row, col; // Can do this because gColors are actually stored as LLColor4U const LLColor4U self_coloru = gColors.getColor4U("PropertyColorSelf"); const LLColor4U other_coloru = gColors.getColor4U("PropertyColorOther"); const LLColor4U group_coloru = gColors.getColor4U("PropertyColorGroup"); const LLColor4U for_sale_coloru = gColors.getColor4U("PropertyColorForSale"); const LLColor4U auction_coloru = gColors.getColor4U("PropertyColorAuction"); // Build into dynamic arrays, then copy into static arrays. LLDynamicArray new_vertex_array; LLDynamicArray new_color_array; LLDynamicArray new_coord_array; U8 overlay = 0; BOOL add_edge = FALSE; const F32 GRID_STEP = PARCEL_GRID_STEP_METERS; const S32 GRIDS_PER_EDGE = mParcelGridsPerEdge; for (row = 0; row < GRIDS_PER_EDGE; row++) { for (col = 0; col < GRIDS_PER_EDGE; col++) { overlay = mOwnership[row*GRIDS_PER_EDGE+col]; F32 left = col*GRID_STEP; F32 right = left+GRID_STEP; F32 bottom = row*GRID_STEP; F32 top = bottom+GRID_STEP; // West edge if (overlay & PARCEL_WEST_LINE) { switch(overlay & PARCEL_COLOR_MASK) { case PARCEL_SELF: addPropertyLine(new_vertex_array, new_color_array, new_coord_array, left, bottom, WEST, self_coloru); break; case PARCEL_GROUP: addPropertyLine(new_vertex_array, new_color_array, new_coord_array, left, bottom, WEST, group_coloru); break; case PARCEL_OWNED: addPropertyLine(new_vertex_array, new_color_array, new_coord_array, left, bottom, WEST, other_coloru); break; case PARCEL_FOR_SALE: addPropertyLine(new_vertex_array, new_color_array, new_coord_array, left, bottom, WEST, for_sale_coloru); break; case PARCEL_AUCTION: addPropertyLine(new_vertex_array, new_color_array, new_coord_array, left, bottom, WEST, auction_coloru); break; default: break; } } // East edge if (col < GRIDS_PER_EDGE-1) { U8 east_overlay = mOwnership[row*GRIDS_PER_EDGE+col+1]; add_edge = east_overlay & PARCEL_WEST_LINE; } else { add_edge = TRUE; } if (add_edge) { switch(overlay & PARCEL_COLOR_MASK) { case PARCEL_SELF: addPropertyLine(new_vertex_array, new_color_array, new_coord_array, right, bottom, EAST, self_coloru); break; case PARCEL_GROUP: addPropertyLine(new_vertex_array, new_color_array, new_coord_array, right, bottom, EAST, group_coloru); break; case PARCEL_OWNED: addPropertyLine(new_vertex_array, new_color_array, new_coord_array, right, bottom, EAST, other_coloru); break; case PARCEL_FOR_SALE: addPropertyLine(new_vertex_array, new_color_array, new_coord_array, right, bottom, EAST, for_sale_coloru); break; case PARCEL_AUCTION: addPropertyLine(new_vertex_array, new_color_array, new_coord_array, right, bottom, EAST, auction_coloru); break; default: break; } } // South edge if (overlay & PARCEL_SOUTH_LINE) { switch(overlay & PARCEL_COLOR_MASK) { case PARCEL_SELF: addPropertyLine(new_vertex_array, new_color_array, new_coord_array, left, bottom, SOUTH, self_coloru); break; case PARCEL_GROUP: addPropertyLine(new_vertex_array, new_color_array, new_coord_array, left, bottom, SOUTH, group_coloru); break; case PARCEL_OWNED: addPropertyLine(new_vertex_array, new_color_array, new_coord_array, left, bottom, SOUTH, other_coloru); break; case PARCEL_FOR_SALE: addPropertyLine(new_vertex_array, new_color_array, new_coord_array, left, bottom, SOUTH, for_sale_coloru); break; case PARCEL_AUCTION: addPropertyLine(new_vertex_array, new_color_array, new_coord_array, left, bottom, SOUTH, auction_coloru); break; default: break; } } // North edge if (row < GRIDS_PER_EDGE-1) { U8 north_overlay = mOwnership[(row+1)*GRIDS_PER_EDGE+col]; add_edge = north_overlay & PARCEL_SOUTH_LINE; } else { add_edge = TRUE; } if (add_edge) { switch(overlay & PARCEL_COLOR_MASK) { case PARCEL_SELF: addPropertyLine(new_vertex_array, new_color_array, new_coord_array, left, top, NORTH, self_coloru); break; case PARCEL_GROUP: addPropertyLine(new_vertex_array, new_color_array, new_coord_array, left, top, NORTH, group_coloru); break; case PARCEL_OWNED: addPropertyLine(new_vertex_array, new_color_array, new_coord_array, left, top, NORTH, other_coloru); break; case PARCEL_FOR_SALE: addPropertyLine(new_vertex_array, new_color_array, new_coord_array, left, top, NORTH, for_sale_coloru); break; case PARCEL_AUCTION: addPropertyLine(new_vertex_array, new_color_array, new_coord_array, left, top, NORTH, auction_coloru); break; default: break; } } } } // Now copy into static arrays for faster rendering. // Attempt to recycle old arrays if possible to avoid memory // shuffling. S32 new_vertex_count = new_vertex_array.count(); // NOTE: If the new_vertex_count is 0 and wasn't 0 previously // the arrays are still allocated as the arrays aren't set to NULL, etc. // This won't cause any problems, but might waste a few cycles copying over // old data. - jwolk if ( !(mVertexArray && mColorArray && new_vertex_count == mVertexCount) && new_vertex_count > 0 ) { // ...need new arrays delete[] mVertexArray; delete[] mColorArray; mVertexCount = new_vertex_count; mVertexArray = new F32[3 * mVertexCount]; mColorArray = new U8 [4 * mVertexCount]; } // Copy the new data into the arrays S32 i; F32* vertex = mVertexArray; for (i = 0; i < mVertexCount; i++) { const LLVector3& point = new_vertex_array.get(i); *vertex = point.mV[VX]; vertex++; *vertex = point.mV[VY]; vertex++; *vertex = point.mV[VZ]; vertex++; } U8* colorp = mColorArray; for (i = 0; i < mVertexCount; i++) { const LLColor4U& color = new_color_array.get(i); *colorp = color.mV[VRED]; colorp++; *colorp = color.mV[VGREEN]; colorp++; *colorp = color.mV[VBLUE]; colorp++; *colorp = color.mV[VALPHA]; colorp++; } // Everything's clean now mDirty = FALSE; } void LLViewerParcelOverlay::addPropertyLine( LLDynamicArray& vertex_array, LLDynamicArray& color_array, LLDynamicArray& coord_array, const F32 start_x, const F32 start_y, const U32 edge, const LLColor4U& color) { LLColor4U underwater( color ); underwater.mV[VALPHA] /= 2; LLSurface& land = mRegion->getLand(); F32 dx; F32 dy; F32 tick_dx; F32 tick_dy; //const F32 LINE_WIDTH = 0.125f; const F32 LINE_WIDTH = 0.0625f; switch(edge) { case WEST: dx = 0.f; dy = 1.f; tick_dx = LINE_WIDTH; tick_dy = 0.f; break; case EAST: dx = 0.f; dy = 1.f; tick_dx = -LINE_WIDTH; tick_dy = 0.f; break; case NORTH: dx = 1.f; dy = 0.f; tick_dx = 0.f; tick_dy = -LINE_WIDTH; break; case SOUTH: dx = 1.f; dy = 0.f; tick_dx = 0.f; tick_dy = LINE_WIDTH; break; default: llerrs << "Invalid edge in addPropertyLine" << llendl; return; } F32 outside_x = start_x; F32 outside_y = start_y; F32 outside_z = 0.f; F32 inside_x = start_x + tick_dx; F32 inside_y = start_y + tick_dy; F32 inside_z = 0.f; // First part, only one vertex outside_z = land.resolveHeightRegion( outside_x, outside_y ); if (outside_z > 20.f) color_array.put( color ); else color_array.put( underwater ); vertex_array.put( LLVector3(outside_x, outside_y, outside_z) ); coord_array.put( LLVector2(outside_x - start_x, 0.f) ); inside_x += dx * LINE_WIDTH; inside_y += dy * LINE_WIDTH; outside_x += dx * LINE_WIDTH; outside_y += dy * LINE_WIDTH; // Then the "actual edge" inside_z = land.resolveHeightRegion( inside_x, inside_y ); outside_z = land.resolveHeightRegion( outside_x, outside_y ); if (inside_z > 20.f) color_array.put( color ); else color_array.put( underwater ); if (outside_z > 20.f) color_array.put( color ); else color_array.put( underwater ); vertex_array.put( LLVector3(inside_x, inside_y, inside_z) ); vertex_array.put( LLVector3(outside_x, outside_y, outside_z) ); coord_array.put( LLVector2(outside_x - start_x, 1.f) ); coord_array.put( LLVector2(outside_x - start_x, 0.f) ); inside_x += dx * (dx - LINE_WIDTH); inside_y += dy * (dy - LINE_WIDTH); outside_x += dx * (dx - LINE_WIDTH); outside_y += dy * (dy - LINE_WIDTH); // Middle part, full width S32 i; const S32 GRID_STEP = S32( PARCEL_GRID_STEP_METERS ); for (i = 1; i < GRID_STEP; i++) { inside_z = land.resolveHeightRegion( inside_x, inside_y ); outside_z = land.resolveHeightRegion( outside_x, outside_y ); if (inside_z > 20.f) color_array.put( color ); else color_array.put( underwater ); if (outside_z > 20.f) color_array.put( color ); else color_array.put( underwater ); vertex_array.put( LLVector3(inside_x, inside_y, inside_z) ); vertex_array.put( LLVector3(outside_x, outside_y, outside_z) ); coord_array.put( LLVector2(outside_x - start_x, 1.f) ); coord_array.put( LLVector2(outside_x - start_x, 0.f) ); inside_x += dx; inside_y += dy; outside_x += dx; outside_y += dy; } // Extra buffer for edge inside_x -= dx * LINE_WIDTH; inside_y -= dy * LINE_WIDTH; outside_x -= dx * LINE_WIDTH; outside_y -= dy * LINE_WIDTH; inside_z = land.resolveHeightRegion( inside_x, inside_y ); outside_z = land.resolveHeightRegion( outside_x, outside_y ); if (inside_z > 20.f) color_array.put( color ); else color_array.put( underwater ); if (outside_z > 20.f) color_array.put( color ); else color_array.put( underwater ); vertex_array.put( LLVector3(inside_x, inside_y, inside_z) ); vertex_array.put( LLVector3(outside_x, outside_y, outside_z) ); coord_array.put( LLVector2(outside_x - start_x, 1.f) ); coord_array.put( LLVector2(outside_x - start_x, 0.f) ); inside_x += dx * LINE_WIDTH; inside_y += dy * LINE_WIDTH; outside_x += dx * LINE_WIDTH; outside_y += dy * LINE_WIDTH; // Last edge is not drawn to the edge outside_z = land.resolveHeightRegion( outside_x, outside_y ); if (outside_z > 20.f) color_array.put( color ); else color_array.put( underwater ); vertex_array.put( LLVector3(outside_x, outside_y, outside_z) ); coord_array.put( LLVector2(outside_x - start_x, 0.f) ); } void LLViewerParcelOverlay::setDirty() { mDirty = TRUE; } void LLViewerParcelOverlay::idleUpdate(bool force_update) { if (gGLManager.mIsDisabled) { return; } if (mOverlayTextureIdx >= 0 && (!(mDirty && force_update))) { // We are in the middle of updating the overlay texture updateOverlayTexture(); return; } // Only if we're dirty and it's been a while since the last update. if (mDirty) { if (force_update || mTimeSinceLastUpdate.getElapsedTimeF32() > 4.0f) { updateOverlayTexture(); updatePropertyLines(); mTimeSinceLastUpdate.reset(); } } } S32 LLViewerParcelOverlay::renderPropertyLines () { if (!gSavedSettings.getBOOL("ShowPropertyLines")) { return 0; } if (!mVertexArray || !mColorArray) { return 0; } LLSurface& land = mRegion->getLand(); LLGLSUIDefault gls_ui; // called from pipeline LLGLSNoTexture gls_no_texture; LLGLDepthTest mDepthTest(GL_TRUE); // JC - This doesn't work. //gGLSUITextureDepth.set(); //LLViewerImage* image = gImageList.getImage( mLineImageID ); //image->bindTexture(); // Find camera height off the ground (not from zero) F32 ground_height_at_camera = land.resolveHeightGlobal( gAgent.getCameraPositionGlobal() ); F32 camera_z = gCamera->getOrigin().mV[VZ]; F32 camera_height = camera_z - ground_height_at_camera; camera_height = llclamp(camera_height, 0.f, 100.f); // Pull lines toward camera by 1 cm per meter off the ground. const LLVector3& CAMERA_AT = gCamera->getAtAxis(); F32 pull_toward_camera_scale = 0.01f * camera_height; LLVector3 pull_toward_camera = CAMERA_AT; pull_toward_camera *= -pull_toward_camera_scale; // Always fudge a little vertically. pull_toward_camera.mV[VZ] += 0.01f; glMatrixMode( GL_MODELVIEW ); glPushMatrix(); // Move to appropriate region coords LLVector3 origin = mRegion->getOriginAgent(); glTranslatef( origin.mV[VX], origin.mV[VY], origin.mV[VZ] ); glTranslatef(pull_toward_camera.mV[VX], pull_toward_camera.mV[VY], pull_toward_camera.mV[VZ]); // Include +1 because vertices are fenceposts. // *2 because it's a quad strip const S32 GRID_STEP = S32( PARCEL_GRID_STEP_METERS ); const S32 vertex_per_edge = 3 + 2 * (GRID_STEP-1) + 3; // Stomp the camera into two dimensions LLVector3 camera_region = mRegion->getPosRegionFromGlobal( gAgent.getCameraPositionGlobal() ); // Set up a cull plane 2 * PARCEL_GRID_STEP_METERS behind // the camera. The cull plane normal is the camera's at axis. LLVector3 cull_plane_point = gCamera->getAtAxis(); cull_plane_point *= -2.f * PARCEL_GRID_STEP_METERS; cull_plane_point += camera_region; LLVector3 vertex; const S32 BYTES_PER_COLOR = 4; const S32 FLOATS_PER_VERTEX = 3; //const S32 FLOATS_PER_TEX_COORD = 2; S32 i, j; S32 drawn = 0; F32* vertexp; U8* colorp; const F32 PROPERTY_LINE_CLIP_DIST = 256.f; for (i = 0; i < mVertexCount; i += vertex_per_edge) { colorp = mColorArray + BYTES_PER_COLOR * i; vertexp = mVertexArray + FLOATS_PER_VERTEX * i; vertex.mV[VX] = *(vertexp); vertex.mV[VY] = *(vertexp+1); vertex.mV[VZ] = *(vertexp+2); if (dist_vec_squared2D(vertex, camera_region) > PROPERTY_LINE_CLIP_DIST*PROPERTY_LINE_CLIP_DIST) { continue; } // Destroy vertex, transform to plane-local. vertex -= cull_plane_point; // negative dot product means it is in back of the plane if ( vertex * CAMERA_AT < 0.f ) { continue; } glBegin(GL_TRIANGLE_STRIP); for (j = 0; j < vertex_per_edge; j++) { // JC - This doesn't work //glTexCoord2fv(mTexCoordArray + FLOATS_PER_TEX_COORD*offset); glColor4ubv(colorp); glVertex3fv(vertexp); colorp += BYTES_PER_COLOR; vertexp += FLOATS_PER_VERTEX; } drawn += vertex_per_edge; glEnd(); } glPopMatrix(); return drawn; }