Files
libremetaverse/OpenMetaverse.Rendering.GPL/GPLRenderer.cs
John Hurliman 81e6342d36 Removing LL prefix from all basic types
git-svn-id: http://libopenmetaverse.googlecode.com/svn/trunk@1998 52acb1d6-8a22-11de-b505-999d5b087335
2008-07-25 05:15:05 +00:00

1210 lines
48 KiB
C#

/*
* This file is part of OpenMetaverse.Rendering.GPL.
*
* libprimrender is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2.0 as
* published by the Free Software Foundation.
*
* libprimrender is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with OpenMetaverse.Rendering.GPL. If not, see
* <http://www.gnu.org/licenses/>.
*/
using System;
using System.Collections.Generic;
namespace OpenMetaverse.Rendering
{
[RendererName("GPL Renderer")]
public partial class GPLRenderer : IRendering
{
private const float F_PI = (float)Math.PI;
private const int MIN_DETAIL_FACES = 6;
private const int MIN_LOD = 0;
private const int MAX_LOD = 3;
private const int SCULPT_REZ_1 = 6;
private const int SCULPT_REZ_2 = 8;
private const int SCULPT_REZ_3 = 16;
private const int SCULPT_REZ_4 = 32;
private const float SCULPT_MIN_AREA = 0.002f;
private static readonly float[] DETAIL_LEVELS = new float[] { 1f, 1.5f, 2.5f, 4f };
private static readonly float[] TABLE_SCALE = new float[] { 1f, 1f, 1f, 0.5f, 0.707107f, 0.53f, 0.525f, 0.5f };
public SimpleMesh GenerateSimpleMesh(Primitive prim, DetailLevel lod)
{
float detail = DETAIL_LEVELS[(int)lod];
Path path = GeneratePath(prim.Data, detail);
Profile profile = GenerateProfile(prim.Data, path, detail);
SimpleMesh mesh = new SimpleMesh();
mesh.Prim = prim;
mesh.Path = path;
mesh.Profile = profile;
mesh.Vertices = GenerateVertices(prim.Data, detail, path, profile);
mesh.Indices = GenerateIndices(prim.Data, path, profile);
return mesh;
}
public FacetedMesh GenerateFacetedMesh(Primitive prim, DetailLevel lod)
{
float detail = DETAIL_LEVELS[(int)lod];
Path path = GeneratePath(prim.Data, detail);
Profile profile = GenerateProfile(prim.Data, path, detail);
List<Vertex> vertices = GenerateVertices(prim.Data, detail, path, profile);
FacetedMesh mesh = new FacetedMesh();
mesh.Prim = prim;
mesh.Path = path;
mesh.Profile = profile;
mesh.Faces = CreateVolumeFaces(prim, path, profile, vertices);
return mesh;
}
private static List<Face> CreateVolumeFaces(Primitive prim, Path path,
Profile profile, List<Vertex> vertices)
{
int numFaces = profile.Faces.Count;
List<Face> faces = new List<Face>(numFaces);
// Initialize faces with parameter data
for (int i = 0; i < numFaces; i++)
{
ProfileFace pf = profile.Faces[i];
Face face = new Face();
face.Vertices = new List<Vertex>();
face.Indices = new List<ushort>();
face.Edge = new List<int>();
face.BeginS = pf.Index;
face.NumS = pf.Count;
face.BeginT = 0;
face.NumT = path.Points.Count;
face.ID = i;
// Set the type mask bits correctly
if (prim.Data.ProfileHollow > 0f)
face.Mask |= FaceMask.Hollow;
if (profile.Open)
face.Mask |= FaceMask.Open;
if (pf.Cap)
{
face.Mask |= FaceMask.Cap;
if (pf.Type == FaceType.PathBegin)
face.Mask |= FaceMask.Top;
else
face.Mask |= FaceMask.Bottom;
}
else if (pf.Type == FaceType.ProfileBegin || pf.Type == FaceType.ProfileEnd)
{
face.Mask |= FaceMask.Flat;
face.Mask |= FaceMask.End;
}
else
{
face.Mask |= FaceMask.Side;
if (pf.Flat)
face.Mask |= FaceMask.Flat;
if (pf.Type == FaceType.InnerSide)
{
face.Mask |= FaceMask.Inner;
if (pf.Flat && face.NumS > 2)
face.NumS *= 2; // Flat inner faces have to copy vert normals
}
else
{
face.Mask |= FaceMask.Outer;
}
}
faces.Add(face);
}
for (int i = 0; i < faces.Count; i++)
{
Face face = faces[i];
BuildFace(ref face, prim.Data, vertices, path, profile, prim.Textures.GetFace((uint)i));
faces[i] = face;
}
return faces;
}
private static List<Vertex> GenerateVertices(LLObject.ObjectData prim, float detail, Path path, Profile profile)
{
int sizeS = path.Points.Count;
int sizeT = profile.Positions.Count;
// Generate vertex positions
List<Vertex> vertices = new List<Vertex>(sizeT * sizeS);
for (int i = 0; i < sizeT * sizeS; i++)
vertices.Add(new Vertex());
// Run along the path
for (int s = 0; s < sizeS; ++s)
{
Vector2 scale = path.Points[s].Scale;
Quaternion rot = path.Points[s].Rotation;
Vector3 pos = path.Points[s].Position;
// Run along the profile
for (int t = 0; t < sizeT; ++t)
{
int m = s * sizeT + t;
Vertex vertex = vertices[m];
vertex.Position.X = profile.Positions[t].X * scale.X;
vertex.Position.Y = profile.Positions[t].Y * scale.Y;
vertex.Position.Z = 0f;
vertex.Position *= rot;
vertex.Position += pos;
vertices[m] = vertex;
}
}
return vertices;
}
private static List<ushort> GenerateIndices(LLObject.ObjectData prim, Path path, Profile profile)
{
bool open = profile.Open;
bool hollow = (prim.ProfileHollow > 0f);
int sizeS = profile.Positions.Count;
int sizeSOut = profile.TotalOutsidePoints;
int sizeT = path.Points.Count;
int s, t, i;
List<ushort> indices = new List<ushort>();
if (open)
{
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 < sizeT - 1; t++)
{
// The outer face, first cut, and inner face
for (s = 0; s < sizeS - 1; s++)
{
i = s + t * sizeS;
indices.Add((ushort)i); // x,y
indices.Add((ushort)(i + 1)); // x+1,y
indices.Add((ushort)(i + sizeS)); // x,y+1
indices.Add((ushort)(i + sizeS)); // x,y+1
indices.Add((ushort)(i + 1)); // x+1,y
indices.Add((ushort)(i + sizeS + 1)); // x+1,y+1
}
// The other cut face
indices.Add((ushort)(s + t * sizeS)); // x,y
indices.Add((ushort)(0 + t * sizeS)); // x+1,y
indices.Add((ushort)(s + (t + 1) * sizeS)); // x,y+1
indices.Add((ushort)(s + (t + 1) * sizeS)); // x,y+1
indices.Add((ushort)(0 + t * sizeS)); // x+1,y
indices.Add((ushort)(0 + (t + 1) * sizeS)); // x+1,y+1
}
// Do the top and bottom caps, if necessary
if (path.Open)
{
// Top cap
int pt1 = 0;
int pt2 = sizeS - 1;
i = (sizeT - 1) * sizeS;
while (pt2 - pt1 > 1)
{
if (use_tri_1a2(profile, pt1, pt2))
{
indices.Add((ushort)(pt1 + i));
indices.Add((ushort)(pt1 + 1 + i));
indices.Add((ushort)(pt2 + i));
pt1++;
}
else
{
indices.Add((ushort)(pt1 + i));
indices.Add((ushort)(pt2 - 1 + i));
indices.Add((ushort)(pt2 + i));
pt2--;
}
}
// Bottom cap
pt1 = 0;
pt2 = sizeS - 1;
while (pt2 - pt1 > 1)
{
if (use_tri_1a2(profile, pt1, pt2))
{
indices.Add((ushort)pt1);
indices.Add((ushort)pt2);
indices.Add((ushort)(pt1 + 1));
pt1++;
}
else
{
indices.Add((ushort)pt1);
indices.Add((ushort)pt2);
indices.Add((ushort)(pt2 - 1));
pt2--;
}
}
}
}
else
{
// Open solid
for (t = 0; t < sizeT - 1; t++)
{
// Outer face + 1 cut face
for (s = 0; s < sizeS - 1; s++)
{
i = s + t * sizeS;
indices.Add((ushort)i); // x,y
indices.Add((ushort)(i + 1)); // x+1,y
indices.Add((ushort)(i + sizeS)); // x,y+1
indices.Add((ushort)(i + sizeS)); // x,y+1
indices.Add((ushort)(i + 1)); // x+1,y
indices.Add((ushort)(i + sizeS + 1)); // x+1,y+1
}
// The other cut face
indices.Add((ushort)((sizeS - 1) + (t * sizeS))); // x,y
indices.Add((ushort)(0 + t * sizeS)); // x+1,y
indices.Add((ushort)((sizeS - 1) + (t + 1) * sizeS)); // x,y+1
indices.Add((ushort)((sizeS - 1) + (t + 1) * sizeS)); // x,y+1
indices.Add((ushort)(0 + (t * sizeS))); // x+1,y
indices.Add((ushort)(0 + (t + 1) * sizeS)); // x+1,y+1
}
// Do the top and bottom caps, if necessary
if (path.Open)
{
for (s = 0; s < sizeS - 2; s++)
{
indices.Add((ushort)(s + 1));
indices.Add((ushort)s);
indices.Add((ushort)(sizeS - 1));
}
// We've got a top cap
int offset = (sizeT - 1) * sizeS;
for (s = 0; s < sizeS - 2; s++)
{
// Inverted ordering from bottom cap
indices.Add((ushort)(offset + sizeS - 1));
indices.Add((ushort)(offset + s));
indices.Add((ushort)(offset + s + 1));
}
}
}
}
else if (hollow)
{
// Closed hollow
// Outer face
for (t = 0; t < sizeT - 1; t++)
{
for (s = 0; s < sizeSOut - 1; s++)
{
i = s + t * sizeS;
indices.Add((ushort)i); // x,y
indices.Add((ushort)(i + 1)); // x+1,y
indices.Add((ushort)(i + sizeS)); // x,y+1
indices.Add((ushort)(i + sizeS)); // x,y+1
indices.Add((ushort)(i + 1)); // x+1,y
indices.Add((ushort)(i + 1 + sizeS)); // x+1,y+1
}
}
// Inner face
// Invert facing from outer face
for (t = 0; t < sizeT - 1; t++)
{
for (s = sizeSOut; s < sizeS - 1; s++)
{
i = s + t * sizeS;
indices.Add((ushort)i); // x,y
indices.Add((ushort)(i + 1)); // x+1,y
indices.Add((ushort)(i + sizeS)); // x,y+1
indices.Add((ushort)(i + sizeS)); // x,y+1
indices.Add((ushort)(i + 1)); // x+1,y
indices.Add((ushort)(i + 1 + sizeS)); // x+1,y+1
}
}
// Do the top and bottom caps, if necessary
if (path.Open)
{
// Top cap
int pt1 = 0;
int pt2 = sizeS - 1;
i = (sizeT - 1) * sizeS;
while (pt2 - pt1 > 1)
{
if (use_tri_1a2(profile, pt1, pt2))
{
indices.Add((ushort)(pt1 + i));
indices.Add((ushort)(pt2 + 1 + i));
indices.Add((ushort)(pt2 + i));
pt1++;
}
else
{
indices.Add((ushort)(pt1 + i));
indices.Add((ushort)(pt2 - 1 + i));
indices.Add((ushort)(pt2 + i));
pt2--;
}
}
// Bottom cap
pt1 = 0;
pt2 = sizeS - 1;
while (pt2 - pt1 > 1)
{
if (use_tri_1a2(profile, pt1, pt2))
{
indices.Add((ushort)pt1);
indices.Add((ushort)pt2);
indices.Add((ushort)(pt1 + 1));
pt1++;
}
else
{
indices.Add((ushort)pt1);
indices.Add((ushort)pt2);
indices.Add((ushort)(pt2 - 1));
pt2--;
}
}
}
}
else
{
// Closed solid. Easy case
for (t = 0; t < sizeT - 1; t++)
{
for (s = 0; s < sizeS - 1; s++)
{
// Should wrap properly, but for now...
i = s + t * sizeS;
indices.Add((ushort)i); // x,y
indices.Add((ushort)(i + 1)); // x+1,y
indices.Add((ushort)(i + sizeS)); // x,y+1
indices.Add((ushort)(i + sizeS)); // x,y+1
indices.Add((ushort)(i + 1)); // x+1,y
indices.Add((ushort)(i + sizeS + 1)); // x+1,y+1
}
}
// Do the top and bottom caps, if necessary
if (path.Open)
{
// Bottom cap
for (s = 1; s < sizeS - 2; s++)
{
indices.Add((ushort)(s + 1));
indices.Add((ushort)s);
indices.Add((ushort)0);
}
// Top cap
int offset = (sizeT - 1) * sizeS;
for (s = 1; s < sizeS - 2; s++)
{
// Inverted ordering from bottom cap
indices.Add((ushort)offset);
indices.Add((ushort)(offset + s));
indices.Add((ushort)(offset + s + 1));
}
}
}
return indices;
}
private static Profile GenerateProfile(LLObject.ObjectData prim, Path path, float detail)
{
Profile profile;
if (detail < (float)MIN_LOD)
detail = (float)MIN_LOD;
// Generate the face data
int i;
float begin = prim.ProfileBegin;
float end = prim.ProfileEnd;
float hollow = prim.ProfileHollow;
// Sanity check
if (begin > end - 0.01f)
begin = end - 0.01f;
int faceNum = 0;
switch (prim.ProfileCurve)
{
#region Squares
case LLObject.ProfileCurve.Square:
{
profile = GenerateProfilePolygon(prim, 4, -0.375f, 1f);
#region Create Faces
if (path.Open)
profile.Faces.Add(CreateProfileCap(FaceType.PathBegin, profile.Positions.Count));
int iBegin = (int)Math.Floor(begin * 4f);
int iEnd = (int)Math.Floor(end * 4f + 0.999f);
for (i = iBegin; i < iEnd; i++)
{
FaceType type = (FaceType)((ushort)FaceType.OuterSide0 << i);
profile.Faces.Add(CreateProfileFace(faceNum++, 2, 1f, type, true));
}
#endregion Create Faces
for (i = 0; i < profile.Positions.Count; i++)
{
// Scale by 4 to generate proper tex coords
Vector3 point = profile.Positions[i];
point.Z *= 4f;
profile.Positions[i] = point;
}
if (hollow > 0f)
{
switch (prim.ProfileHole)
{
case LLObject.HoleType.Triangle:
// This is the wrong offset, but it is what the official viewer uses
GenerateProfileHole(prim, ref profile, true, 3f, -0.375f, hollow, 1f);
break;
case LLObject.HoleType.Circle:
// TODO: Compute actual detail levels for cubes
GenerateProfileHole(prim, ref profile, false, MIN_DETAIL_FACES * detail, -0.375f, hollow, 1f);
break;
case LLObject.HoleType.Same:
case LLObject.HoleType.Square:
default:
GenerateProfileHole(prim, ref profile, true, 4, -0.375f, hollow, 1f);
break;
}
}
}
break;
#endregion Squares
#region Triangles
case LLObject.ProfileCurve.IsoTriangle:
case LLObject.ProfileCurve.RightTriangle:
case LLObject.ProfileCurve.EqualTriangle:
{
profile = GenerateProfilePolygon(prim, 3, 0f, 1f);
#region Create Faces
if (path.Open)
profile.Faces.Add(CreateProfileCap(FaceType.PathBegin, profile.Positions.Count));
int iBegin = (int)Math.Floor(begin * 3f);
int iEnd = (int)Math.Floor(end * 3f + 0.999f);
for (i = iBegin; i < iEnd; i++)
{
FaceType type = (FaceType)((ushort)FaceType.OuterSide0 << i);
profile.Faces.Add(CreateProfileFace(faceNum++, 2, 1f, type, true));
}
#endregion Create Faces
for (i = 0; i < profile.Positions.Count; i++)
{
// Scale by 3 to generate proper tex coords
Vector3 point = profile.Positions[i];
point.Z *= 3f;
profile.Positions[i] = point;
}
if (hollow > 0f)
{
// Swept triangles need smaller hollowness values,
// because the triangle doesn't fill the bounding box
float triangleHollow = hollow / 2f;
switch (prim.ProfileHole)
{
case LLObject.HoleType.Circle:
GenerateProfileHole(prim, ref profile, false, MIN_DETAIL_FACES * detail, 0f, triangleHollow, 1f);
break;
case LLObject.HoleType.Square:
GenerateProfileHole(prim, ref profile, true, 4f, 0f, triangleHollow, 1f);
break;
case LLObject.HoleType.Same:
case LLObject.HoleType.Triangle:
default:
GenerateProfileHole(prim, ref profile, true, 3f, 0f, triangleHollow, 1f);
break;
}
}
}
break;
#endregion Triangles
#region Circles
case LLObject.ProfileCurve.Circle:
{
float circleDetail = MIN_DETAIL_FACES * detail;
// If this has a square hollow, we should adjust the
// number of faces a bit so that the geometry lines up
if (hollow > 0f && prim.ProfileHole == LLObject.HoleType.Square)
{
// Snap to the next multiple of four sides,
// so that corners line up
circleDetail = (float)Math.Ceiling(circleDetail / 4f) * 4f;
}
int sides = (int)circleDetail;
// FIXME: Handle sculpted prims at some point
//if (is_sculpted)
// sides = sculpt_sides(detail);
profile = GenerateProfilePolygon(prim, sides, 0f, 1f);
#region Create Faces
if (path.Open)
profile.Faces.Add(CreateProfileCap(FaceType.PathBegin, profile.Positions.Count));
if (profile.Open && prim.ProfileHollow == 0f)
profile.Faces.Add(CreateProfileFace(0, profile.Positions.Count - 1, 0f, FaceType.OuterSide0, false));
else
profile.Faces.Add(CreateProfileFace(0, profile.Positions.Count, 0f, FaceType.OuterSide0, false));
#endregion Create Faces
if (hollow > 0f)
{
switch (prim.ProfileHole)
{
case LLObject.HoleType.Square:
GenerateProfileHole(prim, ref profile, true, 4f, 0f, hollow, 1f);
break;
case LLObject.HoleType.Triangle:
GenerateProfileHole(prim, ref profile, true, 3f, 0f, hollow, 1f);
break;
case LLObject.HoleType.Circle:
case LLObject.HoleType.Same:
default:
GenerateProfileHole(prim, ref profile, false, circleDetail, 0f, hollow, 1f);
break;
}
}
}
break;
#endregion Circles
#region HalfCircles
case LLObject.ProfileCurve.HalfCircle:
{
// Number of faces is cut in half because it's only a half-circle
float circleDetail = MIN_DETAIL_FACES * detail * 0.5f;
// If this has a square hollow, we should adjust the
// number of faces a bit so that the geometry lines up
if (hollow > 0f && prim.ProfileHole == LLObject.HoleType.Square)
{
// Snap to the next multiple of four sides (div 2),
// so that corners line up
circleDetail = (float)Math.Ceiling(circleDetail / 2f) * 2f;
}
profile = GenerateProfilePolygon(prim, (int)Math.Floor(circleDetail), 0.5f, 0.5f);
#region Create Faces
if (path.Open)
profile.Faces.Add(CreateProfileCap(FaceType.PathBegin, profile.Positions.Count));
if (profile.Open && prim.ProfileHollow == 0f)
profile.Faces.Add(CreateProfileFace(0, profile.Positions.Count - 1, 0f, FaceType.OuterSide0, false));
else
profile.Faces.Add(CreateProfileFace(0, profile.Positions.Count, 0f, FaceType.OuterSide0, false));
#endregion Create Faces
if (hollow > 0f)
{
switch (prim.ProfileHole)
{
case LLObject.HoleType.Square:
GenerateProfileHole(prim, ref profile, true, 2f, 0.5f, hollow, 0.5f);
break;
case LLObject.HoleType.Triangle:
GenerateProfileHole(prim, ref profile, true, 3f, 0.5f, hollow, 0.5f);
break;
case LLObject.HoleType.Circle:
case LLObject.HoleType.Same:
default:
GenerateProfileHole(prim, ref profile, false, circleDetail, 0.5f, hollow, 0.5f);
break;
}
}
// Special case for openness of sphere
if (prim.ProfileEnd - prim.ProfileEnd < 1f)
{
profile.Open = true;
}
else if (hollow == 0f)
{
profile.Open = false;
Vector3 first = profile.Positions[0];
profile.Positions.Add(first);
}
}
break;
#endregion HalfCircles
default:
throw new RenderingException("Unknown profile curve type " + prim.ProfileCurve.ToString());
}
// Bottom cap
if (path.Open)
profile.Faces.Add(CreateProfileCap(FaceType.PathEnd, profile.Positions.Count));
if (profile.Open)
{
// Interior edge caps
profile.Faces.Add(CreateProfileFace(profile.Positions.Count - 1, 2, 0.5f, FaceType.ProfileBegin, true));
if (prim.ProfileHollow > 0f)
profile.Faces.Add(CreateProfileFace(profile.TotalOutsidePoints - 1, 2, 0.5f, FaceType.ProfileEnd, true));
else
profile.Faces.Add(CreateProfileFace(profile.Positions.Count - 2, 2, 0.5f, FaceType.ProfileEnd, true));
}
return profile;
}
private static void GenerateProfileHole(LLObject.ObjectData prim, ref Profile profile, bool flat, float sides,
float offset, float boxHollow, float angScale)
{
// Total add has number of vertices on outside
profile.TotalOutsidePoints = profile.Positions.Count;
// Create the hole
Profile hole = GenerateProfilePolygon(prim, (int)Math.Floor(sides), offset, angScale);
// FIXME: Should we overwrite profile.Values with hole.Values?
// Apply the hollow scale modifier
for (int i = 0; i < hole.Positions.Count; i++)
{
Vector3 point = hole.Positions[i];
point *= boxHollow;
hole.Positions[i] = point;
}
// Reverse the order
hole.Positions.Reverse();
// Add the hole to the profile
profile.Positions.AddRange(hole.Positions);
// Create the inner side face for the hole
ProfileFace innerFace = CreateProfileFace(profile.TotalOutsidePoints,
profile.Positions.Count - profile.TotalOutsidePoints, 0f, FaceType.InnerSide, flat);
profile.Faces.Add(innerFace);
}
private static Path GeneratePath(LLObject.ObjectData prim, float detail)
{
Path path;
path.Open = true;
int np = 2; // Hardcode for line
float step;
switch (prim.PathCurve)
{
default:
case LLObject.PathCurve.Line:
{
// Take the begin/end twist into account for detail
np = (int)Math.Floor(Math.Abs(prim.PathTwistBegin - prim.PathTwist) * 3.5f * (detail - 0.5f)) + 2;
path.Points = new List<PathPoint>(np);
step = 1f / (np - 1);
Vector2 startScale = prim.PathBeginScale;
Vector2 endScale = prim.PathEndScale;
for (int i = 0; i < np; i++)
{
PathPoint point = new PathPoint();
float t = Helpers.Lerp(prim.PathBegin, prim.PathEnd, (float)i * step);
point.Position = new Vector3(
Helpers.Lerp(0, prim.PathShearX, t),
Helpers.Lerp(0, prim.PathShearY, t),
t - 0.5f);
point.Rotation.SetQuaternion(Helpers.Lerp(F_PI * prim.PathTwistBegin, F_PI * prim.PathTwist, t), 0f, 0f, 1f);
point.Scale.X = Helpers.Lerp(startScale.X, endScale.X, t);
point.Scale.Y = Helpers.Lerp(startScale.Y, endScale.Y, t);
point.TexT = t;
path.Points.Add(point);
}
}
break;
case LLObject.PathCurve.Circle:
{
// Increase the detail as the revolutions and twist increase
float twistMag = Math.Abs(prim.PathTwistBegin - prim.PathTwist);
int sides = (int)Math.Floor(
Math.Floor(MIN_DETAIL_FACES * detail + twistMag * 3.5f * (detail - 0.5f))
* prim.PathRevolutions);
// FIXME: Sculpty support
//if (is_sculpted)
// sides = sculpt_sides(detail);
path = GeneratePathPolygon(prim, sides);
}
break;
case LLObject.PathCurve.Circle2:
{
if (prim.PathEnd - prim.PathBegin >= 0.99f && prim.PathScaleX >= 0.99f)
path.Open = false;
path = GeneratePathPolygon(prim, (int)Math.Floor(MIN_DETAIL_FACES * detail));
float t = 0f;
float tStep = 1f / path.Points.Count;
float toggle = 0.5f;
for (int i = 0; i < path.Points.Count; i++)
{
PathPoint point = path.Points[i];
point.Position.X = toggle;
path.Points[i] = point;
if (toggle == 0.5f)
toggle = -0.5f;
else
toggle = 0.5f;
t += tStep;
}
}
break;
case LLObject.PathCurve.Test:
throw new NotImplementedException("PathCurve.Test is not supported");
}
if (prim.PathTwist != prim.PathTwistBegin)
path.Open = true;
return path;
}
private static Profile GenerateProfilePolygon(LLObject.ObjectData prim, int sides, float offset, float angScale)
{
// Create a polygon by starting at (1, 0) and proceeding counterclockwise generating vectors
Profile profile = new Profile();
profile.Positions = new List<Vector3>();
profile.Faces = new List<ProfileFace>();
float scale = 0.5f;
float t, tStep, tFirst, tFraction, ang, angStep;
Vector3 pt1, pt2;
float begin = prim.ProfileBegin;
float end = prim.ProfileEnd;
tStep = 1f / sides;
angStep = 2f * F_PI * tStep * angScale;
// Scale to have size "match" scale. Compensates to get object to generally fill bounding box
int totalSides = Helpers.Round(sides / angScale);
if (totalSides < 8)
scale = TABLE_SCALE[totalSides];
tFirst = (float)Math.Floor(begin * sides) / (float)sides;
// pt1 is the first point on the fractional face.
// Starting t and ang values for the first face
t = tFirst;
ang = 2f * F_PI * (t * angScale + offset);
pt1 = new Vector3((float)Math.Cos(ang) * scale, (float)Math.Sin(ang) * scale, t);
// Increment to the next point.
// pt2 is the end point on the fractional face
t += tStep;
ang += angStep;
pt2 = new Vector3((float)Math.Cos(ang) * scale, (float)Math.Sin(ang) * scale, t);
tFraction = (begin - tFirst) * sides;
// Only use if it's not almost exactly on an edge
if (tFraction < 0.9999f)
{
Vector3 newPt = Helpers.Lerp(pt1, pt2, tFraction);
float ptX = newPt.X;
if (ptX < profile.MinX)
profile.MinX = ptX;
else if (ptX > profile.MaxX)
profile.MaxX = ptX;
profile.Positions.Add(newPt);
}
// There's lots of potential here for floating point error to generate unneeded extra points
while (t < end)
{
// Iterate through all the integer steps of t.
pt1 = new Vector3((float)Math.Cos(ang) * scale, (float)Math.Sin(ang) * scale, t);
float ptX = pt1.X;
if (ptX < profile.MinX)
profile.MinX = ptX;
else if (ptX > profile.MaxX)
profile.MaxX = ptX;
profile.Positions.Add(pt1);
t += tStep;
ang += angStep;
}
// pt1 is the first point on the fractional face
// pt2 is the end point on the fractional face
pt2 = new Vector3((float)Math.Cos(ang) * scale, (float)Math.Sin(ang) * scale, t);
// Find the fraction that we need to add to the end point
tFraction = (end - (t - tStep)) * sides;
if (tFraction > 0.0001f)
{
Vector3 newPt = Helpers.Lerp(pt1, pt2, tFraction);
float ptX = newPt.X;
if (ptX < profile.MinX)
profile.MinX = ptX;
else if (ptX > profile.MaxX)
profile.MaxX = ptX;
profile.Positions.Add(newPt);
}
// If we're sliced, the profile is open
if ((end - begin) * angScale < 0.99f)
{
if ((end - begin) * angScale > 0.5f)
profile.Concave = true;
else
profile.Concave = false;
profile.Open = true;
// Put center point if not hollow
if (prim.ProfileHollow == 0f)
profile.Positions.Add(Vector3.Zero);
}
else
{
// The profile isn't open
profile.Open = false;
profile.Concave = false;
}
return profile;
}
private static Path GeneratePathPolygon(LLObject.ObjectData prim, int sides)
{
return GeneratePathPolygon(prim, sides, 0f, 1f, 1f);
}
private static Path GeneratePathPolygon(LLObject.ObjectData prim, int sides, float startOff, float endScale,
float twistScale)
{
Path path = new Path();
path.Points = new List<PathPoint>();
float revolutions = prim.PathRevolutions;
float skew = prim.PathSkew;
float skewMag = (float)Math.Abs(skew);
float holeX = prim.PathScaleX * (1f - skewMag);
float holeY = prim.PathScaleY;
// Calculate taper begin/end for x,y (Negative means taper the beginning)
float taperXBegin = 1f;
float taperXEnd = 1f - prim.PathTaperX;
float taperYBegin = 1f;
float taperYEnd = 1f - prim.PathTaperY;
if (taperXEnd > 1f)
{
// Flip tapering
taperXBegin = 2f - taperXEnd;
taperXEnd = 1f;
}
if (taperYEnd > 1f)
{
// Flip tapering
taperYBegin = 2f - taperYEnd;
taperYEnd = 1f;
}
// For spheres, the radius is usually zero
float radiusStart = 0.5f;
if (sides < 8)
radiusStart = TABLE_SCALE[sides];
// Scale the radius to take the hole size into account
radiusStart *= 1f - holeY;
// Now check the radius offset to calculate the start,end radius. (Negative means
// decrease the start radius instead)
float radiusEnd = radiusStart;
float radiusOffset = prim.PathRadiusOffset;
if (radiusOffset < 0f)
radiusStart *= 1f + radiusOffset;
else
radiusEnd *= 1f - radiusOffset;
// Is the path NOT a closed loop?
path.Open =
((prim.PathEnd * endScale - prim.PathBegin < 1f) ||
(skewMag > 0.001f) ||
(Math.Abs(taperXEnd - taperXBegin) > 0.001d) ||
(Math.Abs(taperYEnd - taperYBegin) > 0.001d) ||
(Math.Abs(radiusEnd - radiusStart) > 0.001d));
float ang, c, s;
Quaternion twist = Quaternion.Identity;
Quaternion qang = Quaternion.Identity;
PathPoint point;
Vector3 pathAxis = new Vector3(1f, 0f, 0f);
float twistBegin = prim.PathTwistBegin * twistScale;
float twistEnd = prim.PathTwist * twistScale;
// We run through this once before the main loop, to make sure
// the path begins at the correct cut
float step = 1f / sides;
float t = prim.PathBegin;
ang = 2f * F_PI * revolutions * t;
s = (float)Math.Sin(ang) * Helpers.Lerp(radiusStart, radiusEnd, t);
c = (float)Math.Cos(ang) * Helpers.Lerp(radiusStart, radiusEnd, t);
point = new PathPoint();
point.Position = new Vector3(
0 + Helpers.Lerp(0, prim.PathShearX, s) +
0 + Helpers.Lerp(-skew, skew, t) * 0.5f,
c + Helpers.Lerp(0, prim.PathShearY, s),
s);
point.Scale.X = holeX * Helpers.Lerp(taperXBegin, taperXEnd, t);
point.Scale.Y = holeY * Helpers.Lerp(taperYBegin, taperYEnd, t);
point.TexT = t;
// Twist rotates the path along the x,y plane
twist.SetQuaternion(Helpers.Lerp(twistBegin, twistEnd, t) * 2f * F_PI - F_PI, 0f, 0f, 1f);
// Rotate the point around the circle's center
qang.SetQuaternion(ang, pathAxis);
point.Rotation = twist * qang;
path.Points.Add(point);
t += step;
// Snap to a quantized parameter, so that cut does not
// affect most sample points
t = ((int)(t * sides)) / (float)sides;
// Run through the non-cut dependent points
point = new PathPoint();
while (t < prim.PathEnd)
{
ang = 2f * F_PI * revolutions * t;
c = (float)Math.Cos(ang) * Helpers.Lerp(radiusStart, radiusEnd, t);
s = (float)Math.Sin(ang) * Helpers.Lerp(radiusStart, radiusEnd, t);
point.Position = new Vector3(
0 + Helpers.Lerp(0, prim.PathShearX, s) +
0 + Helpers.Lerp(-skew, skew, t) * 0.5f,
c + Helpers.Lerp(0, prim.PathShearY, s),
s);
point.Scale.X = holeX * Helpers.Lerp(taperXBegin, taperXEnd, t);
point.Scale.Y = holeY * Helpers.Lerp(taperYBegin, taperYEnd, t);
point.TexT = t;
// Twist rotates the path along the x,y plane
twist.SetQuaternion(Helpers.Lerp(twistBegin, twistEnd, t) * 2f * F_PI - F_PI, 0f, 0f, 1f);
// Rotate the point around the circle's center
qang.SetQuaternion(ang, pathAxis);
point.Rotation = twist * qang;
path.Points.Add(point);
t += step;
}
// Make one final pass for the end cut
t = prim.PathEnd;
point = new PathPoint();
ang = 2f * F_PI * revolutions * t;
c = (float)Math.Cos(ang) * Helpers.Lerp(radiusStart, radiusEnd, t);
s = (float)Math.Sin(ang) * Helpers.Lerp(radiusStart, radiusEnd, t);
point.Position = new Vector3(
Helpers.Lerp(0, prim.PathShearX, s) + Helpers.Lerp(-skew, skew, t) * 0.5f,
c + Helpers.Lerp(0, prim.PathShearY, s),
s);
point.Scale.X = holeX * Helpers.Lerp(taperXBegin, taperXEnd, t);
point.Scale.Y = holeY * Helpers.Lerp(taperYBegin, taperYEnd, t);
point.TexT = t;
// Twist rotates the path along the x,y plane
twist.SetQuaternion(Helpers.Lerp(twistBegin, twistEnd, t) * 2f * F_PI - F_PI, 0f, 0f, 1f);
qang.SetQuaternion(ang, pathAxis);
point.Rotation = twist * qang;
path.Points.Add(point);
return path;
}
private static ProfileFace CreateProfileCap(FaceType type, int count)
{
ProfileFace face = new ProfileFace();
face.Index = 0;
face.Count = count;
face.ScaleU = 1f;
face.Cap = true;
face.Type = type;
return face;
}
private static ProfileFace CreateProfileFace(int index, int count, float scaleU, FaceType type, bool flat)
{
ProfileFace face = new ProfileFace();
face.Index = index;
face.Count = count;
face.ScaleU = scaleU;
face.Flat = flat;
face.Cap = false;
face.Type = type;
return face;
}
private static bool use_tri_1a2(Profile profilePoints, int pt1, int pt2)
{
// Use the profile points instead of the mesh, since you want
// the un-transformed profile distances
Vector3 p1 = profilePoints.Positions[pt1];
Vector3 p2 = profilePoints.Positions[pt2];
Vector3 pa = profilePoints.Positions[pt1 + 1];
Vector3 pb = profilePoints.Positions[pt2 - 1];
p1.Z = 0f;
p2.Z = 0f;
pa.Z = 0f;
pb.Z = 0f;
// Use area of triangle to determine backfacing
float area_1a2, area_1ba, area_21b, area_2ab;
area_1a2 =
(p1.X * pa.Y - pa.X * p1.Y) +
(pa.X * p2.Y - p2.X * pa.Y) +
(p2.X * p1.Y - p1.X * p2.Y);
area_1ba =
(p1.X * pb.Y - pb.X * p1.Y) +
(pb.X * pa.Y - pa.X * pb.Y) +
(pa.X * p1.Y - p1.X * pa.Y);
area_21b =
(p2.X * p1.Y - p1.X * p2.Y) +
(p1.X * pb.Y - pb.X * p1.Y) +
(pb.X * p2.Y - p2.X * pb.Y);
area_2ab =
(p2.X * pa.Y - pa.X * p2.Y) +
(pa.X * pb.Y - pb.X * pa.Y) +
(pb.X * p2.Y - p2.X * pb.Y);
bool use_tri_1a2 = true;
bool tri_1a2 = true;
bool tri_21b = true;
if (area_1a2 < 0)
tri_1a2 = false;
if (area_2ab < 0)
tri_1a2 = false; // Can't use, because it contains point b
if (area_21b < 0)
tri_21b = false;
if (area_1ba < 0)
tri_21b = false; // Can't use, because it contains point b
if (!tri_1a2)
{
use_tri_1a2 = false;
}
else if (!tri_21b)
{
use_tri_1a2 = true;
}
else
{
Vector3 d1 = p1 - pa;
Vector3 d2 = p2 - pb;
if (Vector3.MagSquared(d1) < Vector3.MagSquared(d2))
use_tri_1a2 = true;
else
use_tri_1a2 = false;
}
return use_tri_1a2;
}
}
}