From ae8e089b9c73a6a675038759e3e3f9491819eb72 Mon Sep 17 00:00:00 2001 From: UbitUmarov Date: Fri, 16 Mar 2012 15:33:49 +0000 Subject: [PATCH 1/2] some more work on costs --- .../Linden/Caps/BunchOfCaps/BunchOfCaps.cs | 56 ++++++----- .../Framework/Scenes/SceneObjectGroup.cs | 96 +++++++++++++++++++ .../Framework/Scenes/SceneObjectPart.cs | 62 ++++++++++++ 3 files changed, 192 insertions(+), 22 deletions(-) diff --git a/OpenSim/Region/ClientStack/Linden/Caps/BunchOfCaps/BunchOfCaps.cs b/OpenSim/Region/ClientStack/Linden/Caps/BunchOfCaps/BunchOfCaps.cs index 5542680323..dfd5c98807 100644 --- a/OpenSim/Region/ClientStack/Linden/Caps/BunchOfCaps/BunchOfCaps.cs +++ b/OpenSim/Region/ClientStack/Linden/Caps/BunchOfCaps/BunchOfCaps.cs @@ -845,9 +845,7 @@ namespace OpenSim.Region.ClientStack.Linden public string GetObjectCost(string request, string path, string param, IOSHttpRequest httpRequest, IOSHttpResponse httpResponse) - { - // see being triggered but see no efect .. have something wrong ?? - // + { OSDMap req = (OSDMap)OSDParser.DeserializeLLSDXml(request); OSDMap resp = new OSDMap(); @@ -856,19 +854,29 @@ namespace OpenSim.Region.ClientStack.Linden for (int i = 0; i < object_ids.Count; i++) { UUID uuid = object_ids[i].AsUUID(); + + SceneObjectPart part = m_Scene.GetSceneObjectPart(uuid); - // only see root parts .. so guess should go by SOG only - SceneObjectPart obj = m_Scene.GetSceneObjectPart(uuid); - if (obj != null) + if (part != null) { - OSDMap object_data = new OSDMap(); + SceneObjectGroup grp = part.ParentGroup; + if (grp != null) + { + float linksetCost; + float linksetPhysCost; + float partCost; + float partPhysCost; - object_data["linked_set_resource_cost"] = 1.0f; - object_data["resource_cost"] = 1.0f; - object_data["physics_cost"] = 1.0f; - object_data["linked_set_physics_cost"] = 1.0f; + grp.GetResourcesCosts(part, out linksetCost, out linksetPhysCost, out partCost, out partPhysCost); - resp[uuid.ToString()] = object_data; + OSDMap object_data = new OSDMap(); + object_data["linked_set_resource_cost"] = linksetCost; + object_data["resource_cost"] = partCost; + object_data["physics_cost"] = partPhysCost; + object_data["linked_set_physics_cost"] = linksetPhysCost; + + resp[uuid.ToString()] = object_data; + } } } @@ -899,13 +907,17 @@ namespace OpenSim.Region.ClientStack.Linden for (int i = 0; i < object_ids.Count; i++) { UUID uuid = object_ids[i].AsUUID(); + float Physc; + float simulc; + float streamc; - SceneObjectPart obj = m_Scene.GetSceneObjectPart(uuid); - if (obj != null) + SceneObjectGroup grp = m_Scene.GetGroupByPrim(uuid); + if (grp != null) { - phys += 0.1f; // just to see... - stream += 0.2f; - simul += 0.5f; + grp.GetSelectedCosts(out Physc, out streamc, out simulc); + phys += Physc; + stream += streamc; + simul += simulc; } } } @@ -920,12 +932,12 @@ namespace OpenSim.Region.ClientStack.Linden { UUID uuid = object_ids[i].AsUUID(); - SceneObjectPart obj = m_Scene.GetSceneObjectPart(uuid); - if (obj != null) + SceneObjectPart part = m_Scene.GetSceneObjectPart(uuid); + if (part != null) { - phys += 0.1f; - stream += 0.2f; - simul += 0.5f; + phys += part.PhysicsCost; + stream += part.StreamingCost; + simul += part.SimulationCost; } } } diff --git a/OpenSim/Region/Framework/Scenes/SceneObjectGroup.cs b/OpenSim/Region/Framework/Scenes/SceneObjectGroup.cs index 5507aa0107..3e4d552d2b 100644 --- a/OpenSim/Region/Framework/Scenes/SceneObjectGroup.cs +++ b/OpenSim/Region/Framework/Scenes/SceneObjectGroup.cs @@ -1231,6 +1231,102 @@ namespace OpenSim.Region.Framework.Scenes #endregion + public void GetResourcesCosts(SceneObjectPart apart, + out float linksetResCost, out float linksetPhysCost, out float partCost, out float partPhysCost) + { + // this information may need to be cached + + float cost; + float tmpcost; + + bool ComplexCost = false; + + SceneObjectPart p; + SceneObjectPart[] parts; + + lock (m_parts) + { + parts = m_parts.GetArray(); + } + + int nparts = parts.Length; + + + for (int i = 0; i < nparts; i++) + { + p = parts[i]; + + if (p.UsesComplexCost) + { + ComplexCost = true; + break; + } + } + + if (ComplexCost) + { + linksetResCost = 0; + linksetPhysCost = 0; + partCost = 0; + partPhysCost = 0; + + for (int i = 0; i < nparts; i++) + { + p = parts[i]; + + cost = p.StreamingCost; + tmpcost = p.SimulationCost; + if (tmpcost > cost) + cost = tmpcost; + tmpcost = p.PhysicsCost; + if (tmpcost > cost) + cost = tmpcost; + + linksetPhysCost += tmpcost; + linksetResCost += cost; + + if (p == apart) + { + partCost = cost; + partPhysCost = tmpcost; + } + } + } + else + { + partPhysCost = 1.0f; + partCost = 1.0f; + linksetResCost = (float)nparts; + linksetPhysCost = linksetResCost; + } + } + + public void GetSelectedCosts(out float PhysCost, out float StreamCost, out float SimulCost) + { + SceneObjectPart p; + SceneObjectPart[] parts; + + lock (m_parts) + { + parts = m_parts.GetArray(); + } + + int nparts = parts.Length; + + PhysCost = 0; + StreamCost = 0; + SimulCost = 0; + + for (int i = 0; i < nparts; i++) + { + p = parts[i]; + + StreamCost += p.StreamingCost; + SimulCost += p.SimulationCost; + PhysCost += p.PhysicsCost; + } + } + public void SaveScriptedState(XmlTextWriter writer) { SaveScriptedState(writer, false); diff --git a/OpenSim/Region/Framework/Scenes/SceneObjectPart.cs b/OpenSim/Region/Framework/Scenes/SceneObjectPart.cs index f188e8d821..fbe959af5e 100644 --- a/OpenSim/Region/Framework/Scenes/SceneObjectPart.cs +++ b/OpenSim/Region/Framework/Scenes/SceneObjectPart.cs @@ -1415,6 +1415,14 @@ namespace OpenSim.Region.Framework.Scenes } } + // not a propriety to move to methods place later + private bool HasMesh() + { + if (Shape != null && (Shape.SculptType == (byte)SculptType.Mesh)) + return true; + return false; + } + // not a propriety to move to methods place later public byte DefaultPhysicsShapeType() { @@ -1428,6 +1436,60 @@ namespace OpenSim.Region.Framework.Scenes return type; } + [XmlIgnore] + public bool UsesComplexCost + { + get + { + byte pst = PhysicsShapeType; + if(pst == (byte) PhysShapeType.none || pst == (byte) PhysShapeType.convex || HasMesh()) + return true; + return false; + } + } + + [XmlIgnore] + public float PhysicsCost + { + get + { + if(PhysicsShapeType == (byte)PhysShapeType.none) + return 0; + + float cost = 0.1f; + if (PhysActor != null) +// cost += PhysActor.Cost; + + if ((Flags & PrimFlags.Physics) != 0) + cost *= (1.0f + 0.01333f * Scale.LengthSquared()); // 0.01333 == 0.04/3 + return cost; + } + } + + [XmlIgnore] + public float StreamingCost + { + get + { + + + return 0.1f; + } + } + + [XmlIgnore] + public float SimulationCost + { + get + { + // ignoring scripts. Don't like considering them for this + if((Flags & PrimFlags.Physics) != 0) + return 1.0f; + + return 0.5f; + } + } + public byte PhysicsShapeType { get { return m_physicsShapeType; } From 41a0c850f887d72ff64e9e616ea4bda25fd85de7 Mon Sep 17 00:00:00 2001 From: UbitUmarov Date: Sat, 17 Mar 2012 09:27:56 +0000 Subject: [PATCH 2/2] added a new UbitMeshing module so i can mess it... --- .../Region/Physics/UbitMeshing/HelperTypes.cs | 436 ++++ OpenSim/Region/Physics/UbitMeshing/Mesh.cs | 401 +++ .../Region/Physics/UbitMeshing/Meshmerizer.cs | 762 ++++++ .../Region/Physics/UbitMeshing/PrimMesher.cs | 2284 +++++++++++++++++ .../Region/Physics/UbitMeshing/SculptMap.cs | 197 ++ .../Region/Physics/UbitMeshing/SculptMesh.cs | 646 +++++ prebuild.xml | 31 + 7 files changed, 4757 insertions(+) create mode 100644 OpenSim/Region/Physics/UbitMeshing/HelperTypes.cs create mode 100644 OpenSim/Region/Physics/UbitMeshing/Mesh.cs create mode 100644 OpenSim/Region/Physics/UbitMeshing/Meshmerizer.cs create mode 100644 OpenSim/Region/Physics/UbitMeshing/PrimMesher.cs create mode 100644 OpenSim/Region/Physics/UbitMeshing/SculptMap.cs create mode 100644 OpenSim/Region/Physics/UbitMeshing/SculptMesh.cs diff --git a/OpenSim/Region/Physics/UbitMeshing/HelperTypes.cs b/OpenSim/Region/Physics/UbitMeshing/HelperTypes.cs new file mode 100644 index 0000000000..8cd8dcf6ff --- /dev/null +++ b/OpenSim/Region/Physics/UbitMeshing/HelperTypes.cs @@ -0,0 +1,436 @@ +/* + * Copyright (c) Contributors, http://opensimulator.org/ + * See CONTRIBUTORS.TXT for a full list of copyright holders. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name of the OpenSimulator Project nor the + * names of its contributors may be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE DEVELOPERS ``AS IS'' AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE CONTRIBUTORS BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Globalization; +using OpenMetaverse; +using OpenSim.Region.Physics.Manager; +using OpenSim.Region.Physics.Meshing; + +public class Vertex : IComparable +{ + Vector3 vector; + + public float X + { + get { return vector.X; } + set { vector.X = value; } + } + + public float Y + { + get { return vector.Y; } + set { vector.Y = value; } + } + + public float Z + { + get { return vector.Z; } + set { vector.Z = value; } + } + + public Vertex(float x, float y, float z) + { + vector.X = x; + vector.Y = y; + vector.Z = z; + } + + public Vertex normalize() + { + float tlength = vector.Length(); + if (tlength != 0f) + { + float mul = 1.0f / tlength; + return new Vertex(vector.X * mul, vector.Y * mul, vector.Z * mul); + } + else + { + return new Vertex(0f, 0f, 0f); + } + } + + public Vertex cross(Vertex v) + { + return new Vertex(vector.Y * v.Z - vector.Z * v.Y, vector.Z * v.X - vector.X * v.Z, vector.X * v.Y - vector.Y * v.X); + } + + // disable warning: mono compiler moans about overloading + // operators hiding base operator but should not according to C# + // language spec +#pragma warning disable 0108 + public static Vertex operator *(Vertex v, Quaternion q) + { + // From http://www.euclideanspace.com/maths/algebra/realNormedAlgebra/quaternions/transforms/ + + Vertex v2 = new Vertex(0f, 0f, 0f); + + v2.X = q.W * q.W * v.X + + 2f * q.Y * q.W * v.Z - + 2f * q.Z * q.W * v.Y + + q.X * q.X * v.X + + 2f * q.Y * q.X * v.Y + + 2f * q.Z * q.X * v.Z - + q.Z * q.Z * v.X - + q.Y * q.Y * v.X; + + v2.Y = + 2f * q.X * q.Y * v.X + + q.Y * q.Y * v.Y + + 2f * q.Z * q.Y * v.Z + + 2f * q.W * q.Z * v.X - + q.Z * q.Z * v.Y + + q.W * q.W * v.Y - + 2f * q.X * q.W * v.Z - + q.X * q.X * v.Y; + + v2.Z = + 2f * q.X * q.Z * v.X + + 2f * q.Y * q.Z * v.Y + + q.Z * q.Z * v.Z - + 2f * q.W * q.Y * v.X - + q.Y * q.Y * v.Z + + 2f * q.W * q.X * v.Y - + q.X * q.X * v.Z + + q.W * q.W * v.Z; + + return v2; + } + + public static Vertex operator +(Vertex v1, Vertex v2) + { + return new Vertex(v1.X + v2.X, v1.Y + v2.Y, v1.Z + v2.Z); + } + + public static Vertex operator -(Vertex v1, Vertex v2) + { + return new Vertex(v1.X - v2.X, v1.Y - v2.Y, v1.Z - v2.Z); + } + + public static Vertex operator *(Vertex v1, Vertex v2) + { + return new Vertex(v1.X * v2.X, v1.Y * v2.Y, v1.Z * v2.Z); + } + + public static Vertex operator +(Vertex v1, float am) + { + v1.X += am; + v1.Y += am; + v1.Z += am; + return v1; + } + + public static Vertex operator -(Vertex v1, float am) + { + v1.X -= am; + v1.Y -= am; + v1.Z -= am; + return v1; + } + + public static Vertex operator *(Vertex v1, float am) + { + v1.X *= am; + v1.Y *= am; + v1.Z *= am; + return v1; + } + + public static Vertex operator /(Vertex v1, float am) + { + if (am == 0f) + { + return new Vertex(0f,0f,0f); + } + float mul = 1.0f / am; + v1.X *= mul; + v1.Y *= mul; + v1.Z *= mul; + return v1; + } +#pragma warning restore 0108 + + + public float dot(Vertex v) + { + return X * v.X + Y * v.Y + Z * v.Z; + } + + public Vertex(Vector3 v) + { + vector = v; + } + + public Vertex Clone() + { + return new Vertex(X, Y, Z); + } + + public static Vertex FromAngle(double angle) + { + return new Vertex((float) Math.Cos(angle), (float) Math.Sin(angle), 0.0f); + } + + public float Length() + { + return vector.Length(); + } + + public virtual bool Equals(Vertex v, float tolerance) + { + Vertex diff = this - v; + float d = diff.Length(); + if (d < tolerance) + return true; + + return false; + } + + + public int CompareTo(Vertex other) + { + if (X < other.X) + return -1; + + if (X > other.X) + return 1; + + if (Y < other.Y) + return -1; + + if (Y > other.Y) + return 1; + + if (Z < other.Z) + return -1; + + if (Z > other.Z) + return 1; + + return 0; + } + + public static bool operator >(Vertex me, Vertex other) + { + return me.CompareTo(other) > 0; + } + + public static bool operator <(Vertex me, Vertex other) + { + return me.CompareTo(other) < 0; + } + + public String ToRaw() + { + // Why this stuff with the number formatter? + // Well, the raw format uses the english/US notation of numbers + // where the "," separates groups of 1000 while the "." marks the border between 1 and 10E-1. + // The german notation uses these characters exactly vice versa! + // The Float.ToString() routine is a localized one, giving different results depending on the country + // settings your machine works with. Unusable for a machine readable file format :-( + NumberFormatInfo nfi = new NumberFormatInfo(); + nfi.NumberDecimalSeparator = "."; + nfi.NumberDecimalDigits = 3; + + String s1 = X.ToString("N2", nfi) + " " + Y.ToString("N2", nfi) + " " + Z.ToString("N2", nfi); + + return s1; + } +} + +public class Triangle +{ + public Vertex v1; + public Vertex v2; + public Vertex v3; + + private float radius_square; + private float cx; + private float cy; + + public Triangle(Vertex _v1, Vertex _v2, Vertex _v3) + { + v1 = _v1; + v2 = _v2; + v3 = _v3; + + CalcCircle(); + } + + public bool isInCircle(float x, float y) + { + float dx, dy; + float dd; + + dx = x - cx; + dy = y - cy; + + dd = dx*dx + dy*dy; + if (dd < radius_square) + return true; + else + return false; + } + + public bool isDegraded() + { + // This means, the vertices of this triangle are somewhat strange. + // They either line up or at least two of them are identical + return (radius_square == 0.0); + } + + private void CalcCircle() + { + // Calculate the center and the radius of a circle given by three points p1, p2, p3 + // It is assumed, that the triangles vertices are already set correctly + double p1x, p2x, p1y, p2y, p3x, p3y; + + // Deviation of this routine: + // A circle has the general equation (M-p)^2=r^2, where M and p are vectors + // this gives us three equations f(p)=r^2, each for one point p1, p2, p3 + // putting respectively two equations together gives two equations + // f(p1)=f(p2) and f(p1)=f(p3) + // bringing all constant terms to one side brings them to the form + // M*v1=c1 resp.M*v2=c2 where v1=(p1-p2) and v2=(p1-p3) (still vectors) + // and c1, c2 are scalars (Naming conventions like the variables below) + // Now using the equations that are formed by the components of the vectors + // and isolate Mx lets you make one equation that only holds My + // The rest is straight forward and eaasy :-) + // + + /* helping variables for temporary results */ + double c1, c2; + double v1x, v1y, v2x, v2y; + + double z, n; + + double rx, ry; + + // Readout the three points, the triangle consists of + p1x = v1.X; + p1y = v1.Y; + + p2x = v2.X; + p2y = v2.Y; + + p3x = v3.X; + p3y = v3.Y; + + /* calc helping values first */ + c1 = (p1x*p1x + p1y*p1y - p2x*p2x - p2y*p2y)/2; + c2 = (p1x*p1x + p1y*p1y - p3x*p3x - p3y*p3y)/2; + + v1x = p1x - p2x; + v1y = p1y - p2y; + + v2x = p1x - p3x; + v2y = p1y - p3y; + + z = (c1*v2x - c2*v1x); + n = (v1y*v2x - v2y*v1x); + + if (n == 0.0) // This is no triangle, i.e there are (at least) two points at the same location + { + radius_square = 0.0f; + return; + } + + cy = (float) (z/n); + + if (v2x != 0.0) + { + cx = (float) ((c2 - v2y*cy)/v2x); + } + else if (v1x != 0.0) + { + cx = (float) ((c1 - v1y*cy)/v1x); + } + else + { + Debug.Assert(false, "Malformed triangle"); /* Both terms zero means nothing good */ + } + + rx = (p1x - cx); + ry = (p1y - cy); + + radius_square = (float) (rx*rx + ry*ry); + } + + public override String ToString() + { + NumberFormatInfo nfi = new NumberFormatInfo(); + nfi.CurrencyDecimalDigits = 2; + nfi.CurrencyDecimalSeparator = "."; + + String s1 = "<" + v1.X.ToString(nfi) + "," + v1.Y.ToString(nfi) + "," + v1.Z.ToString(nfi) + ">"; + String s2 = "<" + v2.X.ToString(nfi) + "," + v2.Y.ToString(nfi) + "," + v2.Z.ToString(nfi) + ">"; + String s3 = "<" + v3.X.ToString(nfi) + "," + v3.Y.ToString(nfi) + "," + v3.Z.ToString(nfi) + ">"; + + return s1 + ";" + s2 + ";" + s3; + } + + public Vector3 getNormal() + { + // Vertices + + // Vectors for edges + Vector3 e1; + Vector3 e2; + + e1 = new Vector3(v1.X - v2.X, v1.Y - v2.Y, v1.Z - v2.Z); + e2 = new Vector3(v1.X - v3.X, v1.Y - v3.Y, v1.Z - v3.Z); + + // Cross product for normal + Vector3 n = Vector3.Cross(e1, e2); + + // Length + float l = n.Length(); + + // Normalized "normal" + n = n/l; + + return n; + } + + public void invertNormal() + { + Vertex vt; + vt = v1; + v1 = v2; + v2 = vt; + } + + // Dumps a triangle in the "raw faces" format, blender can import. This is for visualisation and + // debugging purposes + public String ToStringRaw() + { + String output = v1.ToRaw() + " " + v2.ToRaw() + " " + v3.ToRaw(); + return output; + } +} diff --git a/OpenSim/Region/Physics/UbitMeshing/Mesh.cs b/OpenSim/Region/Physics/UbitMeshing/Mesh.cs new file mode 100644 index 0000000000..c715642bac --- /dev/null +++ b/OpenSim/Region/Physics/UbitMeshing/Mesh.cs @@ -0,0 +1,401 @@ +/* + * Copyright (c) Contributors, http://opensimulator.org/ + * See CONTRIBUTORS.TXT for a full list of copyright holders. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name of the OpenSimulator Project nor the + * names of its contributors may be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE DEVELOPERS ``AS IS'' AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE CONTRIBUTORS BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +using System; +using System.Collections.Generic; +using System.IO; +using System.Runtime.InteropServices; +using OpenSim.Region.Physics.Manager; +using PrimMesher; +using OpenMetaverse; + +namespace OpenSim.Region.Physics.Meshing +{ + public class Mesh : IMesh + { + private Dictionary m_vertices; + private List m_triangles; + GCHandle m_pinnedVertexes; + GCHandle m_pinnedIndex; + IntPtr m_verticesPtr = IntPtr.Zero; + int m_vertexCount = 0; + IntPtr m_indicesPtr = IntPtr.Zero; + int m_indexCount = 0; + public float[] m_normals; + Vector3 _centroid; + int _centroidDiv; + + private class vertexcomp : IEqualityComparer + { + public bool Equals(Vertex v1, Vertex v2) + { + if (v1.X == v2.X && v1.Y == v2.Y && v1.Z == v2.Z) + return true; + else + return false; + } + public int GetHashCode(Vertex v) + { + int a = v.X.GetHashCode(); + int b = v.Y.GetHashCode(); + int c = v.Z.GetHashCode(); + return (a << 16) ^ (b << 8) ^ c; + } + + } + + public Mesh() + { + vertexcomp vcomp = new vertexcomp(); + + m_vertices = new Dictionary(vcomp); + m_triangles = new List(); + _centroid = Vector3.Zero; + _centroidDiv = 0; + } + + public Mesh Clone() + { + Mesh result = new Mesh(); + + foreach (Triangle t in m_triangles) + { + result.Add(new Triangle(t.v1.Clone(), t.v2.Clone(), t.v3.Clone())); + } + result._centroid = _centroid; + result._centroidDiv = _centroidDiv; + return result; + } + + public void Add(Triangle triangle) + { + if (m_pinnedIndex.IsAllocated || m_pinnedVertexes.IsAllocated || m_indicesPtr != IntPtr.Zero || m_verticesPtr != IntPtr.Zero) + throw new NotSupportedException("Attempt to Add to a pinned Mesh"); + // If a vertex of the triangle is not yet in the vertices list, + // add it and set its index to the current index count + // vertex == seems broken + // skip colapsed triangles + if ((triangle.v1.X == triangle.v2.X && triangle.v1.Y == triangle.v2.Y && triangle.v1.Z == triangle.v2.Z) + || (triangle.v1.X == triangle.v3.X && triangle.v1.Y == triangle.v3.Y && triangle.v1.Z == triangle.v3.Z) + || (triangle.v2.X == triangle.v3.X && triangle.v2.Y == triangle.v3.Y && triangle.v2.Z == triangle.v3.Z) + ) + { + return; + } + + if (m_vertices.Count == 0) + { + _centroidDiv = 0; + _centroid = Vector3.Zero; + } + + if (!m_vertices.ContainsKey(triangle.v1)) + { + m_vertices[triangle.v1] = m_vertices.Count; + _centroid.X += triangle.v1.X; + _centroid.Y += triangle.v1.Y; + _centroid.Z += triangle.v1.Z; + _centroidDiv++; + } + if (!m_vertices.ContainsKey(triangle.v2)) + { + m_vertices[triangle.v2] = m_vertices.Count; + _centroid.X += triangle.v2.X; + _centroid.Y += triangle.v2.Y; + _centroid.Z += triangle.v2.Z; + _centroidDiv++; + } + if (!m_vertices.ContainsKey(triangle.v3)) + { + m_vertices[triangle.v3] = m_vertices.Count; + _centroid.X += triangle.v3.X; + _centroid.Y += triangle.v3.Y; + _centroid.Z += triangle.v3.Z; + _centroidDiv++; + } + m_triangles.Add(triangle); + } + + public Vector3 GetCentroid() + { + if (_centroidDiv > 0) + return new Vector3(_centroid.X / _centroidDiv, _centroid.Y / _centroidDiv, _centroid.Z / _centroidDiv); + else + return Vector3.Zero; + } + + public void CalcNormals() + { + int iTriangles = m_triangles.Count; + + this.m_normals = new float[iTriangles * 3]; + + int i = 0; + foreach (Triangle t in m_triangles) + { + float ux, uy, uz; + float vx, vy, vz; + float wx, wy, wz; + + ux = t.v1.X; + uy = t.v1.Y; + uz = t.v1.Z; + + vx = t.v2.X; + vy = t.v2.Y; + vz = t.v2.Z; + + wx = t.v3.X; + wy = t.v3.Y; + wz = t.v3.Z; + + + // Vectors for edges + float e1x, e1y, e1z; + float e2x, e2y, e2z; + + e1x = ux - vx; + e1y = uy - vy; + e1z = uz - vz; + + e2x = ux - wx; + e2y = uy - wy; + e2z = uz - wz; + + + // Cross product for normal + float nx, ny, nz; + nx = e1y * e2z - e1z * e2y; + ny = e1z * e2x - e1x * e2z; + nz = e1x * e2y - e1y * e2x; + + // Length + float l = (float)Math.Sqrt(nx * nx + ny * ny + nz * nz); + float lReciprocal = 1.0f / l; + + // Normalized "normal" + //nx /= l; + //ny /= l; + //nz /= l; + + m_normals[i] = nx * lReciprocal; + m_normals[i + 1] = ny * lReciprocal; + m_normals[i + 2] = nz * lReciprocal; + + i += 3; + } + } + + public List getVertexList() + { + List result = new List(); + foreach (Vertex v in m_vertices.Keys) + { + result.Add(new Vector3(v.X, v.Y, v.Z)); + } + return result; + } + + private float[] getVertexListAsFloat() + { + if (m_vertices == null) + throw new NotSupportedException(); + float[] result = new float[m_vertices.Count * 3]; + foreach (KeyValuePair kvp in m_vertices) + { + Vertex v = kvp.Key; + int i = kvp.Value; + result[3 * i + 0] = v.X; + result[3 * i + 1] = v.Y; + result[3 * i + 2] = v.Z; + } + return result; + } + + public float[] getVertexListAsFloatLocked() + { + if (m_pinnedVertexes.IsAllocated) + return (float[])(m_pinnedVertexes.Target); + + float[] result = getVertexListAsFloat(); + m_pinnedVertexes = GCHandle.Alloc(result, GCHandleType.Pinned); + // Inform the garbage collector of this unmanaged allocation so it can schedule + // the next GC round more intelligently + GC.AddMemoryPressure(Buffer.ByteLength(result)); + + return result; + } + + public void getVertexListAsPtrToFloatArray(out IntPtr vertices, out int vertexStride, out int vertexCount) + { + // A vertex is 3 floats + vertexStride = 3 * sizeof(float); + + // If there isn't an unmanaged array allocated yet, do it now + if (m_verticesPtr == IntPtr.Zero) + { + float[] vertexList = getVertexListAsFloat(); + // Each vertex is 3 elements (floats) + m_vertexCount = vertexList.Length / 3; + int byteCount = m_vertexCount * vertexStride; + m_verticesPtr = System.Runtime.InteropServices.Marshal.AllocHGlobal(byteCount); + System.Runtime.InteropServices.Marshal.Copy(vertexList, 0, m_verticesPtr, m_vertexCount * 3); + } + vertices = m_verticesPtr; + vertexCount = m_vertexCount; + } + + public int[] getIndexListAsInt() + { + if (m_triangles == null) + throw new NotSupportedException(); + int[] result = new int[m_triangles.Count * 3]; + for (int i = 0; i < m_triangles.Count; i++) + { + Triangle t = m_triangles[i]; + result[3 * i + 0] = m_vertices[t.v1]; + result[3 * i + 1] = m_vertices[t.v2]; + result[3 * i + 2] = m_vertices[t.v3]; + } + return result; + } + + /// + /// creates a list of index values that defines triangle faces. THIS METHOD FREES ALL NON-PINNED MESH DATA + /// + /// + public int[] getIndexListAsIntLocked() + { + if (m_pinnedIndex.IsAllocated) + return (int[])(m_pinnedIndex.Target); + + int[] result = getIndexListAsInt(); + m_pinnedIndex = GCHandle.Alloc(result, GCHandleType.Pinned); + // Inform the garbage collector of this unmanaged allocation so it can schedule + // the next GC round more intelligently + GC.AddMemoryPressure(Buffer.ByteLength(result)); + + return result; + } + + public void getIndexListAsPtrToIntArray(out IntPtr indices, out int triStride, out int indexCount) + { + // If there isn't an unmanaged array allocated yet, do it now + if (m_indicesPtr == IntPtr.Zero) + { + int[] indexList = getIndexListAsInt(); + m_indexCount = indexList.Length; + int byteCount = m_indexCount * sizeof(int); + m_indicesPtr = System.Runtime.InteropServices.Marshal.AllocHGlobal(byteCount); + System.Runtime.InteropServices.Marshal.Copy(indexList, 0, m_indicesPtr, m_indexCount); + } + // A triangle is 3 ints (indices) + triStride = 3 * sizeof(int); + indices = m_indicesPtr; + indexCount = m_indexCount; + } + + public void releasePinned() + { + if (m_pinnedVertexes.IsAllocated) + m_pinnedVertexes.Free(); + if (m_pinnedIndex.IsAllocated) + m_pinnedIndex.Free(); + if (m_verticesPtr != IntPtr.Zero) + { + System.Runtime.InteropServices.Marshal.FreeHGlobal(m_verticesPtr); + m_verticesPtr = IntPtr.Zero; + } + if (m_indicesPtr != IntPtr.Zero) + { + System.Runtime.InteropServices.Marshal.FreeHGlobal(m_indicesPtr); + m_indicesPtr = IntPtr.Zero; + } + } + + /// + /// frees up the source mesh data to minimize memory - call this method after calling get*Locked() functions + /// + public void releaseSourceMeshData() + { + m_triangles = null; + m_vertices = null; + } + + public void Append(IMesh newMesh) + { + if (m_pinnedIndex.IsAllocated || m_pinnedVertexes.IsAllocated || m_indicesPtr != IntPtr.Zero || m_verticesPtr != IntPtr.Zero) + throw new NotSupportedException("Attempt to Append to a pinned Mesh"); + + if (!(newMesh is Mesh)) + return; + + foreach (Triangle t in ((Mesh)newMesh).m_triangles) + Add(t); + } + + // Do a linear transformation of mesh. + public void TransformLinear(float[,] matrix, float[] offset) + { + if (m_pinnedIndex.IsAllocated || m_pinnedVertexes.IsAllocated || m_indicesPtr != IntPtr.Zero || m_verticesPtr != IntPtr.Zero) + throw new NotSupportedException("Attempt to TransformLinear a pinned Mesh"); + + foreach (Vertex v in m_vertices.Keys) + { + if (v == null) + continue; + float x, y, z; + x = v.X*matrix[0, 0] + v.Y*matrix[1, 0] + v.Z*matrix[2, 0]; + y = v.X*matrix[0, 1] + v.Y*matrix[1, 1] + v.Z*matrix[2, 1]; + z = v.X*matrix[0, 2] + v.Y*matrix[1, 2] + v.Z*matrix[2, 2]; + v.X = x + offset[0]; + v.Y = y + offset[1]; + v.Z = z + offset[2]; + } + } + + public void DumpRaw(String path, String name, String title) + { + if (path == null) + return; + String fileName = name + "_" + title + ".raw"; + String completePath = System.IO.Path.Combine(path, fileName); + StreamWriter sw = new StreamWriter(completePath); + foreach (Triangle t in m_triangles) + { + String s = t.ToStringRaw(); + sw.WriteLine(s); + } + sw.Close(); + } + + public void TrimExcess() + { + m_triangles.TrimExcess(); + } + } +} diff --git a/OpenSim/Region/Physics/UbitMeshing/Meshmerizer.cs b/OpenSim/Region/Physics/UbitMeshing/Meshmerizer.cs new file mode 100644 index 0000000000..c9c52c0fba --- /dev/null +++ b/OpenSim/Region/Physics/UbitMeshing/Meshmerizer.cs @@ -0,0 +1,762 @@ +/* + * Copyright (c) Contributors, http://opensimulator.org/ + * See CONTRIBUTORS.TXT for a full list of copyright holders. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name of the OpenSimulator Project nor the + * names of its contributors may be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE DEVELOPERS ``AS IS'' AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE CONTRIBUTORS BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +//#define SPAM + +using System; +using System.Collections.Generic; +using OpenSim.Framework; +using OpenSim.Region.Physics.Manager; +using OpenMetaverse; +using OpenMetaverse.StructuredData; +using System.Drawing; +using System.Drawing.Imaging; +using System.IO.Compression; +using PrimMesher; +using log4net; +using Nini.Config; +using System.Reflection; +using System.IO; +using ComponentAce.Compression.Libs.zlib; + +namespace OpenSim.Region.Physics.Meshing +{ + public class MeshmerizerPlugin : IMeshingPlugin + { + public MeshmerizerPlugin() + { + } + + public string GetName() + { + return "UbitMeshmerizer"; + } + + public IMesher GetMesher(IConfigSource config) + { + return new Meshmerizer(config); + } + } + + public class Meshmerizer : IMesher + { + private static readonly ILog m_log = LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType); + + // Setting baseDir to a path will enable the dumping of raw files + // raw files can be imported by blender so a visual inspection of the results can be done +#if SPAM + const string baseDir = "rawFiles"; +#else + private const string baseDir = null; //"rawFiles"; +#endif + + private bool cacheSculptMaps = true; + private bool cacheSculptAlphaMaps = true; + + private string decodedSculptMapPath = null; + private bool useMeshiesPhysicsMesh = false; + + private float minSizeForComplexMesh = 0.2f; // prims with all dimensions smaller than this will have a bounding box mesh + + private Dictionary m_uniqueMeshes = new Dictionary(); + + public Meshmerizer(IConfigSource config) + { + IConfig start_config = config.Configs["Startup"]; + IConfig mesh_config = config.Configs["Mesh"]; + + decodedSculptMapPath = start_config.GetString("DecodedSculptMapPath","j2kDecodeCache"); + + cacheSculptMaps = start_config.GetBoolean("CacheSculptMaps", cacheSculptMaps); + + if (Environment.OSVersion.Platform == PlatformID.Unix) + { + cacheSculptAlphaMaps = false; + } + else + cacheSculptAlphaMaps = cacheSculptMaps; + + if(mesh_config != null) + useMeshiesPhysicsMesh = mesh_config.GetBoolean("UseMeshiesPhysicsMesh", useMeshiesPhysicsMesh); + + try + { + if (!Directory.Exists(decodedSculptMapPath)) + Directory.CreateDirectory(decodedSculptMapPath); + } + catch (Exception e) + { + m_log.WarnFormat("[SCULPT]: Unable to create {0} directory: ", decodedSculptMapPath, e.Message); + } + } + + /// + /// creates a simple box mesh of the specified size. This mesh is of very low vertex count and may + /// be useful as a backup proxy when level of detail is not needed or when more complex meshes fail + /// for some reason + /// + /// + /// + /// + /// + /// + /// + /// + private static Mesh CreateSimpleBoxMesh(float minX, float maxX, float minY, float maxY, float minZ, float maxZ) + { + Mesh box = new Mesh(); + List vertices = new List(); + // bottom + + vertices.Add(new Vertex(minX, maxY, minZ)); + vertices.Add(new Vertex(maxX, maxY, minZ)); + vertices.Add(new Vertex(maxX, minY, minZ)); + vertices.Add(new Vertex(minX, minY, minZ)); + + box.Add(new Triangle(vertices[0], vertices[1], vertices[2])); + box.Add(new Triangle(vertices[0], vertices[2], vertices[3])); + + // top + + vertices.Add(new Vertex(maxX, maxY, maxZ)); + vertices.Add(new Vertex(minX, maxY, maxZ)); + vertices.Add(new Vertex(minX, minY, maxZ)); + vertices.Add(new Vertex(maxX, minY, maxZ)); + + box.Add(new Triangle(vertices[4], vertices[5], vertices[6])); + box.Add(new Triangle(vertices[4], vertices[6], vertices[7])); + + // sides + + box.Add(new Triangle(vertices[5], vertices[0], vertices[3])); + box.Add(new Triangle(vertices[5], vertices[3], vertices[6])); + + box.Add(new Triangle(vertices[1], vertices[0], vertices[5])); + box.Add(new Triangle(vertices[1], vertices[5], vertices[4])); + + box.Add(new Triangle(vertices[7], vertices[1], vertices[4])); + box.Add(new Triangle(vertices[7], vertices[2], vertices[1])); + + box.Add(new Triangle(vertices[3], vertices[2], vertices[7])); + box.Add(new Triangle(vertices[3], vertices[7], vertices[6])); + + return box; + } + + /// + /// Creates a simple bounding box mesh for a complex input mesh + /// + /// + /// + private static Mesh CreateBoundingBoxMesh(Mesh meshIn) + { + float minX = float.MaxValue; + float maxX = float.MinValue; + float minY = float.MaxValue; + float maxY = float.MinValue; + float minZ = float.MaxValue; + float maxZ = float.MinValue; + + foreach (Vector3 v in meshIn.getVertexList()) + { + if (v.X < minX) minX = v.X; + if (v.Y < minY) minY = v.Y; + if (v.Z < minZ) minZ = v.Z; + + if (v.X > maxX) maxX = v.X; + if (v.Y > maxY) maxY = v.Y; + if (v.Z > maxZ) maxZ = v.Z; + } + + return CreateSimpleBoxMesh(minX, maxX, minY, maxY, minZ, maxZ); + } + + private void ReportPrimError(string message, string primName, PrimMesh primMesh) + { + m_log.Error(message); + m_log.Error("\nPrim Name: " + primName); + m_log.Error("****** PrimMesh Parameters ******\n" + primMesh.ParamsToDisplayString()); + } + + /// + /// Add a submesh to an existing list of coords and faces. + /// + /// + /// Size of entire object + /// + /// + private void AddSubMesh(OSDMap subMeshData, Vector3 size, List coords, List faces) + { + // Console.WriteLine("subMeshMap for {0} - {1}", primName, Util.GetFormattedXml((OSD)subMeshMap)); + + // As per http://wiki.secondlife.com/wiki/Mesh/Mesh_Asset_Format, some Mesh Level + // of Detail Blocks (maps) contain just a NoGeometry key to signal there is no + // geometry for this submesh. + if (subMeshData.ContainsKey("NoGeometry") && ((OSDBoolean)subMeshData["NoGeometry"])) + return; + + OpenMetaverse.Vector3 posMax = ((OSDMap)subMeshData["PositionDomain"])["Max"].AsVector3(); + OpenMetaverse.Vector3 posMin = ((OSDMap)subMeshData["PositionDomain"])["Min"].AsVector3(); + ushort faceIndexOffset = (ushort)coords.Count; + + byte[] posBytes = subMeshData["Position"].AsBinary(); + for (int i = 0; i < posBytes.Length; i += 6) + { + ushort uX = Utils.BytesToUInt16(posBytes, i); + ushort uY = Utils.BytesToUInt16(posBytes, i + 2); + ushort uZ = Utils.BytesToUInt16(posBytes, i + 4); + + Coord c = new Coord( + Utils.UInt16ToFloat(uX, posMin.X, posMax.X) * size.X, + Utils.UInt16ToFloat(uY, posMin.Y, posMax.Y) * size.Y, + Utils.UInt16ToFloat(uZ, posMin.Z, posMax.Z) * size.Z); + + coords.Add(c); + } + + byte[] triangleBytes = subMeshData["TriangleList"].AsBinary(); + for (int i = 0; i < triangleBytes.Length; i += 6) + { + ushort v1 = (ushort)(Utils.BytesToUInt16(triangleBytes, i) + faceIndexOffset); + ushort v2 = (ushort)(Utils.BytesToUInt16(triangleBytes, i + 2) + faceIndexOffset); + ushort v3 = (ushort)(Utils.BytesToUInt16(triangleBytes, i + 4) + faceIndexOffset); + Face f = new Face(v1, v2, v3); + faces.Add(f); + } + } + + /// + /// Create a physics mesh from data that comes with the prim. The actual data used depends on the prim type. + /// + /// + /// + /// + /// + /// + private Mesh CreateMeshFromPrimMesher(string primName, PrimitiveBaseShape primShape, Vector3 size, float lod) + { +// m_log.DebugFormat( +// "[MESH]: Creating physics proxy for {0}, shape {1}", +// primName, (OpenMetaverse.SculptType)primShape.SculptType); + + List coords; + List faces; + + if (primShape.SculptEntry) + { + if (((OpenMetaverse.SculptType)primShape.SculptType) == SculptType.Mesh) + { + if (!useMeshiesPhysicsMesh) + return null; + + if (!GenerateCoordsAndFacesFromPrimMeshData(primName, primShape, size, out coords, out faces)) + return null; + } + else + { + if (!GenerateCoordsAndFacesFromPrimSculptData(primName, primShape, size, lod, out coords, out faces)) + return null; + // Remove the reference to any JPEG2000 sculpt data so it can be GCed + // don't loose it + // primShape.SculptData = Utils.EmptyBytes; + } +// primShape.SculptDataLoaded = true; + } + else + { + if (!GenerateCoordsAndFacesFromPrimShapeData(primName, primShape, size, lod, out coords, out faces)) + return null; + } + // keep compatible + primShape.SculptData = Utils.EmptyBytes; + + int numCoords = coords.Count; + int numFaces = faces.Count; + + // Create the list of vertices + List vertices = new List(); + for (int i = 0; i < numCoords; i++) + { + Coord c = coords[i]; + vertices.Add(new Vertex(c.X, c.Y, c.Z)); + } + + Mesh mesh = new Mesh(); + // Add the corresponding triangles to the mesh + for (int i = 0; i < numFaces; i++) + { + Face f = faces[i]; + mesh.Add(new Triangle(vertices[f.v1], vertices[f.v2], vertices[f.v3])); + } + + return mesh; + } + + /// + /// Generate the co-ords and faces necessary to construct a mesh from the mesh data the accompanies a prim. + /// + /// + /// + /// + /// Coords are added to this list by the method. + /// Faces are added to this list by the method. + /// true if coords and faces were successfully generated, false if not + private bool GenerateCoordsAndFacesFromPrimMeshData( + string primName, PrimitiveBaseShape primShape, Vector3 size, out List coords, out List faces) + { +// m_log.DebugFormat("[MESH]: experimental mesh proxy generation for {0}", primName); + + coords = new List(); + faces = new List(); + OSD meshOsd = null; + + if (primShape.SculptData.Length <= 0) + { + m_log.ErrorFormat("[MESH]: asset data for {0} is zero length", primName); + return false; + } + + long start = 0; + using (MemoryStream data = new MemoryStream(primShape.SculptData)) + { + try + { + OSD osd = OSDParser.DeserializeLLSDBinary(data); + if (osd is OSDMap) + meshOsd = (OSDMap)osd; + else + { + m_log.Warn("[Mesh}: unable to cast mesh asset to OSDMap"); + return false; + } + } + catch (Exception e) + { + m_log.Error("[MESH]: Exception deserializing mesh asset header:" + e.ToString()); + } + + start = data.Position; + } + + if (meshOsd is OSDMap) + { + OSDMap physicsParms = null; + OSDMap map = (OSDMap)meshOsd; + if (map.ContainsKey("physics_shape")) + physicsParms = (OSDMap)map["physics_shape"]; // old asset format + else if (map.ContainsKey("physics_mesh")) + physicsParms = (OSDMap)map["physics_mesh"]; // new asset format + + if (physicsParms == null) + { + m_log.Warn("[MESH]: no recognized physics mesh found in mesh asset"); + return false; + } + + int physOffset = physicsParms["offset"].AsInteger() + (int)start; + int physSize = physicsParms["size"].AsInteger(); + + if (physOffset < 0 || physSize == 0) + return false; // no mesh data in asset + + OSD decodedMeshOsd = new OSD(); + byte[] meshBytes = new byte[physSize]; + System.Buffer.BlockCopy(primShape.SculptData, physOffset, meshBytes, 0, physSize); +// byte[] decompressed = new byte[physSize * 5]; + try + { + using (MemoryStream inMs = new MemoryStream(meshBytes)) + { + using (MemoryStream outMs = new MemoryStream()) + { + using (ZOutputStream zOut = new ZOutputStream(outMs)) + { + byte[] readBuffer = new byte[2048]; + int readLen = 0; + while ((readLen = inMs.Read(readBuffer, 0, readBuffer.Length)) > 0) + { + zOut.Write(readBuffer, 0, readLen); + } + zOut.Flush(); + outMs.Seek(0, SeekOrigin.Begin); + + byte[] decompressedBuf = outMs.GetBuffer(); + + decodedMeshOsd = OSDParser.DeserializeLLSDBinary(decompressedBuf); + } + } + } + } + catch (Exception e) + { + m_log.Error("[MESH]: exception decoding physical mesh: " + e.ToString()); + return false; + } + + OSDArray decodedMeshOsdArray = null; + + // physics_shape is an array of OSDMaps, one for each submesh + if (decodedMeshOsd is OSDArray) + { +// Console.WriteLine("decodedMeshOsd for {0} - {1}", primName, Util.GetFormattedXml(decodedMeshOsd)); + + decodedMeshOsdArray = (OSDArray)decodedMeshOsd; + foreach (OSD subMeshOsd in decodedMeshOsdArray) + { + if (subMeshOsd is OSDMap) + AddSubMesh(subMeshOsd as OSDMap, size, coords, faces); + } + } + } + + return true; + } + + /// + /// Generate the co-ords and faces necessary to construct a mesh from the sculpt data the accompanies a prim. + /// + /// + /// + /// + /// + /// Coords are added to this list by the method. + /// Faces are added to this list by the method. + /// true if coords and faces were successfully generated, false if not + private bool GenerateCoordsAndFacesFromPrimSculptData( + string primName, PrimitiveBaseShape primShape, Vector3 size, float lod, out List coords, out List faces) + { + coords = new List(); + faces = new List(); + PrimMesher.SculptMesh sculptMesh; + Image idata = null; + string decodedSculptFileName = ""; + + if (cacheSculptMaps && primShape.SculptTexture != UUID.Zero) + { + decodedSculptFileName = System.IO.Path.Combine(decodedSculptMapPath, "smap_" + primShape.SculptTexture.ToString()); + try + { + if (File.Exists(decodedSculptFileName)) + { + idata = Image.FromFile(decodedSculptFileName); + } + } + catch (Exception e) + { + m_log.Error("[SCULPT]: unable to load cached sculpt map " + decodedSculptFileName + " " + e.Message); + + } + //if (idata != null) + // m_log.Debug("[SCULPT]: loaded cached map asset for map ID: " + primShape.SculptTexture.ToString()); + } + + if (idata == null) + { + if (primShape.SculptData == null || primShape.SculptData.Length == 0) + return false; + + try + { + OpenMetaverse.Imaging.ManagedImage unusedData; + OpenMetaverse.Imaging.OpenJPEG.DecodeToImage(primShape.SculptData, out unusedData, out idata); + + if (idata == null) + { + // In some cases it seems that the decode can return a null bitmap without throwing + // an exception + m_log.WarnFormat("[PHYSICS]: OpenJPEG decoded sculpt data for {0} to a null bitmap. Ignoring.", primName); + + return false; + } + + unusedData = null; + + //idata = CSJ2K.J2kImage.FromBytes(primShape.SculptData); + + if (cacheSculptMaps && (cacheSculptAlphaMaps || (((ImageFlags)(idata.Flags) & ImageFlags.HasAlpha) ==0))) + // don't cache images with alpha channel in linux since mono can't load them correctly) + { + try { idata.Save(decodedSculptFileName, ImageFormat.MemoryBmp); } + catch (Exception e) { m_log.Error("[SCULPT]: unable to cache sculpt map " + decodedSculptFileName + " " + e.Message); } + } + } + catch (DllNotFoundException) + { + m_log.Error("[PHYSICS]: OpenJpeg is not installed correctly on this system. Physics Proxy generation failed. Often times this is because of an old version of GLIBC. You must have version 2.4 or above!"); + return false; + } + catch (IndexOutOfRangeException) + { + m_log.Error("[PHYSICS]: OpenJpeg was unable to decode this. Physics Proxy generation failed"); + return false; + } + catch (Exception ex) + { + m_log.Error("[PHYSICS]: Unable to generate a Sculpty physics proxy. Sculpty texture decode failed: " + ex.Message); + return false; + } + } + + PrimMesher.SculptMesh.SculptType sculptType; + switch ((OpenMetaverse.SculptType)primShape.SculptType) + { + case OpenMetaverse.SculptType.Cylinder: + sculptType = PrimMesher.SculptMesh.SculptType.cylinder; + break; + case OpenMetaverse.SculptType.Plane: + sculptType = PrimMesher.SculptMesh.SculptType.plane; + break; + case OpenMetaverse.SculptType.Torus: + sculptType = PrimMesher.SculptMesh.SculptType.torus; + break; + case OpenMetaverse.SculptType.Sphere: + sculptType = PrimMesher.SculptMesh.SculptType.sphere; + break; + default: + sculptType = PrimMesher.SculptMesh.SculptType.plane; + break; + } + + bool mirror = ((primShape.SculptType & 128) != 0); + bool invert = ((primShape.SculptType & 64) != 0); + + sculptMesh = new PrimMesher.SculptMesh((Bitmap)idata, sculptType, (int)lod, false, mirror, invert); + + idata.Dispose(); + + sculptMesh.DumpRaw(baseDir, primName, "primMesh"); + + sculptMesh.Scale(size.X, size.Y, size.Z); + + coords = sculptMesh.coords; + faces = sculptMesh.faces; + + return true; + } + + /// + /// Generate the co-ords and faces necessary to construct a mesh from the shape data the accompanies a prim. + /// + /// + /// + /// + /// Coords are added to this list by the method. + /// Faces are added to this list by the method. + /// true if coords and faces were successfully generated, false if not + private bool GenerateCoordsAndFacesFromPrimShapeData( + string primName, PrimitiveBaseShape primShape, Vector3 size, float lod, out List coords, out List faces) + { + PrimMesh primMesh; + coords = new List(); + faces = new List(); + + float pathShearX = primShape.PathShearX < 128 ? (float)primShape.PathShearX * 0.01f : (float)(primShape.PathShearX - 256) * 0.01f; + float pathShearY = primShape.PathShearY < 128 ? (float)primShape.PathShearY * 0.01f : (float)(primShape.PathShearY - 256) * 0.01f; + float pathBegin = (float)primShape.PathBegin * 2.0e-5f; + float pathEnd = 1.0f - (float)primShape.PathEnd * 2.0e-5f; + float pathScaleX = (float)(primShape.PathScaleX - 100) * 0.01f; + float pathScaleY = (float)(primShape.PathScaleY - 100) * 0.01f; + + float profileBegin = (float)primShape.ProfileBegin * 2.0e-5f; + float profileEnd = 1.0f - (float)primShape.ProfileEnd * 2.0e-5f; + float profileHollow = (float)primShape.ProfileHollow * 2.0e-5f; + if (profileHollow > 0.95f) + profileHollow = 0.95f; + + int sides = 4; + LevelOfDetail iLOD = (LevelOfDetail)lod; + if ((primShape.ProfileCurve & 0x07) == (byte)ProfileShape.EquilateralTriangle) + sides = 3; + else if ((primShape.ProfileCurve & 0x07) == (byte)ProfileShape.Circle) + { + switch (iLOD) + { + case LevelOfDetail.High: sides = 24; break; + case LevelOfDetail.Medium: sides = 12; break; + case LevelOfDetail.Low: sides = 6; break; + case LevelOfDetail.VeryLow: sides = 3; break; + default: sides = 24; break; + } + } + else if ((primShape.ProfileCurve & 0x07) == (byte)ProfileShape.HalfCircle) + { // half circle, prim is a sphere + switch (iLOD) + { + case LevelOfDetail.High: sides = 24; break; + case LevelOfDetail.Medium: sides = 12; break; + case LevelOfDetail.Low: sides = 6; break; + case LevelOfDetail.VeryLow: sides = 3; break; + default: sides = 24; break; + } + + profileBegin = 0.5f * profileBegin + 0.5f; + profileEnd = 0.5f * profileEnd + 0.5f; + } + + int hollowSides = sides; + if (primShape.HollowShape == HollowShape.Circle) + { + switch (iLOD) + { + case LevelOfDetail.High: hollowSides = 24; break; + case LevelOfDetail.Medium: hollowSides = 12; break; + case LevelOfDetail.Low: hollowSides = 6; break; + case LevelOfDetail.VeryLow: hollowSides = 3; break; + default: hollowSides = 24; break; + } + } + else if (primShape.HollowShape == HollowShape.Square) + hollowSides = 4; + else if (primShape.HollowShape == HollowShape.Triangle) + hollowSides = 3; + + primMesh = new PrimMesh(sides, profileBegin, profileEnd, profileHollow, hollowSides); + + if (primMesh.errorMessage != null) + if (primMesh.errorMessage.Length > 0) + m_log.Error("[ERROR] " + primMesh.errorMessage); + + primMesh.topShearX = pathShearX; + primMesh.topShearY = pathShearY; + primMesh.pathCutBegin = pathBegin; + primMesh.pathCutEnd = pathEnd; + + if (primShape.PathCurve == (byte)Extrusion.Straight || primShape.PathCurve == (byte) Extrusion.Flexible) + { + primMesh.twistBegin = primShape.PathTwistBegin * 18 / 10; + primMesh.twistEnd = primShape.PathTwist * 18 / 10; + primMesh.taperX = pathScaleX; + primMesh.taperY = pathScaleY; + + if (profileBegin < 0.0f || profileBegin >= profileEnd || profileEnd > 1.0f) + { + ReportPrimError("*** CORRUPT PRIM!! ***", primName, primMesh); + if (profileBegin < 0.0f) profileBegin = 0.0f; + if (profileEnd > 1.0f) profileEnd = 1.0f; + } +#if SPAM + m_log.Debug("****** PrimMesh Parameters (Linear) ******\n" + primMesh.ParamsToDisplayString()); +#endif + try + { + primMesh.ExtrudeLinear(); + } + catch (Exception ex) + { + ReportPrimError("Extrusion failure: exception: " + ex.ToString(), primName, primMesh); + return false; + } + } + else + { + primMesh.holeSizeX = (200 - primShape.PathScaleX) * 0.01f; + primMesh.holeSizeY = (200 - primShape.PathScaleY) * 0.01f; + primMesh.radius = 0.01f * primShape.PathRadiusOffset; + primMesh.revolutions = 1.0f + 0.015f * primShape.PathRevolutions; + primMesh.skew = 0.01f * primShape.PathSkew; + primMesh.twistBegin = primShape.PathTwistBegin * 36 / 10; + primMesh.twistEnd = primShape.PathTwist * 36 / 10; + primMesh.taperX = primShape.PathTaperX * 0.01f; + primMesh.taperY = primShape.PathTaperY * 0.01f; + + if (profileBegin < 0.0f || profileBegin >= profileEnd || profileEnd > 1.0f) + { + ReportPrimError("*** CORRUPT PRIM!! ***", primName, primMesh); + if (profileBegin < 0.0f) profileBegin = 0.0f; + if (profileEnd > 1.0f) profileEnd = 1.0f; + } +#if SPAM + m_log.Debug("****** PrimMesh Parameters (Circular) ******\n" + primMesh.ParamsToDisplayString()); +#endif + try + { + primMesh.ExtrudeCircular(); + } + catch (Exception ex) + { + ReportPrimError("Extrusion failure: exception: " + ex.ToString(), primName, primMesh); + return false; + } + } + + primMesh.DumpRaw(baseDir, primName, "primMesh"); + + primMesh.Scale(size.X, size.Y, size.Z); + + coords = primMesh.coords; + faces = primMesh.faces; + + return true; + } + + public IMesh CreateMesh(String primName, PrimitiveBaseShape primShape, Vector3 size, float lod) + { + return CreateMesh(primName, primShape, size, lod, false); + } + + public IMesh CreateMesh(String primName, PrimitiveBaseShape primShape, Vector3 size, float lod, bool isPhysical) + { +#if SPAM + m_log.DebugFormat("[MESH]: Creating mesh for {0}", primName); +#endif + + Mesh mesh = null; + ulong key = 0; + + // If this mesh has been created already, return it instead of creating another copy + // For large regions with 100k+ prims and hundreds of copies of each, this can save a GB or more of memory + key = primShape.GetMeshKey(size, lod); + if (m_uniqueMeshes.TryGetValue(key, out mesh)) + return mesh; + + if (size.X < 0.01f) size.X = 0.01f; + if (size.Y < 0.01f) size.Y = 0.01f; + if (size.Z < 0.01f) size.Z = 0.01f; + + mesh = CreateMeshFromPrimMesher(primName, primShape, size, lod); + + if (mesh != null) + { + if ((!isPhysical) && size.X < minSizeForComplexMesh && size.Y < minSizeForComplexMesh && size.Z < minSizeForComplexMesh) + { +#if SPAM + m_log.Debug("Meshmerizer: prim " + primName + " has a size of " + size.ToString() + " which is below threshold of " + + minSizeForComplexMesh.ToString() + " - creating simple bounding box"); +#endif + mesh = CreateBoundingBoxMesh(mesh); + mesh.DumpRaw(baseDir, primName, "Z extruded"); + } + + // trim the vertex and triangle lists to free up memory + mesh.TrimExcess(); + + m_uniqueMeshes.Add(key, mesh); + } + + return mesh; + } + } +} diff --git a/OpenSim/Region/Physics/UbitMeshing/PrimMesher.cs b/OpenSim/Region/Physics/UbitMeshing/PrimMesher.cs new file mode 100644 index 0000000000..53022ad6b4 --- /dev/null +++ b/OpenSim/Region/Physics/UbitMeshing/PrimMesher.cs @@ -0,0 +1,2284 @@ +/* + * Copyright (c) Contributors + * See CONTRIBUTORS.TXT for a full list of copyright holders. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name of the OpenSimulator Project nor the + * names of its contributors may be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE DEVELOPERS ``AS IS'' AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE CONTRIBUTORS BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +using System; +using System.Collections.Generic; +using System.Text; +using System.IO; + +namespace PrimMesher +{ + public struct Quat + { + /// X value + public float X; + /// Y value + public float Y; + /// Z value + public float Z; + /// W value + public float W; + + public Quat(float x, float y, float z, float w) + { + X = x; + Y = y; + Z = z; + W = w; + } + + public Quat(Coord axis, float angle) + { + axis = axis.Normalize(); + + angle *= 0.5f; + float c = (float)Math.Cos(angle); + float s = (float)Math.Sin(angle); + + X = axis.X * s; + Y = axis.Y * s; + Z = axis.Z * s; + W = c; + + Normalize(); + } + + public float Length() + { + return (float)Math.Sqrt(X * X + Y * Y + Z * Z + W * W); + } + + public Quat Normalize() + { + const float MAG_THRESHOLD = 0.0000001f; + float mag = Length(); + + // Catch very small rounding errors when normalizing + if (mag > MAG_THRESHOLD) + { + float oomag = 1f / mag; + X *= oomag; + Y *= oomag; + Z *= oomag; + W *= oomag; + } + else + { + X = 0f; + Y = 0f; + Z = 0f; + W = 1f; + } + + return this; + } + + public static Quat operator *(Quat q1, Quat q2) + { + float x = q1.W * q2.X + q1.X * q2.W + q1.Y * q2.Z - q1.Z * q2.Y; + float y = q1.W * q2.Y - q1.X * q2.Z + q1.Y * q2.W + q1.Z * q2.X; + float z = q1.W * q2.Z + q1.X * q2.Y - q1.Y * q2.X + q1.Z * q2.W; + float w = q1.W * q2.W - q1.X * q2.X - q1.Y * q2.Y - q1.Z * q2.Z; + return new Quat(x, y, z, w); + } + + public override string ToString() + { + return "< X: " + this.X.ToString() + ", Y: " + this.Y.ToString() + ", Z: " + this.Z.ToString() + ", W: " + this.W.ToString() + ">"; + } + } + + public struct Coord + { + public float X; + public float Y; + public float Z; + + public Coord(float x, float y, float z) + { + this.X = x; + this.Y = y; + this.Z = z; + } + + public float Length() + { + return (float)Math.Sqrt(this.X * this.X + this.Y * this.Y + this.Z * this.Z); + } + + public Coord Invert() + { + this.X = -this.X; + this.Y = -this.Y; + this.Z = -this.Z; + + return this; + } + + public Coord Normalize() + { + const float MAG_THRESHOLD = 0.0000001f; + float mag = Length(); + + // Catch very small rounding errors when normalizing + if (mag > MAG_THRESHOLD) + { + float oomag = 1.0f / mag; + this.X *= oomag; + this.Y *= oomag; + this.Z *= oomag; + } + else + { + this.X = 0.0f; + this.Y = 0.0f; + this.Z = 0.0f; + } + + return this; + } + + public override string ToString() + { + return this.X.ToString() + " " + this.Y.ToString() + " " + this.Z.ToString(); + } + + public static Coord Cross(Coord c1, Coord c2) + { + return new Coord( + c1.Y * c2.Z - c2.Y * c1.Z, + c1.Z * c2.X - c2.Z * c1.X, + c1.X * c2.Y - c2.X * c1.Y + ); + } + + public static Coord operator +(Coord v, Coord a) + { + return new Coord(v.X + a.X, v.Y + a.Y, v.Z + a.Z); + } + + public static Coord operator *(Coord v, Coord m) + { + return new Coord(v.X * m.X, v.Y * m.Y, v.Z * m.Z); + } + + public static Coord operator *(Coord v, Quat q) + { + // From http://www.euclideanspace.com/maths/algebra/realNormedAlgebra/quaternions/transforms/ + + Coord c2 = new Coord(0.0f, 0.0f, 0.0f); + + c2.X = q.W * q.W * v.X + + 2f * q.Y * q.W * v.Z - + 2f * q.Z * q.W * v.Y + + q.X * q.X * v.X + + 2f * q.Y * q.X * v.Y + + 2f * q.Z * q.X * v.Z - + q.Z * q.Z * v.X - + q.Y * q.Y * v.X; + + c2.Y = + 2f * q.X * q.Y * v.X + + q.Y * q.Y * v.Y + + 2f * q.Z * q.Y * v.Z + + 2f * q.W * q.Z * v.X - + q.Z * q.Z * v.Y + + q.W * q.W * v.Y - + 2f * q.X * q.W * v.Z - + q.X * q.X * v.Y; + + c2.Z = + 2f * q.X * q.Z * v.X + + 2f * q.Y * q.Z * v.Y + + q.Z * q.Z * v.Z - + 2f * q.W * q.Y * v.X - + q.Y * q.Y * v.Z + + 2f * q.W * q.X * v.Y - + q.X * q.X * v.Z + + q.W * q.W * v.Z; + + return c2; + } + } + + public struct UVCoord + { + public float U; + public float V; + + + public UVCoord(float u, float v) + { + this.U = u; + this.V = v; + } + } + + public struct Face + { + public int primFace; + + // vertices + public int v1; + public int v2; + public int v3; + + //normals + public int n1; + public int n2; + public int n3; + + // uvs + public int uv1; + public int uv2; + public int uv3; + + public Face(int v1, int v2, int v3) + { + primFace = 0; + + this.v1 = v1; + this.v2 = v2; + this.v3 = v3; + + this.n1 = 0; + this.n2 = 0; + this.n3 = 0; + + this.uv1 = 0; + this.uv2 = 0; + this.uv3 = 0; + + } + + public Face(int v1, int v2, int v3, int n1, int n2, int n3) + { + primFace = 0; + + this.v1 = v1; + this.v2 = v2; + this.v3 = v3; + + this.n1 = n1; + this.n2 = n2; + this.n3 = n3; + + this.uv1 = 0; + this.uv2 = 0; + this.uv3 = 0; + } + + public Coord SurfaceNormal(List coordList) + { + Coord c1 = coordList[this.v1]; + Coord c2 = coordList[this.v2]; + Coord c3 = coordList[this.v3]; + + Coord edge1 = new Coord(c2.X - c1.X, c2.Y - c1.Y, c2.Z - c1.Z); + Coord edge2 = new Coord(c3.X - c1.X, c3.Y - c1.Y, c3.Z - c1.Z); + + return Coord.Cross(edge1, edge2).Normalize(); + } + } + + public struct ViewerFace + { + public int primFaceNumber; + + public Coord v1; + public Coord v2; + public Coord v3; + + public int coordIndex1; + public int coordIndex2; + public int coordIndex3; + + public Coord n1; + public Coord n2; + public Coord n3; + + public UVCoord uv1; + public UVCoord uv2; + public UVCoord uv3; + + public ViewerFace(int primFaceNumber) + { + this.primFaceNumber = primFaceNumber; + + this.v1 = new Coord(); + this.v2 = new Coord(); + this.v3 = new Coord(); + + this.coordIndex1 = this.coordIndex2 = this.coordIndex3 = -1; // -1 means not assigned yet + + this.n1 = new Coord(); + this.n2 = new Coord(); + this.n3 = new Coord(); + + this.uv1 = new UVCoord(); + this.uv2 = new UVCoord(); + this.uv3 = new UVCoord(); + } + + public void Scale(float x, float y, float z) + { + this.v1.X *= x; + this.v1.Y *= y; + this.v1.Z *= z; + + this.v2.X *= x; + this.v2.Y *= y; + this.v2.Z *= z; + + this.v3.X *= x; + this.v3.Y *= y; + this.v3.Z *= z; + } + + public void AddPos(float x, float y, float z) + { + this.v1.X += x; + this.v2.X += x; + this.v3.X += x; + + this.v1.Y += y; + this.v2.Y += y; + this.v3.Y += y; + + this.v1.Z += z; + this.v2.Z += z; + this.v3.Z += z; + } + + public void AddRot(Quat q) + { + this.v1 *= q; + this.v2 *= q; + this.v3 *= q; + + this.n1 *= q; + this.n2 *= q; + this.n3 *= q; + } + + public void CalcSurfaceNormal() + { + + Coord edge1 = new Coord(this.v2.X - this.v1.X, this.v2.Y - this.v1.Y, this.v2.Z - this.v1.Z); + Coord edge2 = new Coord(this.v3.X - this.v1.X, this.v3.Y - this.v1.Y, this.v3.Z - this.v1.Z); + + this.n1 = this.n2 = this.n3 = Coord.Cross(edge1, edge2).Normalize(); + } + } + + internal struct Angle + { + internal float angle; + internal float X; + internal float Y; + + internal Angle(float angle, float x, float y) + { + this.angle = angle; + this.X = x; + this.Y = y; + } + } + + internal class AngleList + { + private float iX, iY; // intersection point + + private static Angle[] angles3 = + { + new Angle(0.0f, 1.0f, 0.0f), + new Angle(0.33333333333333333f, -0.5f, 0.86602540378443871f), + new Angle(0.66666666666666667f, -0.5f, -0.86602540378443837f), + new Angle(1.0f, 1.0f, 0.0f) + }; + + private static Coord[] normals3 = + { + new Coord(0.25f, 0.4330127019f, 0.0f).Normalize(), + new Coord(-0.5f, 0.0f, 0.0f).Normalize(), + new Coord(0.25f, -0.4330127019f, 0.0f).Normalize(), + new Coord(0.25f, 0.4330127019f, 0.0f).Normalize() + }; + + private static Angle[] angles4 = + { + new Angle(0.0f, 1.0f, 0.0f), + new Angle(0.25f, 0.0f, 1.0f), + new Angle(0.5f, -1.0f, 0.0f), + new Angle(0.75f, 0.0f, -1.0f), + new Angle(1.0f, 1.0f, 0.0f) + }; + + private static Coord[] normals4 = + { + new Coord(0.5f, 0.5f, 0.0f).Normalize(), + new Coord(-0.5f, 0.5f, 0.0f).Normalize(), + new Coord(-0.5f, -0.5f, 0.0f).Normalize(), + new Coord(0.5f, -0.5f, 0.0f).Normalize(), + new Coord(0.5f, 0.5f, 0.0f).Normalize() + }; + + private static Angle[] angles24 = + { + new Angle(0.0f, 1.0f, 0.0f), + new Angle(0.041666666666666664f, 0.96592582628906831f, 0.25881904510252074f), + new Angle(0.083333333333333329f, 0.86602540378443871f, 0.5f), + new Angle(0.125f, 0.70710678118654757f, 0.70710678118654746f), + new Angle(0.16666666666666667f, 0.5f, 0.8660254037844386f), + new Angle(0.20833333333333331f, 0.25881904510252096f, 0.9659258262890682f), + new Angle(0.25f, 0.0f, 1.0f), + new Angle(0.29166666666666663f, -0.25881904510252063f, 0.96592582628906831f), + new Angle(0.33333333333333333f, -0.5f, 0.86602540378443871f), + new Angle(0.375f, -0.70710678118654746f, 0.70710678118654757f), + new Angle(0.41666666666666663f, -0.86602540378443849f, 0.5f), + new Angle(0.45833333333333331f, -0.9659258262890682f, 0.25881904510252102f), + new Angle(0.5f, -1.0f, 0.0f), + new Angle(0.54166666666666663f, -0.96592582628906842f, -0.25881904510252035f), + new Angle(0.58333333333333326f, -0.86602540378443882f, -0.5f), + new Angle(0.62499999999999989f, -0.70710678118654791f, -0.70710678118654713f), + new Angle(0.66666666666666667f, -0.5f, -0.86602540378443837f), + new Angle(0.70833333333333326f, -0.25881904510252152f, -0.96592582628906809f), + new Angle(0.75f, 0.0f, -1.0f), + new Angle(0.79166666666666663f, 0.2588190451025203f, -0.96592582628906842f), + new Angle(0.83333333333333326f, 0.5f, -0.86602540378443904f), + new Angle(0.875f, 0.70710678118654735f, -0.70710678118654768f), + new Angle(0.91666666666666663f, 0.86602540378443837f, -0.5f), + new Angle(0.95833333333333326f, 0.96592582628906809f, -0.25881904510252157f), + new Angle(1.0f, 1.0f, 0.0f) + }; + + private Angle interpolatePoints(float newPoint, Angle p1, Angle p2) + { + float m = (newPoint - p1.angle) / (p2.angle - p1.angle); + return new Angle(newPoint, p1.X + m * (p2.X - p1.X), p1.Y + m * (p2.Y - p1.Y)); + } + + private void intersection(double x1, double y1, double x2, double y2, double x3, double y3, double x4, double y4) + { // ref: http://local.wasp.uwa.edu.au/~pbourke/geometry/lineline2d/ + double denom = (y4 - y3) * (x2 - x1) - (x4 - x3) * (y2 - y1); + double uaNumerator = (x4 - x3) * (y1 - y3) - (y4 - y3) * (x1 - x3); + + if (denom != 0.0) + { + double ua = uaNumerator / denom; + iX = (float)(x1 + ua * (x2 - x1)); + iY = (float)(y1 + ua * (y2 - y1)); + } + } + + internal List angles; + internal List normals; + + internal void makeAngles(int sides, float startAngle, float stopAngle) + { + angles = new List(); + normals = new List(); + + double twoPi = System.Math.PI * 2.0; + float twoPiInv = 1.0f / (float)twoPi; + + if (sides < 1) + throw new Exception("number of sides not greater than zero"); + if (stopAngle <= startAngle) + throw new Exception("stopAngle not greater than startAngle"); + + if ((sides == 3 || sides == 4 || sides == 24)) + { + startAngle *= twoPiInv; + stopAngle *= twoPiInv; + + Angle[] sourceAngles; + if (sides == 3) + sourceAngles = angles3; + else if (sides == 4) + sourceAngles = angles4; + else sourceAngles = angles24; + + int startAngleIndex = (int)(startAngle * sides); + int endAngleIndex = sourceAngles.Length - 1; + if (stopAngle < 1.0f) + endAngleIndex = (int)(stopAngle * sides) + 1; + if (endAngleIndex == startAngleIndex) + endAngleIndex++; + + for (int angleIndex = startAngleIndex; angleIndex < endAngleIndex + 1; angleIndex++) + { + angles.Add(sourceAngles[angleIndex]); + if (sides == 3) + normals.Add(normals3[angleIndex]); + else if (sides == 4) + normals.Add(normals4[angleIndex]); + } + + if (startAngle > 0.0f) + angles[0] = interpolatePoints(startAngle, angles[0], angles[1]); + + if (stopAngle < 1.0f) + { + int lastAngleIndex = angles.Count - 1; + angles[lastAngleIndex] = interpolatePoints(stopAngle, angles[lastAngleIndex - 1], angles[lastAngleIndex]); + } + } + else + { + double stepSize = twoPi / sides; + + int startStep = (int)(startAngle / stepSize); + double angle = stepSize * startStep; + int step = startStep; + double stopAngleTest = stopAngle; + if (stopAngle < twoPi) + { + stopAngleTest = stepSize * ((int)(stopAngle / stepSize) + 1); + if (stopAngleTest < stopAngle) + stopAngleTest += stepSize; + if (stopAngleTest > twoPi) + stopAngleTest = twoPi; + } + + while (angle <= stopAngleTest) + { + Angle newAngle; + newAngle.angle = (float)angle; + newAngle.X = (float)System.Math.Cos(angle); + newAngle.Y = (float)System.Math.Sin(angle); + angles.Add(newAngle); + step += 1; + angle = stepSize * step; + } + + if (startAngle > angles[0].angle) + { + Angle newAngle; + intersection(angles[0].X, angles[0].Y, angles[1].X, angles[1].Y, 0.0f, 0.0f, (float)Math.Cos(startAngle), (float)Math.Sin(startAngle)); + newAngle.angle = startAngle; + newAngle.X = iX; + newAngle.Y = iY; + angles[0] = newAngle; + } + + int index = angles.Count - 1; + if (stopAngle < angles[index].angle) + { + Angle newAngle; + intersection(angles[index - 1].X, angles[index - 1].Y, angles[index].X, angles[index].Y, 0.0f, 0.0f, (float)Math.Cos(stopAngle), (float)Math.Sin(stopAngle)); + newAngle.angle = stopAngle; + newAngle.X = iX; + newAngle.Y = iY; + angles[index] = newAngle; + } + } + } + } + + /// + /// generates a profile for extrusion + /// + internal class Profile + { + private const float twoPi = 2.0f * (float)Math.PI; + + internal string errorMessage = null; + + internal List coords; + internal List faces; + internal List vertexNormals; + internal List us; + internal List faceUVs; + internal List faceNumbers; + + // use these for making individual meshes for each prim face + internal List outerCoordIndices = null; + internal List hollowCoordIndices = null; + internal List cut1CoordIndices = null; + internal List cut2CoordIndices = null; + + internal Coord faceNormal = new Coord(0.0f, 0.0f, 1.0f); + internal Coord cutNormal1 = new Coord(); + internal Coord cutNormal2 = new Coord(); + + internal int numOuterVerts = 0; + internal int numHollowVerts = 0; + + internal int outerFaceNumber = -1; + internal int hollowFaceNumber = -1; + + internal bool calcVertexNormals = false; + internal int bottomFaceNumber = 0; + internal int numPrimFaces = 0; + + internal Profile() + { + this.coords = new List(); + this.faces = new List(); + this.vertexNormals = new List(); + this.us = new List(); + this.faceUVs = new List(); + this.faceNumbers = new List(); + } + + internal Profile(int sides, float profileStart, float profileEnd, float hollow, int hollowSides, bool createFaces, bool calcVertexNormals) + { + this.calcVertexNormals = calcVertexNormals; + this.coords = new List(); + this.faces = new List(); + this.vertexNormals = new List(); + this.us = new List(); + this.faceUVs = new List(); + this.faceNumbers = new List(); + + Coord center = new Coord(0.0f, 0.0f, 0.0f); + //bool hasCenter = false; + + List hollowCoords = new List(); + List hollowNormals = new List(); + List hollowUs = new List(); + + if (calcVertexNormals) + { + this.outerCoordIndices = new List(); + this.hollowCoordIndices = new List(); + this.cut1CoordIndices = new List(); + this.cut2CoordIndices = new List(); + } + + bool hasHollow = (hollow > 0.0f); + + bool hasProfileCut = (profileStart > 0.0f || profileEnd < 1.0f); + + AngleList angles = new AngleList(); + AngleList hollowAngles = new AngleList(); + + float xScale = 0.5f; + float yScale = 0.5f; + if (sides == 4) // corners of a square are sqrt(2) from center + { + xScale = 0.707f; + yScale = 0.707f; + } + + float startAngle = profileStart * twoPi; + float stopAngle = profileEnd * twoPi; + + try { angles.makeAngles(sides, startAngle, stopAngle); } + catch (Exception ex) + { + + errorMessage = "makeAngles failed: Exception: " + ex.ToString() + + "\nsides: " + sides.ToString() + " startAngle: " + startAngle.ToString() + " stopAngle: " + stopAngle.ToString(); + + return; + } + + this.numOuterVerts = angles.angles.Count; + + // flag to create as few triangles as possible for 3 or 4 side profile + bool simpleFace = (sides < 5 && !hasHollow && !hasProfileCut); + + if (hasHollow) + { + if (sides == hollowSides) + hollowAngles = angles; + else + { + try { hollowAngles.makeAngles(hollowSides, startAngle, stopAngle); } + catch (Exception ex) + { + errorMessage = "makeAngles failed: Exception: " + ex.ToString() + + "\nsides: " + sides.ToString() + " startAngle: " + startAngle.ToString() + " stopAngle: " + stopAngle.ToString(); + + return; + } + } + this.numHollowVerts = hollowAngles.angles.Count; + } + else if (!simpleFace) + { + this.coords.Add(center); + //hasCenter = true; + if (this.calcVertexNormals) + this.vertexNormals.Add(new Coord(0.0f, 0.0f, 1.0f)); + this.us.Add(0.0f); + } + + float z = 0.0f; + + Angle angle; + Coord newVert = new Coord(); + if (hasHollow && hollowSides != sides) + { + int numHollowAngles = hollowAngles.angles.Count; + for (int i = 0; i < numHollowAngles; i++) + { + angle = hollowAngles.angles[i]; + newVert.X = hollow * xScale * angle.X; + newVert.Y = hollow * yScale * angle.Y; + newVert.Z = z; + + hollowCoords.Add(newVert); + if (this.calcVertexNormals) + { + if (hollowSides < 5) + hollowNormals.Add(hollowAngles.normals[i].Invert()); + else + hollowNormals.Add(new Coord(-angle.X, -angle.Y, 0.0f)); + + hollowUs.Add(angle.angle * hollow); + } + } + } + + int index = 0; + int numAngles = angles.angles.Count; + + for (int i = 0; i < numAngles; i++) + { + angle = angles.angles[i]; + newVert.X = angle.X * xScale; + newVert.Y = angle.Y * yScale; + newVert.Z = z; + this.coords.Add(newVert); + if (this.calcVertexNormals) + { + this.outerCoordIndices.Add(this.coords.Count - 1); + + if (sides < 5) + { + this.vertexNormals.Add(angles.normals[i]); + float u = angle.angle; + this.us.Add(u); + } + else + { + this.vertexNormals.Add(new Coord(angle.X, angle.Y, 0.0f)); + this.us.Add(angle.angle); + } + } + + if (hasHollow) + { + if (hollowSides == sides) + { + newVert.X *= hollow; + newVert.Y *= hollow; + newVert.Z = z; + hollowCoords.Add(newVert); + if (this.calcVertexNormals) + { + if (sides < 5) + { + hollowNormals.Add(angles.normals[i].Invert()); + } + + else + hollowNormals.Add(new Coord(-angle.X, -angle.Y, 0.0f)); + + hollowUs.Add(angle.angle * hollow); + } + } + } + else if (!simpleFace && createFaces && angle.angle > 0.0001f) + { + Face newFace = new Face(); + newFace.v1 = 0; + newFace.v2 = index; + newFace.v3 = index + 1; + + this.faces.Add(newFace); + } + index += 1; + } + + if (hasHollow) + { + hollowCoords.Reverse(); + if (this.calcVertexNormals) + { + hollowNormals.Reverse(); + hollowUs.Reverse(); + } + + if (createFaces) + { + //int numOuterVerts = this.coords.Count; + //numOuterVerts = this.coords.Count; + //int numHollowVerts = hollowCoords.Count; + int numTotalVerts = this.numOuterVerts + this.numHollowVerts; + + if (this.numOuterVerts == this.numHollowVerts) + { + Face newFace = new Face(); + + for (int coordIndex = 0; coordIndex < this.numOuterVerts - 1; coordIndex++) + { + newFace.v1 = coordIndex; + newFace.v2 = coordIndex + 1; + newFace.v3 = numTotalVerts - coordIndex - 1; + this.faces.Add(newFace); + + newFace.v1 = coordIndex + 1; + newFace.v2 = numTotalVerts - coordIndex - 2; + newFace.v3 = numTotalVerts - coordIndex - 1; + this.faces.Add(newFace); + } + } + else + { + if (this.numOuterVerts < this.numHollowVerts) + { + Face newFace = new Face(); + int j = 0; // j is the index for outer vertices + int maxJ = this.numOuterVerts - 1; + for (int i = 0; i < this.numHollowVerts; i++) // i is the index for inner vertices + { + if (j < maxJ) + if (angles.angles[j + 1].angle - hollowAngles.angles[i].angle < hollowAngles.angles[i].angle - angles.angles[j].angle + 0.000001f) + { + newFace.v1 = numTotalVerts - i - 1; + newFace.v2 = j; + newFace.v3 = j + 1; + + this.faces.Add(newFace); + j += 1; + } + + newFace.v1 = j; + newFace.v2 = numTotalVerts - i - 2; + newFace.v3 = numTotalVerts - i - 1; + + this.faces.Add(newFace); + } + } + else // numHollowVerts < numOuterVerts + { + Face newFace = new Face(); + int j = 0; // j is the index for inner vertices + int maxJ = this.numHollowVerts - 1; + for (int i = 0; i < this.numOuterVerts; i++) + { + if (j < maxJ) + if (hollowAngles.angles[j + 1].angle - angles.angles[i].angle < angles.angles[i].angle - hollowAngles.angles[j].angle + 0.000001f) + { + newFace.v1 = i; + newFace.v2 = numTotalVerts - j - 2; + newFace.v3 = numTotalVerts - j - 1; + + this.faces.Add(newFace); + j += 1; + } + + newFace.v1 = numTotalVerts - j - 1; + newFace.v2 = i; + newFace.v3 = i + 1; + + this.faces.Add(newFace); + } + } + } + } + + if (calcVertexNormals) + { + foreach (Coord hc in hollowCoords) + { + this.coords.Add(hc); + hollowCoordIndices.Add(this.coords.Count - 1); + } + } + else + this.coords.AddRange(hollowCoords); + + if (this.calcVertexNormals) + { + this.vertexNormals.AddRange(hollowNormals); + this.us.AddRange(hollowUs); + + } + } + + if (simpleFace && createFaces) + { + if (sides == 3) + this.faces.Add(new Face(0, 1, 2)); + else if (sides == 4) + { + this.faces.Add(new Face(0, 1, 2)); + this.faces.Add(new Face(0, 2, 3)); + } + } + + if (calcVertexNormals && hasProfileCut) + { + int lastOuterVertIndex = this.numOuterVerts - 1; + + if (hasHollow) + { + this.cut1CoordIndices.Add(0); + this.cut1CoordIndices.Add(this.coords.Count - 1); + + this.cut2CoordIndices.Add(lastOuterVertIndex + 1); + this.cut2CoordIndices.Add(lastOuterVertIndex); + + this.cutNormal1.X = this.coords[0].Y - this.coords[this.coords.Count - 1].Y; + this.cutNormal1.Y = -(this.coords[0].X - this.coords[this.coords.Count - 1].X); + + this.cutNormal2.X = this.coords[lastOuterVertIndex + 1].Y - this.coords[lastOuterVertIndex].Y; + this.cutNormal2.Y = -(this.coords[lastOuterVertIndex + 1].X - this.coords[lastOuterVertIndex].X); + } + + else + { + this.cut1CoordIndices.Add(0); + this.cut1CoordIndices.Add(1); + + this.cut2CoordIndices.Add(lastOuterVertIndex); + this.cut2CoordIndices.Add(0); + + this.cutNormal1.X = this.vertexNormals[1].Y; + this.cutNormal1.Y = -this.vertexNormals[1].X; + + this.cutNormal2.X = -this.vertexNormals[this.vertexNormals.Count - 2].Y; + this.cutNormal2.Y = this.vertexNormals[this.vertexNormals.Count - 2].X; + + } + this.cutNormal1.Normalize(); + this.cutNormal2.Normalize(); + } + + this.MakeFaceUVs(); + + hollowCoords = null; + hollowNormals = null; + hollowUs = null; + + if (calcVertexNormals) + { // calculate prim face numbers + + // face number order is top, outer, hollow, bottom, start cut, end cut + // I know it's ugly but so is the whole concept of prim face numbers + + int faceNum = 1; // start with outer faces + this.outerFaceNumber = faceNum; + + int startVert = hasProfileCut && !hasHollow ? 1 : 0; + if (startVert > 0) + this.faceNumbers.Add(-1); + for (int i = 0; i < this.numOuterVerts - 1; i++) + //this.faceNumbers.Add(sides < 5 ? faceNum++ : faceNum); + this.faceNumbers.Add(sides < 5 && i < sides ? faceNum++ : faceNum); + + //if (!hasHollow && !hasProfileCut) + // this.bottomFaceNumber = faceNum++; + + this.faceNumbers.Add(hasProfileCut ? -1 : faceNum++); + + if (sides > 4 && (hasHollow || hasProfileCut)) + faceNum++; + + if (sides < 5 && (hasHollow || hasProfileCut) && this.numOuterVerts < sides) + faceNum++; + + if (hasHollow) + { + for (int i = 0; i < this.numHollowVerts; i++) + this.faceNumbers.Add(faceNum); + + this.hollowFaceNumber = faceNum++; + } + //if (hasProfileCut || hasHollow) + // this.bottomFaceNumber = faceNum++; + this.bottomFaceNumber = faceNum++; + + if (hasHollow && hasProfileCut) + this.faceNumbers.Add(faceNum++); + + for (int i = 0; i < this.faceNumbers.Count; i++) + if (this.faceNumbers[i] == -1) + this.faceNumbers[i] = faceNum++; + + this.numPrimFaces = faceNum; + } + + } + + internal void MakeFaceUVs() + { + this.faceUVs = new List(); + foreach (Coord c in this.coords) + this.faceUVs.Add(new UVCoord(0.5f + c.X, 0.5f - c.Y)); + } + + internal Profile Copy() + { + return this.Copy(true); + } + + internal Profile Copy(bool needFaces) + { + Profile copy = new Profile(); + + copy.coords.AddRange(this.coords); + copy.faceUVs.AddRange(this.faceUVs); + + if (needFaces) + copy.faces.AddRange(this.faces); + if ((copy.calcVertexNormals = this.calcVertexNormals) == true) + { + copy.vertexNormals.AddRange(this.vertexNormals); + copy.faceNormal = this.faceNormal; + copy.cutNormal1 = this.cutNormal1; + copy.cutNormal2 = this.cutNormal2; + copy.us.AddRange(this.us); + copy.faceNumbers.AddRange(this.faceNumbers); + + copy.cut1CoordIndices = new List(this.cut1CoordIndices); + copy.cut2CoordIndices = new List(this.cut2CoordIndices); + copy.hollowCoordIndices = new List(this.hollowCoordIndices); + copy.outerCoordIndices = new List(this.outerCoordIndices); + } + copy.numOuterVerts = this.numOuterVerts; + copy.numHollowVerts = this.numHollowVerts; + + return copy; + } + + internal void AddPos(Coord v) + { + this.AddPos(v.X, v.Y, v.Z); + } + + internal void AddPos(float x, float y, float z) + { + int i; + int numVerts = this.coords.Count; + Coord vert; + + for (i = 0; i < numVerts; i++) + { + vert = this.coords[i]; + vert.X += x; + vert.Y += y; + vert.Z += z; + this.coords[i] = vert; + } + } + + internal void AddRot(Quat q) + { + int i; + int numVerts = this.coords.Count; + + for (i = 0; i < numVerts; i++) + this.coords[i] *= q; + + if (this.calcVertexNormals) + { + int numNormals = this.vertexNormals.Count; + for (i = 0; i < numNormals; i++) + this.vertexNormals[i] *= q; + + this.faceNormal *= q; + this.cutNormal1 *= q; + this.cutNormal2 *= q; + + } + } + + internal void Scale(float x, float y) + { + int i; + int numVerts = this.coords.Count; + Coord vert; + + for (i = 0; i < numVerts; i++) + { + vert = this.coords[i]; + vert.X *= x; + vert.Y *= y; + this.coords[i] = vert; + } + } + + /// + /// Changes order of the vertex indices and negates the center vertex normal. Does not alter vertex normals of radial vertices + /// + internal void FlipNormals() + { + int i; + int numFaces = this.faces.Count; + Face tmpFace; + int tmp; + + for (i = 0; i < numFaces; i++) + { + tmpFace = this.faces[i]; + tmp = tmpFace.v3; + tmpFace.v3 = tmpFace.v1; + tmpFace.v1 = tmp; + this.faces[i] = tmpFace; + } + + if (this.calcVertexNormals) + { + int normalCount = this.vertexNormals.Count; + if (normalCount > 0) + { + Coord n = this.vertexNormals[normalCount - 1]; + n.Z = -n.Z; + this.vertexNormals[normalCount - 1] = n; + } + } + + this.faceNormal.X = -this.faceNormal.X; + this.faceNormal.Y = -this.faceNormal.Y; + this.faceNormal.Z = -this.faceNormal.Z; + + int numfaceUVs = this.faceUVs.Count; + for (i = 0; i < numfaceUVs; i++) + { + UVCoord uv = this.faceUVs[i]; + uv.V = 1.0f - uv.V; + this.faceUVs[i] = uv; + } + } + + internal void AddValue2FaceVertexIndices(int num) + { + int numFaces = this.faces.Count; + Face tmpFace; + for (int i = 0; i < numFaces; i++) + { + tmpFace = this.faces[i]; + tmpFace.v1 += num; + tmpFace.v2 += num; + tmpFace.v3 += num; + + this.faces[i] = tmpFace; + } + } + + internal void AddValue2FaceNormalIndices(int num) + { + if (this.calcVertexNormals) + { + int numFaces = this.faces.Count; + Face tmpFace; + for (int i = 0; i < numFaces; i++) + { + tmpFace = this.faces[i]; + tmpFace.n1 += num; + tmpFace.n2 += num; + tmpFace.n3 += num; + + this.faces[i] = tmpFace; + } + } + } + + internal void DumpRaw(String path, String name, String title) + { + if (path == null) + return; + String fileName = name + "_" + title + ".raw"; + String completePath = System.IO.Path.Combine(path, fileName); + StreamWriter sw = new StreamWriter(completePath); + + for (int i = 0; i < this.faces.Count; i++) + { + string s = this.coords[this.faces[i].v1].ToString(); + s += " " + this.coords[this.faces[i].v2].ToString(); + s += " " + this.coords[this.faces[i].v3].ToString(); + + sw.WriteLine(s); + } + + sw.Close(); + } + } + + public struct PathNode + { + public Coord position; + public Quat rotation; + public float xScale; + public float yScale; + public float percentOfPath; + } + + public enum PathType { Linear = 0, Circular = 1, Flexible = 2 } + + public class Path + { + public List pathNodes = new List(); + + public float twistBegin = 0.0f; + public float twistEnd = 0.0f; + public float topShearX = 0.0f; + public float topShearY = 0.0f; + public float pathCutBegin = 0.0f; + public float pathCutEnd = 1.0f; + public float dimpleBegin = 0.0f; + public float dimpleEnd = 1.0f; + public float skew = 0.0f; + public float holeSizeX = 1.0f; // called pathScaleX in pbs + public float holeSizeY = 0.25f; + public float taperX = 0.0f; + public float taperY = 0.0f; + public float radius = 0.0f; + public float revolutions = 1.0f; + public int stepsPerRevolution = 24; + + private const float twoPi = 2.0f * (float)Math.PI; + + public void Create(PathType pathType, int steps) + { + if (pathType == PathType.Linear || pathType == PathType.Flexible) + { + int step = 0; + + float length = this.pathCutEnd - this.pathCutBegin; + float twistTotal = twistEnd - twistBegin; + float twistTotalAbs = Math.Abs(twistTotal); + if (twistTotalAbs > 0.01f) + steps += (int)(twistTotalAbs * 3.66); // dahlia's magic number + + float start = -0.5f; + float stepSize = length / (float)steps; + float percentOfPathMultiplier = stepSize; + float xOffset = 0.0f; + float yOffset = 0.0f; + float zOffset = start; + float xOffsetStepIncrement = this.topShearX / steps; + float yOffsetStepIncrement = this.topShearY / steps; + + float percentOfPath = this.pathCutBegin; + zOffset += percentOfPath; + + // sanity checks + + bool done = false; + + while (!done) + { + PathNode newNode = new PathNode(); + + newNode.xScale = 1.0f; + if (this.taperX == 0.0f) + newNode.xScale = 1.0f; + else if (this.taperX > 0.0f) + newNode.xScale = 1.0f - percentOfPath * this.taperX; + else newNode.xScale = 1.0f + (1.0f - percentOfPath) * this.taperX; + + newNode.yScale = 1.0f; + if (this.taperY == 0.0f) + newNode.yScale = 1.0f; + else if (this.taperY > 0.0f) + newNode.yScale = 1.0f - percentOfPath * this.taperY; + else newNode.yScale = 1.0f + (1.0f - percentOfPath) * this.taperY; + + float twist = twistBegin + twistTotal * percentOfPath; + + newNode.rotation = new Quat(new Coord(0.0f, 0.0f, 1.0f), twist); + newNode.position = new Coord(xOffset, yOffset, zOffset); + newNode.percentOfPath = percentOfPath; + + pathNodes.Add(newNode); + + if (step < steps) + { + step += 1; + percentOfPath += percentOfPathMultiplier; + xOffset += xOffsetStepIncrement; + yOffset += yOffsetStepIncrement; + zOffset += stepSize; + if (percentOfPath > this.pathCutEnd) + done = true; + } + else done = true; + } + } // end of linear path code + + else // pathType == Circular + { + float twistTotal = twistEnd - twistBegin; + + // if the profile has a lot of twist, add more layers otherwise the layers may overlap + // and the resulting mesh may be quite inaccurate. This method is arbitrary and doesn't + // accurately match the viewer + float twistTotalAbs = Math.Abs(twistTotal); + if (twistTotalAbs > 0.01f) + { + if (twistTotalAbs > Math.PI * 1.5f) + steps *= 2; + if (twistTotalAbs > Math.PI * 3.0f) + steps *= 2; + } + + float yPathScale = this.holeSizeY * 0.5f; + float pathLength = this.pathCutEnd - this.pathCutBegin; + float totalSkew = this.skew * 2.0f * pathLength; + float skewStart = this.pathCutBegin * 2.0f * this.skew - this.skew; + float xOffsetTopShearXFactor = this.topShearX * (0.25f + 0.5f * (0.5f - this.holeSizeY)); + float yShearCompensation = 1.0f + Math.Abs(this.topShearY) * 0.25f; + + // It's not quite clear what pushY (Y top shear) does, but subtracting it from the start and end + // angles appears to approximate it's effects on path cut. Likewise, adding it to the angle used + // to calculate the sine for generating the path radius appears to approximate it's effects there + // too, but there are some subtle differences in the radius which are noticeable as the prim size + // increases and it may affect megaprims quite a bit. The effect of the Y top shear parameter on + // the meshes generated with this technique appear nearly identical in shape to the same prims when + // displayed by the viewer. + + float startAngle = (twoPi * this.pathCutBegin * this.revolutions) - this.topShearY * 0.9f; + float endAngle = (twoPi * this.pathCutEnd * this.revolutions) - this.topShearY * 0.9f; + float stepSize = twoPi / this.stepsPerRevolution; + + int step = (int)(startAngle / stepSize); + float angle = startAngle; + + bool done = false; + while (!done) // loop through the length of the path and add the layers + { + PathNode newNode = new PathNode(); + + float xProfileScale = (1.0f - Math.Abs(this.skew)) * this.holeSizeX; + float yProfileScale = this.holeSizeY; + + float percentOfPath = angle / (twoPi * this.revolutions); + float percentOfAngles = (angle - startAngle) / (endAngle - startAngle); + + if (this.taperX > 0.01f) + xProfileScale *= 1.0f - percentOfPath * this.taperX; + else if (this.taperX < -0.01f) + xProfileScale *= 1.0f + (1.0f - percentOfPath) * this.taperX; + + if (this.taperY > 0.01f) + yProfileScale *= 1.0f - percentOfPath * this.taperY; + else if (this.taperY < -0.01f) + yProfileScale *= 1.0f + (1.0f - percentOfPath) * this.taperY; + + newNode.xScale = xProfileScale; + newNode.yScale = yProfileScale; + + float radiusScale = 1.0f; + if (this.radius > 0.001f) + radiusScale = 1.0f - this.radius * percentOfPath; + else if (this.radius < 0.001f) + radiusScale = 1.0f + this.radius * (1.0f - percentOfPath); + + float twist = twistBegin + twistTotal * percentOfPath; + + float xOffset = 0.5f * (skewStart + totalSkew * percentOfAngles); + xOffset += (float)Math.Sin(angle) * xOffsetTopShearXFactor; + + float yOffset = yShearCompensation * (float)Math.Cos(angle) * (0.5f - yPathScale) * radiusScale; + + float zOffset = (float)Math.Sin(angle + this.topShearY) * (0.5f - yPathScale) * radiusScale; + + newNode.position = new Coord(xOffset, yOffset, zOffset); + + // now orient the rotation of the profile layer relative to it's position on the path + // adding taperY to the angle used to generate the quat appears to approximate the viewer + + newNode.rotation = new Quat(new Coord(1.0f, 0.0f, 0.0f), angle + this.topShearY); + + // next apply twist rotation to the profile layer + if (twistTotal != 0.0f || twistBegin != 0.0f) + newNode.rotation *= new Quat(new Coord(0.0f, 0.0f, 1.0f), twist); + + newNode.percentOfPath = percentOfPath; + + pathNodes.Add(newNode); + + // calculate terms for next iteration + // calculate the angle for the next iteration of the loop + + if (angle >= endAngle - 0.01) + done = true; + else + { + step += 1; + angle = stepSize * step; + if (angle > endAngle) + angle = endAngle; + } + } + } + } + } + + public class PrimMesh + { + public string errorMessage = ""; + private const float twoPi = 2.0f * (float)Math.PI; + + public List coords; + public List normals; + public List faces; + + public List viewerFaces; + + private int sides = 4; + private int hollowSides = 4; + private float profileStart = 0.0f; + private float profileEnd = 1.0f; + private float hollow = 0.0f; + public int twistBegin = 0; + public int twistEnd = 0; + public float topShearX = 0.0f; + public float topShearY = 0.0f; + public float pathCutBegin = 0.0f; + public float pathCutEnd = 1.0f; + public float dimpleBegin = 0.0f; + public float dimpleEnd = 1.0f; + public float skew = 0.0f; + public float holeSizeX = 1.0f; // called pathScaleX in pbs + public float holeSizeY = 0.25f; + public float taperX = 0.0f; + public float taperY = 0.0f; + public float radius = 0.0f; + public float revolutions = 1.0f; + public int stepsPerRevolution = 24; + + private int profileOuterFaceNumber = -1; + private int profileHollowFaceNumber = -1; + + private bool hasProfileCut = false; + private bool hasHollow = false; + public bool calcVertexNormals = false; + private bool normalsProcessed = false; + public bool viewerMode = false; + public bool sphereMode = false; + + public int numPrimFaces = 0; + + /// + /// Human readable string representation of the parameters used to create a mesh. + /// + /// + public string ParamsToDisplayString() + { + string s = ""; + s += "sides..................: " + this.sides.ToString(); + s += "\nhollowSides..........: " + this.hollowSides.ToString(); + s += "\nprofileStart.........: " + this.profileStart.ToString(); + s += "\nprofileEnd...........: " + this.profileEnd.ToString(); + s += "\nhollow...............: " + this.hollow.ToString(); + s += "\ntwistBegin...........: " + this.twistBegin.ToString(); + s += "\ntwistEnd.............: " + this.twistEnd.ToString(); + s += "\ntopShearX............: " + this.topShearX.ToString(); + s += "\ntopShearY............: " + this.topShearY.ToString(); + s += "\npathCutBegin.........: " + this.pathCutBegin.ToString(); + s += "\npathCutEnd...........: " + this.pathCutEnd.ToString(); + s += "\ndimpleBegin..........: " + this.dimpleBegin.ToString(); + s += "\ndimpleEnd............: " + this.dimpleEnd.ToString(); + s += "\nskew.................: " + this.skew.ToString(); + s += "\nholeSizeX............: " + this.holeSizeX.ToString(); + s += "\nholeSizeY............: " + this.holeSizeY.ToString(); + s += "\ntaperX...............: " + this.taperX.ToString(); + s += "\ntaperY...............: " + this.taperY.ToString(); + s += "\nradius...............: " + this.radius.ToString(); + s += "\nrevolutions..........: " + this.revolutions.ToString(); + s += "\nstepsPerRevolution...: " + this.stepsPerRevolution.ToString(); + s += "\nsphereMode...........: " + this.sphereMode.ToString(); + s += "\nhasProfileCut........: " + this.hasProfileCut.ToString(); + s += "\nhasHollow............: " + this.hasHollow.ToString(); + s += "\nviewerMode...........: " + this.viewerMode.ToString(); + + return s; + } + + public int ProfileOuterFaceNumber + { + get { return profileOuterFaceNumber; } + } + + public int ProfileHollowFaceNumber + { + get { return profileHollowFaceNumber; } + } + + public bool HasProfileCut + { + get { return hasProfileCut; } + } + + public bool HasHollow + { + get { return hasHollow; } + } + + + /// + /// Constructs a PrimMesh object and creates the profile for extrusion. + /// + /// + /// + /// + /// + /// + public PrimMesh(int sides, float profileStart, float profileEnd, float hollow, int hollowSides) + { + this.coords = new List(); + this.faces = new List(); + + this.sides = sides; + this.profileStart = profileStart; + this.profileEnd = profileEnd; + this.hollow = hollow; + this.hollowSides = hollowSides; + + if (sides < 3) + this.sides = 3; + if (hollowSides < 3) + this.hollowSides = 3; + if (profileStart < 0.0f) + this.profileStart = 0.0f; + if (profileEnd > 1.0f) + this.profileEnd = 1.0f; + if (profileEnd < 0.02f) + this.profileEnd = 0.02f; + if (profileStart >= profileEnd) + this.profileStart = profileEnd - 0.02f; + if (hollow > 0.99f) + this.hollow = 0.99f; + if (hollow < 0.0f) + this.hollow = 0.0f; + + //if (sphereMode) + // this.hasProfileCut = this.profileEnd - this.profileStart < 0.4999f; + //else + // //this.hasProfileCut = (this.profileStart > 0.0f || this.profileEnd < 1.0f); + // this.hasProfileCut = this.profileEnd - this.profileStart < 0.9999f; + //this.hasHollow = (this.hollow > 0.001f); + } + + /// + /// Extrudes a profile along a path. + /// + public void Extrude(PathType pathType) + { + bool needEndFaces = false; + + this.coords = new List(); + this.faces = new List(); + + if (this.viewerMode) + { + this.viewerFaces = new List(); + this.calcVertexNormals = true; + } + + if (this.calcVertexNormals) + this.normals = new List(); + + int steps = 1; + + float length = this.pathCutEnd - this.pathCutBegin; + normalsProcessed = false; + + if (this.viewerMode && this.sides == 3) + { + // prisms don't taper well so add some vertical resolution + // other prims may benefit from this but just do prisms for now + if (Math.Abs(this.taperX) > 0.01 || Math.Abs(this.taperY) > 0.01) + steps = (int)(steps * 4.5 * length); + } + + if (sphereMode) + this.hasProfileCut = this.profileEnd - this.profileStart < 0.4999f; + else + //this.hasProfileCut = (this.profileStart > 0.0f || this.profileEnd < 1.0f); + this.hasProfileCut = this.profileEnd - this.profileStart < 0.9999f; + this.hasHollow = (this.hollow > 0.001f); + + float twistBegin = this.twistBegin / 360.0f * twoPi; + float twistEnd = this.twistEnd / 360.0f * twoPi; + float twistTotal = twistEnd - twistBegin; + float twistTotalAbs = Math.Abs(twistTotal); + if (twistTotalAbs > 0.01f) + steps += (int)(twistTotalAbs * 3.66); // dahlia's magic number + + float hollow = this.hollow; + + // sanity checks + float initialProfileRot = 0.0f; + if (pathType == PathType.Circular) + { + if (this.sides == 3) + { + initialProfileRot = (float)Math.PI; + if (this.hollowSides == 4) + { + if (hollow > 0.7f) + hollow = 0.7f; + hollow *= 0.707f; + } + else hollow *= 0.5f; + } + else if (this.sides == 4) + { + initialProfileRot = 0.25f * (float)Math.PI; + if (this.hollowSides != 4) + hollow *= 0.707f; + } + else if (this.sides > 4) + { + initialProfileRot = (float)Math.PI; + if (this.hollowSides == 4) + { + if (hollow > 0.7f) + hollow = 0.7f; + hollow /= 0.7f; + } + } + } + else + { + if (this.sides == 3) + { + if (this.hollowSides == 4) + { + if (hollow > 0.7f) + hollow = 0.7f; + hollow *= 0.707f; + } + else hollow *= 0.5f; + } + else if (this.sides == 4) + { + initialProfileRot = 1.25f * (float)Math.PI; + if (this.hollowSides != 4) + hollow *= 0.707f; + } + else if (this.sides == 24 && this.hollowSides == 4) + hollow *= 1.414f; + } + + Profile profile = new Profile(this.sides, this.profileStart, this.profileEnd, hollow, this.hollowSides, true, calcVertexNormals); + this.errorMessage = profile.errorMessage; + + this.numPrimFaces = profile.numPrimFaces; + + //profileOuterFaceNumber = profile.faceNumbers[0]; + //if (!needEndFaces) + // profileOuterFaceNumber--; + //profileOuterFaceNumber = needEndFaces ? 1 : 0; + + + //if (hasHollow) + //{ + // if (needEndFaces) + // profileHollowFaceNumber = profile.faceNumbers[profile.numOuterVerts + 1]; + // else + // profileHollowFaceNumber = profile.faceNumbers[profile.numOuterVerts] - 1; + //} + + + profileOuterFaceNumber = profile.outerFaceNumber; + if (!needEndFaces) + profileOuterFaceNumber--; + + if (hasHollow) + { + profileHollowFaceNumber = profile.hollowFaceNumber; + if (!needEndFaces) + profileHollowFaceNumber--; + } + + int cut1Vert = -1; + int cut2Vert = -1; + if (hasProfileCut) + { + cut1Vert = hasHollow ? profile.coords.Count - 1 : 0; + cut2Vert = hasHollow ? profile.numOuterVerts - 1 : profile.numOuterVerts; + } + + if (initialProfileRot != 0.0f) + { + profile.AddRot(new Quat(new Coord(0.0f, 0.0f, 1.0f), initialProfileRot)); + if (viewerMode) + profile.MakeFaceUVs(); + } + + Coord lastCutNormal1 = new Coord(); + Coord lastCutNormal2 = new Coord(); + float lastV = 1.0f; + + Path path = new Path(); + path.twistBegin = twistBegin; + path.twistEnd = twistEnd; + path.topShearX = topShearX; + path.topShearY = topShearY; + path.pathCutBegin = pathCutBegin; + path.pathCutEnd = pathCutEnd; + path.dimpleBegin = dimpleBegin; + path.dimpleEnd = dimpleEnd; + path.skew = skew; + path.holeSizeX = holeSizeX; + path.holeSizeY = holeSizeY; + path.taperX = taperX; + path.taperY = taperY; + path.radius = radius; + path.revolutions = revolutions; + path.stepsPerRevolution = stepsPerRevolution; + + path.Create(pathType, steps); + + + if (pathType == PathType.Circular) + { + needEndFaces = false; + if (this.pathCutBegin != 0.0f || this.pathCutEnd != 1.0f) + needEndFaces = true; + else if (this.taperX != 0.0f || this.taperY != 0.0f) + needEndFaces = true; + else if (this.skew != 0.0f) + needEndFaces = true; + else if (twistTotal != 0.0f) + needEndFaces = true; + else if (this.radius != 0.0f) + needEndFaces = true; + } + else needEndFaces = true; + + for (int nodeIndex = 0; nodeIndex < path.pathNodes.Count; nodeIndex++) + { + PathNode node = path.pathNodes[nodeIndex]; + Profile newLayer = profile.Copy(); + newLayer.Scale(node.xScale, node.yScale); + + newLayer.AddRot(node.rotation); + newLayer.AddPos(node.position); + + if (needEndFaces && nodeIndex == 0) + { + newLayer.FlipNormals(); + + // add the top faces to the viewerFaces list here + if (this.viewerMode) + { + Coord faceNormal = newLayer.faceNormal; + ViewerFace newViewerFace = new ViewerFace(profile.bottomFaceNumber); + int numFaces = newLayer.faces.Count; + List faces = newLayer.faces; + + for (int i = 0; i < numFaces; i++) + { + Face face = faces[i]; + newViewerFace.v1 = newLayer.coords[face.v1]; + newViewerFace.v2 = newLayer.coords[face.v2]; + newViewerFace.v3 = newLayer.coords[face.v3]; + + newViewerFace.coordIndex1 = face.v1; + newViewerFace.coordIndex2 = face.v2; + newViewerFace.coordIndex3 = face.v3; + + newViewerFace.n1 = faceNormal; + newViewerFace.n2 = faceNormal; + newViewerFace.n3 = faceNormal; + + newViewerFace.uv1 = newLayer.faceUVs[face.v1]; + newViewerFace.uv2 = newLayer.faceUVs[face.v2]; + newViewerFace.uv3 = newLayer.faceUVs[face.v3]; + + this.viewerFaces.Add(newViewerFace); + } + } + } // if (nodeIndex == 0) + + // append this layer + + int coordsLen = this.coords.Count; + newLayer.AddValue2FaceVertexIndices(coordsLen); + + this.coords.AddRange(newLayer.coords); + + if (this.calcVertexNormals) + { + newLayer.AddValue2FaceNormalIndices(this.normals.Count); + this.normals.AddRange(newLayer.vertexNormals); + } + + if (node.percentOfPath < this.pathCutBegin + 0.01f || node.percentOfPath > this.pathCutEnd - 0.01f) + this.faces.AddRange(newLayer.faces); + + // fill faces between layers + + int numVerts = newLayer.coords.Count; + Face newFace = new Face(); + + if (nodeIndex > 0) + { + int startVert = coordsLen + 1; + int endVert = this.coords.Count; + + if (sides < 5 || this.hasProfileCut || this.hasHollow) + startVert--; + + for (int i = startVert; i < endVert; i++) + { + int iNext = i + 1; + if (i == endVert - 1) + iNext = startVert; + + int whichVert = i - startVert; + + newFace.v1 = i; + newFace.v2 = i - numVerts; + newFace.v3 = iNext - numVerts; + this.faces.Add(newFace); + + newFace.v2 = iNext - numVerts; + newFace.v3 = iNext; + this.faces.Add(newFace); + + if (this.viewerMode) + { + // add the side faces to the list of viewerFaces here + + int primFaceNum = profile.faceNumbers[whichVert]; + if (!needEndFaces) + primFaceNum -= 1; + + ViewerFace newViewerFace1 = new ViewerFace(primFaceNum); + ViewerFace newViewerFace2 = new ViewerFace(primFaceNum); + + float u1 = newLayer.us[whichVert]; + float u2 = 1.0f; + if (whichVert < newLayer.us.Count - 1) + u2 = newLayer.us[whichVert + 1]; + + if (whichVert == cut1Vert || whichVert == cut2Vert) + { + u1 = 0.0f; + u2 = 1.0f; + } + else if (sides < 5) + { + if (whichVert < profile.numOuterVerts) + { // boxes and prisms have one texture face per side of the prim, so the U values have to be scaled + // to reflect the entire texture width + u1 *= sides; + u2 *= sides; + u2 -= (int)u1; + u1 -= (int)u1; + if (u2 < 0.1f) + u2 = 1.0f; + //this.profileOuterFaceNumber = primFaceNum; + } + else if (whichVert > profile.coords.Count - profile.numHollowVerts - 1) + { + u1 *= 2.0f; + u2 *= 2.0f; + //this.profileHollowFaceNumber = primFaceNum; + } + } + + newViewerFace1.uv1.U = u1; + newViewerFace1.uv2.U = u1; + newViewerFace1.uv3.U = u2; + + newViewerFace1.uv1.V = 1.0f - node.percentOfPath; + newViewerFace1.uv2.V = lastV; + newViewerFace1.uv3.V = lastV; + + newViewerFace2.uv1.U = u1; + newViewerFace2.uv2.U = u2; + newViewerFace2.uv3.U = u2; + + newViewerFace2.uv1.V = 1.0f - node.percentOfPath; + newViewerFace2.uv2.V = lastV; + newViewerFace2.uv3.V = 1.0f - node.percentOfPath; + + newViewerFace1.v1 = this.coords[i]; + newViewerFace1.v2 = this.coords[i - numVerts]; + newViewerFace1.v3 = this.coords[iNext - numVerts]; + + newViewerFace2.v1 = this.coords[i]; + newViewerFace2.v2 = this.coords[iNext - numVerts]; + newViewerFace2.v3 = this.coords[iNext]; + + newViewerFace1.coordIndex1 = i; + newViewerFace1.coordIndex2 = i - numVerts; + newViewerFace1.coordIndex3 = iNext - numVerts; + + newViewerFace2.coordIndex1 = i; + newViewerFace2.coordIndex2 = iNext - numVerts; + newViewerFace2.coordIndex3 = iNext; + + // profile cut faces + if (whichVert == cut1Vert) + { + newViewerFace1.n1 = newLayer.cutNormal1; + newViewerFace1.n2 = newViewerFace1.n3 = lastCutNormal1; + + newViewerFace2.n1 = newViewerFace2.n3 = newLayer.cutNormal1; + newViewerFace2.n2 = lastCutNormal1; + } + else if (whichVert == cut2Vert) + { + newViewerFace1.n1 = newLayer.cutNormal2; + newViewerFace1.n2 = newViewerFace1.n3 = lastCutNormal2; + + newViewerFace2.n1 = newViewerFace2.n3 = newLayer.cutNormal2; + newViewerFace2.n2 = lastCutNormal2; + } + + else // outer and hollow faces + { + if ((sides < 5 && whichVert < newLayer.numOuterVerts) || (hollowSides < 5 && whichVert >= newLayer.numOuterVerts)) + { // looks terrible when path is twisted... need vertex normals here + newViewerFace1.CalcSurfaceNormal(); + newViewerFace2.CalcSurfaceNormal(); + } + else + { + newViewerFace1.n1 = this.normals[i]; + newViewerFace1.n2 = this.normals[i - numVerts]; + newViewerFace1.n3 = this.normals[iNext - numVerts]; + + newViewerFace2.n1 = this.normals[i]; + newViewerFace2.n2 = this.normals[iNext - numVerts]; + newViewerFace2.n3 = this.normals[iNext]; + } + } + + this.viewerFaces.Add(newViewerFace1); + this.viewerFaces.Add(newViewerFace2); + + } + } + } + + lastCutNormal1 = newLayer.cutNormal1; + lastCutNormal2 = newLayer.cutNormal2; + lastV = 1.0f - node.percentOfPath; + + if (needEndFaces && nodeIndex == path.pathNodes.Count - 1 && viewerMode) + { + // add the top faces to the viewerFaces list here + Coord faceNormal = newLayer.faceNormal; + ViewerFace newViewerFace = new ViewerFace(); + newViewerFace.primFaceNumber = 0; + int numFaces = newLayer.faces.Count; + List faces = newLayer.faces; + + for (int i = 0; i < numFaces; i++) + { + Face face = faces[i]; + newViewerFace.v1 = newLayer.coords[face.v1 - coordsLen]; + newViewerFace.v2 = newLayer.coords[face.v2 - coordsLen]; + newViewerFace.v3 = newLayer.coords[face.v3 - coordsLen]; + + newViewerFace.coordIndex1 = face.v1 - coordsLen; + newViewerFace.coordIndex2 = face.v2 - coordsLen; + newViewerFace.coordIndex3 = face.v3 - coordsLen; + + newViewerFace.n1 = faceNormal; + newViewerFace.n2 = faceNormal; + newViewerFace.n3 = faceNormal; + + newViewerFace.uv1 = newLayer.faceUVs[face.v1 - coordsLen]; + newViewerFace.uv2 = newLayer.faceUVs[face.v2 - coordsLen]; + newViewerFace.uv3 = newLayer.faceUVs[face.v3 - coordsLen]; + + this.viewerFaces.Add(newViewerFace); + } + } + + + } // for (int nodeIndex = 0; nodeIndex < path.pathNodes.Count; nodeIndex++) + + } + + + /// + /// DEPRICATED - use Extrude(PathType.Linear) instead + /// Extrudes a profile along a straight line path. Used for prim types box, cylinder, and prism. + /// + /// + public void ExtrudeLinear() + { + this.Extrude(PathType.Linear); + } + + + /// + /// DEPRICATED - use Extrude(PathType.Circular) instead + /// Extrude a profile into a circular path prim mesh. Used for prim types torus, tube, and ring. + /// + /// + public void ExtrudeCircular() + { + this.Extrude(PathType.Circular); + } + + + private Coord SurfaceNormal(Coord c1, Coord c2, Coord c3) + { + Coord edge1 = new Coord(c2.X - c1.X, c2.Y - c1.Y, c2.Z - c1.Z); + Coord edge2 = new Coord(c3.X - c1.X, c3.Y - c1.Y, c3.Z - c1.Z); + + Coord normal = Coord.Cross(edge1, edge2); + + normal.Normalize(); + + return normal; + } + + private Coord SurfaceNormal(Face face) + { + return SurfaceNormal(this.coords[face.v1], this.coords[face.v2], this.coords[face.v3]); + } + + /// + /// Calculate the surface normal for a face in the list of faces + /// + /// + /// + public Coord SurfaceNormal(int faceIndex) + { + int numFaces = this.faces.Count; + if (faceIndex < 0 || faceIndex >= numFaces) + throw new Exception("faceIndex out of range"); + + return SurfaceNormal(this.faces[faceIndex]); + } + + /// + /// Duplicates a PrimMesh object. All object properties are copied by value, including lists. + /// + /// + public PrimMesh Copy() + { + PrimMesh copy = new PrimMesh(this.sides, this.profileStart, this.profileEnd, this.hollow, this.hollowSides); + copy.twistBegin = this.twistBegin; + copy.twistEnd = this.twistEnd; + copy.topShearX = this.topShearX; + copy.topShearY = this.topShearY; + copy.pathCutBegin = this.pathCutBegin; + copy.pathCutEnd = this.pathCutEnd; + copy.dimpleBegin = this.dimpleBegin; + copy.dimpleEnd = this.dimpleEnd; + copy.skew = this.skew; + copy.holeSizeX = this.holeSizeX; + copy.holeSizeY = this.holeSizeY; + copy.taperX = this.taperX; + copy.taperY = this.taperY; + copy.radius = this.radius; + copy.revolutions = this.revolutions; + copy.stepsPerRevolution = this.stepsPerRevolution; + copy.calcVertexNormals = this.calcVertexNormals; + copy.normalsProcessed = this.normalsProcessed; + copy.viewerMode = this.viewerMode; + copy.numPrimFaces = this.numPrimFaces; + copy.errorMessage = this.errorMessage; + + copy.coords = new List(this.coords); + copy.faces = new List(this.faces); + copy.viewerFaces = new List(this.viewerFaces); + copy.normals = new List(this.normals); + + return copy; + } + + /// + /// Calculate surface normals for all of the faces in the list of faces in this mesh + /// + public void CalcNormals() + { + if (normalsProcessed) + return; + + normalsProcessed = true; + + int numFaces = faces.Count; + + if (!this.calcVertexNormals) + this.normals = new List(); + + for (int i = 0; i < numFaces; i++) + { + Face face = faces[i]; + + this.normals.Add(SurfaceNormal(i).Normalize()); + + int normIndex = normals.Count - 1; + face.n1 = normIndex; + face.n2 = normIndex; + face.n3 = normIndex; + + this.faces[i] = face; + } + } + + /// + /// Adds a value to each XYZ vertex coordinate in the mesh + /// + /// + /// + /// + public void AddPos(float x, float y, float z) + { + int i; + int numVerts = this.coords.Count; + Coord vert; + + for (i = 0; i < numVerts; i++) + { + vert = this.coords[i]; + vert.X += x; + vert.Y += y; + vert.Z += z; + this.coords[i] = vert; + } + + if (this.viewerFaces != null) + { + int numViewerFaces = this.viewerFaces.Count; + + for (i = 0; i < numViewerFaces; i++) + { + ViewerFace v = this.viewerFaces[i]; + v.AddPos(x, y, z); + this.viewerFaces[i] = v; + } + } + } + + /// + /// Rotates the mesh + /// + /// + public void AddRot(Quat q) + { + int i; + int numVerts = this.coords.Count; + + for (i = 0; i < numVerts; i++) + this.coords[i] *= q; + + if (this.normals != null) + { + int numNormals = this.normals.Count; + for (i = 0; i < numNormals; i++) + this.normals[i] *= q; + } + + if (this.viewerFaces != null) + { + int numViewerFaces = this.viewerFaces.Count; + + for (i = 0; i < numViewerFaces; i++) + { + ViewerFace v = this.viewerFaces[i]; + v.v1 *= q; + v.v2 *= q; + v.v3 *= q; + + v.n1 *= q; + v.n2 *= q; + v.n3 *= q; + this.viewerFaces[i] = v; + } + } + } + +#if VERTEX_INDEXER + public VertexIndexer GetVertexIndexer() + { + if (this.viewerMode && this.viewerFaces.Count > 0) + return new VertexIndexer(this); + return null; + } +#endif + + /// + /// Scales the mesh + /// + /// + /// + /// + public void Scale(float x, float y, float z) + { + int i; + int numVerts = this.coords.Count; + //Coord vert; + + Coord m = new Coord(x, y, z); + for (i = 0; i < numVerts; i++) + this.coords[i] *= m; + + if (this.viewerFaces != null) + { + int numViewerFaces = this.viewerFaces.Count; + for (i = 0; i < numViewerFaces; i++) + { + ViewerFace v = this.viewerFaces[i]; + v.v1 *= m; + v.v2 *= m; + v.v3 *= m; + this.viewerFaces[i] = v; + } + + } + + } + + /// + /// Dumps the mesh to a Blender compatible "Raw" format file + /// + /// + /// + /// + public void DumpRaw(String path, String name, String title) + { + if (path == null) + return; + String fileName = name + "_" + title + ".raw"; + String completePath = System.IO.Path.Combine(path, fileName); + StreamWriter sw = new StreamWriter(completePath); + + for (int i = 0; i < this.faces.Count; i++) + { + string s = this.coords[this.faces[i].v1].ToString(); + s += " " + this.coords[this.faces[i].v2].ToString(); + s += " " + this.coords[this.faces[i].v3].ToString(); + + sw.WriteLine(s); + } + + sw.Close(); + } + } +} diff --git a/OpenSim/Region/Physics/UbitMeshing/SculptMap.cs b/OpenSim/Region/Physics/UbitMeshing/SculptMap.cs new file mode 100644 index 0000000000..b3d9cb62f3 --- /dev/null +++ b/OpenSim/Region/Physics/UbitMeshing/SculptMap.cs @@ -0,0 +1,197 @@ +/* + * Copyright (c) Contributors + * See CONTRIBUTORS.TXT for a full list of copyright holders. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name of the OpenSimulator Project nor the + * names of its contributors may be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE DEVELOPERS ``AS IS'' AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE CONTRIBUTORS BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +// to build without references to System.Drawing, comment this out +#define SYSTEM_DRAWING + +using System; +using System.Collections.Generic; +using System.Text; + +#if SYSTEM_DRAWING +using System.Drawing; +using System.Drawing.Imaging; + +namespace PrimMesher +{ + public class SculptMap + { + public int width; + public int height; + public byte[] redBytes; + public byte[] greenBytes; + public byte[] blueBytes; + + public SculptMap() + { + } + + public SculptMap(Bitmap bm, int lod) + { + int bmW = bm.Width; + int bmH = bm.Height; + + if (bmW == 0 || bmH == 0) + throw new Exception("SculptMap: bitmap has no data"); + + int numLodPixels = lod * lod; // (32 * 2)^2 = 64^2 pixels for default sculpt map image + + bool smallMap = bmW * bmH <= numLodPixels; + bool needsScaling = false; + + width = bmW; + height = bmH; + while (width * height > numLodPixels * 4) + { + width >>= 1; + height >>= 1; + needsScaling = true; + } + + try + { + if (needsScaling) + bm = ScaleImage(bm, width, height); + } + + catch (Exception e) + { + throw new Exception("Exception in ScaleImage(): e: " + e.ToString()); + } + + if (width * height > numLodPixels) + { + width >>= 1; + height >>= 1; + } + + int numBytes = (width + 1) * (height + 1); + redBytes = new byte[numBytes]; + greenBytes = new byte[numBytes]; + blueBytes = new byte[numBytes]; + + int byteNdx = 0; + + try + { + for (int y = 0; y <= height; y++) + { + for (int x = 0; x <= width; x++) + { + Color c; + + if (smallMap) + c = bm.GetPixel(x < width ? x : x - 1, + y < height ? y : y - 1); + else + c = bm.GetPixel(x < width ? x * 2 : x * 2 - 1, + y < height ? y * 2 : y * 2 - 1); + + redBytes[byteNdx] = c.R; + greenBytes[byteNdx] = c.G; + blueBytes[byteNdx] = c.B; + + ++byteNdx; + } + } + } + catch (Exception e) + { + throw new Exception("Caught exception processing byte arrays in SculptMap(): e: " + e.ToString()); + } + + width++; + height++; + } + + public List> ToRows(bool mirror) + { + int numRows = height; + int numCols = width; + + List> rows = new List>(numRows); + + float pixScale = 1.0f / 255; + + int rowNdx, colNdx; + int smNdx = 0; + + + for (rowNdx = 0; rowNdx < numRows; rowNdx++) + { + List row = new List(numCols); + for (colNdx = 0; colNdx < numCols; colNdx++) + { + + if (mirror) + row.Add(new Coord(-((float)redBytes[smNdx] * pixScale - 0.5f), ((float)greenBytes[smNdx] * pixScale - 0.5f), (float)blueBytes[smNdx] * pixScale - 0.5f)); + else + row.Add(new Coord((float)redBytes[smNdx] * pixScale - 0.5f, (float)greenBytes[smNdx] * pixScale - 0.5f, (float)blueBytes[smNdx] * pixScale - 0.5f)); + + ++smNdx; + } + rows.Add(row); + } + return rows; + } + + private Bitmap ScaleImage(Bitmap srcImage, int destWidth, int destHeight) + { + + Bitmap scaledImage = new Bitmap(destWidth, destHeight, PixelFormat.Format24bppRgb); + + Color c; + float xscale = srcImage.Width / destWidth; + float yscale = srcImage.Height / destHeight; + + float sy = 0.5f; + for (int y = 0; y < destHeight; y++) + { + float sx = 0.5f; + for (int x = 0; x < destWidth; x++) + { + try + { + c = srcImage.GetPixel((int)(sx), (int)(sy)); + scaledImage.SetPixel(x, y, Color.FromArgb(c.R, c.G, c.B)); + } + catch (IndexOutOfRangeException) + { + } + + sx += xscale; + } + sy += yscale; + } + srcImage.Dispose(); + return scaledImage; + } + + } + + } +#endif diff --git a/OpenSim/Region/Physics/UbitMeshing/SculptMesh.cs b/OpenSim/Region/Physics/UbitMeshing/SculptMesh.cs new file mode 100644 index 0000000000..4a7f3ada5c --- /dev/null +++ b/OpenSim/Region/Physics/UbitMeshing/SculptMesh.cs @@ -0,0 +1,646 @@ +/* + * Copyright (c) Contributors + * See CONTRIBUTORS.TXT for a full list of copyright holders. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name of the OpenSimulator Project nor the + * names of its contributors may be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE DEVELOPERS ``AS IS'' AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE CONTRIBUTORS BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +// to build without references to System.Drawing, comment this out +#define SYSTEM_DRAWING + +using System; +using System.Collections.Generic; +using System.Text; +using System.IO; + +#if SYSTEM_DRAWING +using System.Drawing; +using System.Drawing.Imaging; +#endif + +namespace PrimMesher +{ + + public class SculptMesh + { + public List coords; + public List faces; + + public List viewerFaces; + public List normals; + public List uvs; + + public enum SculptType { sphere = 1, torus = 2, plane = 3, cylinder = 4 }; + +#if SYSTEM_DRAWING + + public SculptMesh SculptMeshFromFile(string fileName, SculptType sculptType, int lod, bool viewerMode) + { + Bitmap bitmap = (Bitmap)Bitmap.FromFile(fileName); + SculptMesh sculptMesh = new SculptMesh(bitmap, sculptType, lod, viewerMode); + bitmap.Dispose(); + return sculptMesh; + } + + + public SculptMesh(string fileName, int sculptType, int lod, int viewerMode, int mirror, int invert) + { + Bitmap bitmap = (Bitmap)Bitmap.FromFile(fileName); + _SculptMesh(bitmap, (SculptType)sculptType, lod, viewerMode != 0, mirror != 0, invert != 0); + bitmap.Dispose(); + } +#endif + + /// + /// ** Experimental ** May disappear from future versions ** not recommeneded for use in applications + /// Construct a sculpt mesh from a 2D array of floats + /// + /// + /// + /// + /// + /// + /// + public SculptMesh(float[,] zMap, float xBegin, float xEnd, float yBegin, float yEnd, bool viewerMode) + { + float xStep, yStep; + float uStep, vStep; + + int numYElements = zMap.GetLength(0); + int numXElements = zMap.GetLength(1); + + try + { + xStep = (xEnd - xBegin) / (float)(numXElements - 1); + yStep = (yEnd - yBegin) / (float)(numYElements - 1); + + uStep = 1.0f / (numXElements - 1); + vStep = 1.0f / (numYElements - 1); + } + catch (DivideByZeroException) + { + return; + } + + coords = new List(); + faces = new List(); + normals = new List(); + uvs = new List(); + + viewerFaces = new List(); + + int p1, p2, p3, p4; + + int x, y; + int xStart = 0, yStart = 0; + + for (y = yStart; y < numYElements; y++) + { + int rowOffset = y * numXElements; + + for (x = xStart; x < numXElements; x++) + { + /* + * p1-----p2 + * | \ f2 | + * | \ | + * | f1 \| + * p3-----p4 + */ + + p4 = rowOffset + x; + p3 = p4 - 1; + + p2 = p4 - numXElements; + p1 = p3 - numXElements; + + Coord c = new Coord(xBegin + x * xStep, yBegin + y * yStep, zMap[y, x]); + this.coords.Add(c); + if (viewerMode) + { + this.normals.Add(new Coord()); + this.uvs.Add(new UVCoord(uStep * x, 1.0f - vStep * y)); + } + + if (y > 0 && x > 0) + { + Face f1, f2; + + if (viewerMode) + { + f1 = new Face(p1, p4, p3, p1, p4, p3); + f1.uv1 = p1; + f1.uv2 = p4; + f1.uv3 = p3; + + f2 = new Face(p1, p2, p4, p1, p2, p4); + f2.uv1 = p1; + f2.uv2 = p2; + f2.uv3 = p4; + } + else + { + f1 = new Face(p1, p4, p3); + f2 = new Face(p1, p2, p4); + } + + this.faces.Add(f1); + this.faces.Add(f2); + } + } + } + + if (viewerMode) + calcVertexNormals(SculptType.plane, numXElements, numYElements); + } + +#if SYSTEM_DRAWING + public SculptMesh(Bitmap sculptBitmap, SculptType sculptType, int lod, bool viewerMode) + { + _SculptMesh(sculptBitmap, sculptType, lod, viewerMode, false, false); + } + + public SculptMesh(Bitmap sculptBitmap, SculptType sculptType, int lod, bool viewerMode, bool mirror, bool invert) + { + _SculptMesh(sculptBitmap, sculptType, lod, viewerMode, mirror, invert); + } +#endif + + public SculptMesh(List> rows, SculptType sculptType, bool viewerMode, bool mirror, bool invert) + { + _SculptMesh(rows, sculptType, viewerMode, mirror, invert); + } + +#if SYSTEM_DRAWING + /// + /// converts a bitmap to a list of lists of coords, while scaling the image. + /// the scaling is done in floating point so as to allow for reduced vertex position + /// quantization as the position will be averaged between pixel values. this routine will + /// likely fail if the bitmap width and height are not powers of 2. + /// + /// + /// + /// + /// + private List> bitmap2Coords(Bitmap bitmap, int scale, bool mirror) + { + int numRows = bitmap.Height / scale; + int numCols = bitmap.Width / scale; + List> rows = new List>(numRows); + + float pixScale = 1.0f / (scale * scale); + pixScale /= 255; + + int imageX, imageY = 0; + + int rowNdx, colNdx; + + for (rowNdx = 0; rowNdx < numRows; rowNdx++) + { + List row = new List(numCols); + for (colNdx = 0; colNdx < numCols; colNdx++) + { + imageX = colNdx * scale; + int imageYStart = rowNdx * scale; + int imageYEnd = imageYStart + scale; + int imageXEnd = imageX + scale; + float rSum = 0.0f; + float gSum = 0.0f; + float bSum = 0.0f; + for (; imageX < imageXEnd; imageX++) + { + for (imageY = imageYStart; imageY < imageYEnd; imageY++) + { + Color c = bitmap.GetPixel(imageX, imageY); + if (c.A != 255) + { + bitmap.SetPixel(imageX, imageY, Color.FromArgb(255, c.R, c.G, c.B)); + c = bitmap.GetPixel(imageX, imageY); + } + rSum += c.R; + gSum += c.G; + bSum += c.B; + } + } + if (mirror) + row.Add(new Coord(-(rSum * pixScale - 0.5f), gSum * pixScale - 0.5f, bSum * pixScale - 0.5f)); + else + row.Add(new Coord(rSum * pixScale - 0.5f, gSum * pixScale - 0.5f, bSum * pixScale - 0.5f)); + + } + rows.Add(row); + } + return rows; + } + + private List> bitmap2CoordsSampled(Bitmap bitmap, int scale, bool mirror) + { + int numRows = bitmap.Height / scale; + int numCols = bitmap.Width / scale; + List> rows = new List>(numRows); + + float pixScale = 1.0f / 256.0f; + + int imageX, imageY = 0; + + int rowNdx, colNdx; + + for (rowNdx = 0; rowNdx <= numRows; rowNdx++) + { + List row = new List(numCols); + imageY = rowNdx * scale; + if (rowNdx == numRows) imageY--; + for (colNdx = 0; colNdx <= numCols; colNdx++) + { + imageX = colNdx * scale; + if (colNdx == numCols) imageX--; + + Color c = bitmap.GetPixel(imageX, imageY); + if (c.A != 255) + { + bitmap.SetPixel(imageX, imageY, Color.FromArgb(255, c.R, c.G, c.B)); + c = bitmap.GetPixel(imageX, imageY); + } + + if (mirror) + row.Add(new Coord(-(c.R * pixScale - 0.5f), c.G * pixScale - 0.5f, c.B * pixScale - 0.5f)); + else + row.Add(new Coord(c.R * pixScale - 0.5f, c.G * pixScale - 0.5f, c.B * pixScale - 0.5f)); + + } + rows.Add(row); + } + return rows; + } + + + void _SculptMesh(Bitmap sculptBitmap, SculptType sculptType, int lod, bool viewerMode, bool mirror, bool invert) + { + _SculptMesh(new SculptMap(sculptBitmap, lod).ToRows(mirror), sculptType, viewerMode, mirror, invert); + } +#endif + + void _SculptMesh(List> rows, SculptType sculptType, bool viewerMode, bool mirror, bool invert) + { + coords = new List(); + faces = new List(); + normals = new List(); + uvs = new List(); + + sculptType = (SculptType)(((int)sculptType) & 0x07); + + if (mirror) + invert = !invert; + + viewerFaces = new List(); + + int width = rows[0].Count; + + int p1, p2, p3, p4; + + int imageX, imageY; + + if (sculptType != SculptType.plane) + { + if (rows.Count % 2 == 0) + { + for (int rowNdx = 0; rowNdx < rows.Count; rowNdx++) + rows[rowNdx].Add(rows[rowNdx][0]); + } + else + { + int lastIndex = rows[0].Count - 1; + + for (int i = 0; i < rows.Count; i++) + rows[i][0] = rows[i][lastIndex]; + } + } + + Coord topPole = rows[0][width / 2]; + Coord bottomPole = rows[rows.Count - 1][width / 2]; + + if (sculptType == SculptType.sphere) + { + if (rows.Count % 2 == 0) + { + int count = rows[0].Count; + List topPoleRow = new List(count); + List bottomPoleRow = new List(count); + + for (int i = 0; i < count; i++) + { + topPoleRow.Add(topPole); + bottomPoleRow.Add(bottomPole); + } + rows.Insert(0, topPoleRow); + rows.Add(bottomPoleRow); + } + else + { + int count = rows[0].Count; + + List topPoleRow = rows[0]; + List bottomPoleRow = rows[rows.Count - 1]; + + for (int i = 0; i < count; i++) + { + topPoleRow[i] = topPole; + bottomPoleRow[i] = bottomPole; + } + } + } + + if (sculptType == SculptType.torus) + rows.Add(rows[0]); + + int coordsDown = rows.Count; + int coordsAcross = rows[0].Count; +// int lastColumn = coordsAcross - 1; + + float widthUnit = 1.0f / (coordsAcross - 1); + float heightUnit = 1.0f / (coordsDown - 1); + + for (imageY = 0; imageY < coordsDown; imageY++) + { + int rowOffset = imageY * coordsAcross; + + for (imageX = 0; imageX < coordsAcross; imageX++) + { + /* + * p1-----p2 + * | \ f2 | + * | \ | + * | f1 \| + * p3-----p4 + */ + + p4 = rowOffset + imageX; + p3 = p4 - 1; + + p2 = p4 - coordsAcross; + p1 = p3 - coordsAcross; + + this.coords.Add(rows[imageY][imageX]); + if (viewerMode) + { + this.normals.Add(new Coord()); + this.uvs.Add(new UVCoord(widthUnit * imageX, heightUnit * imageY)); + } + + if (imageY > 0 && imageX > 0) + { + Face f1, f2; + + if (viewerMode) + { + if (invert) + { + f1 = new Face(p1, p4, p3, p1, p4, p3); + f1.uv1 = p1; + f1.uv2 = p4; + f1.uv3 = p3; + + f2 = new Face(p1, p2, p4, p1, p2, p4); + f2.uv1 = p1; + f2.uv2 = p2; + f2.uv3 = p4; + } + else + { + f1 = new Face(p1, p3, p4, p1, p3, p4); + f1.uv1 = p1; + f1.uv2 = p3; + f1.uv3 = p4; + + f2 = new Face(p1, p4, p2, p1, p4, p2); + f2.uv1 = p1; + f2.uv2 = p4; + f2.uv3 = p2; + } + } + else + { + if (invert) + { + f1 = new Face(p1, p4, p3); + f2 = new Face(p1, p2, p4); + } + else + { + f1 = new Face(p1, p3, p4); + f2 = new Face(p1, p4, p2); + } + } + + this.faces.Add(f1); + this.faces.Add(f2); + } + } + } + + if (viewerMode) + calcVertexNormals(sculptType, coordsAcross, coordsDown); + } + + /// + /// Duplicates a SculptMesh object. All object properties are copied by value, including lists. + /// + /// + public SculptMesh Copy() + { + return new SculptMesh(this); + } + + public SculptMesh(SculptMesh sm) + { + coords = new List(sm.coords); + faces = new List(sm.faces); + viewerFaces = new List(sm.viewerFaces); + normals = new List(sm.normals); + uvs = new List(sm.uvs); + } + + private void calcVertexNormals(SculptType sculptType, int xSize, int ySize) + { // compute vertex normals by summing all the surface normals of all the triangles sharing + // each vertex and then normalizing + int numFaces = this.faces.Count; + for (int i = 0; i < numFaces; i++) + { + Face face = this.faces[i]; + Coord surfaceNormal = face.SurfaceNormal(this.coords); + this.normals[face.n1] += surfaceNormal; + this.normals[face.n2] += surfaceNormal; + this.normals[face.n3] += surfaceNormal; + } + + int numNormals = this.normals.Count; + for (int i = 0; i < numNormals; i++) + this.normals[i] = this.normals[i].Normalize(); + + if (sculptType != SculptType.plane) + { // blend the vertex normals at the cylinder seam + for (int y = 0; y < ySize; y++) + { + int rowOffset = y * xSize; + + this.normals[rowOffset] = this.normals[rowOffset + xSize - 1] = (this.normals[rowOffset] + this.normals[rowOffset + xSize - 1]).Normalize(); + } + } + + foreach (Face face in this.faces) + { + ViewerFace vf = new ViewerFace(0); + vf.v1 = this.coords[face.v1]; + vf.v2 = this.coords[face.v2]; + vf.v3 = this.coords[face.v3]; + + vf.coordIndex1 = face.v1; + vf.coordIndex2 = face.v2; + vf.coordIndex3 = face.v3; + + vf.n1 = this.normals[face.n1]; + vf.n2 = this.normals[face.n2]; + vf.n3 = this.normals[face.n3]; + + vf.uv1 = this.uvs[face.uv1]; + vf.uv2 = this.uvs[face.uv2]; + vf.uv3 = this.uvs[face.uv3]; + + this.viewerFaces.Add(vf); + } + } + + /// + /// Adds a value to each XYZ vertex coordinate in the mesh + /// + /// + /// + /// + public void AddPos(float x, float y, float z) + { + int i; + int numVerts = this.coords.Count; + Coord vert; + + for (i = 0; i < numVerts; i++) + { + vert = this.coords[i]; + vert.X += x; + vert.Y += y; + vert.Z += z; + this.coords[i] = vert; + } + + if (this.viewerFaces != null) + { + int numViewerFaces = this.viewerFaces.Count; + + for (i = 0; i < numViewerFaces; i++) + { + ViewerFace v = this.viewerFaces[i]; + v.AddPos(x, y, z); + this.viewerFaces[i] = v; + } + } + } + + /// + /// Rotates the mesh + /// + /// + public void AddRot(Quat q) + { + int i; + int numVerts = this.coords.Count; + + for (i = 0; i < numVerts; i++) + this.coords[i] *= q; + + int numNormals = this.normals.Count; + for (i = 0; i < numNormals; i++) + this.normals[i] *= q; + + if (this.viewerFaces != null) + { + int numViewerFaces = this.viewerFaces.Count; + + for (i = 0; i < numViewerFaces; i++) + { + ViewerFace v = this.viewerFaces[i]; + v.v1 *= q; + v.v2 *= q; + v.v3 *= q; + + v.n1 *= q; + v.n2 *= q; + v.n3 *= q; + + this.viewerFaces[i] = v; + } + } + } + + public void Scale(float x, float y, float z) + { + int i; + int numVerts = this.coords.Count; + + Coord m = new Coord(x, y, z); + for (i = 0; i < numVerts; i++) + this.coords[i] *= m; + + if (this.viewerFaces != null) + { + int numViewerFaces = this.viewerFaces.Count; + for (i = 0; i < numViewerFaces; i++) + { + ViewerFace v = this.viewerFaces[i]; + v.v1 *= m; + v.v2 *= m; + v.v3 *= m; + this.viewerFaces[i] = v; + } + } + } + + public void DumpRaw(String path, String name, String title) + { + if (path == null) + return; + String fileName = name + "_" + title + ".raw"; + String completePath = System.IO.Path.Combine(path, fileName); + StreamWriter sw = new StreamWriter(completePath); + + for (int i = 0; i < this.faces.Count; i++) + { + string s = this.coords[this.faces[i].v1].ToString(); + s += " " + this.coords[this.faces[i].v2].ToString(); + s += " " + this.coords[this.faces[i].v3].ToString(); + + sw.WriteLine(s); + } + + sw.Close(); + } + } +} diff --git a/prebuild.xml b/prebuild.xml index 64e8e32ed4..d009e87e62 100644 --- a/prebuild.xml +++ b/prebuild.xml @@ -701,6 +701,37 @@ + + + + ../../../../bin/Physics/ + + + + + ../../../../bin/Physics/ + + + + ../../../../bin/ + + + + + + + + + + + + + + + + + +