LIBOMV-576 Start of Abstracting library into two separate libraries. For now this means: There will be a new dependency for OpenMetaverse.dll named OpenMetaverseCore.dll, the new will be required for OpenMetaverse to operate properly, the inverse is not true. OpenMetaverseCore will eventually contain all packet and message related code. * Need to create a singleton logger instance (or move the current logger to Core. * Currently only Packets, Helpers and some common types have been moved to Core. * Helpers will need to be split and non-core required helpers moved back to OpenMetaverse. * Lots more work to be done here, but these changes should not break anything (yet) git-svn-id: http://libopenmetaverse.googlecode.com/svn/libopenmetaverse/trunk@3021 52acb1d6-8a22-11de-b505-999d5b087335
1953 lines
74 KiB
C#
1953 lines
74 KiB
C#
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<uint, Primitive> RenderFoliageList = new Dictionary<uint, Primitive>();
|
|
Dictionary<uint, RenderablePrim> RenderPrimList = new Dictionary<uint, RenderablePrim>();
|
|
Dictionary<UUID, GlacialComponents.Controls.GLItem> DownloadList = new Dictionary<UUID, GlacialComponents.Controls.GLItem>();
|
|
EventHandler IdleEvent;
|
|
|
|
System.Timers.Timer ProgressTimer;
|
|
int TotalPrims;
|
|
|
|
// Textures
|
|
Dictionary<UUID, TextureInfo> Textures = new Dictionary<UUID, TextureInfo>();
|
|
|
|
// 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); }
|
|
}
|
|
|
|
/// <summary>
|
|
/// Default constructor
|
|
/// </summary>
|
|
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.OnLogin += new NetworkManager.LoginCallback(Network_OnLogin);
|
|
Client.Network.OnDisconnected += new NetworkManager.DisconnectedCallback(Network_OnDisconnected);
|
|
Client.Network.OnCurrentSimChanged += new NetworkManager.CurrentSimChangedCallback(Network_OnCurrentSimChanged);
|
|
Client.Network.OnEventQueueRunning += new NetworkManager.EventQueueRunningCallback(Network_OnEventQueueRunning);
|
|
Client.Objects.OnNewPrim += new ObjectManager.NewPrimCallback(Objects_OnNewPrim);
|
|
Client.Objects.OnObjectKilled += new ObjectManager.KillObjectCallback(Objects_OnObjectKilled);
|
|
Client.Terrain.OnLandPatch += new TerrainManager.LandPatchCallback(Terrain_OnLandPatch);
|
|
Client.Parcels.OnSimParcelsDownloaded += new ParcelManager.SimParcelsDownloaded(Parcels_OnSimParcelsDownloaded);
|
|
|
|
Client.Assets.OnImageRecieveProgress += new AssetManager.ImageReceiveProgressCallback(Assets_OnImageRecieveProgress);
|
|
// 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);
|
|
*/
|
|
}
|
|
|
|
|
|
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(i + (j * 256), ((float)i * ((float)j / 127.0f)));
|
|
}
|
|
}
|
|
Array.Sort<HeightmapLookupValue>(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<Primitive> primList = new List<Primitive>();
|
|
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<Primitive> primList = new List<Primitive>();
|
|
|
|
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<Primitive> primList, string fileName, out int prims, out int textures, out string error)
|
|
{
|
|
List<UUID> textureList = new List<UUID>();
|
|
prims = 0;
|
|
textures = 0;
|
|
|
|
// Write the LLSD to the hard drive in XML format
|
|
string output = OSDParser.SerializeLLSDXmlString(ClientHelpers.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<Primitive> 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 ClientHelpers.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<HeightmapLookupValue>(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<Primitive> 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(Simulator simulator, Primitive prim, ulong regionHandle, ushort timeDilation)
|
|
{
|
|
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;
|
|
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);
|
|
|
|
// 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 Objects_OnObjectKilled(Simulator simulator, uint objectID)
|
|
{
|
|
//
|
|
}
|
|
|
|
private void Parcels_OnSimParcelsDownloaded(Simulator simulator, InternalDictionary<int, Parcel> simParcels, int[,] parcelMap)
|
|
{
|
|
TotalPrims = 0;
|
|
|
|
simParcels.ForEach(
|
|
delegate(Parcel parcel)
|
|
{
|
|
TotalPrims += parcel.TotalPrims;
|
|
});
|
|
|
|
UpdatePrimProgress();
|
|
}
|
|
|
|
private void Terrain_OnLandPatch(Simulator simulator, int x, int y, int width, float[] data)
|
|
{
|
|
if (Client != null && Client.Network.CurrentSim == simulator)
|
|
{
|
|
Heightmap[y, x].Data = data;
|
|
}
|
|
|
|
// Find the new max height
|
|
for (int i = 0; i < data.Length; i++)
|
|
{
|
|
if (data[i] > MaxHeight)
|
|
MaxHeight = data[i];
|
|
}
|
|
}
|
|
|
|
private void Network_OnLogin(LoginStatus login, string message)
|
|
{
|
|
if (login == LoginStatus.Success)
|
|
{
|
|
// Success!
|
|
}
|
|
else if (login == 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(NetworkManager.DisconnectType reason, string message)
|
|
{
|
|
BeginInvoke(
|
|
(MethodInvoker)delegate()
|
|
{
|
|
cmdTeleport.Enabled = false;
|
|
DoLogout();
|
|
});
|
|
}
|
|
|
|
private void Network_OnCurrentSimChanged(Simulator PreviousSimulator)
|
|
{
|
|
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(Simulator simulator)
|
|
{
|
|
if (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_OnImageRecieveProgress(UUID imageID, int recieved, int total)
|
|
{
|
|
lock (DownloadList)
|
|
{
|
|
GlacialComponents.Controls.GLItem item;
|
|
if (DownloadList.TryGetValue(imageID, out item))
|
|
{
|
|
// Update an existing item
|
|
BeginInvoke(
|
|
(MethodInvoker)delegate()
|
|
{
|
|
ProgressBar prog = (ProgressBar)item.SubItems[1].Control;
|
|
if (total >= recieved)
|
|
prog.Value = (int)Math.Round((((double)recieved / (double)total) * 100.0d));
|
|
});
|
|
}
|
|
else
|
|
{
|
|
// Progress bar
|
|
ProgressBar prog = new ProgressBar();
|
|
prog.Minimum = 0;
|
|
prog.Maximum = 100;
|
|
if (total >= recieved)
|
|
prog.Value = (int)Math.Round((((double)recieved / (double)total) * 100.0d));
|
|
else
|
|
prog.Value = 0;
|
|
|
|
// List item
|
|
item = new GlacialComponents.Controls.GLItem();
|
|
item.SubItems[0].Text = imageID.ToString();
|
|
item.SubItems[1].Control = prog;
|
|
|
|
DownloadList[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
|
|
{
|
|
/// <summary>OpenGL Texture ID</summary>
|
|
public int ID;
|
|
/// <summary>True if this texture has an alpha component</summary>
|
|
public bool Alpha;
|
|
|
|
public TextureInfo(int id, bool alpha)
|
|
{
|
|
ID = id;
|
|
Alpha = alpha;
|
|
}
|
|
}
|
|
|
|
public struct HeightmapLookupValue : IComparable<HeightmapLookupValue>
|
|
{
|
|
public int Index;
|
|
public float Value;
|
|
|
|
public HeightmapLookupValue(int 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;
|
|
}
|
|
}
|
|
}
|