// // Radegast Metaverse Client // Copyright (c) 2009-2014, Radegast Development Team // All rights reserved. // // 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 application "Radegast", nor the names of its // contributors may be used to endorse or promote products derived from // this software without specific prior CreateReflectionTexture permission. // // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "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 COPYRIGHT HOLDER OR 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. // // $Id: RenderingHelpers.cs 1136 2011-09-05 22:45:11Z latifer $ // using System; using System.Collections.Generic; using System.Collections; using System.Linq; using System.Text; using System.IO; using System.IO.Compression; using System.Xml; using System.Threading; using OpenTK.Graphics.OpenGL; using System.Runtime.InteropServices; using System.Drawing; using System.Drawing.Imaging; using OpenMetaverse; using OpenMetaverse.Rendering; namespace Radegast.Rendering { [StructLayout(LayoutKind.Sequential)] public struct Color4b { public byte R; public byte G; public byte B; public byte A; } [StructLayout(LayoutKind.Explicit)] public struct ColorVertex { [FieldOffset(0)] public Vertex Vertex; [FieldOffset(32)] public Color4b Color; public static int Size = 36; } public class TextureInfo { public System.Drawing.Image Texture; public int TexturePointer; public bool HasAlpha; public bool FullAlpha; public bool IsMask; public bool IsInvisible; public UUID TextureID; public bool FetchFailed; } public class TextureLoadItem { public FaceData Data; public Primitive Prim; public Primitive.TextureEntryFace TeFace; public byte[] TextureData = null; public byte[] TGAData = null; public bool LoadAssetFromCache = false; public OpenMetaverse.ImageType ImageType = OpenMetaverse.ImageType.Normal; public string BakeName = string.Empty; public UUID AvatarID = UUID.Zero; } public enum RenderPass { Picking, Simple, Alpha, Invisible } public enum SceneObjectType { None, Primitive, Avatar, } /// /// Base class for all scene objects /// public abstract class SceneObject : IComparable, IDisposable { #region Public fields /// Interpolated local position of the object public Vector3 InterpolatedPosition; /// Interpolated local rotation of the object/summary> public Quaternion InterpolatedRotation; /// Rendered position of the object in the region public Vector3 RenderPosition; /// Rendered rotationm of the object in the region public Quaternion RenderRotation; /// Per frame calculated square of the distance from camera public float DistanceSquared; /// Bounding volume of the object public BoundingVolume BoundingVolume; /// Was the sim position and distance from camera calculated during this frame public bool PositionCalculated; /// Scene object type public SceneObjectType Type = SceneObjectType.None; /// Libomv primitive public virtual Primitive BasePrim { get; set; } /// Were initial initialization tasks done public bool Initialized; /// Is this object disposed public bool IsDisposed = false; public int AlphaQueryID = -1; public int SimpleQueryID = -1; public bool HasAlphaFaces; public bool HasSimpleFaces; public bool HasInvisibleFaces; #endregion Public fields uint previousParent = uint.MaxValue; /// /// Cleanup resources used /// public virtual void Dispose() { IsDisposed = true; } /// /// Task performed the fist time object is set for rendering /// public virtual void Initialize() { RenderPosition = InterpolatedPosition = BasePrim.Position; RenderRotation = InterpolatedRotation = BasePrim.Rotation; Initialized = true; } /// /// Perform per frame tasks /// /// Time since the last call (last frame time in seconds) public virtual void Step(float time) { if (BasePrim == null) return; // Don't interpolate when parent changes (sit/stand link/unlink) if (previousParent != BasePrim.ParentID) { previousParent = BasePrim.ParentID; InterpolatedPosition = BasePrim.Position; InterpolatedRotation = BasePrim.Rotation; return; } // Linear velocity and acceleration if (BasePrim.Velocity != Vector3.Zero) { BasePrim.Position = InterpolatedPosition = BasePrim.Position + BasePrim.Velocity * time * 0.98f * RadegastInstance.GlobalInstance.Client.Network.CurrentSim.Stats.Dilation; BasePrim.Velocity += BasePrim.Acceleration * time; } else if (InterpolatedPosition != BasePrim.Position) { InterpolatedPosition = RHelp.Smoothed1stOrder(InterpolatedPosition, BasePrim.Position, time); } // Angular velocity (target omega) if (BasePrim.AngularVelocity != Vector3.Zero) { Vector3 angVel = BasePrim.AngularVelocity; float angle = time * angVel.Length(); Quaternion dQ = Quaternion.CreateFromAxisAngle(angVel, angle); InterpolatedRotation = dQ * InterpolatedRotation; } else if (InterpolatedRotation != BasePrim.Rotation && !(this is RenderAvatar)) { InterpolatedRotation = Quaternion.Slerp(InterpolatedRotation, BasePrim.Rotation, time * 10f); if (1f - Math.Abs(Quaternion.Dot(InterpolatedRotation, BasePrim.Rotation)) < 0.0001) InterpolatedRotation = BasePrim.Rotation; } else { InterpolatedRotation = BasePrim.Rotation; } } /// /// Render scene object /// /// Which pass are we currently in /// ID used to identify which object was picked /// Main scene renderer /// Time it took to render the last frame public virtual void Render(RenderPass pass, int pickingID, SceneWindow scene, float time) { } /// /// Implementation of the IComparable interface /// used for sorting by distance /// /// Object we are comparing to /// Result of the comparison public virtual int CompareTo(object other) { SceneObject o = (SceneObject)other; if (this.DistanceSquared < o.DistanceSquared) return -1; else if (this.DistanceSquared > o.DistanceSquared) return 1; else return 0; } #region Occlusion queries public void StartQuery(RenderPass pass) { if (!RenderSettings.OcclusionCullingEnabled) return; if (pass == RenderPass.Simple) { StartSimpleQuery(); } else if (pass == RenderPass.Alpha) { StartAlphaQuery(); } } public void EndQuery(RenderPass pass) { if (!RenderSettings.OcclusionCullingEnabled) return; if (pass == RenderPass.Simple) { EndSimpleQuery(); } else if (pass == RenderPass.Alpha) { EndAlphaQuery(); } } public void StartAlphaQuery() { if (!RenderSettings.OcclusionCullingEnabled) return; if (AlphaQueryID == -1) { Compat.GenQueries(out AlphaQueryID); } if (AlphaQueryID > 0) { Compat.BeginQuery(QueryTarget.SamplesPassed, AlphaQueryID); } } public void EndAlphaQuery() { if (!RenderSettings.OcclusionCullingEnabled) return; if (AlphaQueryID > 0) { Compat.EndQuery(QueryTarget.SamplesPassed); } } public void StartSimpleQuery() { if (!RenderSettings.OcclusionCullingEnabled) return; if (SimpleQueryID == -1) { Compat.GenQueries(out SimpleQueryID); } if (SimpleQueryID > 0) { Compat.BeginQuery(QueryTarget.SamplesPassed, SimpleQueryID); } } public void EndSimpleQuery() { if (!RenderSettings.OcclusionCullingEnabled) return; if (SimpleQueryID > 0) { Compat.EndQuery(QueryTarget.SamplesPassed); } } public bool Occluded() { if (!RenderSettings.OcclusionCullingEnabled) return false; if (HasInvisibleFaces) return false; if ((SimpleQueryID == -1 && AlphaQueryID == -1)) { return false; } if ((!HasAlphaFaces && !HasSimpleFaces)) return true; int samples = 1; if (HasSimpleFaces && SimpleQueryID > 0) { Compat.GetQueryObject(SimpleQueryID, GetQueryObjectParam.QueryResult, out samples); } if (HasSimpleFaces && samples > 0) { return false; } samples = 1; if (HasAlphaFaces && AlphaQueryID > 0) { Compat.GetQueryObject(AlphaQueryID, GetQueryObjectParam.QueryResult, out samples); } if (HasAlphaFaces && samples > 0) { return false; } return true; } #endregion Occlusion queries } public static class RHelp { public static readonly Vector3 InvalidPosition = new Vector3(99999f, 99999f, 99999f); static float t1 = 0.075f; static float t2 = t1 / 5.7f; public static Vector3 Smoothed1stOrder(Vector3 curPos, Vector3 targetPos, float lastFrameTime) { int numIterations = (int)(lastFrameTime * 100); do { curPos += (targetPos - curPos) * t1; numIterations--; } while (numIterations > 0); if (Vector3.DistanceSquared(curPos, targetPos) < 0.00001f) { curPos = targetPos; } return curPos; } public static Vector3 Smoothed2ndOrder(Vector3 curPos, Vector3 targetPos, ref Vector3 accel, float lastFrameTime) { int numIterations = (int)(lastFrameTime * 100); do { accel += (targetPos - accel - curPos) * t1; curPos += accel * t2; numIterations--; } while (numIterations > 0); if (Vector3.DistanceSquared(curPos, targetPos) < 0.00001f) { curPos = targetPos; } return curPos; } public static OpenTK.Vector2 TKVector3(Vector2 v) { return new OpenTK.Vector2(v.X, v.Y); } public static OpenTK.Vector3 TKVector3(Vector3 v) { return new OpenTK.Vector3(v.X, v.Y, v.Z); } public static OpenTK.Vector4 TKVector3(Vector4 v) { return new OpenTK.Vector4(v.X, v.Y, v.Z, v.W); } public static Vector2 OMVVector2(OpenTK.Vector2 v) { return new Vector2(v.X, v.Y); } public static Vector3 OMVVector3(OpenTK.Vector3 v) { return new Vector3(v.X, v.Y, v.Z); } public static Vector4 OMVVector4(OpenTK.Vector4 v) { return new Vector4(v.X, v.Y, v.Z, v.W); } public static Color WinColor(OpenTK.Graphics.Color4 color) { return Color.FromArgb((int)(color.A * 255), (int)(color.R * 255), (int)(color.G * 255), (int)(color.B * 255)); } public static Color WinColor(Color4 color) { return Color.FromArgb((int)(color.A * 255), (int)(color.R * 255), (int)(color.G * 255), (int)(color.B * 255)); } public static int NextPow2(int start) { int pow = 1; while (pow < start) pow *= 2; return pow; } #region Cached image save and load public static readonly string RAD_IMG_MAGIC = "radegast_img"; public static bool LoadCachedImage(UUID textureID, out byte[] tgaData, out bool hasAlpha, out bool fullAlpha, out bool isMask) { tgaData = null; hasAlpha = fullAlpha = isMask = false; try { string fname = RadegastInstance.GlobalInstance.ComputeCacheName(RadegastInstance.GlobalInstance.Client.Settings.ASSET_CACHE_DIR, textureID) + ".rzi"; using (var f = File.Open(fname, FileMode.Open, FileAccess.Read, FileShare.Read)) { byte[] header = new byte[36]; int i = 0; f.Read(header, 0, header.Length); // check if the file is starting with magic string if (RAD_IMG_MAGIC != Utils.BytesToString(header, 0, RAD_IMG_MAGIC.Length)) return false; i += RAD_IMG_MAGIC.Length; if (header[i++] != 1) // check version return false; hasAlpha = header[i++] == 1; fullAlpha = header[i++] == 1; isMask = header[i++] == 1; int uncompressedSize = Utils.BytesToInt(header, i); i += 4; textureID = new UUID(header, i); i += 16; tgaData = new byte[uncompressedSize]; using (var compressed = new DeflateStream(f, CompressionMode.Decompress)) { int read = 0; while ((read = compressed.Read(tgaData, read, uncompressedSize - read)) > 0) ; } } return true; } catch (FileNotFoundException) { } catch (Exception ex) { Logger.DebugLog(string.Format("Failed to load radegast cache file {0}: {1}", textureID, ex.Message)); } return false; } public static bool SaveCachedImage(byte[] tgaData, UUID textureID, bool hasAlpha, bool fullAlpha, bool isMask) { try { string fname = RadegastInstance.GlobalInstance.ComputeCacheName(RadegastInstance.GlobalInstance.Client.Settings.ASSET_CACHE_DIR, textureID) + ".rzi"; using (var f = File.Open(fname, FileMode.Create, FileAccess.Write, FileShare.None)) { int i = 0; // magic header f.Write(Utils.StringToBytes(RAD_IMG_MAGIC), 0, RAD_IMG_MAGIC.Length); i += RAD_IMG_MAGIC.Length; // version f.WriteByte((byte)1); i++; // texture info f.WriteByte(hasAlpha ? (byte)1 : (byte)0); f.WriteByte(fullAlpha ? (byte)1 : (byte)0); f.WriteByte(isMask ? (byte)1 : (byte)0); i += 3; // texture size byte[] uncompressedSize = Utils.IntToBytes(tgaData.Length); f.Write(uncompressedSize, 0, uncompressedSize.Length); i += uncompressedSize.Length; // texture id byte[] id = new byte[16]; textureID.ToBytes(id, 0); f.Write(id, 0, 16); i += 16; // compressed texture data using (var compressed = new DeflateStream(f, CompressionMode.Compress)) { compressed.Write(tgaData, 0, tgaData.Length); } } return true; } catch (Exception ex) { Logger.DebugLog(string.Format("Failed to save radegast cache file {0}: {1}", textureID, ex.Message)); return false; } } #endregion Cached image save and load #region Static vertices and indices for a cube (used for bounding box drawing) /********************************************** 5 --- 4 /| /| 1 --- 0 | | 6 --| 7 |/ |/ 2 --- 3 ***********************************************/ public static readonly float[] CubeVertices = new float[] { 0.5f, 0.5f, 0.5f, // 0 -0.5f, 0.5f, 0.5f, // 1 -0.5f, -0.5f, 0.5f, // 2 0.5f, -0.5f, 0.5f, // 3 0.5f, 0.5f, -0.5f, // 4 -0.5f, 0.5f, -0.5f, // 5 -0.5f, -0.5f, -0.5f, // 6 0.5f, -0.5f, -0.5f // 7 }; public static readonly ushort[] CubeIndices = new ushort[] { 0, 1, 2, 3, // Front Face 4, 5, 6, 7, // Back Face 1, 2, 6, 5, // Left Face 0, 3, 7, 4, // Right Face 0, 1, 5, 4, // Top Face 2, 3, 7, 6 // Bottom Face }; #endregion Static vertices and indices for a cube (used for bounding box drawing) public static int GLLoadImage(Bitmap bitmap, bool hasAlpha) { return GLLoadImage(bitmap, hasAlpha, true); } public static int GLLoadImage(Bitmap bitmap, bool hasAlpha, bool useMipmap) { useMipmap = useMipmap && RenderSettings.HasMipmap; int ret = -1; GL.GenTextures(1, out ret); GL.BindTexture(TextureTarget.Texture2D, ret); Rectangle rectangle = new Rectangle(0, 0, bitmap.Width, bitmap.Height); BitmapData bitmapData = bitmap.LockBits( rectangle, ImageLockMode.ReadOnly, hasAlpha ? System.Drawing.Imaging.PixelFormat.Format32bppArgb : System.Drawing.Imaging.PixelFormat.Format24bppRgb); GL.TexImage2D( TextureTarget.Texture2D, 0, hasAlpha ? PixelInternalFormat.Rgba : PixelInternalFormat.Rgb8, bitmap.Width, bitmap.Height, 0, hasAlpha ? OpenTK.Graphics.OpenGL.PixelFormat.Bgra : OpenTK.Graphics.OpenGL.PixelFormat.Bgr, PixelType.UnsignedByte, bitmapData.Scan0); GL.TexParameter(TextureTarget.Texture2D, TextureParameterName.TextureMagFilter, (int)TextureMagFilter.Linear); GL.TexParameter(TextureTarget.Texture2D, TextureParameterName.TextureWrapS, (int)TextureWrapMode.Repeat); GL.TexParameter(TextureTarget.Texture2D, TextureParameterName.TextureWrapT, (int)TextureWrapMode.Repeat); if (useMipmap) { GL.TexParameter(TextureTarget.Texture2D, TextureParameterName.TextureMinFilter, (int)TextureMinFilter.LinearMipmapLinear); GL.TexParameter(TextureTarget.Texture2D, TextureParameterName.GenerateMipmap, 1); GL.GenerateMipmap(GenerateMipmapTarget.Texture2D); } else { GL.TexParameter(TextureTarget.Texture2D, TextureParameterName.TextureMinFilter, (int)TextureMinFilter.Linear); } bitmap.UnlockBits(bitmapData); return ret; } public static void Draw2DBox(float x, float y, float width, float height, float depth) { GL.Begin(BeginMode.Quads); { GL.TexCoord2(0, 1); GL.Vertex3(x, y, depth); GL.TexCoord2(1, 1); GL.Vertex3(x + width, y, depth); GL.TexCoord2(1, 0); GL.Vertex3(x + width, y + height, depth); GL.TexCoord2(0, 0); GL.Vertex3(x, y + height, depth); } GL.End(); } public static void ResetMaterial() { GL.Material(MaterialFace.FrontAndBack, MaterialParameter.Ambient, new float[] { 0.2f, 0.2f, 0.2f, 1.0f }); GL.Material(MaterialFace.FrontAndBack, MaterialParameter.Diffuse, new float[] { 0.8f, 0.8f, 0.8f, 1.0f }); GL.Material(MaterialFace.FrontAndBack, MaterialParameter.Specular, new float[] { 0f, 0f, 0f, 1.0f }); GL.Material(MaterialFace.FrontAndBack, MaterialParameter.Emission, new float[] { 0f, 0f, 0f, 1.0f }); GL.Material(MaterialFace.FrontAndBack, MaterialParameter.Shininess, 0f); ShaderProgram.Stop(); } } /// /// Represents camera object /// public class Camera { /// /// Indicates that there was manual camera movement, stop tracking objects /// public bool Manual; Vector3 mPosition; Vector3 mFocalPoint; bool mModified; /// Camera position public Vector3 Position { get { return mPosition; } set { if (mPosition != value) { mPosition = value; Modify(); } } } /// Camera target public Vector3 FocalPoint { get { return mFocalPoint; } set { if (mFocalPoint != value) { mFocalPoint = value; Modify(); } } } /// Zoom level public float Zoom; /// Draw distance public float Far; /// Has camera been modified public bool Modified { get { return mModified; } set { mModified = value; } } public float TimeToTarget = 0f; public Vector3 RenderPosition; public Vector3 RenderFocalPoint; void Modify() { mModified = true; } public void Step(float time) { if (RenderPosition != Position) { RenderPosition = RHelp.Smoothed1stOrder(RenderPosition, Position, time); Modified = true; } if (RenderFocalPoint != FocalPoint) { RenderFocalPoint = RHelp.Smoothed1stOrder(RenderFocalPoint, FocalPoint, time); Modified = true; } } #if OBSOLETE_CODE [Obsolete("Use Step(), left in here for reference")] public void Step2(float time) { TimeToTarget -= time; if (TimeToTarget <= time) { EndMove(); return; } mModified = true; float pctElapsed = time / TimeToTarget; if (RenderPosition != Position) { float distance = Vector3.Distance(RenderPosition, Position); RenderPosition = Vector3.Lerp(RenderPosition, Position, distance * pctElapsed); } if (RenderFocalPoint != FocalPoint) { RenderFocalPoint = Interpolate(RenderFocalPoint, FocalPoint, pctElapsed); } } Vector3 Interpolate(Vector3 start, Vector3 end, float fraction) { float distance = Vector3.Distance(start, end); Vector3 direction = end - start; return start + direction * fraction; } public void EndMove() { mModified = true; TimeToTarget = 0; RenderPosition = Position; RenderFocalPoint = FocalPoint; } #endif public void Pan(float deltaX, float deltaY) { Manual = true; Vector3 direction = Position - FocalPoint; direction.Normalize(); Vector3 vy = direction % Vector3.UnitZ; Vector3 vx = vy % direction; Vector3 vxy = vx * deltaY + vy * deltaX; Position += vxy; FocalPoint += vxy; } public void Rotate(float delta, bool horizontal) { Manual = true; Vector3 direction = Position - FocalPoint; if (horizontal) { Position = FocalPoint + direction * new Quaternion(0f, 0f, (float)Math.Sin(delta), (float)Math.Cos(delta)); } else { Position = FocalPoint + direction * Quaternion.CreateFromAxisAngle(direction % Vector3.UnitZ, delta); } } public void MoveToTarget(float delta) { Manual = true; Position += (Position - FocalPoint) * delta; } /// /// Sets the world in perspective of the camera /// public void LookAt() { OpenTK.Matrix4 lookAt = OpenTK.Matrix4.LookAt( RenderPosition.X, RenderPosition.Y, RenderPosition.Z, RenderFocalPoint.X, RenderFocalPoint.Y, RenderFocalPoint.Z, 0f, 0f, 1f); GL.MultMatrix(ref lookAt); } } public static class MeshToOBJ { public static bool MeshesToOBJ(Dictionary meshes, string filename) { StringBuilder obj = new StringBuilder(); StringBuilder mtl = new StringBuilder(); FileInfo objFileInfo = new FileInfo(filename); string mtlFilename = objFileInfo.FullName.Substring(objFileInfo.DirectoryName.Length + 1, objFileInfo.FullName.Length - (objFileInfo.DirectoryName.Length + 1) - 4) + ".mtl"; obj.AppendLine("# Created by libprimrender"); obj.AppendLine("mtllib ./" + mtlFilename); obj.AppendLine(); mtl.AppendLine("# Created by libprimrender"); mtl.AppendLine(); int primNr = 0; foreach (FacetedMesh mesh in meshes.Values) { for (int j = 0; j < mesh.Faces.Count; j++) { Face face = mesh.Faces[j]; if (face.Vertices.Count > 2) { string mtlName = String.Format("material{0}-{1}", primNr, face.ID); Primitive.TextureEntryFace tex = face.TextureFace; string texName = tex.TextureID.ToString() + ".tga"; // FIXME: Convert the source to TGA (if needed) and copy to the destination float shiny = 0.00f; switch (tex.Shiny) { case Shininess.High: shiny = 1.00f; break; case Shininess.Medium: shiny = 0.66f; break; case Shininess.Low: shiny = 0.33f; break; } obj.AppendFormat("g face{0}-{1}{2}", primNr, face.ID, Environment.NewLine); mtl.AppendLine("newmtl " + mtlName); mtl.AppendFormat("Ka {0} {1} {2}{3}", tex.RGBA.R, tex.RGBA.G, tex.RGBA.B, Environment.NewLine); mtl.AppendFormat("Kd {0} {1} {2}{3}", tex.RGBA.R, tex.RGBA.G, tex.RGBA.B, Environment.NewLine); //mtl.AppendFormat("Ks {0} {1} {2}{3}"); mtl.AppendLine("Tr " + tex.RGBA.A); mtl.AppendLine("Ns " + shiny); mtl.AppendLine("illum 1"); if (tex.TextureID != UUID.Zero && tex.TextureID != Primitive.TextureEntry.WHITE_TEXTURE) mtl.AppendLine("map_Kd ./" + texName); mtl.AppendLine(); // Write the vertices, texture coordinates, and vertex normals for this side for (int k = 0; k < face.Vertices.Count; k++) { Vertex vertex = face.Vertices[k]; #region Vertex Vector3 pos = vertex.Position; // Apply scaling pos *= mesh.Prim.Scale; // Apply rotation pos *= mesh.Prim.Rotation; // The root prim position is sim-relative, while child prim positions are // parent-relative. We want to apply parent-relative translations but not // sim-relative ones if (mesh.Prim.ParentID != 0) pos += mesh.Prim.Position; obj.AppendFormat("v {0} {1} {2}{3}", pos.X, pos.Y, pos.Z, Environment.NewLine); #endregion Vertex #region Texture Coord obj.AppendFormat("vt {0} {1}{2}", vertex.TexCoord.X, vertex.TexCoord.Y, Environment.NewLine); #endregion Texture Coord #region Vertex Normal // HACK: Sometimes normals are getting set to if (!Single.IsNaN(vertex.Normal.X) && !Single.IsNaN(vertex.Normal.Y) && !Single.IsNaN(vertex.Normal.Z)) obj.AppendFormat("vn {0} {1} {2}{3}", vertex.Normal.X, vertex.Normal.Y, vertex.Normal.Z, Environment.NewLine); else obj.AppendLine("vn 0.0 1.0 0.0"); #endregion Vertex Normal } obj.AppendFormat("# {0} vertices{1}", face.Vertices.Count, Environment.NewLine); obj.AppendLine(); obj.AppendLine("usemtl " + mtlName); #region Elements // Write all of the faces (triangles) for this side for (int k = 0; k < face.Indices.Count / 3; k++) { obj.AppendFormat("f -{0}/-{0}/-{0} -{1}/-{1}/-{1} -{2}/-{2}/-{2}{3}", face.Vertices.Count - face.Indices[k * 3 + 0], face.Vertices.Count - face.Indices[k * 3 + 1], face.Vertices.Count - face.Indices[k * 3 + 2], Environment.NewLine); } obj.AppendFormat("# {0} elements{1}", face.Indices.Count / 3, Environment.NewLine); obj.AppendLine(); #endregion Elements } } primNr++; } try { File.WriteAllText(filename, obj.ToString()); File.WriteAllText(mtlFilename, mtl.ToString()); } catch (Exception) { return false; } return true; } } public static class Math3D { // Column-major: // | 0 4 8 12 | // | 1 5 9 13 | // | 2 6 10 14 | // | 3 7 11 15 | public static float[] CreateTranslationMatrix(Vector3 v) { float[] mat = new float[16]; mat[12] = v.X; mat[13] = v.Y; mat[14] = v.Z; mat[0] = mat[5] = mat[10] = mat[15] = 1; return mat; } public static float[] CreateRotationMatrix(Quaternion q) { float[] mat = new float[16]; // Transpose the quaternion (don't ask me why) q.X = q.X * -1f; q.Y = q.Y * -1f; q.Z = q.Z * -1f; float x2 = q.X + q.X; float y2 = q.Y + q.Y; float z2 = q.Z + q.Z; float xx = q.X * x2; float xy = q.X * y2; float xz = q.X * z2; float yy = q.Y * y2; float yz = q.Y * z2; float zz = q.Z * z2; float wx = q.W * x2; float wy = q.W * y2; float wz = q.W * z2; mat[0] = 1.0f - (yy + zz); mat[1] = xy - wz; mat[2] = xz + wy; mat[3] = 0.0f; mat[4] = xy + wz; mat[5] = 1.0f - (xx + zz); mat[6] = yz - wx; mat[7] = 0.0f; mat[8] = xz - wy; mat[9] = yz + wx; mat[10] = 1.0f - (xx + yy); mat[11] = 0.0f; mat[12] = 0.0f; mat[13] = 0.0f; mat[14] = 0.0f; mat[15] = 1.0f; return mat; } public static float[] CreateSRTMatrix(Vector3 scale, Quaternion q, Vector3 pos) { float[] mat = new float[16]; // Transpose the quaternion (don't ask me why) q.X = q.X * -1f; q.Y = q.Y * -1f; q.Z = q.Z * -1f; float x2 = q.X + q.X; float y2 = q.Y + q.Y; float z2 = q.Z + q.Z; float xx = q.X * x2; float xy = q.X * y2; float xz = q.X * z2; float yy = q.Y * y2; float yz = q.Y * z2; float zz = q.Z * z2; float wx = q.W * x2; float wy = q.W * y2; float wz = q.W * z2; mat[0] = (1.0f - (yy + zz)) * scale.X; mat[1] = (xy - wz) * scale.X; mat[2] = (xz + wy) * scale.X; mat[3] = 0.0f; mat[4] = (xy + wz) * scale.Y; mat[5] = (1.0f - (xx + zz)) * scale.Y; mat[6] = (yz - wx) * scale.Y; mat[7] = 0.0f; mat[8] = (xz - wy) * scale.Z; mat[9] = (yz + wx) * scale.Z; mat[10] = (1.0f - (xx + yy)) * scale.Z; mat[11] = 0.0f; //Positional parts mat[12] = pos.X; mat[13] = pos.Y; mat[14] = pos.Z; mat[15] = 1.0f; return mat; } public static float[] CreateScaleMatrix(Vector3 v) { float[] mat = new float[16]; mat[0] = v.X; mat[5] = v.Y; mat[10] = v.Z; mat[15] = 1; return mat; } public static float[] Lerp(float[] matrix1, float[] matrix2, float amount) { float[] lerp = new float[16]; //Probably not doing this as a loop is cheaper(unrolling) //also for performance we probably should not create new objects // but meh. for (int x = 0; x < 16; x++) { lerp[x] = matrix1[x] + ((matrix2[x] - matrix1[x]) * amount); } return lerp; } public static bool GluProject(OpenTK.Vector3 objPos, OpenTK.Matrix4 modelMatrix, OpenTK.Matrix4 projMatrix, int[] viewport, out OpenTK.Vector3 screenPos) { OpenTK.Vector4 _in; OpenTK.Vector4 _out; _in.X = objPos.X; _in.Y = objPos.Y; _in.Z = objPos.Z; _in.W = 1.0f; _out = OpenTK.Vector4.Transform(_in, modelMatrix); _in = OpenTK.Vector4.Transform(_out, projMatrix); if (_in.W <= 0.0) { screenPos = OpenTK.Vector3.Zero; return false; } _in.X /= _in.W; _in.Y /= _in.W; _in.Z /= _in.W; /* Map x, y and z to range 0-1 */ _in.X = _in.X * 0.5f + 0.5f; _in.Y = _in.Y * 0.5f + 0.5f; _in.Z = _in.Z * 0.5f + 0.5f; /* Map x,y to viewport */ _in.X = _in.X * viewport[2] + viewport[0]; _in.Y = _in.Y * viewport[3] + viewport[1]; screenPos.X = _in.X; screenPos.Y = _in.Y; screenPos.Z = _in.Z; return true; } public static bool GluUnProject(float winx, float winy, float winz, OpenTK.Matrix4 modelMatrix, OpenTK.Matrix4 projMatrix, int[] viewport, out OpenTK.Vector3 pos) { OpenTK.Matrix4 finalMatrix; OpenTK.Vector4 _in; OpenTK.Vector4 _out; finalMatrix = OpenTK.Matrix4.Mult(modelMatrix, projMatrix); finalMatrix.Invert(); _in.X = winx; _in.Y = winy; _in.Z = winz; _in.W = 1.0f; /* Map x and y from window coordinates */ _in.X = (_in.X - viewport[0]) / viewport[2]; _in.Y = (_in.Y - viewport[1]) / viewport[3]; pos = OpenTK.Vector3.Zero; /* Map to range -1 to 1 */ _in.X = _in.X * 2 - 1; _in.Y = _in.Y * 2 - 1; _in.Z = _in.Z * 2 - 1; //__gluMultMatrixVecd(finalMatrix, _in, _out); // check if this works: _out = OpenTK.Vector4.Transform(_in, finalMatrix); if (_out.W == 0.0f) return false; _out.X /= _out.W; _out.Y /= _out.W; _out.Z /= _out.W; pos.X = _out.X; pos.Y = _out.Y; pos.Z = _out.Z; return true; } public static double[] AbovePlane(double height) { return new double[] { 0, 0, 1, -height }; } public static double[] BelowPlane(double height) { return new double[] { 0, 0, -1, height }; } } /* * Helper classs for reading the static VFS file, call * staticVFS.readVFSheaders() with the path to the static_data.db2 and static_index.db2 files * and it will pass and dump in to openmetaverse_data for you * This should only be needed to be used if LL update the static VFS in order to refresh our data */ class VFSblock { public int mLocation; public int mLength; public int mAccessTime; public UUID mFileID; public int mSize; public AssetType mAssetType; public int readblock(byte[] blockdata, int offset) { BitPack input = new BitPack(blockdata, offset); mLocation = input.UnpackInt(); mLength = input.UnpackInt(); mAccessTime = input.UnpackInt(); mFileID = input.UnpackUUID(); int filetype = input.UnpackShort(); mAssetType = (AssetType)filetype; mSize = input.UnpackInt(); offset += 34; Logger.Log(String.Format("Found header for {0} type {1} length {2} at {3}", mFileID, mAssetType, mSize, mLocation), Helpers.LogLevel.Info); return offset; } } public class staticVFS { public static void readVFSheaders(string datafile, string indexfile) { FileStream datastream; FileStream indexstream; datastream = File.Open(datafile, FileMode.Open); indexstream = File.Open(indexfile, FileMode.Open); int offset = 0; byte[] blockdata = new byte[indexstream.Length]; indexstream.Read(blockdata, 0, (int)indexstream.Length); while (offset < indexstream.Length) { VFSblock block = new VFSblock(); offset = block.readblock(blockdata, offset); FileStream writer = File.Open(OpenMetaverse.Settings.RESOURCE_DIR + System.IO.Path.DirectorySeparatorChar + block.mFileID.ToString(), FileMode.Create); byte[] data = new byte[block.mSize]; datastream.Seek(block.mLocation, SeekOrigin.Begin); datastream.Read(data, 0, block.mSize); writer.Write(data, 0, block.mSize); writer.Close(); } } } }