Files
SingularityViewer/indra/llmath/llvolume.cpp
Shyotl e756140e1d Innitial commit of experimental v2 texture system port work. Compiles and runs on windows, at least. Fixing bugs as they come.
Need to test:
localassetbrowser
preview related floaters
hgfloatertexteditor
maps
media textures! Currently very hacky
web browser
alpha masks on avatars
bumpmaps
Are all sky components appearing?
LLViewerDynamicTexture (texture baking, browser, animated textures, anim previews, etc)
Snapshot related features
Customize avatar
vfs floater
UI textures in general
Texture priority issues
2011-03-31 03:22:01 -05:00

5400 lines
130 KiB
C++

/**
* @file llvolume.cpp
*
* $LicenseInfo:firstyear=2002&license=viewergpl$
*
* Copyright (c) 2002-2009, 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://secondlifegrid.net/programs/open_source/licensing/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://secondlifegrid.net/programs/open_source/licensing/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.
* $/LicenseInfo$
*/
#include "linden_common.h"
#include "llmath.h"
#include <set>
#include "llerror.h"
#include "llmemtype.h"
#include "llvolumemgr.h"
#include "v2math.h"
#include "v3math.h"
#include "v4math.h"
#include "m4math.h"
#include "m3math.h"
#include "lldarray.h"
#include "llvolume.h"
#include "llstl.h"
#define DEBUG_SILHOUETTE_BINORMALS 0
#define DEBUG_SILHOUETTE_NORMALS 0 // TomY: Use this to display normals using the silhouette
#define DEBUG_SILHOUETTE_EDGE_MAP 0 // DaveP: Use this to display edge map using the silhouette
const F32 CUT_MIN = 0.f;
const F32 CUT_MAX = 1.f;
const F32 MIN_CUT_DELTA = 0.02f;
const F32 HOLLOW_MIN = 0.f;
const F32 HOLLOW_MAX = 0.95f;
const F32 HOLLOW_MAX_SQUARE = 0.7f;
const F32 TWIST_MIN = -1.f;
const F32 TWIST_MAX = 1.f;
const F32 RATIO_MIN = 0.f;
const F32 RATIO_MAX = 2.f; // Tom Y: Inverted sense here: 0 = top taper, 2 = bottom taper
const F32 HOLE_X_MIN= 0.05f;
const F32 HOLE_X_MAX= 1.0f;
const F32 HOLE_Y_MIN= 0.05f;
const F32 HOLE_Y_MAX= 0.5f;
const F32 SHEAR_MIN = -0.5f;
const F32 SHEAR_MAX = 0.5f;
const F32 REV_MIN = 1.f;
const F32 REV_MAX = 4.f;
const F32 TAPER_MIN = -1.f;
const F32 TAPER_MAX = 1.f;
const F32 SKEW_MIN = -0.95f;
const F32 SKEW_MAX = 0.95f;
const F32 SCULPT_MIN_AREA = 0.002f;
const S32 SCULPT_MIN_AREA_DETAIL = 1;
BOOL check_same_clock_dir( const LLVector3& pt1, const LLVector3& pt2, const LLVector3& pt3, const LLVector3& norm)
{
LLVector3 test = (pt2-pt1)%(pt3-pt2);
//answer
if(test * norm < 0)
{
return FALSE;
}
else
{
return TRUE;
}
}
BOOL LLLineSegmentBoxIntersect(const LLVector3& start, const LLVector3& end, const LLVector3& center, const LLVector3& size)
{
float fAWdU[3];
LLVector3 dir;
LLVector3 diff;
for (U32 i = 0; i < 3; i++)
{
dir.mV[i] = 0.5f * (end.mV[i] - start.mV[i]);
diff.mV[i] = (0.5f * (end.mV[i] + start.mV[i])) - center.mV[i];
fAWdU[i] = fabsf(dir.mV[i]);
if(fabsf(diff.mV[i])>size.mV[i] + fAWdU[i]) return false;
}
float f;
f = dir.mV[1] * diff.mV[2] - dir.mV[2] * diff.mV[1]; if(fabsf(f)>size.mV[1]*fAWdU[2] + size.mV[2]*fAWdU[1]) return false;
f = dir.mV[2] * diff.mV[0] - dir.mV[0] * diff.mV[2]; if(fabsf(f)>size.mV[0]*fAWdU[2] + size.mV[2]*fAWdU[0]) return false;
f = dir.mV[0] * diff.mV[1] - dir.mV[1] * diff.mV[0]; if(fabsf(f)>size.mV[0]*fAWdU[1] + size.mV[1]*fAWdU[0]) return false;
return true;
}
// intersect test between triangle vert0, vert1, vert2 and a ray from orig in direction dir.
// returns TRUE if intersecting and returns barycentric coordinates in intersection_a, intersection_b,
// and returns the intersection point along dir in intersection_t.
// Moller-Trumbore algorithm
BOOL LLTriangleRayIntersect(const LLVector3& vert0, const LLVector3& vert1, const LLVector3& vert2, const LLVector3& orig, const LLVector3& dir,
F32* intersection_a, F32* intersection_b, F32* intersection_t, BOOL two_sided)
{
F32 u, v, t;
/* find vectors for two edges sharing vert0 */
LLVector3 edge1 = vert1 - vert0;
LLVector3 edge2 = vert2 - vert0;;
/* begin calculating determinant - also used to calculate U parameter */
LLVector3 pvec = dir % edge2;
/* if determinant is near zero, ray lies in plane of triangle */
F32 det = edge1 * pvec;
if (!two_sided)
{
if (det < F_APPROXIMATELY_ZERO)
{
return FALSE;
}
/* calculate distance from vert0 to ray origin */
LLVector3 tvec = orig - vert0;
/* calculate U parameter and test bounds */
u = tvec * pvec;
if (u < 0.f || u > det)
{
return FALSE;
}
/* prepare to test V parameter */
LLVector3 qvec = tvec % edge1;
/* calculate V parameter and test bounds */
v = dir * qvec;
if (v < 0.f || u + v > det)
{
return FALSE;
}
/* calculate t, scale parameters, ray intersects triangle */
t = edge2 * qvec;
F32 inv_det = 1.0 / det;
t *= inv_det;
u *= inv_det;
v *= inv_det;
}
else // two sided
{
if (det > -F_APPROXIMATELY_ZERO && det < F_APPROXIMATELY_ZERO)
{
return FALSE;
}
F32 inv_det = 1.0 / det;
/* calculate distance from vert0 to ray origin */
LLVector3 tvec = orig - vert0;
/* calculate U parameter and test bounds */
u = (tvec * pvec) * inv_det;
if (u < 0.f || u > 1.f)
{
return FALSE;
}
/* prepare to test V parameter */
LLVector3 qvec = tvec - edge1;
/* calculate V parameter and test bounds */
v = (dir * qvec) * inv_det;
if (v < 0.f || u + v > 1.f)
{
return FALSE;
}
/* calculate t, ray intersects triangle */
t = (edge2 * qvec) * inv_det;
}
if (intersection_a != NULL)
*intersection_a = u;
if (intersection_b != NULL)
*intersection_b = v;
if (intersection_t != NULL)
*intersection_t = t;
return TRUE;
}
//-------------------------------------------------------------------
// statics
//-------------------------------------------------------------------
//----------------------------------------------------
LLProfile::Face* LLProfile::addCap(S16 faceID)
{
LLMemType m1(LLMemType::MTYPE_VOLUME);
Face *face = vector_append(mFaces, 1);
face->mIndex = 0;
face->mCount = mTotal;
face->mScaleU= 1.0f;
face->mCap = TRUE;
face->mFaceID = faceID;
return face;
}
LLProfile::Face* LLProfile::addFace(S32 i, S32 count, F32 scaleU, S16 faceID, BOOL flat)
{
LLMemType m1(LLMemType::MTYPE_VOLUME);
Face *face = vector_append(mFaces, 1);
face->mIndex = i;
face->mCount = count;
face->mScaleU= scaleU;
face->mFlat = flat;
face->mCap = FALSE;
face->mFaceID = faceID;
return face;
}
// What is the bevel parameter used for? - DJS 04/05/02
// Bevel parameter is currently unused but presumedly would support
// filleted and chamfered corners
void LLProfile::genNGon(const LLProfileParams& params, S32 sides, F32 offset, F32 bevel, F32 ang_scale, S32 split)
{
LLMemType m1(LLMemType::MTYPE_VOLUME);
// Generate an n-sided "circular" path.
// 0 is (1,0), and we go counter-clockwise along a circular path from there.
const F32 tableScale[] = { 1, 1, 1, 0.5f, 0.707107f, 0.53f, 0.525f, 0.5f };
F32 scale = 0.5f;
F32 t, t_step, t_first, t_fraction, ang, ang_step;
LLVector3 pt1,pt2;
F32 begin = params.getBegin();
F32 end = params.getEnd();
t_step = 1.0f / sides;
ang_step = 2.0f*F_PI*t_step*ang_scale;
// Scale to have size "match" scale. Compensates to get object to generally fill bounding box.
S32 total_sides = llround(sides / ang_scale); // Total number of sides all around
if (total_sides < 8)
{
scale = tableScale[total_sides];
}
t_first = floor(begin * sides) / (F32)sides;
// pt1 is the first point on the fractional face.
// Starting t and ang values for the first face
t = t_first;
ang = 2.0f*F_PI*(t*ang_scale + offset);
pt1.setVec(cos(ang)*scale,sin(ang)*scale, t);
// Increment to the next point.
// pt2 is the end point on the fractional face
t += t_step;
ang += ang_step;
pt2.setVec(cos(ang)*scale,sin(ang)*scale,t);
t_fraction = (begin - t_first)*sides;
// Only use if it's not almost exactly on an edge.
if (t_fraction < 0.9999f)
{
LLVector3 new_pt = lerp(pt1, pt2, t_fraction);
mProfile.push_back(new_pt);
}
// There's lots of potential here for floating point error to generate unneeded extra points - DJS 04/05/02
while (t < end)
{
// Iterate through all the integer steps of t.
pt1.setVec(cos(ang)*scale,sin(ang)*scale,t);
if (mProfile.size() > 0) {
LLVector3 p = mProfile[mProfile.size()-1];
for (S32 i = 0; i < split && mProfile.size() > 0; i++) {
mProfile.push_back(p+(pt1-p) * 1.0f/(float)(split+1) * (float)(i+1));
}
}
mProfile.push_back(pt1);
t += t_step;
ang += ang_step;
}
t_fraction = (end - (t - t_step))*sides;
// pt1 is the first point on the fractional face
// pt2 is the end point on the fractional face
pt2.setVec(cos(ang)*scale,sin(ang)*scale,t);
// Find the fraction that we need to add to the end point.
t_fraction = (end - (t - t_step))*sides;
if (t_fraction > 0.0001f)
{
LLVector3 new_pt = lerp(pt1, pt2, t_fraction);
if (mProfile.size() > 0) {
LLVector3 p = mProfile[mProfile.size()-1];
for (S32 i = 0; i < split && mProfile.size() > 0; i++) {
mProfile.push_back(p+(new_pt-p) * 1.0f/(float)(split+1) * (float)(i+1));
}
}
mProfile.push_back(new_pt);
}
// If we're sliced, the profile is open.
if ((end - begin)*ang_scale < 0.99f)
{
if ((end - begin)*ang_scale > 0.5f)
{
mConcave = TRUE;
}
else
{
mConcave = FALSE;
}
mOpen = TRUE;
if (params.getHollow() <= 0)
{
// put center point if not hollow.
mProfile.push_back(LLVector3(0,0,0));
}
}
else
{
// The profile isn't open.
mOpen = FALSE;
mConcave = FALSE;
}
mTotal = mProfile.size();
}
void LLProfile::genNormals(const LLProfileParams& params)
{
S32 count = mProfile.size();
S32 outer_count;
if (mTotalOut)
{
outer_count = mTotalOut;
}
else
{
outer_count = mTotal / 2;
}
mEdgeNormals.resize(count * 2);
mEdgeCenters.resize(count * 2);
mNormals.resize(count);
LLVector2 pt0,pt1;
BOOL hollow = (params.getHollow() > 0);
S32 i0, i1, i2, i3, i4;
// Parametrically generate normal
for (i2 = 0; i2 < count; i2++)
{
mNormals[i2].mV[0] = mProfile[i2].mV[0];
mNormals[i2].mV[1] = mProfile[i2].mV[1];
if (hollow && (i2 >= outer_count))
{
mNormals[i2] *= -1.f;
}
if (mNormals[i2].magVec() < 0.001)
{
// Special case for point at center, get adjacent points.
i1 = (i2 - 1) >= 0 ? i2 - 1 : count - 1;
i0 = (i1 - 1) >= 0 ? i1 - 1 : count - 1;
i3 = (i2 + 1) < count ? i2 + 1 : 0;
i4 = (i3 + 1) < count ? i3 + 1 : 0;
pt0.setVec(mProfile[i1].mV[VX] + mProfile[i1].mV[VX] - mProfile[i0].mV[VX],
mProfile[i1].mV[VY] + mProfile[i1].mV[VY] - mProfile[i0].mV[VY]);
pt1.setVec(mProfile[i3].mV[VX] + mProfile[i3].mV[VX] - mProfile[i4].mV[VX],
mProfile[i3].mV[VY] + mProfile[i3].mV[VY] - mProfile[i4].mV[VY]);
mNormals[i2] = pt0 + pt1;
mNormals[i2] *= 0.5f;
}
mNormals[i2].normVec();
}
S32 num_normal_sets = isConcave() ? 2 : 1;
for (S32 normal_set = 0; normal_set < num_normal_sets; normal_set++)
{
S32 point_num;
for (point_num = 0; point_num < mTotal; point_num++)
{
LLVector3 point_1 = mProfile[point_num];
point_1.mV[VZ] = 0.f;
LLVector3 point_2;
if (isConcave() && normal_set == 0 && point_num == (mTotal - 1) / 2)
{
point_2 = mProfile[mTotal - 1];
}
else if (isConcave() && normal_set == 1 && point_num == mTotal - 1)
{
point_2 = mProfile[(mTotal - 1) / 2];
}
else
{
LLVector3 delta_pos;
S32 neighbor_point = (point_num + 1) % mTotal;
while(delta_pos.magVecSquared() < 0.01f * 0.01f)
{
point_2 = mProfile[neighbor_point];
delta_pos = point_2 - point_1;
neighbor_point = (neighbor_point + 1) % mTotal;
if (neighbor_point == point_num)
{
break;
}
}
}
point_2.mV[VZ] = 0.f;
LLVector3 face_normal = (point_2 - point_1) % LLVector3::z_axis;
face_normal.normVec();
mEdgeNormals[normal_set * count + point_num] = face_normal;
mEdgeCenters[normal_set * count + point_num] = lerp(point_1, point_2, 0.5f);
}
}
}
// Hollow is percent of the original bounding box, not of this particular
// profile's geometry. Thus, a swept triangle needs lower hollow values than
// a swept square.
LLProfile::Face* LLProfile::addHole(const LLProfileParams& params, BOOL flat, F32 sides, F32 offset, F32 box_hollow, F32 ang_scale, S32 split)
{
// Note that addHole will NOT work for non-"circular" profiles, if we ever decide to use them.
// Total add has number of vertices on outside.
mTotalOut = mTotal;
// Why is the "bevel" parameter -1? DJS 04/05/02
genNGon(params, llfloor(sides),offset,-1, ang_scale, split);
Face *face = addFace(mTotalOut, mTotal-mTotalOut,0,LL_FACE_INNER_SIDE, flat);
std::vector<LLVector3> pt;
pt.resize(mTotal) ;
for (S32 i=mTotalOut;i<mTotal;i++)
{
pt[i] = mProfile[i] * box_hollow;
}
S32 j=mTotal-1;
for (S32 i=mTotalOut;i<mTotal;i++)
{
mProfile[i] = pt[j--];
}
for (S32 i=0;i<(S32)mFaces.size();i++)
{
if (mFaces[i].mCap)
{
mFaces[i].mCount *= 2;
}
}
return face;
}
BOOL LLProfile::generate(const LLProfileParams& params, BOOL path_open,F32 detail, S32 split,
BOOL is_sculpted, S32 sculpt_size)
{
LLMemType m1(LLMemType::MTYPE_VOLUME);
if ((!mDirty) && (!is_sculpted))
{
return FALSE;
}
mDirty = FALSE;
if (detail < MIN_LOD)
{
llinfos << "Generating profile with LOD < MIN_LOD. CLAMPING" << llendl;
detail = MIN_LOD;
}
mProfile.clear();
mFaces.clear();
// Generate the face data
S32 i;
F32 begin = params.getBegin();
F32 end = params.getEnd();
F32 hollow = params.getHollow();
// Quick validation to eliminate some server crashes.
if (begin > end - 0.01f)
{
llwarns << "LLProfile::generate() assertion failed (begin >= end)" << llendl;
return FALSE;
}
S32 face_num = 0;
switch (params.getCurveType() & LL_PCODE_PROFILE_MASK)
{
case LL_PCODE_PROFILE_SQUARE:
{
genNGon(params, 4,-0.375, 0, 1, split);
if (path_open)
{
addCap (LL_FACE_PATH_BEGIN);
}
for (i = llfloor(begin * 4.f); i < llfloor(end * 4.f + .999f); i++)
{
addFace((face_num++) * (split +1), split+2, 1, LL_FACE_OUTER_SIDE_0 << i, TRUE);
}
for (i = 0; i <(S32) mProfile.size(); i++)
{
// Scale by 4 to generate proper tex coords.
mProfile[i].mV[2] *= 4.f;
}
if (hollow)
{
switch (params.getCurveType() & LL_PCODE_HOLE_MASK)
{
case LL_PCODE_HOLE_TRIANGLE:
// This offset is not correct, but we can't change it now... DK 11/17/04
addHole(params, TRUE, 3, -0.375f, hollow, 1.f, split);
break;
case LL_PCODE_HOLE_CIRCLE:
// TODO: Compute actual detail levels for cubes
addHole(params, FALSE, MIN_DETAIL_FACES * detail, -0.375f, hollow, 1.f);
break;
case LL_PCODE_HOLE_SAME:
case LL_PCODE_HOLE_SQUARE:
default:
addHole(params, TRUE, 4, -0.375f, hollow, 1.f, split);
break;
}
}
if (path_open) {
mFaces[0].mCount = mTotal;
}
}
break;
case LL_PCODE_PROFILE_ISOTRI:
case LL_PCODE_PROFILE_RIGHTTRI:
case LL_PCODE_PROFILE_EQUALTRI:
{
genNGon(params, 3,0, 0, 1, split);
for (i = 0; i <(S32) mProfile.size(); i++)
{
// Scale by 3 to generate proper tex coords.
mProfile[i].mV[2] *= 3.f;
}
if (path_open)
{
addCap(LL_FACE_PATH_BEGIN);
}
for (i = llfloor(begin * 3.f); i < llfloor(end * 3.f + .999f); i++)
{
addFace((face_num++) * (split +1), split+2, 1, LL_FACE_OUTER_SIDE_0 << i, TRUE);
}
if (hollow)
{
// Swept triangles need smaller hollowness values,
// because the triangle doesn't fill the bounding box.
F32 triangle_hollow = hollow / 2.f;
switch (params.getCurveType() & LL_PCODE_HOLE_MASK)
{
case LL_PCODE_HOLE_CIRCLE:
// TODO: Actually generate level of detail for triangles
addHole(params, FALSE, MIN_DETAIL_FACES * detail, 0, triangle_hollow, 1.f);
break;
case LL_PCODE_HOLE_SQUARE:
addHole(params, TRUE, 4, 0, triangle_hollow, 1.f, split);
break;
case LL_PCODE_HOLE_SAME:
case LL_PCODE_HOLE_TRIANGLE:
default:
addHole(params, TRUE, 3, 0, triangle_hollow, 1.f, split);
break;
}
}
}
break;
case LL_PCODE_PROFILE_CIRCLE:
{
// If this has a square hollow, we should adjust the
// number of faces a bit so that the geometry lines up.
U8 hole_type=0;
F32 circle_detail = MIN_DETAIL_FACES * detail;
if (hollow)
{
hole_type = params.getCurveType() & LL_PCODE_HOLE_MASK;
if (hole_type == LL_PCODE_HOLE_SQUARE)
{
// Snap to the next multiple of four sides,
// so that corners line up.
circle_detail = llceil(circle_detail / 4.0f) * 4.0f;
}
}
S32 sides = (S32)circle_detail;
if (is_sculpted)
sides = sculpt_size;
genNGon(params, sides);
if (path_open)
{
addCap (LL_FACE_PATH_BEGIN);
}
if (mOpen && !hollow)
{
addFace(0,mTotal-1,0,LL_FACE_OUTER_SIDE_0, FALSE);
}
else
{
addFace(0,mTotal,0,LL_FACE_OUTER_SIDE_0, FALSE);
}
if (hollow)
{
switch (hole_type)
{
case LL_PCODE_HOLE_SQUARE:
addHole(params, TRUE, 4, 0, hollow, 1.f, split);
break;
case LL_PCODE_HOLE_TRIANGLE:
addHole(params, TRUE, 3, 0, hollow, 1.f, split);
break;
case LL_PCODE_HOLE_CIRCLE:
case LL_PCODE_HOLE_SAME:
default:
addHole(params, FALSE, circle_detail, 0, hollow, 1.f);
break;
}
}
}
break;
case LL_PCODE_PROFILE_CIRCLE_HALF:
{
// If this has a square hollow, we should adjust the
// number of faces a bit so that the geometry lines up.
U8 hole_type=0;
// Number of faces is cut in half because it's only a half-circle.
F32 circle_detail = MIN_DETAIL_FACES * detail * 0.5f;
if (hollow)
{
hole_type = params.getCurveType() & LL_PCODE_HOLE_MASK;
if (hole_type == LL_PCODE_HOLE_SQUARE)
{
// Snap to the next multiple of four sides (div 2),
// so that corners line up.
circle_detail = llceil(circle_detail / 2.0f) * 2.0f;
}
}
genNGon(params, llfloor(circle_detail), 0.5f, 0.f, 0.5f);
if (path_open)
{
addCap(LL_FACE_PATH_BEGIN);
}
if (mOpen && !params.getHollow())
{
addFace(0,mTotal-1,0,LL_FACE_OUTER_SIDE_0, FALSE);
}
else
{
addFace(0,mTotal,0,LL_FACE_OUTER_SIDE_0, FALSE);
}
if (hollow)
{
switch (hole_type)
{
case LL_PCODE_HOLE_SQUARE:
addHole(params, TRUE, 2, 0.5f, hollow, 0.5f, split);
break;
case LL_PCODE_HOLE_TRIANGLE:
addHole(params, TRUE, 3, 0.5f, hollow, 0.5f, split);
break;
case LL_PCODE_HOLE_CIRCLE:
case LL_PCODE_HOLE_SAME:
default:
addHole(params, FALSE, circle_detail, 0.5f, hollow, 0.5f);
break;
}
}
// Special case for openness of sphere
if ((params.getEnd() - params.getBegin()) < 1.f)
{
mOpen = TRUE;
}
else if (!hollow)
{
mOpen = FALSE;
mProfile.push_back(mProfile[0]);
mTotal++;
}
}
break;
default:
llerrs << "Unknown profile: getCurveType()=" << params.getCurveType() << llendl;
break;
};
if (path_open)
{
addCap(LL_FACE_PATH_END); // bottom
}
if ( mOpen) // interior edge caps
{
addFace(mTotal-1, 2,0.5,LL_FACE_PROFILE_BEGIN, TRUE);
if (hollow)
{
addFace(mTotalOut-1, 2,0.5,LL_FACE_PROFILE_END, TRUE);
}
else
{
addFace(mTotal-2, 2,0.5,LL_FACE_PROFILE_END, TRUE);
}
}
//genNormals(params);
return TRUE;
}
BOOL LLProfileParams::importFile(LLFILE *fp)
{
LLMemType m1(LLMemType::MTYPE_VOLUME);
const S32 BUFSIZE = 16384;
char buffer[BUFSIZE]; /* Flawfinder: ignore */
// *NOTE: changing the size or type of these buffers will require
// changing the sscanf below.
char keyword[256]; /* Flawfinder: ignore */
char valuestr[256]; /* Flawfinder: ignore */
keyword[0] = 0;
valuestr[0] = 0;
F32 tempF32;
U32 tempU32;
while (!feof(fp))
{
if (fgets(buffer, BUFSIZE, fp) == NULL)
{
buffer[0] = '\0';
}
sscanf( /* Flawfinder: ignore */
buffer,
" %255s %255s",
keyword, valuestr);
if (!strcmp("{", keyword))
{
continue;
}
if (!strcmp("}",keyword))
{
break;
}
else if (!strcmp("curve", keyword))
{
sscanf(valuestr,"%d",&tempU32);
setCurveType((U8) tempU32);
}
else if (!strcmp("begin",keyword))
{
sscanf(valuestr,"%g",&tempF32);
setBegin(tempF32);
}
else if (!strcmp("end",keyword))
{
sscanf(valuestr,"%g",&tempF32);
setEnd(tempF32);
}
else if (!strcmp("hollow",keyword))
{
sscanf(valuestr,"%g",&tempF32);
setHollow(tempF32);
}
else
{
llwarns << "unknown keyword " << keyword << " in profile import" << llendl;
}
}
return TRUE;
}
BOOL LLProfileParams::exportFile(LLFILE *fp) const
{
fprintf(fp,"\t\tprofile 0\n");
fprintf(fp,"\t\t{\n");
fprintf(fp,"\t\t\tcurve\t%d\n", getCurveType());
fprintf(fp,"\t\t\tbegin\t%g\n", getBegin());
fprintf(fp,"\t\t\tend\t%g\n", getEnd());
fprintf(fp,"\t\t\thollow\t%g\n", getHollow());
fprintf(fp, "\t\t}\n");
return TRUE;
}
BOOL LLProfileParams::importLegacyStream(std::istream& input_stream)
{
LLMemType m1(LLMemType::MTYPE_VOLUME);
const S32 BUFSIZE = 16384;
char buffer[BUFSIZE]; /* Flawfinder: ignore */
// *NOTE: changing the size or type of these buffers will require
// changing the sscanf below.
char keyword[256]; /* Flawfinder: ignore */
char valuestr[256]; /* Flawfinder: ignore */
keyword[0] = 0;
valuestr[0] = 0;
F32 tempF32;
U32 tempU32;
while (input_stream.good())
{
input_stream.getline(buffer, BUFSIZE);
sscanf( /* Flawfinder: ignore */
buffer,
" %255s %255s",
keyword,
valuestr);
if (!strcmp("{", keyword))
{
continue;
}
if (!strcmp("}",keyword))
{
break;
}
else if (!strcmp("curve", keyword))
{
sscanf(valuestr,"%d",&tempU32);
setCurveType((U8) tempU32);
}
else if (!strcmp("begin",keyword))
{
sscanf(valuestr,"%g",&tempF32);
setBegin(tempF32);
}
else if (!strcmp("end",keyword))
{
sscanf(valuestr,"%g",&tempF32);
setEnd(tempF32);
}
else if (!strcmp("hollow",keyword))
{
sscanf(valuestr,"%g",&tempF32);
setHollow(tempF32);
}
else
{
llwarns << "unknown keyword " << keyword << " in profile import" << llendl;
}
}
return TRUE;
}
BOOL LLProfileParams::exportLegacyStream(std::ostream& output_stream) const
{
output_stream <<"\t\tprofile 0\n";
output_stream <<"\t\t{\n";
output_stream <<"\t\t\tcurve\t" << (S32) getCurveType() << "\n";
output_stream <<"\t\t\tbegin\t" << getBegin() << "\n";
output_stream <<"\t\t\tend\t" << getEnd() << "\n";
output_stream <<"\t\t\thollow\t" << getHollow() << "\n";
output_stream << "\t\t}\n";
return TRUE;
}
LLSD LLProfileParams::asLLSD() const
{
LLSD sd;
sd["curve"] = getCurveType();
sd["begin"] = getBegin();
sd["end"] = getEnd();
sd["hollow"] = getHollow();
return sd;
}
bool LLProfileParams::fromLLSD(LLSD& sd)
{
setCurveType(sd["curve"].asInteger());
setBegin((F32)sd["begin"].asReal());
setEnd((F32)sd["end"].asReal());
setHollow((F32)sd["hollow"].asReal());
return true;
}
void LLProfileParams::copyParams(const LLProfileParams &params)
{
LLMemType m1(LLMemType::MTYPE_VOLUME);
setCurveType(params.getCurveType());
setBegin(params.getBegin());
setEnd(params.getEnd());
setHollow(params.getHollow());
}
LLPath::~LLPath()
{
}
void LLPath::genNGon(const LLPathParams& params, S32 sides, F32 startOff, F32 end_scale, F32 twist_scale)
{
// Generates a circular path, starting at (1, 0, 0), counterclockwise along the xz plane.
const F32 tableScale[] = { 1, 1, 1, 0.5f, 0.707107f, 0.53f, 0.525f, 0.5f };
F32 revolutions = params.getRevolutions();
F32 skew = params.getSkew();
F32 skew_mag = fabs(skew);
F32 hole_x = params.getScaleX() * (1.0f - skew_mag);
F32 hole_y = params.getScaleY();
// Calculate taper begin/end for x,y (Negative means taper the beginning)
F32 taper_x_begin = 1.0f;
F32 taper_x_end = 1.0f - params.getTaperX();
F32 taper_y_begin = 1.0f;
F32 taper_y_end = 1.0f - params.getTaperY();
if ( taper_x_end > 1.0f )
{
// Flip tapering.
taper_x_begin = 2.0f - taper_x_end;
taper_x_end = 1.0f;
}
if ( taper_y_end > 1.0f )
{
// Flip tapering.
taper_y_begin = 2.0f - taper_y_end;
taper_y_end = 1.0f;
}
// For spheres, the radius is usually zero.
F32 radius_start = 0.5f;
if (sides < 8)
{
radius_start = tableScale[sides];
}
// Scale the radius to take the hole size into account.
radius_start *= 1.0f - hole_y;
// Now check the radius offset to calculate the start,end radius. (Negative means
// decrease the start radius instead).
F32 radius_end = radius_start;
F32 radius_offset = params.getRadiusOffset();
if (radius_offset < 0.f)
{
radius_start *= 1.f + radius_offset;
}
else
{
radius_end *= 1.f - radius_offset;
}
// Is the path NOT a closed loop?
mOpen = ( (params.getEnd()*end_scale - params.getBegin() < 1.0f) ||
(skew_mag > 0.001f) ||
(fabs(taper_x_end - taper_x_begin) > 0.001f) ||
(fabs(taper_y_end - taper_y_begin) > 0.001f) ||
(fabs(radius_end - radius_start) > 0.001f) );
F32 ang, c, s;
LLQuaternion twist, qang;
PathPt *pt;
LLVector3 path_axis (1.f, 0.f, 0.f);
//LLVector3 twist_axis(0.f, 0.f, 1.f);
F32 twist_begin = params.getTwistBegin() * twist_scale;
F32 twist_end = params.getTwist() * twist_scale;
// We run through this once before the main loop, to make sure
// the path begins at the correct cut.
F32 step= 1.0f / sides;
F32 t = params.getBegin();
pt = vector_append(mPath, 1);
ang = 2.0f*F_PI*revolutions * t;
s = sin(ang)*lerp(radius_start, radius_end, t);
c = cos(ang)*lerp(radius_start, radius_end, t);
pt->mPos.setVec(0 + lerp(0,params.getShear().mV[0],s)
+ lerp(-skew ,skew, t) * 0.5f,
c + lerp(0,params.getShear().mV[1],s),
s);
pt->mScale.mV[VX] = hole_x * lerp(taper_x_begin, taper_x_end, t);
pt->mScale.mV[VY] = hole_y * lerp(taper_y_begin, taper_y_end, t);
pt->mTexT = t;
// Twist rotates the path along the x,y plane (I think) - DJS 04/05/02
twist.setQuat (lerp(twist_begin,twist_end,t) * 2.f * F_PI - F_PI,0,0,1);
// Rotate the point around the circle's center.
qang.setQuat (ang,path_axis);
pt->mRot = twist * qang;
t+=step;
// Snap to a quantized parameter, so that cut does not
// affect most sample points.
t = ((S32)(t * sides)) / (F32)sides;
// Run through the non-cut dependent points.
while (t < params.getEnd())
{
pt = vector_append(mPath, 1);
ang = 2.0f*F_PI*revolutions * t;
c = cos(ang)*lerp(radius_start, radius_end, t);
s = sin(ang)*lerp(radius_start, radius_end, t);
pt->mPos.setVec(0 + lerp(0,params.getShear().mV[0],s)
+ lerp(-skew ,skew, t) * 0.5f,
c + lerp(0,params.getShear().mV[1],s),
s);
pt->mScale.mV[VX] = hole_x * lerp(taper_x_begin, taper_x_end, t);
pt->mScale.mV[VY] = hole_y * lerp(taper_y_begin, taper_y_end, t);
pt->mTexT = t;
// Twist rotates the path along the x,y plane (I think) - DJS 04/05/02
twist.setQuat (lerp(twist_begin,twist_end,t) * 2.f * F_PI - F_PI,0,0,1);
// Rotate the point around the circle's center.
qang.setQuat (ang,path_axis);
pt->mRot = twist * qang;
t+=step;
}
// Make one final pass for the end cut.
t = params.getEnd();
pt = vector_append(mPath, 1);
ang = 2.0f*F_PI*revolutions * t;
c = cos(ang)*lerp(radius_start, radius_end, t);
s = sin(ang)*lerp(radius_start, radius_end, t);
pt->mPos.setVec(0 + lerp(0,params.getShear().mV[0],s)
+ lerp(-skew ,skew, t) * 0.5f,
c + lerp(0,params.getShear().mV[1],s),
s);
pt->mScale.mV[VX] = hole_x * lerp(taper_x_begin, taper_x_end, t);
pt->mScale.mV[VY] = hole_y * lerp(taper_y_begin, taper_y_end, t);
pt->mTexT = t;
// Twist rotates the path along the x,y plane (I think) - DJS 04/05/02
twist.setQuat (lerp(twist_begin,twist_end,t) * 2.f * F_PI - F_PI,0,0,1);
// Rotate the point around the circle's center.
qang.setQuat (ang,path_axis);
pt->mRot = twist * qang;
mTotal = mPath.size();
}
const LLVector2 LLPathParams::getBeginScale() const
{
LLVector2 begin_scale(1.f, 1.f);
if (getScaleX() > 1)
{
begin_scale.mV[0] = 2-getScaleX();
}
if (getScaleY() > 1)
{
begin_scale.mV[1] = 2-getScaleY();
}
return begin_scale;
}
const LLVector2 LLPathParams::getEndScale() const
{
LLVector2 end_scale(1.f, 1.f);
if (getScaleX() < 1)
{
end_scale.mV[0] = getScaleX();
}
if (getScaleY() < 1)
{
end_scale.mV[1] = getScaleY();
}
return end_scale;
}
BOOL LLPath::generate(const LLPathParams& params, F32 detail, S32 split,
BOOL is_sculpted, S32 sculpt_size)
{
LLMemType m1(LLMemType::MTYPE_VOLUME);
if ((!mDirty) && (!is_sculpted))
{
return FALSE;
}
if (detail < MIN_LOD)
{
llinfos << "Generating path with LOD < MIN! Clamping to 1" << llendl;
detail = MIN_LOD;
}
mDirty = FALSE;
S32 np = 2; // hardcode for line
mPath.clear();
mOpen = TRUE;
// Is this 0xf0 mask really necessary? DK 03/02/05
switch (params.getCurveType() & 0xf0)
{
default:
case LL_PCODE_PATH_LINE:
{
// Take the begin/end twist into account for detail.
np = llfloor(fabs(params.getTwistBegin() - params.getTwist()) * 3.5f * (detail-0.5f)) + 2;
if (np < split+2)
{
np = split+2;
}
mStep = 1.0f / (np-1);
mPath.resize(np);
LLVector2 start_scale = params.getBeginScale();
LLVector2 end_scale = params.getEndScale();
for (S32 i=0;i<np;i++)
{
F32 t = lerp(params.getBegin(),params.getEnd(),(F32)i * mStep);
mPath[i].mPos.setVec(lerp(0,params.getShear().mV[0],t),
lerp(0,params.getShear().mV[1],t),
t - 0.5f);
mPath[i].mRot.setQuat(lerp(F_PI * params.getTwistBegin(),F_PI * params.getTwist(),t),0,0,1);
mPath[i].mScale.mV[0] = lerp(start_scale.mV[0],end_scale.mV[0],t);
mPath[i].mScale.mV[1] = lerp(start_scale.mV[1],end_scale.mV[1],t);
mPath[i].mTexT = t;
}
}
break;
case LL_PCODE_PATH_CIRCLE:
{
// Increase the detail as the revolutions and twist increase.
F32 twist_mag = fabs(params.getTwistBegin() - params.getTwist());
S32 sides = (S32)llfloor(llfloor((MIN_DETAIL_FACES * detail + twist_mag * 3.5f * (detail-0.5f))) * params.getRevolutions());
if (is_sculpted)
sides = sculpt_size;
genNGon(params, sides);
}
break;
case LL_PCODE_PATH_CIRCLE2:
{
if (params.getEnd() - params.getBegin() >= 0.99f &&
params.getScaleX() >= .99f)
{
mOpen = FALSE;
}
//genNGon(params, llfloor(MIN_DETAIL_FACES * detail), 4.f, 0.f);
genNGon(params, llfloor(MIN_DETAIL_FACES * detail));
F32 t = 0.f;
F32 tStep = 1.0f / mPath.size();
F32 toggle = 0.5f;
for (S32 i=0;i<(S32)mPath.size();i++)
{
mPath[i].mPos.mV[0] = toggle;
if (toggle == 0.5f)
toggle = -0.5f;
else
toggle = 0.5f;
t += tStep;
}
}
break;
case LL_PCODE_PATH_TEST:
np = 5;
mStep = 1.0f / (np-1);
mPath.resize(np);
for (S32 i=0;i<np;i++)
{
F32 t = (F32)i * mStep;
mPath[i].mPos.setVec(0,
lerp(0, -sin(F_PI*params.getTwist()*t)*0.5f,t),
lerp(-0.5, cos(F_PI*params.getTwist()*t)*0.5f,t));
mPath[i].mScale.mV[0] = lerp(1,params.getScale().mV[0],t);
mPath[i].mScale.mV[1] = lerp(1,params.getScale().mV[1],t);
mPath[i].mTexT = t;
mPath[i].mRot.setQuat(F_PI * params.getTwist() * t,1,0,0);
}
break;
};
if (params.getTwist() != params.getTwistBegin()) mOpen = TRUE;
//if ((int(fabsf(params.getTwist() - params.getTwistBegin())*100))%100 != 0) {
// mOpen = TRUE;
//}
return TRUE;
}
BOOL LLDynamicPath::generate(const LLPathParams& params, F32 detail, S32 split,
BOOL is_sculpted, S32 sculpt_size)
{
LLMemType m1(LLMemType::MTYPE_VOLUME);
mOpen = TRUE; // Draw end caps
if (getPathLength() == 0)
{
// Path hasn't been generated yet.
// Some algorithms later assume at least TWO path points.
resizePath(2);
for (U32 i = 0; i < 2; i++)
{
mPath[i].mPos.setVec(0, 0, 0);
mPath[i].mRot.setQuat(0, 0, 0);
mPath[i].mScale.setVec(1, 1);
mPath[i].mTexT = 0;
}
}
return TRUE;
}
BOOL LLPathParams::importFile(LLFILE *fp)
{
LLMemType m1(LLMemType::MTYPE_VOLUME);
const S32 BUFSIZE = 16384;
char buffer[BUFSIZE]; /* Flawfinder: ignore */
// *NOTE: changing the size or type of these buffers will require
// changing the sscanf below.
char keyword[256]; /* Flawfinder: ignore */
char valuestr[256]; /* Flawfinder: ignore */
keyword[0] = 0;
valuestr[0] = 0;
F32 tempF32;
F32 x, y;
U32 tempU32;
while (!feof(fp))
{
if (fgets(buffer, BUFSIZE, fp) == NULL)
{
buffer[0] = '\0';
}
sscanf( /* Flawfinder: ignore */
buffer,
" %255s %255s",
keyword, valuestr);
if (!strcmp("{", keyword))
{
continue;
}
if (!strcmp("}",keyword))
{
break;
}
else if (!strcmp("curve", keyword))
{
sscanf(valuestr,"%d",&tempU32);
setCurveType((U8) tempU32);
}
else if (!strcmp("begin",keyword))
{
sscanf(valuestr,"%g",&tempF32);
setBegin(tempF32);
}
else if (!strcmp("end",keyword))
{
sscanf(valuestr,"%g",&tempF32);
setEnd(tempF32);
}
else if (!strcmp("scale",keyword))
{
// Legacy for one dimensional scale per path
sscanf(valuestr,"%g",&tempF32);
setScale(tempF32, tempF32);
}
else if (!strcmp("scale_x", keyword))
{
sscanf(valuestr, "%g", &x);
setScaleX(x);
}
else if (!strcmp("scale_y", keyword))
{
sscanf(valuestr, "%g", &y);
setScaleY(y);
}
else if (!strcmp("shear_x", keyword))
{
sscanf(valuestr, "%g", &x);
setShearX(x);
}
else if (!strcmp("shear_y", keyword))
{
sscanf(valuestr, "%g", &y);
setShearY(y);
}
else if (!strcmp("twist",keyword))
{
sscanf(valuestr,"%g",&tempF32);
setTwist(tempF32);
}
else if (!strcmp("twist_begin", keyword))
{
sscanf(valuestr, "%g", &y);
setTwistBegin(y);
}
else if (!strcmp("radius_offset", keyword))
{
sscanf(valuestr, "%g", &y);
setRadiusOffset(y);
}
else if (!strcmp("taper_x", keyword))
{
sscanf(valuestr, "%g", &y);
setTaperX(y);
}
else if (!strcmp("taper_y", keyword))
{
sscanf(valuestr, "%g", &y);
setTaperY(y);
}
else if (!strcmp("revolutions", keyword))
{
sscanf(valuestr, "%g", &y);
setRevolutions(y);
}
else if (!strcmp("skew", keyword))
{
sscanf(valuestr, "%g", &y);
setSkew(y);
}
else
{
llwarns << "unknown keyword " << " in path import" << llendl;
}
}
return TRUE;
}
BOOL LLPathParams::exportFile(LLFILE *fp) const
{
fprintf(fp, "\t\tpath 0\n");
fprintf(fp, "\t\t{\n");
fprintf(fp, "\t\t\tcurve\t%d\n", getCurveType());
fprintf(fp, "\t\t\tbegin\t%g\n", getBegin());
fprintf(fp, "\t\t\tend\t%g\n", getEnd());
fprintf(fp, "\t\t\tscale_x\t%g\n", getScaleX() );
fprintf(fp, "\t\t\tscale_y\t%g\n", getScaleY() );
fprintf(fp, "\t\t\tshear_x\t%g\n", getShearX() );
fprintf(fp, "\t\t\tshear_y\t%g\n", getShearY() );
fprintf(fp,"\t\t\ttwist\t%g\n", getTwist());
fprintf(fp,"\t\t\ttwist_begin\t%g\n", getTwistBegin());
fprintf(fp,"\t\t\tradius_offset\t%g\n", getRadiusOffset());
fprintf(fp,"\t\t\ttaper_x\t%g\n", getTaperX());
fprintf(fp,"\t\t\ttaper_y\t%g\n", getTaperY());
fprintf(fp,"\t\t\trevolutions\t%g\n", getRevolutions());
fprintf(fp,"\t\t\tskew\t%g\n", getSkew());
fprintf(fp, "\t\t}\n");
return TRUE;
}
BOOL LLPathParams::importLegacyStream(std::istream& input_stream)
{
LLMemType m1(LLMemType::MTYPE_VOLUME);
const S32 BUFSIZE = 16384;
char buffer[BUFSIZE]; /* Flawfinder: ignore */
// *NOTE: changing the size or type of these buffers will require
// changing the sscanf below.
char keyword[256]; /* Flawfinder: ignore */
char valuestr[256]; /* Flawfinder: ignore */
keyword[0] = 0;
valuestr[0] = 0;
F32 tempF32;
F32 x, y;
U32 tempU32;
while (input_stream.good())
{
input_stream.getline(buffer, BUFSIZE);
sscanf( /* Flawfinder: ignore */
buffer,
" %255s %255s",
keyword, valuestr);
if (!strcmp("{", keyword))
{
continue;
}
if (!strcmp("}",keyword))
{
break;
}
else if (!strcmp("curve", keyword))
{
sscanf(valuestr,"%d",&tempU32);
setCurveType((U8) tempU32);
}
else if (!strcmp("begin",keyword))
{
sscanf(valuestr,"%g",&tempF32);
setBegin(tempF32);
}
else if (!strcmp("end",keyword))
{
sscanf(valuestr,"%g",&tempF32);
setEnd(tempF32);
}
else if (!strcmp("scale",keyword))
{
// Legacy for one dimensional scale per path
sscanf(valuestr,"%g",&tempF32);
setScale(tempF32, tempF32);
}
else if (!strcmp("scale_x", keyword))
{
sscanf(valuestr, "%g", &x);
setScaleX(x);
}
else if (!strcmp("scale_y", keyword))
{
sscanf(valuestr, "%g", &y);
setScaleY(y);
}
else if (!strcmp("shear_x", keyword))
{
sscanf(valuestr, "%g", &x);
setShearX(x);
}
else if (!strcmp("shear_y", keyword))
{
sscanf(valuestr, "%g", &y);
setShearY(y);
}
else if (!strcmp("twist",keyword))
{
sscanf(valuestr,"%g",&tempF32);
setTwist(tempF32);
}
else if (!strcmp("twist_begin", keyword))
{
sscanf(valuestr, "%g", &y);
setTwistBegin(y);
}
else if (!strcmp("radius_offset", keyword))
{
sscanf(valuestr, "%g", &y);
setRadiusOffset(y);
}
else if (!strcmp("taper_x", keyword))
{
sscanf(valuestr, "%g", &y);
setTaperX(y);
}
else if (!strcmp("taper_y", keyword))
{
sscanf(valuestr, "%g", &y);
setTaperY(y);
}
else if (!strcmp("revolutions", keyword))
{
sscanf(valuestr, "%g", &y);
setRevolutions(y);
}
else if (!strcmp("skew", keyword))
{
sscanf(valuestr, "%g", &y);
setSkew(y);
}
else
{
llwarns << "unknown keyword " << " in path import" << llendl;
}
}
return TRUE;
}
BOOL LLPathParams::exportLegacyStream(std::ostream& output_stream) const
{
output_stream << "\t\tpath 0\n";
output_stream << "\t\t{\n";
output_stream << "\t\t\tcurve\t" << (S32) getCurveType() << "\n";
output_stream << "\t\t\tbegin\t" << getBegin() << "\n";
output_stream << "\t\t\tend\t" << getEnd() << "\n";
output_stream << "\t\t\tscale_x\t" << getScaleX() << "\n";
output_stream << "\t\t\tscale_y\t" << getScaleY() << "\n";
output_stream << "\t\t\tshear_x\t" << getShearX() << "\n";
output_stream << "\t\t\tshear_y\t" << getShearY() << "\n";
output_stream <<"\t\t\ttwist\t" << getTwist() << "\n";
output_stream <<"\t\t\ttwist_begin\t" << getTwistBegin() << "\n";
output_stream <<"\t\t\tradius_offset\t" << getRadiusOffset() << "\n";
output_stream <<"\t\t\ttaper_x\t" << getTaperX() << "\n";
output_stream <<"\t\t\ttaper_y\t" << getTaperY() << "\n";
output_stream <<"\t\t\trevolutions\t" << getRevolutions() << "\n";
output_stream <<"\t\t\tskew\t" << getSkew() << "\n";
output_stream << "\t\t}\n";
return TRUE;
}
LLSD LLPathParams::asLLSD() const
{
LLSD sd = LLSD();
sd["curve"] = getCurveType();
sd["begin"] = getBegin();
sd["end"] = getEnd();
sd["scale_x"] = getScaleX();
sd["scale_y"] = getScaleY();
sd["shear_x"] = getShearX();
sd["shear_y"] = getShearY();
sd["twist"] = getTwist();
sd["twist_begin"] = getTwistBegin();
sd["radius_offset"] = getRadiusOffset();
sd["taper_x"] = getTaperX();
sd["taper_y"] = getTaperY();
sd["revolutions"] = getRevolutions();
sd["skew"] = getSkew();
return sd;
}
bool LLPathParams::fromLLSD(LLSD& sd)
{
setCurveType(sd["curve"].asInteger());
setBegin((F32)sd["begin"].asReal());
setEnd((F32)sd["end"].asReal());
setScaleX((F32)sd["scale_x"].asReal());
setScaleY((F32)sd["scale_y"].asReal());
setShearX((F32)sd["shear_x"].asReal());
setShearY((F32)sd["shear_y"].asReal());
setTwist((F32)sd["twist"].asReal());
setTwistBegin((F32)sd["twist_begin"].asReal());
setRadiusOffset((F32)sd["radius_offset"].asReal());
setTaperX((F32)sd["taper_x"].asReal());
setTaperY((F32)sd["taper_y"].asReal());
setRevolutions((F32)sd["revolutions"].asReal());
setSkew((F32)sd["skew"].asReal());
return true;
}
void LLPathParams::copyParams(const LLPathParams &params)
{
setCurveType(params.getCurveType());
setBegin(params.getBegin());
setEnd(params.getEnd());
setScale(params.getScaleX(), params.getScaleY() );
setShear(params.getShearX(), params.getShearY() );
setTwist(params.getTwist());
setTwistBegin(params.getTwistBegin());
setRadiusOffset(params.getRadiusOffset());
setTaper( params.getTaperX(), params.getTaperY() );
setRevolutions(params.getRevolutions());
setSkew(params.getSkew());
}
S32 profile_delete_lock = 1 ;
LLProfile::~LLProfile()
{
if(profile_delete_lock)
{
llerrs << "LLProfile should not be deleted here!" << llendl ;
}
}
S32 LLVolume::sNumMeshPoints = 0;
LLVolume::LLVolume(const LLVolumeParams &params, const F32 detail, const BOOL generate_single_face, const BOOL is_unique)
: mParams(params)
{
LLMemType m1(LLMemType::MTYPE_VOLUME);
mUnique = is_unique;
mFaceMask = 0x0;
mDetail = detail;
mSculptLevel = -2;
// set defaults
if (mParams.getPathParams().getCurveType() == LL_PCODE_PATH_FLEXIBLE)
{
mPathp = new LLDynamicPath();
}
else
{
mPathp = new LLPath();
}
mProfilep = new LLProfile();
mGenerateSingleFace = generate_single_face;
generate();
if (mParams.getSculptID().isNull() && params.getSculptType() == LL_SCULPT_TYPE_NONE)
{
createVolumeFaces();
}
}
void LLVolume::resizePath(S32 length)
{
mPathp->resizePath(length);
mVolumeFaces.clear();
}
void LLVolume::regen()
{
generate();
createVolumeFaces();
}
void LLVolume::genBinormals(S32 face)
{
mVolumeFaces[face].createBinormals();
}
LLVolume::~LLVolume()
{
sNumMeshPoints -= mMesh.size();
delete mPathp;
profile_delete_lock = 0 ;
delete mProfilep;
profile_delete_lock = 1 ;
mPathp = NULL;
mProfilep = NULL;
mVolumeFaces.clear();
}
BOOL LLVolume::generate()
{
LLMemType m1(LLMemType::MTYPE_VOLUME);
llassert_always(mProfilep);
//Added 10.03.05 Dave Parks
// Split is a parameter to LLProfile::generate that tesselates edges on the profile
// to prevent lighting and texture interpolation errors on triangles that are
// stretched due to twisting or scaling on the path.
S32 split = (S32) ((mDetail)*0.66f);
if (mParams.getPathParams().getCurveType() == LL_PCODE_PATH_LINE &&
(mParams.getPathParams().getScale().mV[0] != 1.0f ||
mParams.getPathParams().getScale().mV[1] != 1.0f) &&
(mParams.getProfileParams().getCurveType() == LL_PCODE_PROFILE_SQUARE ||
mParams.getProfileParams().getCurveType() == LL_PCODE_PROFILE_ISOTRI ||
mParams.getProfileParams().getCurveType() == LL_PCODE_PROFILE_EQUALTRI ||
mParams.getProfileParams().getCurveType() == LL_PCODE_PROFILE_RIGHTTRI))
{
split = 0;
}
mLODScaleBias.setVec(0.5f, 0.5f, 0.5f);
F32 profile_detail = mDetail;
F32 path_detail = mDetail;
U8 path_type = mParams.getPathParams().getCurveType();
U8 profile_type = mParams.getProfileParams().getCurveType();
if (path_type == LL_PCODE_PATH_LINE && profile_type == LL_PCODE_PROFILE_CIRCLE)
{ //cylinders don't care about Z-Axis
mLODScaleBias.setVec(0.6f, 0.6f, 0.0f);
}
else if (path_type == LL_PCODE_PATH_CIRCLE)
{
mLODScaleBias.setVec(0.6f, 0.6f, 0.6f);
}
//********************************************************************
//debug info, to be removed
if((U32)(mPathp->mPath.size() * mProfilep->mProfile.size()) > (1u << 20))
{
llinfos << "sizeS: " << mPathp->mPath.size() << " sizeT: " << mProfilep->mProfile.size() << llendl ;
llinfos << "path_detail : " << path_detail << " split: " << split << " profile_detail: " << profile_detail << llendl ;
llinfos << mParams << llendl ;
llinfos << "more info to check if mProfilep is deleted or not." << llendl ;
llinfos << mProfilep->mNormals.size() << " : " << mProfilep->mFaces.size() << " : " << mProfilep->mEdgeNormals.size() << " : " << mProfilep->mEdgeCenters.size() << llendl ;
llerrs << "LLVolume corrupted!" << llendl ;
}
//********************************************************************
BOOL regenPath = mPathp->generate(mParams.getPathParams(), path_detail, split);
BOOL regenProf = mProfilep->generate(mParams.getProfileParams(), mPathp->isOpen(),profile_detail, split);
if (regenPath || regenProf )
{
S32 sizeS = mPathp->mPath.size();
S32 sizeT = mProfilep->mProfile.size();
//********************************************************************
//debug info, to be removed
if((U32)(sizeS * sizeT) > (1u << 20))
{
llinfos << "regenPath: " << (S32)regenPath << " regenProf: " << (S32)regenProf << llendl ;
llinfos << "sizeS: " << sizeS << " sizeT: " << sizeT << llendl ;
llinfos << "path_detail : " << path_detail << " split: " << split << " profile_detail: " << profile_detail << llendl ;
llinfos << mParams << llendl ;
llinfos << "more info to check if mProfilep is deleted or not." << llendl ;
llinfos << mProfilep->mNormals.size() << " : " << mProfilep->mFaces.size() << " : " << mProfilep->mEdgeNormals.size() << " : " << mProfilep->mEdgeCenters.size() << llendl ;
llerrs << "LLVolume corrupted!" << llendl ;
}
//********************************************************************
sNumMeshPoints -= mMesh.size();
mMesh.resize(sizeT * sizeS);
sNumMeshPoints += mMesh.size();
//generate vertex positions
// Run along the path.
for (S32 s = 0; s < sizeS; ++s)
{
LLVector2 scale = mPathp->mPath[s].mScale;
LLQuaternion rot = mPathp->mPath[s].mRot;
// Run along the profile.
for (S32 t = 0; t < sizeT; ++t)
{
S32 m = s*sizeT + t;
Point& pt = mMesh[m];
pt.mPos.mV[0] = mProfilep->mProfile[t].mV[0] * scale.mV[0];
pt.mPos.mV[1] = mProfilep->mProfile[t].mV[1] * scale.mV[1];
pt.mPos.mV[2] = 0.0f;
pt.mPos = pt.mPos * rot;
pt.mPos += mPathp->mPath[s].mPos;
}
}
for (std::vector<LLProfile::Face>::iterator iter = mProfilep->mFaces.begin();
iter != mProfilep->mFaces.end(); ++iter)
{
LLFaceID id = iter->mFaceID;
mFaceMask |= id;
}
return TRUE;
}
return FALSE;
}
void LLVolume::createVolumeFaces()
{
LLMemType m1(LLMemType::MTYPE_VOLUME);
if (mGenerateSingleFace)
{
// do nothing
}
else
{
S32 num_faces = getNumFaces();
BOOL partial_build = TRUE;
if (num_faces != mVolumeFaces.size())
{
partial_build = FALSE;
mVolumeFaces.resize(num_faces);
}
// Initialize volume faces with parameter data
for (S32 i = 0; i < (S32)mVolumeFaces.size(); i++)
{
LLVolumeFace& vf = mVolumeFaces[i];
LLProfile::Face& face = mProfilep->mFaces[i];
vf.mBeginS = face.mIndex;
vf.mNumS = face.mCount;
if (vf.mNumS < 0)
{
llerrs << "Volume face corruption detected." << llendl;
}
vf.mBeginT = 0;
vf.mNumT= getPath().mPath.size();
vf.mID = i;
// Set the type mask bits correctly
if (mParams.getProfileParams().getHollow() > 0)
{
vf.mTypeMask |= LLVolumeFace::HOLLOW_MASK;
}
if (mProfilep->isOpen())
{
vf.mTypeMask |= LLVolumeFace::OPEN_MASK;
}
if (face.mCap)
{
vf.mTypeMask |= LLVolumeFace::CAP_MASK;
if (face.mFaceID == LL_FACE_PATH_BEGIN)
{
vf.mTypeMask |= LLVolumeFace::TOP_MASK;
}
else
{
llassert(face.mFaceID == LL_FACE_PATH_END);
vf.mTypeMask |= LLVolumeFace::BOTTOM_MASK;
}
}
else if (face.mFaceID & (LL_FACE_PROFILE_BEGIN | LL_FACE_PROFILE_END))
{
vf.mTypeMask |= LLVolumeFace::FLAT_MASK | LLVolumeFace::END_MASK;
}
else
{
vf.mTypeMask |= LLVolumeFace::SIDE_MASK;
if (face.mFlat)
{
vf.mTypeMask |= LLVolumeFace::FLAT_MASK;
}
if (face.mFaceID & LL_FACE_INNER_SIDE)
{
vf.mTypeMask |= LLVolumeFace::INNER_MASK;
if (face.mFlat && vf.mNumS > 2)
{ //flat inner faces have to copy vert normals
vf.mNumS = vf.mNumS*2;
if (vf.mNumS < 0)
{
llerrs << "Volume face corruption detected." << llendl;
}
}
}
else
{
vf.mTypeMask |= LLVolumeFace::OUTER_MASK;
}
}
}
for (face_list_t::iterator iter = mVolumeFaces.begin();
iter != mVolumeFaces.end(); ++iter)
{
(*iter).create(this, partial_build);
}
}
}
inline LLVector3 sculpt_rgb_to_vector(U8 r, U8 g, U8 b)
{
// maps RGB values to vector values [0..255] -> [-0.5..0.5]
LLVector3 value;
value.mV[VX] = r / 255.f - 0.5f;
value.mV[VY] = g / 255.f - 0.5f;
value.mV[VZ] = b / 255.f - 0.5f;
return value;
}
inline U32 sculpt_xy_to_index(U32 x, U32 y, U16 sculpt_width, U16 sculpt_height, S8 sculpt_components)
{
U32 index = (x + y * sculpt_width) * sculpt_components;
return index;
}
inline U32 sculpt_st_to_index(S32 s, S32 t, S32 size_s, S32 size_t, U16 sculpt_width, U16 sculpt_height, S8 sculpt_components)
{
U32 x = (U32) ((F32)s/(size_s) * (F32) sculpt_width);
U32 y = (U32) ((F32)t/(size_t) * (F32) sculpt_height);
return sculpt_xy_to_index(x, y, sculpt_width, sculpt_height, sculpt_components);
}
inline LLVector3 sculpt_index_to_vector(U32 index, const U8* sculpt_data)
{
LLVector3 v = sculpt_rgb_to_vector(sculpt_data[index], sculpt_data[index+1], sculpt_data[index+2]);
return v;
}
inline LLVector3 sculpt_st_to_vector(S32 s, S32 t, S32 size_s, S32 size_t, U16 sculpt_width, U16 sculpt_height, S8 sculpt_components, const U8* sculpt_data)
{
U32 index = sculpt_st_to_index(s, t, size_s, size_t, sculpt_width, sculpt_height, sculpt_components);
return sculpt_index_to_vector(index, sculpt_data);
}
inline LLVector3 sculpt_xy_to_vector(U32 x, U32 y, U16 sculpt_width, U16 sculpt_height, S8 sculpt_components, const U8* sculpt_data)
{
U32 index = sculpt_xy_to_index(x, y, sculpt_width, sculpt_height, sculpt_components);
return sculpt_index_to_vector(index, sculpt_data);
}
F32 LLVolume::sculptGetSurfaceArea()
{
// test to see if image has enough variation to create non-degenerate geometry
F32 area = 0;
S32 sizeS = mPathp->mPath.size();
S32 sizeT = mProfilep->mProfile.size();
for (S32 s = 0; s < sizeS-1; s++)
{
for (S32 t = 0; t < sizeT-1; t++)
{
// get four corners of quad
LLVector3 p1 = mMesh[(s )*sizeT + (t )].mPos;
LLVector3 p2 = mMesh[(s+1)*sizeT + (t )].mPos;
LLVector3 p3 = mMesh[(s )*sizeT + (t+1)].mPos;
LLVector3 p4 = mMesh[(s+1)*sizeT + (t+1)].mPos;
// compute the area of the quad by taking the length of the cross product of the two triangles
LLVector3 cross1 = (p1 - p2) % (p1 - p3);
LLVector3 cross2 = (p4 - p2) % (p4 - p3);
area += (cross1.magVec() + cross2.magVec()) / 2.0;
}
}
return area;
}
// create placeholder shape
void LLVolume::sculptGeneratePlaceholder()
{
LLMemType m1(LLMemType::MTYPE_VOLUME);
S32 sizeS = mPathp->mPath.size();
S32 sizeT = mProfilep->mProfile.size();
S32 line = 0;
// for now, this is a sphere.
for (S32 s = 0; s < sizeS; s++)
{
for (S32 t = 0; t < sizeT; t++)
{
S32 i = t + line;
Point& pt = mMesh[i];
F32 u = (F32)s/(sizeS-1);
F32 v = (F32)t/(sizeT-1);
const F32 RADIUS = (F32) 0.3;
pt.mPos.mV[0] = (F32)(sin(F_PI * v) * cos(2.0 * F_PI * u) * RADIUS);
pt.mPos.mV[1] = (F32)(sin(F_PI * v) * sin(2.0 * F_PI * u) * RADIUS);
pt.mPos.mV[2] = (F32)(cos(F_PI * v) * RADIUS);
}
line += sizeT;
}
}
// create the vertices from the map
void LLVolume::sculptGenerateMapVertices(U16 sculpt_width, U16 sculpt_height, S8 sculpt_components, const U8* sculpt_data, U8 sculpt_type)
{
U8 sculpt_stitching = sculpt_type & LL_SCULPT_TYPE_MASK;
BOOL sculpt_invert = sculpt_type & LL_SCULPT_FLAG_INVERT;
BOOL sculpt_mirror = sculpt_type & LL_SCULPT_FLAG_MIRROR;
BOOL reverse_horizontal = (sculpt_invert ? !sculpt_mirror : sculpt_mirror); // XOR
LLMemType m1(LLMemType::MTYPE_VOLUME);
S32 sizeS = mPathp->mPath.size();
S32 sizeT = mProfilep->mProfile.size();
S32 line = 0;
for (S32 s = 0; s < sizeS; s++)
{
// Run along the profile.
for (S32 t = 0; t < sizeT; t++)
{
S32 i = t + line;
Point& pt = mMesh[i];
S32 reversed_t = t;
if (reverse_horizontal)
{
reversed_t = sizeT - t - 1;
}
U32 x = (U32) ((F32)reversed_t/(sizeT-1) * (F32) sculpt_width);
U32 y = (U32) ((F32)s/(sizeS-1) * (F32) sculpt_height);
if (y == 0) // top row stitching
{
// pinch?
if (sculpt_stitching == LL_SCULPT_TYPE_SPHERE)
{
x = sculpt_width / 2;
}
}
if (y == sculpt_height) // bottom row stitching
{
// wrap?
if (sculpt_stitching == LL_SCULPT_TYPE_TORUS)
{
y = 0;
}
else
{
y = sculpt_height - 1;
}
// pinch?
if (sculpt_stitching == LL_SCULPT_TYPE_SPHERE)
{
x = sculpt_width / 2;
}
}
if (x == sculpt_width) // side stitching
{
// wrap?
if ((sculpt_stitching == LL_SCULPT_TYPE_SPHERE) ||
(sculpt_stitching == LL_SCULPT_TYPE_TORUS) ||
(sculpt_stitching == LL_SCULPT_TYPE_CYLINDER))
{
x = 0;
}
else
{
x = sculpt_width - 1;
}
}
pt.mPos = sculpt_xy_to_vector(x, y, sculpt_width, sculpt_height, sculpt_components, sculpt_data);
if (sculpt_mirror)
{
pt.mPos.mV[VX] *= -1.f;
}
}
line += sizeT;
}
}
const S32 SCULPT_REZ_1 = 6; // changed from 4 to 6 - 6 looks round whereas 4 looks square
const S32 SCULPT_REZ_2 = 8;
const S32 SCULPT_REZ_3 = 16;
const S32 SCULPT_REZ_4 = 32;
S32 sculpt_sides(F32 detail)
{
// detail is usually one of: 1, 1.5, 2.5, 4.0.
if (detail <= 1.0)
{
return SCULPT_REZ_1;
}
if (detail <= 2.0)
{
return SCULPT_REZ_2;
}
if (detail <= 3.0)
{
return SCULPT_REZ_3;
}
else
{
return SCULPT_REZ_4;
}
}
// determine the number of vertices in both s and t direction for this sculpt
void sculpt_calc_mesh_resolution(U16 width, U16 height, U8 type, F32 detail, S32& s, S32& t)
{
// this code has the following properties:
// 1) the aspect ratio of the mesh is as close as possible to the ratio of the map
// while still using all available verts
// 2) the mesh cannot have more verts than is allowed by LOD
// 3) the mesh cannot have more verts than is allowed by the map
S32 max_vertices_lod = (S32)pow((double)sculpt_sides(detail), 2.0);
S32 max_vertices_map = width * height / 4;
S32 vertices;
if (max_vertices_map > 0)
vertices = llmin(max_vertices_lod, max_vertices_map);
else
vertices = max_vertices_lod;
F32 ratio;
if ((width == 0) || (height == 0))
ratio = 1.f;
else
ratio = (F32) width / (F32) height;
s = (S32)fsqrtf(((F32)vertices / ratio));
s = llmax(s, 4); // no degenerate sizes, please
t = vertices / s;
t = llmax(t, 4); // no degenerate sizes, please
s = vertices / t;
}
// sculpt replaces generate() for sculpted surfaces
void LLVolume::sculpt(U16 sculpt_width, U16 sculpt_height, S8 sculpt_components, const U8* sculpt_data, S32 sculpt_level)
{
LLMemType m1(LLMemType::MTYPE_VOLUME);
U8 sculpt_type = mParams.getSculptType();
BOOL data_is_empty = FALSE;
if (sculpt_width == 0 || sculpt_height == 0 || sculpt_components < 3 || sculpt_data == NULL)
{
sculpt_level = -1;
data_is_empty = TRUE;
}
S32 requested_sizeS = 0;
S32 requested_sizeT = 0;
// create oblong sculpties with high LOD always
F32 sculpt_detail = mDetail;
if (sculpt_width != sculpt_height && sculpt_detail < 4.0)
{
sculpt_detail = 4.0;
}
sculpt_calc_mesh_resolution(sculpt_width, sculpt_height, sculpt_type, sculpt_detail, requested_sizeS, requested_sizeT);
mPathp->generate(mParams.getPathParams(), sculpt_detail, 0, TRUE, requested_sizeS);
mProfilep->generate(mParams.getProfileParams(), mPathp->isOpen(), sculpt_detail, 0, TRUE, requested_sizeT);
S32 sizeS = mPathp->mPath.size(); // we requested a specific size, now see what we really got
S32 sizeT = mProfilep->mProfile.size(); // we requested a specific size, now see what we really got
// weird crash bug - DEV-11158 - trying to collect more data:
if ((sizeS == 0) || (sizeT == 0))
{
llwarns << "sculpt bad mesh size " << sizeS << " " << sizeT << llendl;
}
sNumMeshPoints -= mMesh.size();
mMesh.resize(sizeS * sizeT);
sNumMeshPoints += mMesh.size();
//generate vertex positions
if (!data_is_empty)
{
sculptGenerateMapVertices(sculpt_width, sculpt_height, sculpt_components, sculpt_data, sculpt_type);
// don't test lowest LOD to support legacy content DEV-33670
if (mDetail > SCULPT_MIN_AREA_DETAIL)
{
if (sculptGetSurfaceArea() < SCULPT_MIN_AREA)
{
data_is_empty = TRUE;
}
}
}
if (data_is_empty)
{
sculptGeneratePlaceholder();
}
for (S32 i = 0; i < (S32)mProfilep->mFaces.size(); i++)
{
mFaceMask |= mProfilep->mFaces[i].mFaceID;
}
mSculptLevel = sculpt_level;
// Delete any existing faces so that they get regenerated
mVolumeFaces.clear();
createVolumeFaces();
}
BOOL LLVolume::isCap(S32 face)
{
return mProfilep->mFaces[face].mCap;
}
BOOL LLVolume::isFlat(S32 face)
{
return mProfilep->mFaces[face].mFlat;
}
bool LLVolumeParams::operator==(const LLVolumeParams &params) const
{
return ( (getPathParams() == params.getPathParams()) &&
(getProfileParams() == params.getProfileParams()) &&
(mSculptID == params.mSculptID) &&
(mSculptType == params.mSculptType) );
}
bool LLVolumeParams::operator!=(const LLVolumeParams &params) const
{
return ( (getPathParams() != params.getPathParams()) ||
(getProfileParams() != params.getProfileParams()) ||
(mSculptID != params.mSculptID) ||
(mSculptType != params.mSculptType) );
}
bool LLVolumeParams::operator<(const LLVolumeParams &params) const
{
if( getPathParams() != params.getPathParams() )
{
return getPathParams() < params.getPathParams();
}
if (getProfileParams() != params.getProfileParams())
{
return getProfileParams() < params.getProfileParams();
}
if (mSculptID != params.mSculptID)
{
return mSculptID < params.mSculptID;
}
return mSculptType < params.mSculptType;
}
void LLVolumeParams::copyParams(const LLVolumeParams &params)
{
LLMemType m1(LLMemType::MTYPE_VOLUME);
mProfileParams.copyParams(params.mProfileParams);
mPathParams.copyParams(params.mPathParams);
mSculptID = params.getSculptID();
mSculptType = params.getSculptType();
}
// Less restricitve approx 0 for volumes
const F32 APPROXIMATELY_ZERO = 0.001f;
bool approx_zero( F32 f, F32 tolerance = APPROXIMATELY_ZERO)
{
return (f >= -tolerance) && (f <= tolerance);
}
// return true if in range (or nearly so)
static bool limit_range(F32& v, F32 min, F32 max, F32 tolerance = APPROXIMATELY_ZERO)
{
F32 min_delta = v - min;
if (min_delta < 0.f)
{
v = min;
if (!approx_zero(min_delta, tolerance))
return false;
}
F32 max_delta = max - v;
if (max_delta < 0.f)
{
v = max;
if (!approx_zero(max_delta, tolerance))
return false;
}
return true;
}
bool LLVolumeParams::setBeginAndEndS(const F32 b, const F32 e)
{
bool valid = true;
// First, clamp to valid ranges.
F32 begin = b;
valid &= limit_range(begin, 0.f, 1.f - MIN_CUT_DELTA);
F32 end = e;
if (end >= .0149f && end < MIN_CUT_DELTA) end = MIN_CUT_DELTA; // eliminate warning for common rounding error
valid &= limit_range(end, MIN_CUT_DELTA, 1.f);
valid &= limit_range(begin, 0.f, end - MIN_CUT_DELTA, .01f);
// Now set them.
mProfileParams.setBegin(begin);
mProfileParams.setEnd(end);
return valid;
}
bool LLVolumeParams::setBeginAndEndT(const F32 b, const F32 e)
{
bool valid = true;
// First, clamp to valid ranges.
F32 begin = b;
valid &= limit_range(begin, 0.f, 1.f - MIN_CUT_DELTA);
F32 end = e;
valid &= limit_range(end, MIN_CUT_DELTA, 1.f);
valid &= limit_range(begin, 0.f, end - MIN_CUT_DELTA, .01f);
// Now set them.
mPathParams.setBegin(begin);
mPathParams.setEnd(end);
return valid;
}
bool LLVolumeParams::setHollow(const F32 h)
{
// Validate the hollow based on path and profile.
U8 profile = mProfileParams.getCurveType() & LL_PCODE_PROFILE_MASK;
U8 hole_type = mProfileParams.getCurveType() & LL_PCODE_HOLE_MASK;
F32 max_hollow = HOLLOW_MAX;
// Only square holes have trouble.
if (LL_PCODE_HOLE_SQUARE == hole_type)
{
switch(profile)
{
case LL_PCODE_PROFILE_CIRCLE:
case LL_PCODE_PROFILE_CIRCLE_HALF:
case LL_PCODE_PROFILE_EQUALTRI:
max_hollow = HOLLOW_MAX_SQUARE;
}
}
F32 hollow = h;
bool valid = limit_range(hollow, HOLLOW_MIN, max_hollow);
mProfileParams.setHollow(hollow);
return valid;
}
bool LLVolumeParams::setTwistBegin(const F32 b)
{
F32 twist_begin = b;
bool valid = limit_range(twist_begin, TWIST_MIN, TWIST_MAX);
mPathParams.setTwistBegin(twist_begin);
return valid;
}
bool LLVolumeParams::setTwistEnd(const F32 e)
{
F32 twist_end = e;
bool valid = limit_range(twist_end, TWIST_MIN, TWIST_MAX);
mPathParams.setTwistEnd(twist_end);
return valid;
}
bool LLVolumeParams::setRatio(const F32 x, const F32 y)
{
F32 min_x = RATIO_MIN;
F32 max_x = RATIO_MAX;
F32 min_y = RATIO_MIN;
F32 max_y = RATIO_MAX;
// If this is a circular path (and not a sphere) then 'ratio' is actually hole size.
U8 path_type = mPathParams.getCurveType();
U8 profile_type = mProfileParams.getCurveType() & LL_PCODE_PROFILE_MASK;
if ( LL_PCODE_PATH_CIRCLE == path_type &&
LL_PCODE_PROFILE_CIRCLE_HALF != profile_type)
{
// Holes are more restricted...
min_x = HOLE_X_MIN;
max_x = HOLE_X_MAX;
min_y = HOLE_Y_MIN;
max_y = HOLE_Y_MAX;
}
F32 ratio_x = x;
bool valid = limit_range(ratio_x, min_x, max_x);
F32 ratio_y = y;
valid &= limit_range(ratio_y, min_y, max_y);
mPathParams.setScale(ratio_x, ratio_y);
return valid;
}
bool LLVolumeParams::setShear(const F32 x, const F32 y)
{
F32 shear_x = x;
bool valid = limit_range(shear_x, SHEAR_MIN, SHEAR_MAX);
F32 shear_y = y;
valid &= limit_range(shear_y, SHEAR_MIN, SHEAR_MAX);
mPathParams.setShear(shear_x, shear_y);
return valid;
}
bool LLVolumeParams::setTaperX(const F32 v)
{
F32 taper = v;
bool valid = limit_range(taper, TAPER_MIN, TAPER_MAX);
mPathParams.setTaperX(taper);
return valid;
}
bool LLVolumeParams::setTaperY(const F32 v)
{
F32 taper = v;
bool valid = limit_range(taper, TAPER_MIN, TAPER_MAX);
mPathParams.setTaperY(taper);
return valid;
}
bool LLVolumeParams::setRevolutions(const F32 r)
{
F32 revolutions = r;
bool valid = limit_range(revolutions, REV_MIN, REV_MAX);
mPathParams.setRevolutions(revolutions);
return valid;
}
bool LLVolumeParams::setRadiusOffset(const F32 offset)
{
bool valid = true;
// If this is a sphere, just set it to 0 and get out.
U8 path_type = mPathParams.getCurveType();
U8 profile_type = mProfileParams.getCurveType() & LL_PCODE_PROFILE_MASK;
if ( LL_PCODE_PROFILE_CIRCLE_HALF == profile_type ||
LL_PCODE_PATH_CIRCLE != path_type )
{
mPathParams.setRadiusOffset(0.f);
return true;
}
// Limit radius offset, based on taper and hole size y.
F32 radius_offset = offset;
F32 taper_y = getTaperY();
F32 radius_mag = fabs(radius_offset);
F32 hole_y_mag = fabs(getRatioY());
F32 taper_y_mag = fabs(taper_y);
// Check to see if the taper effects us.
if ( (radius_offset > 0.f && taper_y < 0.f) ||
(radius_offset < 0.f && taper_y > 0.f) )
{
// The taper does not help increase the radius offset range.
taper_y_mag = 0.f;
}
F32 max_radius_mag = 1.f - hole_y_mag * (1.f - taper_y_mag) / (1.f - hole_y_mag);
// Enforce the maximum magnitude.
F32 delta = max_radius_mag - radius_mag;
if (delta < 0.f)
{
// Check radius offset sign.
if (radius_offset < 0.f)
{
radius_offset = -max_radius_mag;
}
else
{
radius_offset = max_radius_mag;
}
valid = approx_zero(delta, .1f);
}
mPathParams.setRadiusOffset(radius_offset);
return valid;
}
bool LLVolumeParams::setSkew(const F32 skew_value)
{
bool valid = true;
// Check the skew value against the revolutions.
F32 skew = llclamp(skew_value, SKEW_MIN, SKEW_MAX);
F32 skew_mag = fabs(skew);
F32 revolutions = getRevolutions();
F32 scale_x = getRatioX();
F32 min_skew_mag = 1.0f - 1.0f / (revolutions * scale_x + 1.0f);
// Discontinuity; A revolution of 1 allows skews below 0.5.
if ( fabs(revolutions - 1.0f) < 0.001)
min_skew_mag = 0.0f;
// Clip skew.
F32 delta = skew_mag - min_skew_mag;
if (delta < 0.f)
{
// Check skew sign.
if (skew < 0.0f)
{
skew = -min_skew_mag;
}
else
{
skew = min_skew_mag;
}
valid = approx_zero(delta, .01f);
}
mPathParams.setSkew(skew);
return valid;
}
bool LLVolumeParams::setSculptID(const LLUUID sculpt_id, U8 sculpt_type)
{
mSculptID = sculpt_id;
mSculptType = sculpt_type;
return true;
}
bool LLVolumeParams::setType(U8 profile, U8 path)
{
bool result = true;
// First, check profile and path for validity.
U8 profile_type = profile & LL_PCODE_PROFILE_MASK;
U8 hole_type = (profile & LL_PCODE_HOLE_MASK) >> 4;
U8 path_type = path >> 4;
if (profile_type > LL_PCODE_PROFILE_MAX)
{
// Bad profile. Make it square.
profile = LL_PCODE_PROFILE_SQUARE;
result = false;
llwarns << "LLVolumeParams::setType changing bad profile type (" << profile_type
<< ") to be LL_PCODE_PROFILE_SQUARE" << llendl;
}
else if (hole_type > LL_PCODE_HOLE_MAX)
{
// Bad hole. Make it the same.
profile = profile_type;
result = false;
llwarns << "LLVolumeParams::setType changing bad hole type (" << hole_type
<< ") to be LL_PCODE_HOLE_SAME" << llendl;
}
if (path_type < LL_PCODE_PATH_MIN ||
path_type > LL_PCODE_PATH_MAX)
{
// Bad path. Make it linear.
result = false;
llwarns << "LLVolumeParams::setType changing bad path (" << path
<< ") to be LL_PCODE_PATH_LINE" << llendl;
path = LL_PCODE_PATH_LINE;
}
mProfileParams.setCurveType(profile);
mPathParams.setCurveType(path);
return result;
}
// static
bool LLVolumeParams::validate(U8 prof_curve, F32 prof_begin, F32 prof_end, F32 hollow,
U8 path_curve, F32 path_begin, F32 path_end,
F32 scx, F32 scy, F32 shx, F32 shy,
F32 twistend, F32 twistbegin, F32 radiusoffset,
F32 tx, F32 ty, F32 revolutions, F32 skew)
{
LLVolumeParams test_params;
if (!test_params.setType (prof_curve, path_curve))
{
return false;
}
if (!test_params.setBeginAndEndS (prof_begin, prof_end))
{
return false;
}
if (!test_params.setBeginAndEndT (path_begin, path_end))
{
return false;
}
if (!test_params.setHollow (hollow))
{
return false;
}
if (!test_params.setTwistBegin (twistbegin))
{
return false;
}
if (!test_params.setTwistEnd (twistend))
{
return false;
}
if (!test_params.setRatio (scx, scy))
{
return false;
}
if (!test_params.setShear (shx, shy))
{
return false;
}
if (!test_params.setTaper (tx, ty))
{
return false;
}
if (!test_params.setRevolutions (revolutions))
{
return false;
}
if (!test_params.setRadiusOffset (radiusoffset))
{
return false;
}
if (!test_params.setSkew (skew))
{
return false;
}
return true;
}
S32 *LLVolume::getTriangleIndices(U32 &num_indices) const
{
LLMemType m1(LLMemType::MTYPE_VOLUME);
S32 expected_num_triangle_indices = getNumTriangleIndices();
if (expected_num_triangle_indices > MAX_VOLUME_TRIANGLE_INDICES)
{
// we don't allow LLVolumes with this many vertices
llwarns << "Couldn't allocate triangle indices" << llendl;
num_indices = 0;
return NULL;
}
S32* index = new S32[expected_num_triangle_indices];
S32 count = 0;
// Let's do this totally diffently, as we don't care about faces...
// Counter-clockwise triangles are forward facing...
BOOL open = getProfile().isOpen();
BOOL hollow = (mParams.getProfileParams().getHollow() > 0);
BOOL path_open = getPath().isOpen();
S32 size_s, size_s_out, size_t;
S32 s, t, i;
size_s = getProfile().getTotal();
size_s_out = getProfile().getTotalOut();
size_t = getPath().mPath.size();
// NOTE -- if the construction of the triangles below ever changes
// then getNumTriangleIndices() method may also have to be updated.
if (open) /* Flawfinder: ignore */
{
if (hollow)
{
// Open hollow -- much like the closed solid, except we
// we need to stitch up the gap between s=0 and s=size_s-1
for (t = 0; t < size_t - 1; t++)
{
// The outer face, first cut, and inner face
for (s = 0; s < size_s - 1; s++)
{
i = s + t*size_s;
index[count++] = i; // x,y
index[count++] = i + 1; // x+1,y
index[count++] = i + size_s; // x,y+1
index[count++] = i + size_s; // x,y+1
index[count++] = i + 1; // x+1,y
index[count++] = i + size_s + 1; // x+1,y+1
}
// The other cut face
index[count++] = s + t*size_s; // x,y
index[count++] = 0 + t*size_s; // x+1,y
index[count++] = s + (t+1)*size_s; // x,y+1
index[count++] = s + (t+1)*size_s; // x,y+1
index[count++] = 0 + t*size_s; // x+1,y
index[count++] = 0 + (t+1)*size_s; // x+1,y+1
}
// Do the top and bottom caps, if necessary
if (path_open)
{
// Top cap
S32 pt1 = 0;
S32 pt2 = size_s-1;
S32 i = (size_t - 1)*size_s;
while (pt2 - pt1 > 1)
{
// Use the profile points instead of the mesh, since you want
// the un-transformed profile distances.
LLVector3 p1 = getProfile().mProfile[pt1];
LLVector3 p2 = getProfile().mProfile[pt2];
LLVector3 pa = getProfile().mProfile[pt1+1];
LLVector3 pb = getProfile().mProfile[pt2-1];
p1.mV[VZ] = 0.f;
p2.mV[VZ] = 0.f;
pa.mV[VZ] = 0.f;
pb.mV[VZ] = 0.f;
// Use area of triangle to determine backfacing
F32 area_1a2, area_1ba, area_21b, area_2ab;
area_1a2 = (p1.mV[0]*pa.mV[1] - pa.mV[0]*p1.mV[1]) +
(pa.mV[0]*p2.mV[1] - p2.mV[0]*pa.mV[1]) +
(p2.mV[0]*p1.mV[1] - p1.mV[0]*p2.mV[1]);
area_1ba = (p1.mV[0]*pb.mV[1] - pb.mV[0]*p1.mV[1]) +
(pb.mV[0]*pa.mV[1] - pa.mV[0]*pb.mV[1]) +
(pa.mV[0]*p1.mV[1] - p1.mV[0]*pa.mV[1]);
area_21b = (p2.mV[0]*p1.mV[1] - p1.mV[0]*p2.mV[1]) +
(p1.mV[0]*pb.mV[1] - pb.mV[0]*p1.mV[1]) +
(pb.mV[0]*p2.mV[1] - p2.mV[0]*pb.mV[1]);
area_2ab = (p2.mV[0]*pa.mV[1] - pa.mV[0]*p2.mV[1]) +
(pa.mV[0]*pb.mV[1] - pb.mV[0]*pa.mV[1]) +
(pb.mV[0]*p2.mV[1] - p2.mV[0]*pb.mV[1]);
BOOL use_tri1a2 = TRUE;
BOOL tri_1a2 = TRUE;
BOOL tri_21b = TRUE;
if (area_1a2 < 0)
{
tri_1a2 = FALSE;
}
if (area_2ab < 0)
{
// Can't use, because it contains point b
tri_1a2 = FALSE;
}
if (area_21b < 0)
{
tri_21b = FALSE;
}
if (area_1ba < 0)
{
// Can't use, because it contains point b
tri_21b = FALSE;
}
if (!tri_1a2)
{
use_tri1a2 = FALSE;
}
else if (!tri_21b)
{
use_tri1a2 = TRUE;
}
else
{
LLVector3 d1 = p1 - pa;
LLVector3 d2 = p2 - pb;
if (d1.magVecSquared() < d2.magVecSquared())
{
use_tri1a2 = TRUE;
}
else
{
use_tri1a2 = FALSE;
}
}
if (use_tri1a2)
{
index[count++] = pt1 + i;
index[count++] = pt1 + 1 + i;
index[count++] = pt2 + i;
pt1++;
}
else
{
index[count++] = pt1 + i;
index[count++] = pt2 - 1 + i;
index[count++] = pt2 + i;
pt2--;
}
}
// Bottom cap
pt1 = 0;
pt2 = size_s-1;
while (pt2 - pt1 > 1)
{
// Use the profile points instead of the mesh, since you want
// the un-transformed profile distances.
LLVector3 p1 = getProfile().mProfile[pt1];
LLVector3 p2 = getProfile().mProfile[pt2];
LLVector3 pa = getProfile().mProfile[pt1+1];
LLVector3 pb = getProfile().mProfile[pt2-1];
p1.mV[VZ] = 0.f;
p2.mV[VZ] = 0.f;
pa.mV[VZ] = 0.f;
pb.mV[VZ] = 0.f;
// Use area of triangle to determine backfacing
F32 area_1a2, area_1ba, area_21b, area_2ab;
area_1a2 = (p1.mV[0]*pa.mV[1] - pa.mV[0]*p1.mV[1]) +
(pa.mV[0]*p2.mV[1] - p2.mV[0]*pa.mV[1]) +
(p2.mV[0]*p1.mV[1] - p1.mV[0]*p2.mV[1]);
area_1ba = (p1.mV[0]*pb.mV[1] - pb.mV[0]*p1.mV[1]) +
(pb.mV[0]*pa.mV[1] - pa.mV[0]*pb.mV[1]) +
(pa.mV[0]*p1.mV[1] - p1.mV[0]*pa.mV[1]);
area_21b = (p2.mV[0]*p1.mV[1] - p1.mV[0]*p2.mV[1]) +
(p1.mV[0]*pb.mV[1] - pb.mV[0]*p1.mV[1]) +
(pb.mV[0]*p2.mV[1] - p2.mV[0]*pb.mV[1]);
area_2ab = (p2.mV[0]*pa.mV[1] - pa.mV[0]*p2.mV[1]) +
(pa.mV[0]*pb.mV[1] - pb.mV[0]*pa.mV[1]) +
(pb.mV[0]*p2.mV[1] - p2.mV[0]*pb.mV[1]);
BOOL use_tri1a2 = TRUE;
BOOL tri_1a2 = TRUE;
BOOL tri_21b = TRUE;
if (area_1a2 < 0)
{
tri_1a2 = FALSE;
}
if (area_2ab < 0)
{
// Can't use, because it contains point b
tri_1a2 = FALSE;
}
if (area_21b < 0)
{
tri_21b = FALSE;
}
if (area_1ba < 0)
{
// Can't use, because it contains point b
tri_21b = FALSE;
}
if (!tri_1a2)
{
use_tri1a2 = FALSE;
}
else if (!tri_21b)
{
use_tri1a2 = TRUE;
}
else
{
LLVector3 d1 = p1 - pa;
LLVector3 d2 = p2 - pb;
if (d1.magVecSquared() < d2.magVecSquared())
{
use_tri1a2 = TRUE;
}
else
{
use_tri1a2 = FALSE;
}
}
if (use_tri1a2)
{
index[count++] = pt1;
index[count++] = pt2;
index[count++] = pt1 + 1;
pt1++;
}
else
{
index[count++] = pt1;
index[count++] = pt2;
index[count++] = pt2 - 1;
pt2--;
}
}
}
}
else
{
// Open solid
for (t = 0; t < size_t - 1; t++)
{
// Outer face + 1 cut face
for (s = 0; s < size_s - 1; s++)
{
i = s + t*size_s;
index[count++] = i; // x,y
index[count++] = i + 1; // x+1,y
index[count++] = i + size_s; // x,y+1
index[count++] = i + size_s; // x,y+1
index[count++] = i + 1; // x+1,y
index[count++] = i + size_s + 1; // x+1,y+1
}
// The other cut face
index[count++] = (size_s - 1) + (t*size_s); // x,y
index[count++] = 0 + t*size_s; // x+1,y
index[count++] = (size_s - 1) + (t+1)*size_s; // x,y+1
index[count++] = (size_s - 1) + (t+1)*size_s; // x,y+1
index[count++] = 0 + (t*size_s); // x+1,y
index[count++] = 0 + (t+1)*size_s; // x+1,y+1
}
// Do the top and bottom caps, if necessary
if (path_open)
{
for (s = 0; s < size_s - 2; s++)
{
index[count++] = s+1;
index[count++] = s;
index[count++] = size_s - 1;
}
// We've got a top cap
S32 offset = (size_t - 1)*size_s;
for (s = 0; s < size_s - 2; s++)
{
// Inverted ordering from bottom cap.
index[count++] = offset + size_s - 1;
index[count++] = offset + s;
index[count++] = offset + s + 1;
}
}
}
}
else if (hollow)
{
// Closed hollow
// Outer face
for (t = 0; t < size_t - 1; t++)
{
for (s = 0; s < size_s_out - 1; s++)
{
i = s + t*size_s;
index[count++] = i; // x,y
index[count++] = i + 1; // x+1,y
index[count++] = i + size_s; // x,y+1
index[count++] = i + size_s; // x,y+1
index[count++] = i + 1; // x+1,y
index[count++] = i + 1 + size_s; // x+1,y+1
}
}
// Inner face
// Invert facing from outer face
for (t = 0; t < size_t - 1; t++)
{
for (s = size_s_out; s < size_s - 1; s++)
{
i = s + t*size_s;
index[count++] = i; // x,y
index[count++] = i + 1; // x+1,y
index[count++] = i + size_s; // x,y+1
index[count++] = i + size_s; // x,y+1
index[count++] = i + 1; // x+1,y
index[count++] = i + 1 + size_s; // x+1,y+1
}
}
// Do the top and bottom caps, if necessary
if (path_open)
{
// Top cap
S32 pt1 = 0;
S32 pt2 = size_s-1;
S32 i = (size_t - 1)*size_s;
while (pt2 - pt1 > 1)
{
// Use the profile points instead of the mesh, since you want
// the un-transformed profile distances.
LLVector3 p1 = getProfile().mProfile[pt1];
LLVector3 p2 = getProfile().mProfile[pt2];
LLVector3 pa = getProfile().mProfile[pt1+1];
LLVector3 pb = getProfile().mProfile[pt2-1];
p1.mV[VZ] = 0.f;
p2.mV[VZ] = 0.f;
pa.mV[VZ] = 0.f;
pb.mV[VZ] = 0.f;
// Use area of triangle to determine backfacing
F32 area_1a2, area_1ba, area_21b, area_2ab;
area_1a2 = (p1.mV[0]*pa.mV[1] - pa.mV[0]*p1.mV[1]) +
(pa.mV[0]*p2.mV[1] - p2.mV[0]*pa.mV[1]) +
(p2.mV[0]*p1.mV[1] - p1.mV[0]*p2.mV[1]);
area_1ba = (p1.mV[0]*pb.mV[1] - pb.mV[0]*p1.mV[1]) +
(pb.mV[0]*pa.mV[1] - pa.mV[0]*pb.mV[1]) +
(pa.mV[0]*p1.mV[1] - p1.mV[0]*pa.mV[1]);
area_21b = (p2.mV[0]*p1.mV[1] - p1.mV[0]*p2.mV[1]) +
(p1.mV[0]*pb.mV[1] - pb.mV[0]*p1.mV[1]) +
(pb.mV[0]*p2.mV[1] - p2.mV[0]*pb.mV[1]);
area_2ab = (p2.mV[0]*pa.mV[1] - pa.mV[0]*p2.mV[1]) +
(pa.mV[0]*pb.mV[1] - pb.mV[0]*pa.mV[1]) +
(pb.mV[0]*p2.mV[1] - p2.mV[0]*pb.mV[1]);
BOOL use_tri1a2 = TRUE;
BOOL tri_1a2 = TRUE;
BOOL tri_21b = TRUE;
if (area_1a2 < 0)
{
tri_1a2 = FALSE;
}
if (area_2ab < 0)
{
// Can't use, because it contains point b
tri_1a2 = FALSE;
}
if (area_21b < 0)
{
tri_21b = FALSE;
}
if (area_1ba < 0)
{
// Can't use, because it contains point b
tri_21b = FALSE;
}
if (!tri_1a2)
{
use_tri1a2 = FALSE;
}
else if (!tri_21b)
{
use_tri1a2 = TRUE;
}
else
{
LLVector3 d1 = p1 - pa;
LLVector3 d2 = p2 - pb;
if (d1.magVecSquared() < d2.magVecSquared())
{
use_tri1a2 = TRUE;
}
else
{
use_tri1a2 = FALSE;
}
}
if (use_tri1a2)
{
index[count++] = pt1 + i;
index[count++] = pt1 + 1 + i;
index[count++] = pt2 + i;
pt1++;
}
else
{
index[count++] = pt1 + i;
index[count++] = pt2 - 1 + i;
index[count++] = pt2 + i;
pt2--;
}
}
// Bottom cap
pt1 = 0;
pt2 = size_s-1;
while (pt2 - pt1 > 1)
{
// Use the profile points instead of the mesh, since you want
// the un-transformed profile distances.
LLVector3 p1 = getProfile().mProfile[pt1];
LLVector3 p2 = getProfile().mProfile[pt2];
LLVector3 pa = getProfile().mProfile[pt1+1];
LLVector3 pb = getProfile().mProfile[pt2-1];
p1.mV[VZ] = 0.f;
p2.mV[VZ] = 0.f;
pa.mV[VZ] = 0.f;
pb.mV[VZ] = 0.f;
// Use area of triangle to determine backfacing
F32 area_1a2, area_1ba, area_21b, area_2ab;
area_1a2 = (p1.mV[0]*pa.mV[1] - pa.mV[0]*p1.mV[1]) +
(pa.mV[0]*p2.mV[1] - p2.mV[0]*pa.mV[1]) +
(p2.mV[0]*p1.mV[1] - p1.mV[0]*p2.mV[1]);
area_1ba = (p1.mV[0]*pb.mV[1] - pb.mV[0]*p1.mV[1]) +
(pb.mV[0]*pa.mV[1] - pa.mV[0]*pb.mV[1]) +
(pa.mV[0]*p1.mV[1] - p1.mV[0]*pa.mV[1]);
area_21b = (p2.mV[0]*p1.mV[1] - p1.mV[0]*p2.mV[1]) +
(p1.mV[0]*pb.mV[1] - pb.mV[0]*p1.mV[1]) +
(pb.mV[0]*p2.mV[1] - p2.mV[0]*pb.mV[1]);
area_2ab = (p2.mV[0]*pa.mV[1] - pa.mV[0]*p2.mV[1]) +
(pa.mV[0]*pb.mV[1] - pb.mV[0]*pa.mV[1]) +
(pb.mV[0]*p2.mV[1] - p2.mV[0]*pb.mV[1]);
BOOL use_tri1a2 = TRUE;
BOOL tri_1a2 = TRUE;
BOOL tri_21b = TRUE;
if (area_1a2 < 0)
{
tri_1a2 = FALSE;
}
if (area_2ab < 0)
{
// Can't use, because it contains point b
tri_1a2 = FALSE;
}
if (area_21b < 0)
{
tri_21b = FALSE;
}
if (area_1ba < 0)
{
// Can't use, because it contains point b
tri_21b = FALSE;
}
if (!tri_1a2)
{
use_tri1a2 = FALSE;
}
else if (!tri_21b)
{
use_tri1a2 = TRUE;
}
else
{
LLVector3 d1 = p1 - pa;
LLVector3 d2 = p2 - pb;
if (d1.magVecSquared() < d2.magVecSquared())
{
use_tri1a2 = TRUE;
}
else
{
use_tri1a2 = FALSE;
}
}
if (use_tri1a2)
{
index[count++] = pt1;
index[count++] = pt2;
index[count++] = pt1 + 1;
pt1++;
}
else
{
index[count++] = pt1;
index[count++] = pt2;
index[count++] = pt2 - 1;
pt2--;
}
}
}
}
else
{
// Closed solid. Easy case.
for (t = 0; t < size_t - 1; t++)
{
for (s = 0; s < size_s - 1; s++)
{
// Should wrap properly, but for now...
i = s + t*size_s;
index[count++] = i; // x,y
index[count++] = i + 1; // x+1,y
index[count++] = i + size_s; // x,y+1
index[count++] = i + size_s; // x,y+1
index[count++] = i + 1; // x+1,y
index[count++] = i + size_s + 1; // x+1,y+1
}
}
// Do the top and bottom caps, if necessary
if (path_open)
{
// bottom cap
for (s = 1; s < size_s - 2; s++)
{
index[count++] = s+1;
index[count++] = s;
index[count++] = 0;
}
// top cap
S32 offset = (size_t - 1)*size_s;
for (s = 1; s < size_s - 2; s++)
{
// Inverted ordering from bottom cap.
index[count++] = offset;
index[count++] = offset + s;
index[count++] = offset + s + 1;
}
}
}
#ifdef LL_DEBUG
// assert that we computed the correct number of indices
if (count != expected_num_triangle_indices )
{
llerrs << "bad index count prediciton:"
<< " expected=" << expected_num_triangle_indices
<< " actual=" << count << llendl;
}
#endif
#if 0
// verify that each index does not point beyond the size of the mesh
S32 num_vertices = mMesh.size();
for (i = 0; i < count; i+=3)
{
llinfos << index[i] << ":" << index[i+1] << ":" << index[i+2] << llendl;
llassert(index[i] < num_vertices);
llassert(index[i+1] < num_vertices);
llassert(index[i+2] < num_vertices);
}
#endif
num_indices = count;
return index;
}
S32 LLVolume::getNumTriangleIndices() const
{
BOOL profile_open = getProfile().isOpen();
BOOL hollow = (mParams.getProfileParams().getHollow() > 0);
BOOL path_open = getPath().isOpen();
S32 size_s, size_s_out, size_t;
size_s = getProfile().getTotal();
size_s_out = getProfile().getTotalOut();
size_t = getPath().mPath.size();
S32 count = 0;
if (profile_open) /* Flawfinder: ignore */
{
if (hollow)
{
// Open hollow -- much like the closed solid, except we
// we need to stitch up the gap between s=0 and s=size_s-1
count = (size_t - 1) * (((size_s -1) * 6) + 6);
}
else
{
count = (size_t - 1) * (((size_s -1) * 6) + 6);
}
}
else if (hollow)
{
// Closed hollow
// Outer face
count = (size_t - 1) * (size_s_out - 1) * 6;
// Inner face
count += (size_t - 1) * ((size_s - 1) - size_s_out) * 6;
}
else
{
// Closed solid. Easy case.
count = (size_t - 1) * (size_s - 1) * 6;
}
if (path_open)
{
S32 cap_triangle_count = size_s - 3;
if ( profile_open
|| hollow )
{
cap_triangle_count = size_s - 2;
}
if ( cap_triangle_count > 0 )
{
// top and bottom caps
count += cap_triangle_count * 2 * 3;
}
}
return count;
}
//-----------------------------------------------------------------------------
// generateSilhouetteVertices()
//-----------------------------------------------------------------------------
void LLVolume::generateSilhouetteVertices(std::vector<LLVector3> &vertices,
std::vector<LLVector3> &normals,
std::vector<S32> &segments,
const LLVector3& obj_cam_vec,
const LLMatrix4& mat,
const LLMatrix3& norm_mat,
S32 face_mask)
{
LLMemType m1(LLMemType::MTYPE_VOLUME);
vertices.clear();
normals.clear();
segments.clear();
S32 cur_index = 0;
//for each face
for (face_list_t::iterator iter = mVolumeFaces.begin();
iter != mVolumeFaces.end(); ++iter)
{
const LLVolumeFace& face = *iter;
if (!(face_mask & (0x1 << cur_index++)))
{
continue;
}
if (face.mTypeMask & (LLVolumeFace::CAP_MASK)) {
}
else {
//==============================================
//DEBUG draw edge map instead of silhouette edge
//==============================================
#if DEBUG_SILHOUETTE_EDGE_MAP
//for each triangle
U32 count = face.mIndices.size();
for (U32 j = 0; j < count/3; j++) {
//get vertices
S32 v1 = face.mIndices[j*3+0];
S32 v2 = face.mIndices[j*3+1];
S32 v3 = face.mIndices[j*3+2];
//get current face center
LLVector3 cCenter = (face.mVertices[v1].mPosition +
face.mVertices[v2].mPosition +
face.mVertices[v3].mPosition) / 3.0f;
//for each edge
for (S32 k = 0; k < 3; k++) {
S32 nIndex = face.mEdge[j*3+k];
if (nIndex <= -1) {
continue;
}
if (nIndex >= (S32) count/3) {
continue;
}
//get neighbor vertices
v1 = face.mIndices[nIndex*3+0];
v2 = face.mIndices[nIndex*3+1];
v3 = face.mIndices[nIndex*3+2];
//get neighbor face center
LLVector3 nCenter = (face.mVertices[v1].mPosition +
face.mVertices[v2].mPosition +
face.mVertices[v3].mPosition) / 3.0f;
//draw line
vertices.push_back(cCenter);
vertices.push_back(nCenter);
normals.push_back(LLVector3(1,1,1));
normals.push_back(LLVector3(1,1,1));
segments.push_back(vertices.size());
}
}
continue;
//==============================================
//DEBUG
//==============================================
//==============================================
//DEBUG draw normals instead of silhouette edge
//==============================================
#elif DEBUG_SILHOUETTE_NORMALS
//for each vertex
for (U32 j = 0; j < face.mVertices.size(); j++) {
vertices.push_back(face.mVertices[j].mPosition);
vertices.push_back(face.mVertices[j].mPosition + face.mVertices[j].mNormal*0.1f);
normals.push_back(LLVector3(0,0,1));
normals.push_back(LLVector3(0,0,1));
segments.push_back(vertices.size());
#if DEBUG_SILHOUETTE_BINORMALS
vertices.push_back(face.mVertices[j].mPosition);
vertices.push_back(face.mVertices[j].mPosition + face.mVertices[j].mBinormal*0.1f);
normals.push_back(LLVector3(0,0,1));
normals.push_back(LLVector3(0,0,1));
segments.push_back(vertices.size());
#endif
}
continue;
#else
//==============================================
//DEBUG
//==============================================
static const U8 AWAY = 0x01,
TOWARDS = 0x02;
//for each triangle
std::vector<U8> fFacing;
vector_append(fFacing, face.mIndices.size()/3);
for (U32 j = 0; j < face.mIndices.size()/3; j++)
{
//approximate normal
S32 v1 = face.mIndices[j*3+0];
S32 v2 = face.mIndices[j*3+1];
S32 v3 = face.mIndices[j*3+2];
LLVector3 norm = (face.mVertices[v1].mPosition - face.mVertices[v2].mPosition) %
(face.mVertices[v2].mPosition - face.mVertices[v3].mPosition);
if (norm.magVecSquared() < 0.00000001f)
{
fFacing[j] = AWAY | TOWARDS;
}
else
{
//get view vector
LLVector3 view = (obj_cam_vec-face.mVertices[v1].mPosition);
bool away = view * norm > 0.0f;
if (away)
{
fFacing[j] = AWAY;
}
else
{
fFacing[j] = TOWARDS;
}
}
}
//for each triangle
for (U32 j = 0; j < face.mIndices.size()/3; j++)
{
if (fFacing[j] == (AWAY | TOWARDS))
{ //this is a degenerate triangle
//take neighbor facing (degenerate faces get facing of one of their neighbors)
// *FIX IF NEEDED: this does not deal with neighboring degenerate faces
for (S32 k = 0; k < 3; k++)
{
S32 index = face.mEdge[j*3+k];
if (index != -1)
{
fFacing[j] = fFacing[index];
break;
}
}
continue; //skip degenerate face
}
//for each edge
for (S32 k = 0; k < 3; k++) {
S32 index = face.mEdge[j*3+k];
if (index != -1 && fFacing[index] == (AWAY | TOWARDS)) {
//our neighbor is degenerate, make him face our direction
fFacing[face.mEdge[j*3+k]] = fFacing[j];
continue;
}
if (index == -1 || //edge has no neighbor, MUST be a silhouette edge
(fFacing[index] & fFacing[j]) == 0) { //we found a silhouette edge
S32 v1 = face.mIndices[j*3+k];
S32 v2 = face.mIndices[j*3+((k+1)%3)];
vertices.push_back(face.mVertices[v1].mPosition*mat);
LLVector3 norm1 = face.mVertices[v1].mNormal * norm_mat;
norm1.normVec();
normals.push_back(norm1);
vertices.push_back(face.mVertices[v2].mPosition*mat);
LLVector3 norm2 = face.mVertices[v2].mNormal * norm_mat;
norm2.normVec();
normals.push_back(norm2);
segments.push_back(vertices.size());
}
}
}
#endif
}
}
}
S32 LLVolume::lineSegmentIntersect(const LLVector3& start, const LLVector3& end,
S32 face,
LLVector3* intersection,LLVector2* tex_coord, LLVector3* normal, LLVector3* bi_normal)
{
S32 hit_face = -1;
S32 start_face;
S32 end_face;
if (face == -1) // ALL_SIDES
{
start_face = 0;
end_face = getNumVolumeFaces() - 1;
}
else
{
start_face = face;
end_face = face;
}
LLVector3 dir = end - start;
F32 closest_t = 2.f; // must be larger than 1
for (S32 i = start_face; i <= end_face; i++)
{
const LLVolumeFace &face = getVolumeFace((U32)i);
LLVector3 box_center = (face.mExtents[0] + face.mExtents[1]) / 2.f;
LLVector3 box_size = face.mExtents[1] - face.mExtents[0];
if (LLLineSegmentBoxIntersect(start, end, box_center, box_size))
{
if (bi_normal != NULL) // if the caller wants binormals, we may need to generate them
{
genBinormals(i);
}
for (U32 tri = 0; tri < face.mIndices.size()/3; tri++)
{
S32 index1 = face.mIndices[tri*3+0];
S32 index2 = face.mIndices[tri*3+1];
S32 index3 = face.mIndices[tri*3+2];
F32 a, b, t;
if (LLTriangleRayIntersect(face.mVertices[index1].mPosition,
face.mVertices[index2].mPosition,
face.mVertices[index3].mPosition,
start, dir, &a, &b, &t, FALSE))
{
if ((t >= 0.f) && // if hit is after start
(t <= 1.f) && // and before end
(t < closest_t)) // and this hit is closer
{
closest_t = t;
hit_face = i;
if (intersection != NULL)
{
*intersection = start + dir * closest_t;
}
if (tex_coord != NULL)
{
*tex_coord = ((1.f - a - b) * face.mVertices[index1].mTexCoord +
a * face.mVertices[index2].mTexCoord +
b * face.mVertices[index3].mTexCoord);
}
if (normal != NULL)
{
*normal = ((1.f - a - b) * face.mVertices[index1].mNormal +
a * face.mVertices[index2].mNormal +
b * face.mVertices[index3].mNormal);
}
if (bi_normal != NULL)
{
*bi_normal = ((1.f - a - b) * face.mVertices[index1].mBinormal +
a * face.mVertices[index2].mBinormal +
b * face.mVertices[index3].mBinormal);
}
}
}
}
}
}
return hit_face;
}
class LLVertexIndexPair
{
public:
LLVertexIndexPair(const LLVector3 &vertex, const S32 index);
LLVector3 mVertex;
S32 mIndex;
};
LLVertexIndexPair::LLVertexIndexPair(const LLVector3 &vertex, const S32 index)
{
mVertex = vertex;
mIndex = index;
}
const F32 VERTEX_SLOP = 0.00001f;
const F32 VERTEX_SLOP_SQRD = VERTEX_SLOP * VERTEX_SLOP;
struct lessVertex
{
bool operator()(const LLVertexIndexPair *a, const LLVertexIndexPair *b)
{
const F32 slop = VERTEX_SLOP;
if (a->mVertex.mV[0] + slop < b->mVertex.mV[0])
{
return TRUE;
}
else if (a->mVertex.mV[0] - slop > b->mVertex.mV[0])
{
return FALSE;
}
if (a->mVertex.mV[1] + slop < b->mVertex.mV[1])
{
return TRUE;
}
else if (a->mVertex.mV[1] - slop > b->mVertex.mV[1])
{
return FALSE;
}
if (a->mVertex.mV[2] + slop < b->mVertex.mV[2])
{
return TRUE;
}
else if (a->mVertex.mV[2] - slop > b->mVertex.mV[2])
{
return FALSE;
}
return FALSE;
}
};
struct lessTriangle
{
bool operator()(const S32 *a, const S32 *b)
{
if (*a < *b)
{
return TRUE;
}
else if (*a > *b)
{
return FALSE;
}
if (*(a+1) < *(b+1))
{
return TRUE;
}
else if (*(a+1) > *(b+1))
{
return FALSE;
}
if (*(a+2) < *(b+2))
{
return TRUE;
}
else if (*(a+2) > *(b+2))
{
return FALSE;
}
return FALSE;
}
};
BOOL equalTriangle(const S32 *a, const S32 *b)
{
if ((*a == *b) && (*(a+1) == *(b+1)) && (*(a+2) == *(b+2)))
{
return TRUE;
}
return FALSE;
}
BOOL LLVolume::cleanupTriangleData( const S32 num_input_vertices,
const std::vector<Point>& input_vertices,
const S32 num_input_triangles,
S32 *input_triangles,
S32 &num_output_vertices,
LLVector3 **output_vertices,
S32 &num_output_triangles,
S32 **output_triangles)
{
LLMemType m1(LLMemType::MTYPE_VOLUME);
/* Testing: avoid any cleanup
static BOOL skip_cleanup = TRUE;
if ( skip_cleanup )
{
num_output_vertices = num_input_vertices;
num_output_triangles = num_input_triangles;
*output_vertices = new LLVector3[num_input_vertices];
for (S32 index = 0; index < num_input_vertices; index++)
{
(*output_vertices)[index] = input_vertices[index].mPos;
}
*output_triangles = new S32[num_input_triangles*3];
memcpy(*output_triangles, input_triangles, 3*num_input_triangles*sizeof(S32)); // Flawfinder: ignore
return TRUE;
}
*/
// Here's how we do this:
// Create a structure which contains the original vertex index and the
// LLVector3 data.
// "Sort" the data by the vectors
// Create an array the size of the old vertex list, with a mapping of
// old indices to new indices.
// Go through triangles, shift so the lowest index is first
// Sort triangles by first index
// Remove duplicate triangles
// Allocate and pack new triangle data.
//LLTimer cleanupTimer;
//llinfos << "In vertices: " << num_input_vertices << llendl;
//llinfos << "In triangles: " << num_input_triangles << llendl;
S32 i;
typedef std::multiset<LLVertexIndexPair*, lessVertex> vertex_set_t;
vertex_set_t vertex_list;
LLVertexIndexPair *pairp = NULL;
for (i = 0; i < num_input_vertices; i++)
{
LLVertexIndexPair *new_pairp = new LLVertexIndexPair(input_vertices[i].mPos, i);
vertex_list.insert(new_pairp);
}
// Generate the vertex mapping and the list of vertices without
// duplicates. This will crash if there are no vertices.
llassert(num_input_vertices > 0); // check for no vertices!
S32 *vertex_mapping = new S32[num_input_vertices];
LLVector3 *new_vertices = new LLVector3[num_input_vertices];
LLVertexIndexPair *prev_pairp = NULL;
S32 new_num_vertices;
new_num_vertices = 0;
for (vertex_set_t::iterator iter = vertex_list.begin(),
end = vertex_list.end();
iter != end; iter++)
{
pairp = *iter;
if (!prev_pairp || ((pairp->mVertex - prev_pairp->mVertex).magVecSquared() >= VERTEX_SLOP_SQRD))
{
new_vertices[new_num_vertices] = pairp->mVertex;
//llinfos << "Added vertex " << new_num_vertices << " : " << pairp->mVertex << llendl;
new_num_vertices++;
// Update the previous
prev_pairp = pairp;
}
else
{
//llinfos << "Removed duplicate vertex " << pairp->mVertex << ", distance magVecSquared() is " << (pairp->mVertex - prev_pairp->mVertex).magVecSquared() << llendl;
}
vertex_mapping[pairp->mIndex] = new_num_vertices - 1;
}
// Iterate through triangles and remove degenerates, re-ordering vertices
// along the way.
S32 *new_triangles = new S32[num_input_triangles * 3];
S32 new_num_triangles = 0;
for (i = 0; i < num_input_triangles; i++)
{
S32 v1 = i*3;
S32 v2 = v1 + 1;
S32 v3 = v1 + 2;
//llinfos << "Checking triangle " << input_triangles[v1] << ":" << input_triangles[v2] << ":" << input_triangles[v3] << llendl;
input_triangles[v1] = vertex_mapping[input_triangles[v1]];
input_triangles[v2] = vertex_mapping[input_triangles[v2]];
input_triangles[v3] = vertex_mapping[input_triangles[v3]];
if ((input_triangles[v1] == input_triangles[v2])
|| (input_triangles[v1] == input_triangles[v3])
|| (input_triangles[v2] == input_triangles[v3]))
{
//llinfos << "Removing degenerate triangle " << input_triangles[v1] << ":" << input_triangles[v2] << ":" << input_triangles[v3] << llendl;
// Degenerate triangle, skip
continue;
}
if (input_triangles[v1] < input_triangles[v2])
{
if (input_triangles[v1] < input_triangles[v3])
{
// (0 < 1) && (0 < 2)
new_triangles[new_num_triangles*3] = input_triangles[v1];
new_triangles[new_num_triangles*3+1] = input_triangles[v2];
new_triangles[new_num_triangles*3+2] = input_triangles[v3];
}
else
{
// (0 < 1) && (2 < 0)
new_triangles[new_num_triangles*3] = input_triangles[v3];
new_triangles[new_num_triangles*3+1] = input_triangles[v1];
new_triangles[new_num_triangles*3+2] = input_triangles[v2];
}
}
else if (input_triangles[v2] < input_triangles[v3])
{
// (1 < 0) && (1 < 2)
new_triangles[new_num_triangles*3] = input_triangles[v2];
new_triangles[new_num_triangles*3+1] = input_triangles[v3];
new_triangles[new_num_triangles*3+2] = input_triangles[v1];
}
else
{
// (1 < 0) && (2 < 1)
new_triangles[new_num_triangles*3] = input_triangles[v3];
new_triangles[new_num_triangles*3+1] = input_triangles[v1];
new_triangles[new_num_triangles*3+2] = input_triangles[v2];
}
new_num_triangles++;
}
if (new_num_triangles == 0)
{
llwarns << "Created volume object with 0 faces." << llendl;
delete[] new_triangles;
delete[] vertex_mapping;
delete[] new_vertices;
return FALSE;
}
typedef std::set<S32*, lessTriangle> triangle_set_t;
triangle_set_t triangle_list;
for (i = 0; i < new_num_triangles; i++)
{
triangle_list.insert(&new_triangles[i*3]);
}
// Sort through the triangle list, and delete duplicates
S32 *prevp = NULL;
S32 *curp = NULL;
S32 *sorted_tris = new S32[new_num_triangles*3];
S32 cur_tri = 0;
for (triangle_set_t::iterator iter = triangle_list.begin(),
end = triangle_list.end();
iter != end; iter++)
{
curp = *iter;
if (!prevp || !equalTriangle(prevp, curp))
{
//llinfos << "Added triangle " << *curp << ":" << *(curp+1) << ":" << *(curp+2) << llendl;
sorted_tris[cur_tri*3] = *curp;
sorted_tris[cur_tri*3+1] = *(curp+1);
sorted_tris[cur_tri*3+2] = *(curp+2);
cur_tri++;
prevp = curp;
}
else
{
//llinfos << "Skipped triangle " << *curp << ":" << *(curp+1) << ":" << *(curp+2) << llendl;
}
}
*output_vertices = new LLVector3[new_num_vertices];
num_output_vertices = new_num_vertices;
for (i = 0; i < new_num_vertices; i++)
{
(*output_vertices)[i] = new_vertices[i];
}
*output_triangles = new S32[cur_tri*3];
num_output_triangles = cur_tri;
memcpy(*output_triangles, sorted_tris, 3*cur_tri*sizeof(S32)); /* Flawfinder: ignore */
/*
llinfos << "Out vertices: " << num_output_vertices << llendl;
llinfos << "Out triangles: " << num_output_triangles << llendl;
for (i = 0; i < num_output_vertices; i++)
{
llinfos << i << ":" << (*output_vertices)[i] << llendl;
}
for (i = 0; i < num_output_triangles; i++)
{
llinfos << i << ":" << (*output_triangles)[i*3] << ":" << (*output_triangles)[i*3+1] << ":" << (*output_triangles)[i*3+2] << llendl;
}
*/
//llinfos << "Out vertices: " << num_output_vertices << llendl;
//llinfos << "Out triangles: " << num_output_triangles << llendl;
delete[] vertex_mapping;
vertex_mapping = NULL;
delete[] new_vertices;
new_vertices = NULL;
delete[] new_triangles;
new_triangles = NULL;
delete[] sorted_tris;
sorted_tris = NULL;
triangle_list.clear();
std::for_each(vertex_list.begin(), vertex_list.end(), DeletePointer());
vertex_list.clear();
return TRUE;
}
BOOL LLVolumeParams::importFile(LLFILE *fp)
{
LLMemType m1(LLMemType::MTYPE_VOLUME);
//llinfos << "importing volume" << llendl;
const S32 BUFSIZE = 16384;
char buffer[BUFSIZE]; /* Flawfinder: ignore */
// *NOTE: changing the size or type of this buffer will require
// changing the sscanf below.
char keyword[256]; /* Flawfinder: ignore */
keyword[0] = 0;
while (!feof(fp))
{
if (fgets(buffer, BUFSIZE, fp) == NULL)
{
buffer[0] = '\0';
}
sscanf(buffer, " %255s", keyword); /* Flawfinder: ignore */
if (!strcmp("{", keyword))
{
continue;
}
if (!strcmp("}",keyword))
{
break;
}
else if (!strcmp("profile", keyword))
{
mProfileParams.importFile(fp);
}
else if (!strcmp("path",keyword))
{
mPathParams.importFile(fp);
}
else
{
llwarns << "unknown keyword " << keyword << " in volume import" << llendl;
}
}
return TRUE;
}
BOOL LLVolumeParams::exportFile(LLFILE *fp) const
{
fprintf(fp,"\tshape 0\n");
fprintf(fp,"\t{\n");
mPathParams.exportFile(fp);
mProfileParams.exportFile(fp);
fprintf(fp, "\t}\n");
return TRUE;
}
BOOL LLVolumeParams::importLegacyStream(std::istream& input_stream)
{
LLMemType m1(LLMemType::MTYPE_VOLUME);
//llinfos << "importing volume" << llendl;
const S32 BUFSIZE = 16384;
// *NOTE: changing the size or type of this buffer will require
// changing the sscanf below.
char buffer[BUFSIZE]; /* Flawfinder: ignore */
char keyword[256]; /* Flawfinder: ignore */
keyword[0] = 0;
while (input_stream.good())
{
input_stream.getline(buffer, BUFSIZE);
sscanf(buffer, " %255s", keyword);
if (!strcmp("{", keyword))
{
continue;
}
if (!strcmp("}",keyword))
{
break;
}
else if (!strcmp("profile", keyword))
{
mProfileParams.importLegacyStream(input_stream);
}
else if (!strcmp("path",keyword))
{
mPathParams.importLegacyStream(input_stream);
}
else
{
llwarns << "unknown keyword " << keyword << " in volume import" << llendl;
}
}
return TRUE;
}
BOOL LLVolumeParams::exportLegacyStream(std::ostream& output_stream) const
{
LLMemType m1(LLMemType::MTYPE_VOLUME);
output_stream <<"\tshape 0\n";
output_stream <<"\t{\n";
mPathParams.exportLegacyStream(output_stream);
mProfileParams.exportLegacyStream(output_stream);
output_stream << "\t}\n";
return TRUE;
}
LLSD LLVolumeParams::asLLSD() const
{
LLSD sd = LLSD();
sd["path"] = mPathParams;
sd["profile"] = mProfileParams;
return sd;
}
bool LLVolumeParams::fromLLSD(LLSD& sd)
{
mPathParams.fromLLSD(sd["path"]);
mProfileParams.fromLLSD(sd["profile"]);
return true;
}
void LLVolumeParams::reduceS(F32 begin, F32 end)
{
begin = llclampf(begin);
end = llclampf(end);
if (begin > end)
{
F32 temp = begin;
begin = end;
end = temp;
}
F32 a = mProfileParams.getBegin();
F32 b = mProfileParams.getEnd();
mProfileParams.setBegin(a + begin * (b - a));
mProfileParams.setEnd(a + end * (b - a));
}
void LLVolumeParams::reduceT(F32 begin, F32 end)
{
begin = llclampf(begin);
end = llclampf(end);
if (begin > end)
{
F32 temp = begin;
begin = end;
end = temp;
}
F32 a = mPathParams.getBegin();
F32 b = mPathParams.getEnd();
mPathParams.setBegin(a + begin * (b - a));
mPathParams.setEnd(a + end * (b - a));
}
const F32 MIN_CONCAVE_PROFILE_WEDGE = 0.125f; // 1/8 unity
const F32 MIN_CONCAVE_PATH_WEDGE = 0.111111f; // 1/9 unity
// returns TRUE if the shape can be approximated with a convex shape
// for collison purposes
BOOL LLVolumeParams::isConvex() const
{
F32 path_length = mPathParams.getEnd() - mPathParams.getBegin();
F32 hollow = mProfileParams.getHollow();
U8 path_type = mPathParams.getCurveType();
if ( path_length > MIN_CONCAVE_PATH_WEDGE
&& ( mPathParams.getTwist() != mPathParams.getTwistBegin()
|| (hollow > 0.f
&& LL_PCODE_PATH_LINE != path_type) ) )
{
// twist along a "not too short" path is concave
return FALSE;
}
F32 profile_length = mProfileParams.getEnd() - mProfileParams.getBegin();
BOOL same_hole = hollow == 0.f
|| (mProfileParams.getCurveType() & LL_PCODE_HOLE_MASK) == LL_PCODE_HOLE_SAME;
F32 min_profile_wedge = MIN_CONCAVE_PROFILE_WEDGE;
U8 profile_type = mProfileParams.getCurveType() & LL_PCODE_PROFILE_MASK;
if ( LL_PCODE_PROFILE_CIRCLE_HALF == profile_type )
{
// it is a sphere and spheres get twice the minimum profile wedge
min_profile_wedge = 2.f * MIN_CONCAVE_PROFILE_WEDGE;
}
BOOL convex_profile = ( ( profile_length == 1.f
|| profile_length <= 0.5f )
&& hollow == 0.f ) // trivially convex
|| ( profile_length <= min_profile_wedge
&& same_hole ); // effectvely convex (even when hollow)
if (!convex_profile)
{
// profile is concave
return FALSE;
}
if ( LL_PCODE_PATH_LINE == path_type )
{
// straight paths with convex profile
return TRUE;
}
BOOL concave_path = (path_length < 1.0f) && (path_length > 0.5f);
if (concave_path)
{
return FALSE;
}
// we're left with spheres, toroids and tubes
if ( LL_PCODE_PROFILE_CIRCLE_HALF == profile_type )
{
// at this stage all spheres must be convex
return TRUE;
}
// it's a toroid or tube
if ( path_length <= MIN_CONCAVE_PATH_WEDGE )
{
// effectively convex
return TRUE;
}
return FALSE;
}
// debug
void LLVolumeParams::setCube()
{
mProfileParams.setCurveType(LL_PCODE_PROFILE_SQUARE);
mProfileParams.setBegin(0.f);
mProfileParams.setEnd(1.f);
mProfileParams.setHollow(0.f);
mPathParams.setBegin(0.f);
mPathParams.setEnd(1.f);
mPathParams.setScale(1.f, 1.f);
mPathParams.setShear(0.f, 0.f);
mPathParams.setCurveType(LL_PCODE_PATH_LINE);
mPathParams.setTwistBegin(0.f);
mPathParams.setTwistEnd(0.f);
mPathParams.setRadiusOffset(0.f);
mPathParams.setTaper(0.f, 0.f);
mPathParams.setRevolutions(0.f);
mPathParams.setSkew(0.f);
}
LLFaceID LLVolume::generateFaceMask()
{
LLFaceID new_mask = 0x0000;
switch(mParams.getProfileParams().getCurveType() & LL_PCODE_PROFILE_MASK)
{
case LL_PCODE_PROFILE_CIRCLE:
case LL_PCODE_PROFILE_CIRCLE_HALF:
new_mask |= LL_FACE_OUTER_SIDE_0;
break;
case LL_PCODE_PROFILE_SQUARE:
{
for(S32 side = (S32)(mParams.getProfileParams().getBegin() * 4.f); side < llceil(mParams.getProfileParams().getEnd() * 4.f); side++)
{
new_mask |= LL_FACE_OUTER_SIDE_0 << side;
}
}
break;
case LL_PCODE_PROFILE_ISOTRI:
case LL_PCODE_PROFILE_EQUALTRI:
case LL_PCODE_PROFILE_RIGHTTRI:
{
for(S32 side = (S32)(mParams.getProfileParams().getBegin() * 3.f); side < llceil(mParams.getProfileParams().getEnd() * 3.f); side++)
{
new_mask |= LL_FACE_OUTER_SIDE_0 << side;
}
}
break;
default:
llerrs << "Unknown profile!" << llendl;
break;
}
// handle hollow objects
if (mParams.getProfileParams().getHollow() > 0)
{
new_mask |= LL_FACE_INNER_SIDE;
}
// handle open profile curves
if (mProfilep->isOpen())
{
new_mask |= LL_FACE_PROFILE_BEGIN | LL_FACE_PROFILE_END;
}
// handle open path curves
if (mPathp->isOpen())
{
new_mask |= LL_FACE_PATH_BEGIN | LL_FACE_PATH_END;
}
return new_mask;
}
BOOL LLVolume::isFaceMaskValid(LLFaceID face_mask)
{
LLFaceID test_mask = 0;
for(S32 i = 0; i < getNumFaces(); i++)
{
test_mask |= mProfilep->mFaces[i].mFaceID;
}
return test_mask == face_mask;
}
BOOL LLVolume::isConvex() const
{
// mParams.isConvex() may return FALSE even though the final
// geometry is actually convex due to LOD approximations.
// TODO -- provide LLPath and LLProfile with isConvex() methods
// that correctly determine convexity. -- Leviathan
return mParams.isConvex();
}
std::ostream& operator<<(std::ostream &s, const LLProfileParams &profile_params)
{
s << "{type=" << (U32) profile_params.mCurveType;
s << ", begin=" << profile_params.mBegin;
s << ", end=" << profile_params.mEnd;
s << ", hollow=" << profile_params.mHollow;
s << "}";
return s;
}
std::ostream& operator<<(std::ostream &s, const LLPathParams &path_params)
{
s << "{type=" << (U32) path_params.mCurveType;
s << ", begin=" << path_params.mBegin;
s << ", end=" << path_params.mEnd;
s << ", twist=" << path_params.mTwistEnd;
s << ", scale=" << path_params.mScale;
s << ", shear=" << path_params.mShear;
s << ", twist_begin=" << path_params.mTwistBegin;
s << ", radius_offset=" << path_params.mRadiusOffset;
s << ", taper=" << path_params.mTaper;
s << ", revolutions=" << path_params.mRevolutions;
s << ", skew=" << path_params.mSkew;
s << "}";
return s;
}
std::ostream& operator<<(std::ostream &s, const LLVolumeParams &volume_params)
{
s << "{profileparams = " << volume_params.mProfileParams;
s << ", pathparams = " << volume_params.mPathParams;
s << "}";
return s;
}
std::ostream& operator<<(std::ostream &s, const LLProfile &profile)
{
s << " {open=" << (U32) profile.mOpen;
s << ", dirty=" << profile.mDirty;
s << ", totalout=" << profile.mTotalOut;
s << ", total=" << profile.mTotal;
s << "}";
return s;
}
std::ostream& operator<<(std::ostream &s, const LLPath &path)
{
s << "{open=" << (U32) path.mOpen;
s << ", dirty=" << path.mDirty;
s << ", step=" << path.mStep;
s << ", total=" << path.mTotal;
s << "}";
return s;
}
std::ostream& operator<<(std::ostream &s, const LLVolume &volume)
{
s << "{params = " << volume.getParams();
s << ", path = " << *volume.mPathp;
s << ", profile = " << *volume.mProfilep;
s << "}";
return s;
}
std::ostream& operator<<(std::ostream &s, const LLVolume *volumep)
{
s << "{params = " << volumep->getParams();
s << ", path = " << *(volumep->mPathp);
s << ", profile = " << *(volumep->mProfilep);
s << "}";
return s;
}
BOOL LLVolumeFace::create(LLVolume* volume, BOOL partial_build)
{
BOOL ret = FALSE ;
if (mTypeMask & CAP_MASK)
{
ret = createCap(volume, partial_build);
}
else if ((mTypeMask & END_MASK) || (mTypeMask & SIDE_MASK))
{
ret = createSide(volume, partial_build);
}
else
{
llerrs << "Unknown/uninitialized face type!" << llendl;
}
//update the range of the texture coordinates
if(ret)
{
mTexCoordExtents[0].setVec(1.f, 1.f) ;
mTexCoordExtents[1].setVec(0.f, 0.f) ;
U32 end = mVertices.size() ;
for(U32 i = 0 ; i < end ; i++)
{
if(mTexCoordExtents[0].mV[0] > mVertices[i].mTexCoord.mV[0])
{
mTexCoordExtents[0].mV[0] = mVertices[i].mTexCoord.mV[0] ;
}
if(mTexCoordExtents[1].mV[0] < mVertices[i].mTexCoord.mV[0])
{
mTexCoordExtents[1].mV[0] = mVertices[i].mTexCoord.mV[0] ;
}
if(mTexCoordExtents[0].mV[1] > mVertices[i].mTexCoord.mV[1])
{
mTexCoordExtents[0].mV[1] = mVertices[i].mTexCoord.mV[1] ;
}
if(mTexCoordExtents[1].mV[1] < mVertices[i].mTexCoord.mV[1])
{
mTexCoordExtents[1].mV[1] = mVertices[i].mTexCoord.mV[1] ;
}
}
mTexCoordExtents[0].mV[0] = llmax(0.f, mTexCoordExtents[0].mV[0]) ;
mTexCoordExtents[0].mV[1] = llmax(0.f, mTexCoordExtents[0].mV[1]) ;
mTexCoordExtents[1].mV[0] = llmin(1.f, mTexCoordExtents[1].mV[0]) ;
mTexCoordExtents[1].mV[1] = llmin(1.f, mTexCoordExtents[1].mV[1]) ;
}
return ret ;
}
void LerpPlanarVertex(LLVolumeFace::VertexData& v0,
LLVolumeFace::VertexData& v1,
LLVolumeFace::VertexData& v2,
LLVolumeFace::VertexData& vout,
F32 coef01,
F32 coef02)
{
vout.mPosition = v0.mPosition + ((v1.mPosition-v0.mPosition)*coef01)+((v2.mPosition-v0.mPosition)*coef02);
vout.mTexCoord = v0.mTexCoord + ((v1.mTexCoord-v0.mTexCoord)*coef01)+((v2.mTexCoord-v0.mTexCoord)*coef02);
vout.mNormal = v0.mNormal;
vout.mBinormal = v0.mBinormal;
}
BOOL LLVolumeFace::createUnCutCubeCap(LLVolume* volume, BOOL partial_build)
{
LLMemType m1(LLMemType::MTYPE_VOLUME);
const std::vector<LLVolume::Point>& mesh = volume->getMesh();
const std::vector<LLVector3>& profile = volume->getProfile().mProfile;
S32 max_s = volume->getProfile().getTotal();
S32 max_t = volume->getPath().mPath.size();
// S32 i;
S32 num_vertices = 0, num_indices = 0;
S32 grid_size = (profile.size()-1)/4;
S32 quad_count = (grid_size * grid_size);
num_vertices = (grid_size+1)*(grid_size+1);
num_indices = quad_count * 4;
LLVector3& min = mExtents[0];
LLVector3& max = mExtents[1];
S32 offset = 0;
if (mTypeMask & TOP_MASK)
offset = (max_t-1) * max_s;
else
offset = mBeginS;
VertexData corners[4];
VertexData baseVert;
for(int t = 0; t < 4; t++){
corners[t].mPosition = mesh[offset + (grid_size*t)].mPos;
corners[t].mTexCoord.mV[0] = profile[grid_size*t].mV[0]+0.5f;
corners[t].mTexCoord.mV[1] = 0.5f - profile[grid_size*t].mV[1];
}
baseVert.mNormal =
((corners[1].mPosition-corners[0].mPosition) %
(corners[2].mPosition-corners[1].mPosition));
baseVert.mNormal.normVec();
if(!(mTypeMask & TOP_MASK)){
baseVert.mNormal *= -1.0f;
}else{
//Swap the UVs on the U(X) axis for top face
LLVector2 swap;
swap = corners[0].mTexCoord;
corners[0].mTexCoord=corners[3].mTexCoord;
corners[3].mTexCoord=swap;
swap = corners[1].mTexCoord;
corners[1].mTexCoord=corners[2].mTexCoord;
corners[2].mTexCoord=swap;
}
baseVert.mBinormal = calc_binormal_from_triangle(
corners[0].mPosition, corners[0].mTexCoord,
corners[1].mPosition, corners[1].mTexCoord,
corners[2].mPosition, corners[2].mTexCoord);
for(int t = 0; t < 4; t++){
corners[t].mBinormal = baseVert.mBinormal;
corners[t].mNormal = baseVert.mNormal;
}
mHasBinormals = TRUE;
if (partial_build)
{
mVertices.clear();
}
S32 vtop = mVertices.size();
for(int gx = 0;gx<grid_size+1;gx++){
for(int gy = 0;gy<grid_size+1;gy++){
VertexData newVert;
LerpPlanarVertex(
corners[0],
corners[1],
corners[3],
newVert,
(F32)gx/(F32)grid_size,
(F32)gy/(F32)grid_size);
mVertices.push_back(newVert);
if (gx == 0 && gy == 0)
{
min = max = newVert.mPosition;
}
else
{
update_min_max(min,max,newVert.mPosition);
}
}
}
mCenter = (min + max) * 0.5f;
if (!partial_build)
{
S32 idxs[] = {0,1,(grid_size+1)+1,(grid_size+1)+1,(grid_size+1),0};
for(S32 gx = 0;gx<grid_size;gx++)
{
for(S32 gy = 0;gy<grid_size;gy++)
{
if (mTypeMask & TOP_MASK)
{
for(S32 i=5;i>=0;i--)
{
mIndices.push_back(vtop+(gy*(grid_size+1))+gx+idxs[i]);
}
}
else
{
for(S32 i=0;i<6;i++)
{
mIndices.push_back(vtop+(gy*(grid_size+1))+gx+idxs[i]);
}
}
}
}
}
return TRUE;
}
BOOL LLVolumeFace::createCap(LLVolume* volume, BOOL partial_build)
{
LLMemType m1(LLMemType::MTYPE_VOLUME);
if (!(mTypeMask & HOLLOW_MASK) &&
!(mTypeMask & OPEN_MASK) &&
((volume->getParams().getPathParams().getBegin()==0.0f)&&
(volume->getParams().getPathParams().getEnd()==1.0f))&&
(volume->getParams().getProfileParams().getCurveType()==LL_PCODE_PROFILE_SQUARE &&
volume->getParams().getPathParams().getCurveType()==LL_PCODE_PATH_LINE)
){
return createUnCutCubeCap(volume, partial_build);
}
S32 num_vertices = 0, num_indices = 0;
const std::vector<LLVolume::Point>& mesh = volume->getMesh();
const std::vector<LLVector3>& profile = volume->getProfile().mProfile;
// All types of caps have the same number of vertices and indices
num_vertices = profile.size();
num_indices = (profile.size() - 2)*3;
mVertices.resize(num_vertices);
if (!partial_build)
{
mIndices.resize(num_indices);
}
S32 max_s = volume->getProfile().getTotal();
S32 max_t = volume->getPath().mPath.size();
mCenter.clearVec();
S32 offset = 0;
if (mTypeMask & TOP_MASK)
{
offset = (max_t-1) * max_s;
}
else
{
offset = mBeginS;
}
// Figure out the normal, assume all caps are flat faces.
// Cross product to get normals.
LLVector2 cuv;
LLVector2 min_uv, max_uv;
LLVector3& min = mExtents[0];
LLVector3& max = mExtents[1];
// Copy the vertices into the array
for (S32 i = 0; i < num_vertices; i++)
{
if (mTypeMask & TOP_MASK)
{
mVertices[i].mTexCoord.mV[0] = profile[i].mV[0]+0.5f;
mVertices[i].mTexCoord.mV[1] = profile[i].mV[1]+0.5f;
}
else
{
// Mirror for underside.
mVertices[i].mTexCoord.mV[0] = profile[i].mV[0]+0.5f;
mVertices[i].mTexCoord.mV[1] = 0.5f - profile[i].mV[1];
}
mVertices[i].mPosition = mesh[i + offset].mPos;
if (i == 0)
{
min = max = mVertices[i].mPosition;
min_uv = max_uv = mVertices[i].mTexCoord;
}
else
{
update_min_max(min,max, mVertices[i].mPosition);
update_min_max(min_uv, max_uv, mVertices[i].mTexCoord);
}
}
mCenter = (min+max)*0.5f;
cuv = (min_uv + max_uv)*0.5f;
LLVector3 binormal = calc_binormal_from_triangle(
mCenter, cuv,
mVertices[0].mPosition, mVertices[0].mTexCoord,
mVertices[1].mPosition, mVertices[1].mTexCoord);
binormal.normVec();
LLVector3 d0;
LLVector3 d1;
LLVector3 normal;
d0 = mCenter-mVertices[0].mPosition;
d1 = mCenter-mVertices[1].mPosition;
normal = (mTypeMask & TOP_MASK) ? (d0%d1) : (d1%d0);
normal.normVec();
VertexData vd;
vd.mPosition = mCenter;
vd.mNormal = normal;
vd.mBinormal = binormal;
vd.mTexCoord = cuv;
if (!(mTypeMask & HOLLOW_MASK) && !(mTypeMask & OPEN_MASK))
{
mVertices.push_back(vd);
num_vertices++;
if (!partial_build)
{
vector_append(mIndices, 3);
}
}
for (S32 i = 0; i < num_vertices; i++)
{
mVertices[i].mBinormal = binormal;
mVertices[i].mNormal = normal;
}
mHasBinormals = TRUE;
if (partial_build)
{
return TRUE;
}
if (mTypeMask & HOLLOW_MASK)
{
if (mTypeMask & TOP_MASK)
{
// HOLLOW TOP
// Does it matter if it's open or closed? - djs
S32 pt1 = 0, pt2 = num_vertices - 1;
S32 i = 0;
while (pt2 - pt1 > 1)
{
// Use the profile points instead of the mesh, since you want
// the un-transformed profile distances.
LLVector3 p1 = profile[pt1];
LLVector3 p2 = profile[pt2];
LLVector3 pa = profile[pt1+1];
LLVector3 pb = profile[pt2-1];
p1.mV[VZ] = 0.f;
p2.mV[VZ] = 0.f;
pa.mV[VZ] = 0.f;
pb.mV[VZ] = 0.f;
// Use area of triangle to determine backfacing
F32 area_1a2, area_1ba, area_21b, area_2ab;
area_1a2 = (p1.mV[0]*pa.mV[1] - pa.mV[0]*p1.mV[1]) +
(pa.mV[0]*p2.mV[1] - p2.mV[0]*pa.mV[1]) +
(p2.mV[0]*p1.mV[1] - p1.mV[0]*p2.mV[1]);
area_1ba = (p1.mV[0]*pb.mV[1] - pb.mV[0]*p1.mV[1]) +
(pb.mV[0]*pa.mV[1] - pa.mV[0]*pb.mV[1]) +
(pa.mV[0]*p1.mV[1] - p1.mV[0]*pa.mV[1]);
area_21b = (p2.mV[0]*p1.mV[1] - p1.mV[0]*p2.mV[1]) +
(p1.mV[0]*pb.mV[1] - pb.mV[0]*p1.mV[1]) +
(pb.mV[0]*p2.mV[1] - p2.mV[0]*pb.mV[1]);
area_2ab = (p2.mV[0]*pa.mV[1] - pa.mV[0]*p2.mV[1]) +
(pa.mV[0]*pb.mV[1] - pb.mV[0]*pa.mV[1]) +
(pb.mV[0]*p2.mV[1] - p2.mV[0]*pb.mV[1]);
BOOL use_tri1a2 = TRUE;
BOOL tri_1a2 = TRUE;
BOOL tri_21b = TRUE;
if (area_1a2 < 0)
{
tri_1a2 = FALSE;
}
if (area_2ab < 0)
{
// Can't use, because it contains point b
tri_1a2 = FALSE;
}
if (area_21b < 0)
{
tri_21b = FALSE;
}
if (area_1ba < 0)
{
// Can't use, because it contains point b
tri_21b = FALSE;
}
if (!tri_1a2)
{
use_tri1a2 = FALSE;
}
else if (!tri_21b)
{
use_tri1a2 = TRUE;
}
else
{
LLVector3 d1 = p1 - pa;
LLVector3 d2 = p2 - pb;
if (d1.magVecSquared() < d2.magVecSquared())
{
use_tri1a2 = TRUE;
}
else
{
use_tri1a2 = FALSE;
}
}
if (use_tri1a2)
{
mIndices[i++] = pt1;
mIndices[i++] = pt1 + 1;
mIndices[i++] = pt2;
pt1++;
}
else
{
mIndices[i++] = pt1;
mIndices[i++] = pt2 - 1;
mIndices[i++] = pt2;
pt2--;
}
}
}
else
{
// HOLLOW BOTTOM
// Does it matter if it's open or closed? - djs
llassert(mTypeMask & BOTTOM_MASK);
S32 pt1 = 0, pt2 = num_vertices - 1;
S32 i = 0;
while (pt2 - pt1 > 1)
{
// Use the profile points instead of the mesh, since you want
// the un-transformed profile distances.
LLVector3 p1 = profile[pt1];
LLVector3 p2 = profile[pt2];
LLVector3 pa = profile[pt1+1];
LLVector3 pb = profile[pt2-1];
p1.mV[VZ] = 0.f;
p2.mV[VZ] = 0.f;
pa.mV[VZ] = 0.f;
pb.mV[VZ] = 0.f;
// Use area of triangle to determine backfacing
F32 area_1a2, area_1ba, area_21b, area_2ab;
area_1a2 = (p1.mV[0]*pa.mV[1] - pa.mV[0]*p1.mV[1]) +
(pa.mV[0]*p2.mV[1] - p2.mV[0]*pa.mV[1]) +
(p2.mV[0]*p1.mV[1] - p1.mV[0]*p2.mV[1]);
area_1ba = (p1.mV[0]*pb.mV[1] - pb.mV[0]*p1.mV[1]) +
(pb.mV[0]*pa.mV[1] - pa.mV[0]*pb.mV[1]) +
(pa.mV[0]*p1.mV[1] - p1.mV[0]*pa.mV[1]);
area_21b = (p2.mV[0]*p1.mV[1] - p1.mV[0]*p2.mV[1]) +
(p1.mV[0]*pb.mV[1] - pb.mV[0]*p1.mV[1]) +
(pb.mV[0]*p2.mV[1] - p2.mV[0]*pb.mV[1]);
area_2ab = (p2.mV[0]*pa.mV[1] - pa.mV[0]*p2.mV[1]) +
(pa.mV[0]*pb.mV[1] - pb.mV[0]*pa.mV[1]) +
(pb.mV[0]*p2.mV[1] - p2.mV[0]*pb.mV[1]);
BOOL use_tri1a2 = TRUE;
BOOL tri_1a2 = TRUE;
BOOL tri_21b = TRUE;
if (area_1a2 < 0)
{
tri_1a2 = FALSE;
}
if (area_2ab < 0)
{
// Can't use, because it contains point b
tri_1a2 = FALSE;
}
if (area_21b < 0)
{
tri_21b = FALSE;
}
if (area_1ba < 0)
{
// Can't use, because it contains point b
tri_21b = FALSE;
}
if (!tri_1a2)
{
use_tri1a2 = FALSE;
}
else if (!tri_21b)
{
use_tri1a2 = TRUE;
}
else
{
LLVector3 d1 = p1 - pa;
LLVector3 d2 = p2 - pb;
if (d1.magVecSquared() < d2.magVecSquared())
{
use_tri1a2 = TRUE;
}
else
{
use_tri1a2 = FALSE;
}
}
// Flipped backfacing from top
if (use_tri1a2)
{
mIndices[i++] = pt1;
mIndices[i++] = pt2;
mIndices[i++] = pt1 + 1;
pt1++;
}
else
{
mIndices[i++] = pt1;
mIndices[i++] = pt2;
mIndices[i++] = pt2 - 1;
pt2--;
}
}
}
}
else
{
// Not hollow, generate the triangle fan.
U16 v1 = 2;
U16 v2 = 1;
if (mTypeMask & TOP_MASK)
{
v1 = 1;
v2 = 2;
}
for (S32 i = 0; i < (num_vertices - 2); i++)
{
mIndices[3*i] = num_vertices - 1;
mIndices[3*i+v1] = i;
mIndices[3*i+v2] = i + 1;
}
}
return TRUE;
}
void LLVolumeFace::createBinormals()
{
LLMemType m1(LLMemType::MTYPE_VOLUME);
if (!mHasBinormals)
{
//generate binormals
for (U32 i = 0; i < mIndices.size()/3; i++)
{ //for each triangle
const VertexData& v0 = mVertices[mIndices[i*3+0]];
const VertexData& v1 = mVertices[mIndices[i*3+1]];
const VertexData& v2 = mVertices[mIndices[i*3+2]];
//calculate binormal
LLVector3 binorm = calc_binormal_from_triangle(v0.mPosition, v0.mTexCoord,
v1.mPosition, v1.mTexCoord,
v2.mPosition, v2.mTexCoord);
for (U32 j = 0; j < 3; j++)
{ //add triangle normal to vertices
mVertices[mIndices[i*3+j]].mBinormal += binorm; // * (weight_sum - d[j])/weight_sum;
}
//even out quad contributions
if (i % 2 == 0)
{
mVertices[mIndices[i*3+2]].mBinormal += binorm;
}
else
{
mVertices[mIndices[i*3+1]].mBinormal += binorm;
}
}
//normalize binormals
for (U32 i = 0; i < mVertices.size(); i++)
{
mVertices[i].mBinormal.normVec();
mVertices[i].mNormal.normVec();
}
mHasBinormals = TRUE;
}
}
BOOL LLVolumeFace::createSide(LLVolume* volume, BOOL partial_build)
{
LLMemType m1(LLMemType::MTYPE_VOLUME);
BOOL flat = mTypeMask & FLAT_MASK;
U8 sculpt_type = volume->getParams().getSculptType();
U8 sculpt_stitching = sculpt_type & LL_SCULPT_TYPE_MASK;
BOOL sculpt_invert = sculpt_type & LL_SCULPT_FLAG_INVERT;
BOOL sculpt_mirror = sculpt_type & LL_SCULPT_FLAG_MIRROR;
BOOL sculpt_reverse_horizontal = (sculpt_invert ? !sculpt_mirror : sculpt_mirror); // XOR
S32 num_vertices, num_indices;
const std::vector<LLVolume::Point>& mesh = volume->getMesh();
const std::vector<LLVector3>& profile = volume->getProfile().mProfile;
const std::vector<LLPath::PathPt>& path_data = volume->getPath().mPath;
S32 max_s = volume->getProfile().getTotal();
S32 s, t, i;
F32 ss, tt;
num_vertices = mNumS*mNumT;
num_indices = (mNumS-1)*(mNumT-1)*6;
mVertices.resize(num_vertices);
if (!partial_build)
{
mIndices.resize(num_indices);
mEdge.resize(num_indices);
}
else
{
mHasBinormals = FALSE;
}
S32 begin_stex = llfloor( profile[mBeginS].mV[2] );
S32 num_s = ((mTypeMask & INNER_MASK) && (mTypeMask & FLAT_MASK) && mNumS > 2) ? mNumS/2 : mNumS;
S32 cur_vertex = 0;
// Copy the vertices into the array
for (t = mBeginT; t < mBeginT + mNumT; t++)
{
tt = path_data[t].mTexT;
for (s = 0; s < num_s; s++)
{
if (mTypeMask & END_MASK)
{
if (s)
{
ss = 1.f;
}
else
{
ss = 0.f;
}
}
else
{
// Get s value for tex-coord.
if (!flat)
{
ss = profile[mBeginS + s].mV[2];
}
else
{
ss = profile[mBeginS + s].mV[2] - begin_stex;
}
}
if (sculpt_reverse_horizontal)
{
ss = 1.f - ss;
}
// Check to see if this triangle wraps around the array.
if (mBeginS + s >= max_s)
{
// We're wrapping
i = mBeginS + s + max_s*(t-1);
}
else
{
i = mBeginS + s + max_s*t;
}
mVertices[cur_vertex].mPosition = mesh[i].mPos;
mVertices[cur_vertex].mTexCoord = LLVector2(ss,tt);
mVertices[cur_vertex].mNormal = LLVector3(0,0,0);
mVertices[cur_vertex].mBinormal = LLVector3(0,0,0);
cur_vertex++;
if ((mTypeMask & INNER_MASK) && (mTypeMask & FLAT_MASK) && mNumS > 2 && s > 0)
{
mVertices[cur_vertex].mPosition = mesh[i].mPos;
mVertices[cur_vertex].mTexCoord = LLVector2(ss,tt);
mVertices[cur_vertex].mNormal = LLVector3(0,0,0);
mVertices[cur_vertex].mBinormal = LLVector3(0,0,0);
cur_vertex++;
}
}
if ((mTypeMask & INNER_MASK) && (mTypeMask & FLAT_MASK) && mNumS > 2)
{
if (mTypeMask & OPEN_MASK)
{
s = num_s-1;
}
else
{
s = 0;
}
i = mBeginS + s + max_s*t;
ss = profile[mBeginS + s].mV[2] - begin_stex;
mVertices[cur_vertex].mPosition = mesh[i].mPos;
mVertices[cur_vertex].mTexCoord = LLVector2(ss,tt);
mVertices[cur_vertex].mNormal = LLVector3(0,0,0);
mVertices[cur_vertex].mBinormal = LLVector3(0,0,0);
cur_vertex++;
}
}
//get bounding box for this side
LLVector3& face_min = mExtents[0];
LLVector3& face_max = mExtents[1];
mCenter.clearVec();
face_min = face_max = mVertices[0].mPosition;
for (U32 i = 1; i < mVertices.size(); ++i)
{
update_min_max(face_min, face_max, mVertices[i].mPosition);
}
mCenter = (face_min + face_max) * 0.5f;
S32 cur_index = 0;
S32 cur_edge = 0;
BOOL flat_face = mTypeMask & FLAT_MASK;
if (!partial_build)
{
// Now we generate the indices.
for (t = 0; t < (mNumT-1); t++)
{
for (s = 0; s < (mNumS-1); s++)
{
mIndices[cur_index++] = s + mNumS*t; //bottom left
mIndices[cur_index++] = s+1 + mNumS*(t+1); //top right
mIndices[cur_index++] = s + mNumS*(t+1); //top left
mIndices[cur_index++] = s + mNumS*t; //bottom left
mIndices[cur_index++] = s+1 + mNumS*t; //bottom right
mIndices[cur_index++] = s+1 + mNumS*(t+1); //top right
mEdge[cur_edge++] = (mNumS-1)*2*t+s*2+1; //bottom left/top right neighbor face
if (t < mNumT-2) { //top right/top left neighbor face
mEdge[cur_edge++] = (mNumS-1)*2*(t+1)+s*2+1;
}
else if (mNumT <= 3 || volume->getPath().isOpen() == TRUE) { //no neighbor
mEdge[cur_edge++] = -1;
}
else { //wrap on T
mEdge[cur_edge++] = s*2+1;
}
if (s > 0) { //top left/bottom left neighbor face
mEdge[cur_edge++] = (mNumS-1)*2*t+s*2-1;
}
else if (flat_face || volume->getProfile().isOpen() == TRUE) { //no neighbor
mEdge[cur_edge++] = -1;
}
else { //wrap on S
mEdge[cur_edge++] = (mNumS-1)*2*t+(mNumS-2)*2+1;
}
if (t > 0) { //bottom left/bottom right neighbor face
mEdge[cur_edge++] = (mNumS-1)*2*(t-1)+s*2;
}
else if (mNumT <= 3 || volume->getPath().isOpen() == TRUE) { //no neighbor
mEdge[cur_edge++] = -1;
}
else { //wrap on T
mEdge[cur_edge++] = (mNumS-1)*2*(mNumT-2)+s*2;
}
if (s < mNumS-2) { //bottom right/top right neighbor face
mEdge[cur_edge++] = (mNumS-1)*2*t+(s+1)*2;
}
else if (flat_face || volume->getProfile().isOpen() == TRUE) { //no neighbor
mEdge[cur_edge++] = -1;
}
else { //wrap on S
mEdge[cur_edge++] = (mNumS-1)*2*t;
}
mEdge[cur_edge++] = (mNumS-1)*2*t+s*2; //top right/bottom left neighbor face
}
}
}
//generate normals
for (U32 i = 0; i < mIndices.size()/3; i++) //for each triangle
{
const U16* idx = &(mIndices[i*3]);
VertexData* v[] =
{ &mVertices[idx[0]], &mVertices[idx[1]], &mVertices[idx[2]] };
//calculate triangle normal
LLVector3 norm = (v[0]->mPosition-v[1]->mPosition) % (v[0]->mPosition-v[2]->mPosition);
v[0]->mNormal += norm;
v[1]->mNormal += norm;
v[2]->mNormal += norm;
//even out quad contributions
v[i%2+1]->mNormal += norm;
}
// adjust normals based on wrapping and stitching
BOOL s_bottom_converges = ((mVertices[0].mPosition - mVertices[mNumS*(mNumT-2)].mPosition).magVecSquared() < 0.000001f);
BOOL s_top_converges = ((mVertices[mNumS-1].mPosition - mVertices[mNumS*(mNumT-2)+mNumS-1].mPosition).magVecSquared() < 0.000001f);
if (sculpt_stitching == LL_SCULPT_TYPE_NONE) // logic for non-sculpt volumes
{
if (volume->getPath().isOpen() == FALSE)
{ //wrap normals on T
for (S32 i = 0; i < mNumS; i++)
{
LLVector3 norm = mVertices[i].mNormal + mVertices[mNumS*(mNumT-1)+i].mNormal;
mVertices[i].mNormal = norm;
mVertices[mNumS*(mNumT-1)+i].mNormal = norm;
}
}
if ((volume->getProfile().isOpen() == FALSE) && !(s_bottom_converges))
{ //wrap normals on S
for (S32 i = 0; i < mNumT; i++)
{
LLVector3 norm = mVertices[mNumS*i].mNormal + mVertices[mNumS*i+mNumS-1].mNormal;
mVertices[mNumS * i].mNormal = norm;
mVertices[mNumS * i+mNumS-1].mNormal = norm;
}
}
if (volume->getPathType() == LL_PCODE_PATH_CIRCLE &&
((volume->getProfileType() & LL_PCODE_PROFILE_MASK) == LL_PCODE_PROFILE_CIRCLE_HALF))
{
if (s_bottom_converges)
{ //all lower S have same normal
for (S32 i = 0; i < mNumT; i++)
{
mVertices[mNumS*i].mNormal = LLVector3(1,0,0);
}
}
if (s_top_converges)
{ //all upper S have same normal
for (S32 i = 0; i < mNumT; i++)
{
mVertices[mNumS*i+mNumS-1].mNormal = LLVector3(-1,0,0);
}
}
}
}
else // logic for sculpt volumes
{
BOOL average_poles = FALSE;
BOOL wrap_s = FALSE;
BOOL wrap_t = FALSE;
if (sculpt_stitching == LL_SCULPT_TYPE_SPHERE)
average_poles = TRUE;
if ((sculpt_stitching == LL_SCULPT_TYPE_SPHERE) ||
(sculpt_stitching == LL_SCULPT_TYPE_TORUS) ||
(sculpt_stitching == LL_SCULPT_TYPE_CYLINDER))
wrap_s = TRUE;
if (sculpt_stitching == LL_SCULPT_TYPE_TORUS)
wrap_t = TRUE;
if (average_poles)
{
// average normals for north pole
LLVector3 average(0.0, 0.0, 0.0);
for (S32 i = 0; i < mNumS; i++)
{
average += mVertices[i].mNormal;
}
// set average
for (S32 i = 0; i < mNumS; i++)
{
mVertices[i].mNormal = average;
}
// average normals for south pole
average = LLVector3(0.0, 0.0, 0.0);
for (S32 i = 0; i < mNumS; i++)
{
average += mVertices[i + mNumS * (mNumT - 1)].mNormal;
}
// set average
for (S32 i = 0; i < mNumS; i++)
{
mVertices[i + mNumS * (mNumT - 1)].mNormal = average;
}
}
if (wrap_s)
{
for (S32 i = 0; i < mNumT; i++)
{
LLVector3 norm = mVertices[mNumS*i].mNormal + mVertices[mNumS*i+mNumS-1].mNormal;
mVertices[mNumS * i].mNormal = norm;
mVertices[mNumS * i+mNumS-1].mNormal = norm;
}
}
if (wrap_t)
{
for (S32 i = 0; i < mNumS; i++)
{
LLVector3 norm = mVertices[i].mNormal + mVertices[mNumS*(mNumT-1)+i].mNormal;
mVertices[i].mNormal = norm;
mVertices[mNumS*(mNumT-1)+i].mNormal = norm;
}
}
}
return TRUE;
}
// Finds binormal based on three vertices with texture coordinates.
// Fills in dummy values if the triangle has degenerate texture coordinates.
LLVector3 calc_binormal_from_triangle(
const LLVector3& pos0,
const LLVector2& tex0,
const LLVector3& pos1,
const LLVector2& tex1,
const LLVector3& pos2,
const LLVector2& tex2)
{
LLVector3 rx0( pos0.mV[VX], tex0.mV[VX], tex0.mV[VY] );
LLVector3 rx1( pos1.mV[VX], tex1.mV[VX], tex1.mV[VY] );
LLVector3 rx2( pos2.mV[VX], tex2.mV[VX], tex2.mV[VY] );
LLVector3 ry0( pos0.mV[VY], tex0.mV[VX], tex0.mV[VY] );
LLVector3 ry1( pos1.mV[VY], tex1.mV[VX], tex1.mV[VY] );
LLVector3 ry2( pos2.mV[VY], tex2.mV[VX], tex2.mV[VY] );
LLVector3 rz0( pos0.mV[VZ], tex0.mV[VX], tex0.mV[VY] );
LLVector3 rz1( pos1.mV[VZ], tex1.mV[VX], tex1.mV[VY] );
LLVector3 rz2( pos2.mV[VZ], tex2.mV[VX], tex2.mV[VY] );
LLVector3 r0 = (rx0 - rx1) % (rx0 - rx2);
LLVector3 r1 = (ry0 - ry1) % (ry0 - ry2);
LLVector3 r2 = (rz0 - rz1) % (rz0 - rz2);
if( r0.mV[VX] && r1.mV[VX] && r2.mV[VX] )
{
LLVector3 binormal(
-r0.mV[VZ] / r0.mV[VX],
-r1.mV[VZ] / r1.mV[VX],
-r2.mV[VZ] / r2.mV[VX]);
// binormal.normVec();
return binormal;
}
else
{
return LLVector3( 0, 1 , 0 );
}
}