using System; using System.Collections.Generic; using System.ComponentModel; using System.Drawing; using System.Drawing.Imaging; using System.Windows.Forms; using System.IO; using System.Runtime.InteropServices; using Tao.OpenGl; using Tao.Platform.Windows; using ICSharpCode.SharpZipLib.Zip; using OpenMetaverse; using OpenMetaverse.StructuredData; using OpenMetaverse.Imaging; using OpenMetaverse.Rendering; using OpenMetaverse.Assets; namespace PrimWorkshop { public partial class frmBrowser : Form { const float DEG_TO_RAD = 0.0174532925f; const uint TERRAIN_START = (uint)Int32.MaxValue + 1; ContextMenu ExportPrimMenu; ContextMenu ExportTerrainMenu; GridClient Client; Camera Camera; Dictionary RenderFoliageList = new Dictionary(); Dictionary RenderPrimList = new Dictionary(); Dictionary DownloadList = new Dictionary(); EventHandler IdleEvent; System.Timers.Timer ProgressTimer; int TotalPrims; // Textures Dictionary Textures = new Dictionary(); // Terrain float MaxHeight = 0.1f; TerrainPatch[,] Heightmap; HeightmapLookupValue[] LookupHeightTable; // Picking globals bool Clicked = false; int ClickX = 0; int ClickY = 0; uint LastHit = 0; //warning CS0414: The private field `PrimWorkshop.frmBrowser.PivotPosition' is assigned but its value is never used Vector3 PivotPosition = Vector3.Zero; private bool Pivoting; Point LastPivot; // const int SELECT_BUFSIZE = 512; uint[] SelectBuffer = new uint[SELECT_BUFSIZE]; //warning CS0414: The private field `PrimWorkshop.frmBrowser.msg' is assigned but its value is never used NativeMethods.Message msg; private bool AppStillIdle { get { return !NativeMethods.PeekMessage(out msg, IntPtr.Zero, 0, 0, 0); } } /// /// Default constructor /// public frmBrowser() { InitializeComponent(); // Setup OpenGL glControl.InitializeContexts(); glControl.SwapBuffers(); glControl.MouseWheel += new MouseEventHandler(glControl_MouseWheel); // Login server URLs cboServer.Items.Add(Settings.AGNI_LOGIN_SERVER); cboServer.Items.Add(Settings.ADITI_LOGIN_SERVER); cboServer.Items.Add("http://osgrid.org:8002/"); cboServer.SelectedIndex = 0; // Context menus ExportPrimMenu = new ContextMenu(); ExportPrimMenu.MenuItems.Add("Download", new EventHandler(DownloadMenu_Clicked)); ExportPrimMenu.MenuItems.Add("Download All Objects", new EventHandler(DownloadAllMenu_Clicked)); ExportTerrainMenu = new ContextMenu(); ExportTerrainMenu.MenuItems.Add("Teleport", new EventHandler(TeleportMenu_Clicked)); ExportTerrainMenu.MenuItems.Add("Export Terrain", new EventHandler(ExportTerrainMenu_Clicked)); ExportTerrainMenu.MenuItems.Add("Import Object", new EventHandler(ImportObjectMenu_Clicked)); ExportTerrainMenu.MenuItems.Add("Import Sim", new EventHandler(ImportSimMenu_Clicked)); // Setup a timer for updating the progress bar ProgressTimer = new System.Timers.Timer(250); ProgressTimer.Elapsed += delegate(object sender, System.Timers.ElapsedEventArgs e) { UpdatePrimProgress(); }; ProgressTimer.Start(); IdleEvent = new EventHandler(Application_Idle); Application.Idle += IdleEvent; // Show a flat sim before login so the screen isn't so boring InitHeightmap(); InitOpenGL(); InitCamera(); glControl_Resize(null, null); } private void InitLists() { TotalPrims = 0; lock (Textures) { foreach (TextureInfo tex in Textures.Values) { int id = tex.ID; Gl.glDeleteTextures(1, ref id); } Textures.Clear(); } lock (RenderPrimList) RenderPrimList.Clear(); lock (RenderFoliageList) RenderFoliageList.Clear(); } private void InitializeObjects() { InitLists(); if (DownloadList != null) lock (DownloadList) DownloadList.Clear(); // Initialize the SL client Client = new GridClient(); Client.Settings.MULTIPLE_SIMS = false; Client.Settings.ALWAYS_DECODE_OBJECTS = true; Client.Settings.ALWAYS_REQUEST_OBJECTS = true; Client.Settings.SEND_AGENT_UPDATES = true; Client.Settings.USE_ASSET_CACHE = true; //Client.Settings.ASSET_CACHE_DIR = Application.StartupPath + System.IO.Path.DirectorySeparatorChar + "cache"; Client.Settings.ALWAYS_REQUEST_PARCEL_ACL = false; Client.Settings.ALWAYS_REQUEST_PARCEL_DWELL = false; // Crank up the throttle on texture downloads Client.Throttle.Texture = 446000.0f; // FIXME: Write our own avatar tracker so we don't double store prims Client.Settings.OBJECT_TRACKING = false; // We use our own object tracking system Client.Settings.AVATAR_TRACKING = true; //but we want to use the libsl avatar system Client.Network.LoginProgress += Network_OnLogin; Client.Network.Disconnected += Network_OnDisconnected; Client.Network.SimChanged += Network_OnCurrentSimChanged; Client.Network.EventQueueRunning += Network_OnEventQueueRunning; Client.Objects.ObjectUpdate += Objects_OnNewPrim; Client.Terrain.LandPatchReceived += new EventHandler(Terrain_LandPatchReceived); Client.Parcels.SimParcelsDownloaded += new EventHandler(Parcels_SimParcelsDownloaded); Client.Assets.ImageReceiveProgress += new EventHandler(Assets_ImageReceiveProgress); // Initialize the camera object InitCamera(); // Setup the libsl camera to match our Camera struct UpdateCamera(); glControl_Resize(null, null); /* // Enable lighting Gl.glEnable(Gl.GL_LIGHTING); Gl.glEnable(Gl.GL_LIGHT0); float[] lightPosition = { 128.0f, 64.0f, 96.0f, 0.0f }; Gl.glLightfv(Gl.GL_LIGHT0, Gl.GL_POSITION, lightPosition); // Setup ambient property float[] ambientLight = { 0.2f, 0.2f, 0.2f, 0.0f }; Gl.glLightfv(Gl.GL_LIGHT0, Gl.GL_AMBIENT, ambientLight); // Setup specular property float[] specularLight = { 0.5f, 0.5f, 0.5f, 0.0f }; Gl.glLightfv(Gl.GL_LIGHT0, Gl.GL_SPECULAR, specularLight); */ } void Objects_NewPrim(object sender, PrimEventArgs e) { throw new NotImplementedException(); } void Parcels_SimParcelsDownloaded(object sender, SimParcelsDownloadedEventArgs e) { TotalPrims = 0; e.Parcels.ForEach( delegate(Parcel parcel) { TotalPrims += parcel.TotalPrims; }); UpdatePrimProgress(); TotalPrims = 0; e.Parcels.ForEach( delegate(Parcel parcel) { TotalPrims += parcel.TotalPrims; }); UpdatePrimProgress(); } private void InitOpenGL() { Gl.glShadeModel(Gl.GL_SMOOTH); Gl.glClearDepth(1.0f); Gl.glEnable(Gl.GL_DEPTH_TEST); Gl.glDepthMask(Gl.GL_TRUE); Gl.glDepthFunc(Gl.GL_LEQUAL); Gl.glHint(Gl.GL_PERSPECTIVE_CORRECTION_HINT, Gl.GL_NICEST); } private void InitHeightmap() { // Initialize the heightmap Heightmap = new TerrainPatch[16, 16]; for (int y = 0; y < 16; y++) { for (int x = 0; x < 16; x++) { Heightmap[y, x] = new TerrainPatch(); Heightmap[y, x].Data = new float[16 * 16]; } } // Speed up terrain exports with a lookup table LookupHeightTable = new HeightmapLookupValue[256 * 256]; for (int i = 0; i < 256; i++) { for (int j = 0; j < 256; j++) { LookupHeightTable[i + (j * 256)] = new HeightmapLookupValue((ushort)(i + (j * 256)), ((float)i * ((float)j / 127.0f))); } } Array.Sort(LookupHeightTable); } private void InitCamera() { Camera = new Camera(); Camera.Position = new Vector3(128f, -192f, 90f); Camera.FocalPoint = new Vector3(128f, 128f, 0f); Camera.Zoom = 1.0d; Camera.Far = 512.0d; } private void UpdatePrimProgress() { if (this.InvokeRequired) { BeginInvoke((MethodInvoker)delegate() { UpdatePrimProgress(); }); } else { try { if (RenderPrimList != null && RenderFoliageList != null) { int count = RenderPrimList.Count + RenderFoliageList.Count; lblPrims.Text = String.Format("Prims: {0} / {1}", count, TotalPrims); progPrims.Maximum = (TotalPrims > count) ? TotalPrims : count; progPrims.Value = count; } else { lblPrims.Text = String.Format("Prims: 0 / {0}", TotalPrims); progPrims.Maximum = TotalPrims; progPrims.Value = 0; } } catch (Exception ex) { Console.WriteLine(ex); } } } private void UpdateCamera() { if (Client != null) { Client.Self.Movement.Camera.LookAt(Camera.Position, Camera.FocalPoint); Client.Self.Movement.Camera.Far = (float)Camera.Far; } Gl.glPushMatrix(); Gl.glMatrixMode(Gl.GL_PROJECTION); Gl.glLoadIdentity(); SetPerspective(); Gl.glMatrixMode(Gl.GL_MODELVIEW); Gl.glPopMatrix(); } private bool ExportObject(RenderablePrim parent, string fileName, out int prims, out int textures, out string error) { // Build a list of primitives (parent+children) to export List primList = new List(); primList.Add(parent.Prim); lock (RenderPrimList) { foreach (RenderablePrim render in RenderPrimList.Values) { if (render.Prim.ParentID == parent.Prim.LocalID) primList.Add(render.Prim); } } return ExportObjects(primList, fileName, out prims, out textures, out error); } private bool ExportSim(string fileName, out int prims, out int textures, out string error) { // Add all of the prims in this sim to the export list List primList = new List(); lock (RenderPrimList) { foreach (RenderablePrim render in RenderPrimList.Values) { primList.Add(render.Prim); } } return ExportObjects(primList, fileName, out prims, out textures, out error); } private bool ExportObjects(List primList, string fileName, out int prims, out int textures, out string error) { List textureList = new List(); prims = 0; textures = 0; // Write the LLSD to the hard drive in XML format string output = OSDParser.SerializeLLSDXmlString(Helpers.PrimListToOSD(primList)); try { // Create a temporary directory string tempPath = System.IO.Path.Combine(System.IO.Path.GetTempPath(), System.IO.Path.GetRandomFileName()); Directory.CreateDirectory(tempPath); // Write the prim XML file File.WriteAllText(System.IO.Path.Combine(tempPath, "prims.xml"), output); prims = primList.Count; // Build a list of all the referenced textures in this prim list foreach (Primitive prim in primList) { for (int i = 0; i < prim.Textures.FaceTextures.Length; i++) { Primitive.TextureEntryFace face = prim.Textures.FaceTextures[i]; if (face != null && face.TextureID != UUID.Zero && face.TextureID != Primitive.TextureEntry.WHITE_TEXTURE) { if (!textureList.Contains(face.TextureID)) textureList.Add(face.TextureID); } } } // Copy all of relevant textures from the cache to the temp directory foreach (UUID texture in textureList) { string tempFileName = Client.Assets.Cache.AssetFileName(texture); if (!String.IsNullOrEmpty(tempFileName)) { File.Copy(tempFileName, System.IO.Path.Combine(tempPath, texture.ToString() + ".jp2")); ++textures; } else { Console.WriteLine("Missing texture file during download: " + texture.ToString()); } } // Zip up the directory string[] filenames = Directory.GetFiles(tempPath); using (ZipOutputStream s = new ZipOutputStream(File.Create(fileName))) { s.SetLevel(9); byte[] buffer = new byte[4096]; foreach (string file in filenames) { ZipEntry entry = new ZipEntry(System.IO.Path.GetFileName(file)); entry.DateTime = DateTime.Now; s.PutNextEntry(entry); using (FileStream fs = File.OpenRead(file)) { int sourceBytes; do { sourceBytes = fs.Read(buffer, 0, buffer.Length); s.Write(buffer, 0, sourceBytes); } while (sourceBytes > 0); } } s.Finish(); s.Close(); } error = null; return true; } catch (Exception ex) { error = ex.Message; return false; } } private List ImportObjects(string fileName, out string tempPath, out string error) { tempPath = null; error = null; string primFile = null; try { // Create a temporary directory tempPath = System.IO.Path.Combine(System.IO.Path.GetTempPath(), System.IO.Path.GetRandomFileName()); Directory.CreateDirectory(tempPath); // Unzip the primpackage using (ZipInputStream s = new ZipInputStream(File.OpenRead(fileName))) { ZipEntry theEntry; // Loop through and confirm there is a prims.xml file while ((theEntry = s.GetNextEntry()) != null) { if (String.Equals("prims.xml", theEntry.Name.ToLower())) { primFile = theEntry.Name; break; } } if (primFile != null) { // Prepend the path to the primFile (that will be created in the next loop) primFile = System.IO.Path.Combine(tempPath, primFile); } else { // Didn't find a prims.xml file, bail out error = "No prims.xml file found in the archive"; return null; } // Reset to the beginning of the zip file s.Seek(0, SeekOrigin.Begin); Logger.DebugLog("Unpacking archive to " + tempPath); // Unzip all of the texture and xml files while ((theEntry = s.GetNextEntry()) != null) { string directory = System.IO.Path.GetDirectoryName(theEntry.Name); string file = System.IO.Path.GetFileName(theEntry.Name); // Skip directories if (directory.Length > 0) continue; if (!String.IsNullOrEmpty(file)) { string filelow = file.ToLower(); if (filelow.EndsWith(".jp2") || filelow.EndsWith(".tga") || filelow.EndsWith(".xml")) { Logger.DebugLog("Unpacking " + file); // Create the full path from the temp path and new filename string filePath = System.IO.Path.Combine(tempPath, file); using (FileStream streamWriter = File.Create(filePath)) { const int READ_BUFFER_SIZE = 2048; int size = READ_BUFFER_SIZE; byte[] data = new byte[READ_BUFFER_SIZE]; while (true) { size = s.Read(data, 0, data.Length); if (size > 0) streamWriter.Write(data, 0, size); else break; } } } else { Logger.Log("Skipping file " + file, Helpers.LogLevel.Info); } } } } // Decode the .prims file string raw = File.ReadAllText(primFile); OSD osd = OSDParser.DeserializeLLSDXml(raw); return Helpers.OSDToPrimList(osd); } catch (Exception e) { error = e.Message; return null; } } private void DownloadMenu_Clicked(object sender, EventArgs e) { // Confirm that there actually is a selected object RenderablePrim parent; if (RenderPrimList.TryGetValue(LastHit, out parent)) { if (parent.Prim.ParentID == 0) { // Valid parent prim is selected, throw up the save file dialog SaveFileDialog dialog = new SaveFileDialog(); dialog.Filter = "Prim Package (*.zip)|*.zip"; if (dialog.ShowDialog() == DialogResult.OK) { string error; int prims, textures; if (ExportObject(parent, dialog.FileName, out prims, out textures, out error)) MessageBox.Show(String.Format("Exported {0} prims and {1} textures", prims, textures)); else MessageBox.Show("Export failed: " + error); } } else { // This should have already been fixed in the picking processing code Console.WriteLine("Download menu clicked when a child prim is selected!"); glControl.ContextMenu = null; LastHit = 0; } } else { Console.WriteLine("Download menu clicked when there is no selected prim!"); glControl.ContextMenu = null; LastHit = 0; } } private void DownloadAllMenu_Clicked(object sender, EventArgs e) { SaveFileDialog dialog = new SaveFileDialog(); dialog.Filter = "Prim Package (*.zip)|*.zip"; if (dialog.ShowDialog() == DialogResult.OK) { string error; int prims, textures; if (ExportSim(dialog.FileName, out prims, out textures, out error)) MessageBox.Show(String.Format("Exported {0} prims and {1} textures", prims, textures)); else MessageBox.Show("Export failed: " + error); } } private void ExportTerrainMenu_Clicked(object sender, EventArgs e) { // Valid parent prim is selected, throw up the save file dialog SaveFileDialog dialog = new SaveFileDialog(); dialog.Filter = "Terrain RAW (*.raw)|*.raw"; if (dialog.ShowDialog() == DialogResult.OK) { BackgroundWorker worker = new BackgroundWorker(); worker.DoWork += delegate(object obj, DoWorkEventArgs args) { byte red, green, blue, alpha1, alpha2, alpha3, alpha4, alpha5, alpha6, alpha7, alpha8, alpha9, alpha10; try { FileInfo file = new FileInfo(dialog.FileName); FileStream s = file.Open(FileMode.OpenOrCreate, FileAccess.Write); BinaryWriter binStream = new BinaryWriter(s); for (int y = 0; y < 256; y++) { for (int x = 0; x < 256; x++) { int xBlock = x / 16; int yBlock = y / 16; int xOff = x - (xBlock * 16); int yOff = y - (yBlock * 16); float t = Heightmap[yBlock, xBlock].Data[yOff * 16 + xOff]; //float min = Single.MaxValue; int index = 0; // The lookup table is pre-sorted, so we either find an exact match or // the next closest (smaller) match with a binary search index = Array.BinarySearch(LookupHeightTable, new HeightmapLookupValue(0, t)); if (index < 0) index = ~index - 1; index = LookupHeightTable[index].Index; /*for (int i = 0; i < 65536; i++) { if (Math.Abs(t - LookupHeightTable[i].Value) < min) { min = Math.Abs(t - LookupHeightTable[i].Value); index = i; } }*/ red = (byte)(index & 0xFF); green = (byte)((index >> 8) & 0xFF); blue = 20; alpha1 = 0; // Land Parcels alpha2 = 0; // For Sale Land alpha3 = 0; // Public Edit Object alpha4 = 0; // Public Edit Land alpha5 = 255; // Safe Land alpha6 = 255; // Flying Allowed alpha7 = 255; // Create Landmark alpha8 = 255; // Outside Scripts alpha9 = red; alpha10 = green; binStream.Write(red); binStream.Write(green); binStream.Write(blue); binStream.Write(alpha1); binStream.Write(alpha2); binStream.Write(alpha3); binStream.Write(alpha4); binStream.Write(alpha5); binStream.Write(alpha6); binStream.Write(alpha7); binStream.Write(alpha8); binStream.Write(alpha9); binStream.Write(alpha10); } } binStream.Close(); s.Close(); BeginInvoke((MethodInvoker)delegate() { System.Windows.Forms.MessageBox.Show("Exported heightmap"); }); } catch (Exception ex) { BeginInvoke((MethodInvoker)delegate() { System.Windows.Forms.MessageBox.Show("Error exporting heightmap: " + ex.Message); }); } }; worker.RunWorkerAsync(); } } private void TeleportMenu_Clicked(object sender, EventArgs e) { if (Client != null && Client.Network.CurrentSim != null) { if (LastHit >= TERRAIN_START) { // Determine which piece of terrain was clicked on int y = (int)(LastHit - TERRAIN_START) / 16; int x = (int)(LastHit - (TERRAIN_START + (y * 16))); Vector3 targetPos = new Vector3(x * 16 + 8, y * 16 + 8, 0f); Console.WriteLine("Starting local teleport to " + targetPos.ToString()); Client.Self.RequestTeleport(Client.Network.CurrentSim.Handle, targetPos); } else { // This shouldn't have happened... glControl.ContextMenu = null; } } } private void ImportObjectMenu_Clicked(object sender, EventArgs e) { OpenFileDialog dialog = new OpenFileDialog(); dialog.Filter = "Prim Package (*.zip,*.primpackage)|*.zip;*.primpackage"; if (dialog.ShowDialog() == DialogResult.OK) { // FIXME: Disable any further imports or exports until this is finished // Import the prims string error, texturePath; List primList = ImportObjects(dialog.FileName, out texturePath, out error); if (primList != null) { // Determine the total height of the object float minHeight = Single.MaxValue; float maxHeight = Single.MinValue; //float totalHeight = 0f; for (int i = 0; i < primList.Count; i++) { Primitive prim = primList[i]; // Find the largest scale dimension (quick cheat to avoid figuring in the rotation) float scale = prim.Scale.X; if (prim.Scale.Y > scale) scale = prim.Scale.Y; if (prim.Scale.Z > scale) scale = prim.Scale.Z; float top = prim.Position.Z + (scale * 0.5f); float bottom = top - scale; if (top > maxHeight) maxHeight = top; if (bottom < minHeight) minHeight = bottom; } //totalHeight = maxHeight - minHeight; // Create a progress bar for the import process ProgressBar prog = new ProgressBar(); prog.Minimum = 0; prog.Maximum = primList.Count; prog.Value = 0; // List item GlacialComponents.Controls.GLItem item = new GlacialComponents.Controls.GLItem(); item.SubItems[0].Text = "Import process"; item.SubItems[1].Control = prog; lstDownloads.Items.Add(item); lstDownloads.Invalidate(); // Start the import process in the background BackgroundWorker worker = new BackgroundWorker(); worker.DoWork += delegate(object s, DoWorkEventArgs ea) { // Set the spot choosing state // Wait for a spot to be chosen // mouse2dto3d() // Add (0, 0, totalHeight * 0.5f) to the clicked position for (int i = 0; i < primList.Count; i++) { Primitive prim = primList[i]; for (int j = 0; j < prim.Textures.FaceTextures.Length; j++) { // Check if this texture exists // If not, wait while it uploads } // Create this prim (using weird SL math to get the correct position) // Wait for the callback to fire for this prim being created // Add this prim's localID to a list // Set any additional properties. If this is the root prim, do not apply rotation // Update the progress bar BeginInvoke((MethodInvoker)delegate() { prog.Value = i; }); } // Link all of the prims together // Apply root prim rotation }; worker.RunWorkerCompleted += delegate(object s, RunWorkerCompletedEventArgs ea) { BeginInvoke( (MethodInvoker)delegate() { lstDownloads.Items.Remove(item); lstDownloads.Invalidate(); }); }; worker.RunWorkerAsync(); } else { // FIXME: Re-enable imports and exports MessageBox.Show(error); return; } } } private void ImportSimMenu_Clicked(object sender, EventArgs e) { } private void SetPerspective() { Glu.gluPerspective(50.0d * Camera.Zoom, 1.0d, 0.1d, Camera.Far); } private void StartPicking(int cursorX, int cursorY) { int[] viewport = new int[4]; Gl.glSelectBuffer(SELECT_BUFSIZE, SelectBuffer); Gl.glRenderMode(Gl.GL_SELECT); Gl.glMatrixMode(Gl.GL_PROJECTION); Gl.glPushMatrix(); Gl.glLoadIdentity(); Gl.glGetIntegerv(Gl.GL_VIEWPORT, viewport); Glu.gluPickMatrix(cursorX, viewport[3] - cursorY, 5, 5, viewport); SetPerspective(); Gl.glMatrixMode(Gl.GL_MODELVIEW); Gl.glInitNames(); } private void StopPicking() { int hits; // Resotre the original projection matrix Gl.glMatrixMode(Gl.GL_PROJECTION); Gl.glPopMatrix(); Gl.glMatrixMode(Gl.GL_MODELVIEW); Gl.glFlush(); // Return to normal rendering mode hits = Gl.glRenderMode(Gl.GL_RENDER); // If there are hits process them if (hits != 0) { ProcessHits(hits, SelectBuffer); } else { LastHit = 0; glControl.ContextMenu = null; } } private void ProcessHits(int hits, uint[] selectBuffer) { uint names = 0; uint numNames = 0; uint minZ = 0xffffffff; uint ptr = 0; uint ptrNames = 0; for (uint i = 0; i < hits; i++) { names = selectBuffer[ptr]; ++ptr; if (selectBuffer[ptr] < minZ) { numNames = names; minZ = selectBuffer[ptr]; ptrNames = ptr + 2; } ptr += names + 2; } ptr = ptrNames; for (uint i = 0; i < numNames; i++, ptr++) { LastHit = selectBuffer[ptr]; } if (LastHit >= TERRAIN_START) { // Terrain was clicked on, turn off the context menu glControl.ContextMenu = ExportTerrainMenu; } else { RenderablePrim render; if (RenderPrimList.TryGetValue(LastHit, out render)) { if (render.Prim.ParentID == 0) { Camera.FocalPoint = render.Prim.Position; UpdateCamera(); } else { // See if we have the parent RenderablePrim renderParent; if (RenderPrimList.TryGetValue(render.Prim.ParentID, out renderParent)) { // Turn on the context menu glControl.ContextMenu = ExportPrimMenu; // Change the clicked on prim to the parent. Camera position stays on the // clicked child but the highlighting is applied to all the children LastHit = renderParent.Prim.LocalID; Camera.FocalPoint = renderParent.Prim.Position + render.Prim.Position; UpdateCamera(); } else { Console.WriteLine("Clicked on a child prim with no parent!"); LastHit = 0; } } } } } private void Objects_OnNewPrim(object sender, PrimEventArgs e) { Primitive prim = e.Prim; if (prim.PrimData.PCode == PCode.Grass || prim.PrimData.PCode == PCode.Tree || prim.PrimData.PCode == PCode.NewTree) { lock (RenderFoliageList) RenderFoliageList[prim.LocalID] = prim; return; } RenderablePrim render = new RenderablePrim(); render.Prim = prim; // FIXME: Handle sculpted prims by calling Render.Plugin.GenerateFacetedSculptMesh() instead render.Mesh = Render.Plugin.GenerateFacetedMesh(prim, DetailLevel.High); // Create a FaceData struct for each face that stores the 3D data // in a Tao.OpenGL friendly format for (int j = 0; j < render.Mesh.Faces.Count; j++) { Face face = render.Mesh.Faces[j]; FaceData data = new FaceData(); // Vertices for this face data.Vertices = new float[face.Vertices.Count * 3]; for (int k = 0; k < face.Vertices.Count; k++) { data.Vertices[k * 3 + 0] = face.Vertices[k].Position.X; data.Vertices[k * 3 + 1] = face.Vertices[k].Position.Y; data.Vertices[k * 3 + 2] = face.Vertices[k].Position.Z; } // Indices for this face data.Indices = face.Indices.ToArray(); // Texture transform for this face Primitive.TextureEntryFace teFace = prim.Textures.GetFace((uint)j); Render.Plugin.TransformTexCoords(face.Vertices, face.Center, teFace, prim.Scale); // Texcoords for this face data.TexCoords = new float[face.Vertices.Count * 2]; for (int k = 0; k < face.Vertices.Count; k++) { data.TexCoords[k * 2 + 0] = face.Vertices[k].TexCoord.X; data.TexCoords[k * 2 + 1] = face.Vertices[k].TexCoord.Y; } // Texture for this face if (teFace.TextureID != UUID.Zero && teFace.TextureID != Primitive.TextureEntry.WHITE_TEXTURE) { lock (Textures) { if (!Textures.ContainsKey(teFace.TextureID)) { // We haven't constructed this image in OpenGL yet, get ahold of it Client.Assets.RequestImage(teFace.TextureID, ImageType.Normal, TextureDownloader_OnDownloadFinished); } } } // Set the UserData for this face to our FaceData struct face.UserData = data; render.Mesh.Faces[j] = face; } lock (RenderPrimList) RenderPrimList[prim.LocalID] = render; } private void Terrain_LandPatchReceived(object sender, LandPatchReceivedEventArgs e) { if (Client != null && Client.Network.CurrentSim == e.Simulator) { Heightmap[e.Y, e.X].Data = e.HeightMap; } // Find the new max height for (int i = 0; i < e.HeightMap.Length; i++) { if (e.HeightMap[i] > MaxHeight) MaxHeight = e.HeightMap[i]; } } private void Network_OnLogin(object sender, LoginProgressEventArgs e) { if (e.Status == LoginStatus.Success) { // Success! } else if (e.Status == LoginStatus.Failed) { BeginInvoke( (MethodInvoker)delegate() { MessageBox.Show(this, String.Format("Error logging in ({0}): {1}", Client.Network.LoginErrorKey, Client.Network.LoginMessage)); cmdLogin.Text = "Login"; txtFirst.Enabled = txtLast.Enabled = txtPass.Enabled = true; }); } } private void Network_OnDisconnected(object sender, DisconnectedEventArgs e) { BeginInvoke( (MethodInvoker)delegate() { cmdTeleport.Enabled = false; DoLogout(); }); } private void Network_OnCurrentSimChanged(object sender, SimChangedEventArgs e) { Console.WriteLine("CurrentSim set to " + Client.Network.CurrentSim + ", downloading parcel information"); BeginInvoke((MethodInvoker)delegate() { txtSim.Text = Client.Network.CurrentSim.Name; }); //InitHeightmap(); InitLists(); // Disable teleports until the new event queue comes online if (!Client.Network.CurrentSim.Caps.IsEventQueueRunning) BeginInvoke((MethodInvoker)delegate() { cmdTeleport.Enabled = false; }); } private void Network_OnEventQueueRunning(object sender, EventQueueRunningEventArgs e) { if (e.Simulator == Client.Network.CurrentSim) BeginInvoke((MethodInvoker)delegate() { cmdTeleport.Enabled = true; }); // Now seems like a good time to start requesting parcel information Client.Parcels.RequestAllSimParcels(Client.Network.CurrentSim, false, 100); } private void RenderScene() { try { Gl.glClear(Gl.GL_COLOR_BUFFER_BIT | Gl.GL_DEPTH_BUFFER_BIT); Gl.glLoadIdentity(); Gl.glEnableClientState(Gl.GL_VERTEX_ARRAY); Gl.glEnableClientState(Gl.GL_TEXTURE_COORD_ARRAY); if (Clicked) StartPicking(ClickX, ClickY); // Setup wireframe or solid fill drawing mode Gl.glPolygonMode(Gl.GL_FRONT, Gl.GL_LINE); // Position the camera Glu.gluLookAt( Camera.Position.X, Camera.Position.Y, Camera.Position.Z, Camera.FocalPoint.X, Camera.FocalPoint.Y, Camera.FocalPoint.Z, 0f, 0f, 1f); RenderSkybox(); // Push the world matrix Gl.glPushMatrix(); RenderTerrain(); RenderPrims(); RenderAvatars(); Gl.glDisableClientState(Gl.GL_TEXTURE_COORD_ARRAY); Gl.glDisableClientState(Gl.GL_VERTEX_ARRAY); if (Clicked) { Clicked = false; StopPicking(); } // Pop the world matrix Gl.glPopMatrix(); Gl.glFlush(); glControl.Invalidate(); } catch (Exception) { } } static readonly Vector3[] SkyboxVerts = new Vector3[] { // Right side new Vector3( 10.0f, 10.0f, -10.0f ), //Top left new Vector3( 10.0f, 10.0f, 10.0f ), //Top right new Vector3( 10.0f, -10.0f, 10.0f ), //Bottom right new Vector3( 10.0f, -10.0f, -10.0f ), //Bottom left // Left side new Vector3( -10.0f, 10.0f, 10.0f ), //Top left new Vector3( -10.0f, 10.0f, -10.0f ), //Top right new Vector3( -10.0f, -10.0f, -10.0f ), //Bottom right new Vector3( -10.0f, -10.0f, 10.0f ), //Bottom left // Top side new Vector3( -10.0f, 10.0f, 10.0f ), //Top left new Vector3( 10.0f, 10.0f, 10.0f ), //Top right new Vector3( 10.0f, 10.0f, -10.0f ), //Bottom right new Vector3( -10.0f, 10.0f, -10.0f ), //Bottom left // Bottom side new Vector3( -10.0f, -10.0f, -10.0f ), //Top left new Vector3( 10.0f, -10.0f, -10.0f ), //Top right new Vector3( 10.0f, -10.0f, 10.0f ), //Bottom right new Vector3( -10.0f, -10.0f, 10.0f ), //Bottom left // Front side new Vector3( -10.0f, 10.0f, -10.0f ), //Top left new Vector3( 10.0f, 10.0f, -10.0f ), //Top right new Vector3( 10.0f, -10.0f, -10.0f ), //Bottom right new Vector3( -10.0f, -10.0f, -10.0f ), //Bottom left // Back side new Vector3( 10.0f, 10.0f, 10.0f ), //Top left new Vector3( -10.0f, 10.0f, 10.0f ), //Top right new Vector3( -10.0f, -10.0f, 10.0f ), //Bottom right new Vector3( 10.0f, -10.0f, 10.0f ), //Bottom left }; private void RenderSkybox() { //Gl.glTranslatef(0f, 0f, 0f); } private void RenderTerrain() { if (Heightmap != null) { int i = 0; // No texture Gl.glBindTexture(Gl.GL_TEXTURE_2D, 0); for (int hy = 0; hy < 16; hy++) { for (int hx = 0; hx < 16; hx++) { uint patchName = (uint)(TERRAIN_START + i); Gl.glPushName(patchName); ++i; // Check if this patch is currently selected bool selected = (LastHit == patchName); for (int y = 0; y < 15; y++) { Gl.glBegin(Gl.GL_TRIANGLE_STRIP); for (int x = 0; x < 15; x++) { // Vertex 0 float height = Heightmap[hy, hx].Data[y * 16 + x]; float color = height / MaxHeight; float red = (selected) ? 1f : color; Gl.glColor3f(red, color, color); Gl.glTexCoord2f(0f, 0f); Gl.glVertex3f(hx * 16 + x, hy * 16 + y, height); // Vertex 1 height = Heightmap[hy, hx].Data[y * 16 + (x + 1)]; color = height / MaxHeight; red = (selected) ? 1f : color; Gl.glColor3f(red, color, color); Gl.glTexCoord2f(1f, 0f); Gl.glVertex3f(hx * 16 + x + 1, hy * 16 + y, height); // Vertex 2 height = Heightmap[hy, hx].Data[(y + 1) * 16 + x]; color = height / MaxHeight; red = (selected) ? 1f : color; Gl.glColor3f(red, color, color); Gl.glTexCoord2f(0f, 1f); Gl.glVertex3f(hx * 16 + x, hy * 16 + y + 1, height); // Vertex 3 height = Heightmap[hy, hx].Data[(y + 1) * 16 + (x + 1)]; color = height / MaxHeight; red = (selected) ? 1f : color; Gl.glColor3f(red, color, color); Gl.glTexCoord2f(1f, 1f); Gl.glVertex3f(hx * 16 + x + 1, hy * 16 + y + 1, height); } Gl.glEnd(); } Gl.glPopName(); } } } } //int[] CubeMapDefines = new int[] //{ // Gl.GL_TEXTURE_CUBE_MAP_POSITIVE_X_ARB, // Gl.GL_TEXTURE_CUBE_MAP_NEGATIVE_X_ARB, // Gl.GL_TEXTURE_CUBE_MAP_POSITIVE_Y_ARB, // Gl.GL_TEXTURE_CUBE_MAP_NEGATIVE_Y_ARB, // Gl.GL_TEXTURE_CUBE_MAP_POSITIVE_Z_ARB, // Gl.GL_TEXTURE_CUBE_MAP_NEGATIVE_Z_ARB //}; private void RenderPrims() { if (RenderPrimList != null && RenderPrimList.Count > 0) { Gl.glEnable(Gl.GL_TEXTURE_2D); lock (RenderPrimList) { bool firstPass = true; Gl.glDisable(Gl.GL_BLEND); Gl.glEnable(Gl.GL_DEPTH_TEST); StartRender: foreach (RenderablePrim render in RenderPrimList.Values) { RenderablePrim parentRender = RenderablePrim.Empty; Primitive prim = render.Prim; if (prim.ParentID != 0) { // Get the parent reference if (!RenderPrimList.TryGetValue(prim.ParentID, out parentRender)) { // Can't render a child with no parent prim, skip it continue; } } Gl.glPushName(prim.LocalID); Gl.glPushMatrix(); if (prim.ParentID != 0) { // Child prim Primitive parent = parentRender.Prim; // Apply parent translation and rotation Gl.glMultMatrixf(Math3D.CreateTranslationMatrix(parent.Position)); Gl.glMultMatrixf(Math3D.CreateRotationMatrix(parent.Rotation)); } // Apply prim translation and rotation Gl.glMultMatrixf(Math3D.CreateTranslationMatrix(prim.Position)); Gl.glMultMatrixf(Math3D.CreateRotationMatrix(prim.Rotation)); // Scale the prim Gl.glScalef(prim.Scale.X, prim.Scale.Y, prim.Scale.Z); // Draw the prim faces for (int j = 0; j < render.Mesh.Faces.Count; j++) { Face face = render.Mesh.Faces[j]; FaceData data = (FaceData)face.UserData; Color4 color = face.TextureFace.RGBA; bool alpha = false; int textureID = 0; if (color.A < 1.0f) alpha = true; #region Texturing TextureInfo info; if (Textures.TryGetValue(face.TextureFace.TextureID, out info)) { if (info.Alpha) alpha = true; textureID = info.ID; // Enable texturing for this face Gl.glPolygonMode(Gl.GL_FRONT_AND_BACK, Gl.GL_FILL); } else { if (face.TextureFace.TextureID == Primitive.TextureEntry.WHITE_TEXTURE || face.TextureFace.TextureID == UUID.Zero) { Gl.glPolygonMode(Gl.GL_FRONT, Gl.GL_FILL); } else { Gl.glPolygonMode(Gl.GL_FRONT, Gl.GL_LINE); } } if (firstPass && !alpha || !firstPass && alpha) { // Color this prim differently based on whether it is selected or not if (LastHit == prim.LocalID || (LastHit != 0 && LastHit == prim.ParentID)) { Gl.glColor4f(1f, color.G * 0.3f, color.B * 0.3f, color.A); } else { Gl.glColor4f(color.R, color.G, color.B, color.A); } // Bind the texture Gl.glBindTexture(Gl.GL_TEXTURE_2D, textureID); Gl.glTexCoordPointer(2, Gl.GL_FLOAT, 0, data.TexCoords); Gl.glVertexPointer(3, Gl.GL_FLOAT, 0, data.Vertices); Gl.glDrawElements(Gl.GL_TRIANGLES, data.Indices.Length, Gl.GL_UNSIGNED_SHORT, data.Indices); } #endregion Texturing } Gl.glPopMatrix(); Gl.glPopName(); } if (firstPass) { firstPass = false; Gl.glEnable(Gl.GL_BLEND); Gl.glBlendFunc(Gl.GL_SRC_ALPHA, Gl.GL_ONE_MINUS_SRC_ALPHA); //Gl.glDisable(Gl.GL_DEPTH_TEST); goto StartRender; } } Gl.glEnable(Gl.GL_DEPTH_TEST); Gl.glDisable(Gl.GL_TEXTURE_2D); } } private void RenderAvatars() { if (Client != null && Client.Network.CurrentSim != null) { Gl.glColor3f(0f, 1f, 0f); Client.Network.CurrentSim.ObjectsAvatars.ForEach( delegate(Avatar avatar) { Gl.glPushMatrix(); Gl.glTranslatef(avatar.Position.X, avatar.Position.Y, avatar.Position.Z); Glu.GLUquadric quad = Glu.gluNewQuadric(); Glu.gluSphere(quad, 1.0d, 10, 10); Glu.gluDeleteQuadric(quad); Gl.glPopMatrix(); } ); Gl.glColor3f(1f, 1f, 1f); } } #region Texture Downloading private void TextureDownloader_OnDownloadFinished(TextureRequestState state, AssetTexture asset) { bool alpha = false; ManagedImage imgData = null; byte[] raw = null; bool success = (state == TextureRequestState.Finished); UUID id = asset.AssetID; try { // Load the image off the disk if (success) { //ImageDownload download = TextureDownloader.GetTextureToRender(id); if (OpenJPEG.DecodeToImage(asset.AssetData, out imgData)) { raw = imgData.ExportRaw(); if ((imgData.Channels & ManagedImage.ImageChannels.Alpha) != 0) alpha = true; } else { success = false; Console.WriteLine("Failed to decode texture"); } } // Make sure the OpenGL commands run on the main thread BeginInvoke( (MethodInvoker)delegate() { if (success) { int textureID = 0; try { Gl.glGenTextures(1, out textureID); Gl.glBindTexture(Gl.GL_TEXTURE_2D, textureID); Gl.glTexParameteri(Gl.GL_TEXTURE_2D, Gl.GL_TEXTURE_MIN_FILTER, Gl.GL_LINEAR_MIPMAP_NEAREST); //Gl.GL_NEAREST); Gl.glTexParameteri(Gl.GL_TEXTURE_2D, Gl.GL_TEXTURE_MAG_FILTER, Gl.GL_LINEAR); Gl.glTexParameteri(Gl.GL_TEXTURE_2D, Gl.GL_TEXTURE_WRAP_S, Gl.GL_REPEAT); Gl.glTexParameteri(Gl.GL_TEXTURE_2D, Gl.GL_TEXTURE_WRAP_T, Gl.GL_REPEAT); Gl.glTexParameteri(Gl.GL_TEXTURE_2D, Gl.GL_GENERATE_MIPMAP, Gl.GL_TRUE); //Gl.GL_FALSE); //Gl.glTexImage2D(Gl.GL_TEXTURE_2D, 0, Gl.GL_RGBA, bitmap.Width, bitmap.Height, 0, Gl.GL_BGRA, Gl.GL_UNSIGNED_BYTE, // bitmapData.Scan0); //int error = Gl.glGetError(); int error = Glu.gluBuild2DMipmaps(Gl.GL_TEXTURE_2D, Gl.GL_RGBA, imgData.Width, imgData.Height, Gl.GL_BGRA, Gl.GL_UNSIGNED_BYTE, raw); if (error == 0) { Textures[id] = new TextureInfo(textureID, alpha); Console.WriteLine("Created OpenGL texture for " + id.ToString()); } else { Textures[id] = new TextureInfo(0, false); Console.WriteLine("Error creating OpenGL texture: " + error); } } catch (Exception ex) { Console.WriteLine(ex); } } // Remove this image from the download listbox lock (DownloadList) { GlacialComponents.Controls.GLItem item; if (DownloadList.TryGetValue(id, out item)) { DownloadList.Remove(id); try { lstDownloads.Items.Remove(item); } catch (Exception) { } lstDownloads.Invalidate(); } } }); } catch (Exception ex) { Console.WriteLine(ex); } } private void Assets_ImageReceiveProgress(object sender, ImageReceiveProgressEventArgs e) { lock (DownloadList) { GlacialComponents.Controls.GLItem item; if (DownloadList.TryGetValue(e.ImageID, out item)) { // Update an existing item BeginInvoke( (MethodInvoker)delegate() { ProgressBar prog = (ProgressBar)item.SubItems[1].Control; if (e.Total >= e.Received) prog.Value = (int)Math.Round((((double)e.Received / (double)e.Total) * 100.0d)); }); } else { // Progress bar ProgressBar prog = new ProgressBar(); prog.Minimum = 0; prog.Maximum = 100; if (e.Total >= e.Received) prog.Value = (int)Math.Round((((double)e.Received / (double)e.Total) * 100.0d)); else prog.Value = 0; // List item item = new GlacialComponents.Controls.GLItem(); item.SubItems[0].Text = e.ImageID.ToString(); item.SubItems[1].Control = prog; DownloadList[e.ImageID] = item; BeginInvoke( (MethodInvoker)delegate() { lstDownloads.Items.Add(item); lstDownloads.Invalidate(); }); } } } #endregion Texture Downloading private void frmBrowser_FormClosing(object sender, FormClosingEventArgs e) { DoLogout(); Application.Idle -= IdleEvent; } private void Application_Idle(object sender, EventArgs e) { while (AppStillIdle) { RenderScene(); } } private void cmdLogin_Click(object sender, EventArgs e) { if (cmdLogin.Text == "Login") { // Check that all the input boxes are filled in if (txtFirst.Text.Length == 0) { txtFirst.Select(); return; } if (txtLast.Text.Length == 0) { txtLast.Select(); return; } if (txtPass.Text.Length == 0) { txtPass.Select(); return; } // Disable input controls txtFirst.Enabled = txtLast.Enabled = txtPass.Enabled = false; cmdLogin.Text = "Logout"; // Sanity check that we aren't already logged in if (Client != null && Client.Network.Connected) { Client.Network.Logout(); } // Re-initialize everything InitializeObjects(); // Start the login LoginParams loginParams = Client.Network.DefaultLoginParams(txtFirst.Text, txtLast.Text, txtPass.Text, "Prim Preview", "0.0.1"); if (!String.IsNullOrEmpty(cboServer.Text)) loginParams.URI = cboServer.Text; Client.Network.BeginLogin(loginParams); } else { DoLogout(); } } private void DoLogout() { if (Client != null && Client.Network.Connected) { Client.Network.Logout(); return; } // Clear the download list lstDownloads.Items.Clear(); // Set the login button back to login state cmdLogin.Text = "Login"; // Enable input controls txtFirst.Enabled = txtLast.Enabled = txtPass.Enabled = true; } private void glControl_Resize(object sender, EventArgs e) { Gl.glClearColor(0.39f, 0.58f, 0.93f, 1.0f); Gl.glViewport(0, 0, glControl.Width, glControl.Height); Gl.glPushMatrix(); Gl.glMatrixMode(Gl.GL_PROJECTION); Gl.glLoadIdentity(); SetPerspective(); Gl.glMatrixMode(Gl.GL_MODELVIEW); Gl.glPopMatrix(); // Set the center of the glControl as the default pivot point LastPivot = glControl.PointToScreen(new Point(glControl.Width / 2, glControl.Height / 2)); } private void glControl_MouseClick(object sender, MouseEventArgs e) { if ((Control.ModifierKeys & Keys.Alt) == 0 && e.Button == MouseButtons.Left) { // Only allow clicking if alt is not being held down ClickX = e.X; ClickY = e.Y; Clicked = true; } } private void glControl_MouseDown(object sender, MouseEventArgs e) { if ((Control.ModifierKeys & Keys.Alt) != 0 && LastHit > 0) { // Alt is held down and we have a valid target Pivoting = true; PivotPosition = Camera.FocalPoint; Control control = (Control)sender; LastPivot = control.PointToScreen(new Point(e.X, e.Y)); } } private void glControl_MouseMove(object sender, MouseEventArgs e) { if (Pivoting) { float a, x, y, z; Control control = (Control)sender; Point mouse = control.PointToScreen(new Point(e.X, e.Y)); // Calculate the deltas from the center of the control to the current position int deltaX = (int)((mouse.X - LastPivot.X) * -0.5d); int deltaY = (int)((mouse.Y - LastPivot.Y) * -0.5d); // Translate so the focal point is the origin Vector3 altered = Camera.Position - Camera.FocalPoint; // Rotate the translated point by deltaX a = (float)deltaX * DEG_TO_RAD; x = (float)((altered.X * Math.Cos(a)) - (altered.Y * Math.Sin(a))); y = (float)((altered.X * Math.Sin(a)) + (altered.Y * Math.Cos(a))); altered.X = x; altered.Y = y; // Rotate the translated point by deltaY a = (float)deltaY * DEG_TO_RAD; y = (float)((altered.Y * Math.Cos(a)) - (altered.Z * Math.Sin(a))); z = (float)((altered.Y * Math.Sin(a)) + (altered.Z * Math.Cos(a))); altered.Y = y; altered.Z = z; // Translate back to world space altered += Camera.FocalPoint; // Update the camera Camera.Position = altered; UpdateCamera(); // Update the pivot point LastPivot = mouse; } } private void glControl_MouseWheel(object sender, MouseEventArgs e) { /*if (e.Delta != 0) { Camera.Zoom = Camera.Zoom + (double)(e.Delta / 120) * -0.1d; if (Camera.Zoom < 0.05d) Camera.Zoom = 0.05d; UpdateCamera(); }*/ if (e.Delta != 0) { // Calculate the distance to move to/away float dist = (float)(e.Delta / 120) * 10.0f; if (Vector3.Distance(Camera.Position, Camera.FocalPoint) > dist) { // Move closer or further away from the focal point Vector3 toFocal = Camera.FocalPoint - Camera.Position; toFocal.Normalize(); toFocal = toFocal * dist; Camera.Position += toFocal; UpdateCamera(); } } } private void glControl_MouseUp(object sender, MouseEventArgs e) { // Stop pivoting if we were previously Pivoting = false; } private void txtLogin_Enter(object sender, EventArgs e) { TextBox input = (TextBox)sender; input.SelectAll(); } private void cmdTeleport_Click(object sender, EventArgs e) { if (!String.IsNullOrEmpty(txtSim.Text)) { // Parse X/Y/Z int x, y, z; if (!Int32.TryParse(txtX.Text, out x)) { txtX.SelectAll(); return; } if (!Int32.TryParse(txtY.Text, out y)) { txtY.SelectAll(); return; } if (!Int32.TryParse(txtZ.Text, out z)) { txtZ.SelectAll(); return; } string simName = txtSim.Text.Trim().ToLower(); Vector3 position = new Vector3(x, y, z); if (Client != null && Client.Network.CurrentSim != null) { // Check for a local teleport to shortcut the process if (simName == Client.Network.CurrentSim.Name.ToLower()) { // Local teleport Client.Self.RequestTeleport(Client.Network.CurrentSim.Handle, position); } else { // Cross-sim teleport bool success = false; BackgroundWorker worker = new BackgroundWorker(); worker.DoWork += delegate(object s, DoWorkEventArgs ea) { success = Client.Self.Teleport(simName, position); }; worker.RunWorkerCompleted += delegate(object s, RunWorkerCompletedEventArgs ea) { BeginInvoke((MethodInvoker) delegate() { if (!success) System.Windows.Forms.MessageBox.Show("Teleport failed"); }); }; worker.RunWorkerAsync(); } } else { // Oops! How did the user click this... cmdTeleport.Enabled = false; } } } } public struct TextureInfo { /// OpenGL Texture ID public int ID; /// True if this texture has an alpha component public bool Alpha; public TextureInfo(int id, bool alpha) { ID = id; Alpha = alpha; } } public struct HeightmapLookupValue : IComparable { public ushort Index; public float Value; public HeightmapLookupValue(ushort index, float value) { Index = index; Value = value; } public int CompareTo(HeightmapLookupValue val) { return Value.CompareTo(val.Value); } } public struct RenderablePrim { public Primitive Prim; public FacetedMesh Mesh; public readonly static RenderablePrim Empty = new RenderablePrim(); } public struct Camera { public Vector3 Position; public Vector3 FocalPoint; public double Zoom; public double Far; } public struct NativeMethods { [StructLayout(LayoutKind.Sequential)] public struct Message { public IntPtr HWnd; public uint Msg; public IntPtr WParam; public IntPtr LParam; public uint Time; public System.Drawing.Point Point; } //[System.Security.SuppressUnmanagedCodeSecurity] [DllImport("User32.dll", CharSet = CharSet.Auto)] public static extern bool PeekMessage(out Message msg, IntPtr hWnd, uint messageFilterMin, uint messageFilterMax, uint flags); } 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[] 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; } } }