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
598 lines
22 KiB
C#
598 lines
22 KiB
C#
using System;
|
|
using System.Collections.Generic;
|
|
using System.ComponentModel;
|
|
using System.Drawing;
|
|
using System.Drawing.Imaging;
|
|
using System.IO;
|
|
using System.Windows.Forms;
|
|
using Tao.OpenGl;
|
|
using Tao.Platform.Windows;
|
|
using ICSharpCode.SharpZipLib.Zip;
|
|
using OpenMetaverse;
|
|
using OpenMetaverse.StructuredData;
|
|
using OpenMetaverse.Imaging;
|
|
using OpenMetaverse.Rendering;
|
|
|
|
// NOTE: Batches are divided by texture, fullbright, shiny, transparent, and glow
|
|
|
|
namespace PrimWorkshop
|
|
{
|
|
public struct FaceData
|
|
{
|
|
public float[] Vertices;
|
|
public ushort[] Indices;
|
|
public float[] TexCoords;
|
|
public int TexturePointer;
|
|
public System.Drawing.Image Texture;
|
|
// TODO: Normals / binormals?
|
|
}
|
|
|
|
public static class Render
|
|
{
|
|
public static IRendering Plugin;
|
|
}
|
|
|
|
public partial class frmPrimWorkshop : Form
|
|
{
|
|
#region Form Globals
|
|
|
|
List<FacetedMesh> Prims = null;
|
|
FacetedMesh CurrentPrim = null;
|
|
ProfileFace? CurrentFace = null;
|
|
|
|
bool DraggingTexture = false;
|
|
bool Wireframe = true;
|
|
int[] TexturePointers = new int[1];
|
|
Dictionary<UUID, Image> Textures = new Dictionary<UUID, Image>();
|
|
|
|
#endregion Form Globals
|
|
|
|
public frmPrimWorkshop()
|
|
{
|
|
InitializeComponent();
|
|
glControl.InitializeContexts();
|
|
|
|
Gl.glShadeModel(Gl.GL_SMOOTH);
|
|
Gl.glClearColor(0f, 0f, 0f, 0f);
|
|
|
|
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);
|
|
|
|
TexturePointers[0] = 0;
|
|
|
|
// Call the resizing function which sets up the GL drawing window
|
|
// and will also invalidate the GL control
|
|
glControl_Resize(null, null);
|
|
}
|
|
|
|
private void frmPrimWorkshop_Shown(object sender, EventArgs e)
|
|
{
|
|
// Get a list of rendering plugins
|
|
List<string> renderers = RenderingLoader.ListRenderers(".");
|
|
|
|
foreach (string r in renderers)
|
|
{
|
|
DialogResult result = MessageBox.Show(
|
|
String.Format("Use renderer {0}?", r), "Select Rendering Plugin", MessageBoxButtons.YesNo);
|
|
|
|
if (result == DialogResult.Yes)
|
|
{
|
|
Render.Plugin = RenderingLoader.LoadRenderer(r);
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (Render.Plugin == null)
|
|
{
|
|
MessageBox.Show("No valid rendering plugin loaded, exiting...");
|
|
Application.Exit();
|
|
}
|
|
}
|
|
|
|
#region GLControl Callbacks
|
|
|
|
private void glControl_Paint(object sender, PaintEventArgs e)
|
|
{
|
|
Gl.glClear(Gl.GL_COLOR_BUFFER_BIT | Gl.GL_DEPTH_BUFFER_BIT);
|
|
Gl.glLoadIdentity();
|
|
|
|
// Setup wireframe or solid fill drawing mode
|
|
if (Wireframe)
|
|
Gl.glPolygonMode(Gl.GL_FRONT_AND_BACK, Gl.GL_LINE);
|
|
else
|
|
Gl.glPolygonMode(Gl.GL_FRONT, Gl.GL_FILL);
|
|
|
|
Vector3 center = Vector3.Zero;
|
|
|
|
Glu.gluLookAt(
|
|
center.X, (double)scrollZoom.Value * 0.1d + center.Y, center.Z,
|
|
center.X, center.Y, center.Z,
|
|
0d, 0d, 1d);
|
|
|
|
// Push the world matrix
|
|
Gl.glPushMatrix();
|
|
|
|
Gl.glEnableClientState(Gl.GL_VERTEX_ARRAY);
|
|
Gl.glEnableClientState(Gl.GL_TEXTURE_COORD_ARRAY);
|
|
|
|
// World rotations
|
|
Gl.glRotatef((float)scrollRoll.Value, 1f, 0f, 0f);
|
|
Gl.glRotatef((float)scrollPitch.Value, 0f, 1f, 0f);
|
|
Gl.glRotatef((float)scrollYaw.Value, 0f, 0f, 1f);
|
|
|
|
if (Prims != null)
|
|
{
|
|
for (int i = 0; i < Prims.Count; i++)
|
|
{
|
|
Primitive prim = Prims[i].Prim;
|
|
|
|
if (i == cboPrim.SelectedIndex)
|
|
Gl.glColor3f(1f, 0f, 0f);
|
|
else
|
|
Gl.glColor3f(1f, 1f, 1f);
|
|
|
|
// Individual prim matrix
|
|
Gl.glPushMatrix();
|
|
|
|
// The root prim position is sim-relative, while child prim positions are
|
|
// parent-relative. We want to apply parent-relative translations but not
|
|
// sim-relative ones
|
|
if (Prims[i].Prim.ParentID != 0)
|
|
{
|
|
// Apply prim translation and rotation
|
|
Gl.glMultMatrixf(Math3D.CreateTranslationMatrix(prim.Position));
|
|
Gl.glMultMatrixf(Math3D.CreateRotationMatrix(prim.Rotation));
|
|
}
|
|
|
|
// Prim scaling
|
|
Gl.glScalef(prim.Scale.X, prim.Scale.Y, prim.Scale.Z);
|
|
|
|
// Draw the prim faces
|
|
for (int j = 0; j < Prims[i].Faces.Count; j++)
|
|
{
|
|
if (i == cboPrim.SelectedIndex)
|
|
{
|
|
// This prim is currently selected in the dropdown
|
|
//Gl.glColor3f(0f, 1f, 0f);
|
|
Gl.glColor3f(1f, 1f, 1f);
|
|
|
|
if (j == cboFace.SelectedIndex)
|
|
{
|
|
// This face is currently selected in the dropdown
|
|
}
|
|
else
|
|
{
|
|
// This face is not currently selected in the dropdown
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// This prim is not currently selected in the dropdown
|
|
Gl.glColor3f(1f, 1f, 1f);
|
|
}
|
|
|
|
#region Texturing
|
|
|
|
Face face = Prims[i].Faces[j];
|
|
FaceData data = (FaceData)face.UserData;
|
|
|
|
if (data.TexturePointer != 0)
|
|
{
|
|
// Set the color to solid white so the texture is not altered
|
|
//Gl.glColor3f(1f, 1f, 1f);
|
|
// Enable texturing for this face
|
|
Gl.glEnable(Gl.GL_TEXTURE_2D);
|
|
}
|
|
else
|
|
{
|
|
Gl.glDisable(Gl.GL_TEXTURE_2D);
|
|
}
|
|
|
|
// Bind the texture
|
|
Gl.glBindTexture(Gl.GL_TEXTURE_2D, data.TexturePointer);
|
|
|
|
#endregion Texturing
|
|
|
|
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);
|
|
}
|
|
|
|
// Pop the prim matrix
|
|
Gl.glPopMatrix();
|
|
}
|
|
}
|
|
|
|
// Pop the world matrix
|
|
Gl.glPopMatrix();
|
|
|
|
Gl.glDisableClientState(Gl.GL_TEXTURE_COORD_ARRAY);
|
|
Gl.glDisableClientState(Gl.GL_VERTEX_ARRAY);
|
|
|
|
Gl.glFlush();
|
|
}
|
|
|
|
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();
|
|
|
|
Glu.gluPerspective(50.0d, 1.0d, 0.1d, 50d);
|
|
|
|
Gl.glMatrixMode(Gl.GL_MODELVIEW);
|
|
Gl.glPopMatrix();
|
|
}
|
|
|
|
#endregion GLControl Callbacks
|
|
|
|
#region Menu Callbacks
|
|
|
|
private void openPrimXMLToolStripMenuItem1_Click(object sender, EventArgs e)
|
|
{
|
|
Prims = null;
|
|
OpenFileDialog dialog = new OpenFileDialog();
|
|
dialog.Filter = "Prim Package (*.zip)|*.zip";
|
|
|
|
if (dialog.ShowDialog() == DialogResult.OK)
|
|
{
|
|
string tempPath = System.IO.Path.Combine(System.IO.Path.GetTempPath(),
|
|
System.IO.Path.GetRandomFileName());
|
|
|
|
try
|
|
{
|
|
// Create a temporary directory
|
|
Directory.CreateDirectory(tempPath);
|
|
|
|
FastZip fastzip = new FastZip();
|
|
fastzip.ExtractZip(dialog.FileName, tempPath, String.Empty);
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
MessageBox.Show(ex.Message);
|
|
return;
|
|
}
|
|
|
|
// Check for the prims.xml file
|
|
string primsFile = System.IO.Path.Combine(tempPath, "prims.xml");
|
|
if (!File.Exists(primsFile))
|
|
{
|
|
MessageBox.Show("prims.xml not found in the archive");
|
|
return;
|
|
}
|
|
|
|
OSD osd = null;
|
|
|
|
try { osd = OSDParser.DeserializeLLSDXml(File.ReadAllText(primsFile)); }
|
|
catch (Exception ex) { MessageBox.Show(ex.Message); }
|
|
|
|
if (osd != null && osd.Type == OSDType.Map)
|
|
{
|
|
List<Primitive> primList = ClientHelpers.OSDToPrimList(osd);
|
|
Prims = new List<FacetedMesh>(primList.Count);
|
|
|
|
for (int i = 0; i < primList.Count; i++)
|
|
{
|
|
// TODO: Can't render sculpted prims without the textures
|
|
if (primList[i].Sculpt.SculptTexture != UUID.Zero)
|
|
continue;
|
|
|
|
Primitive prim = primList[i];
|
|
FacetedMesh mesh = Render.Plugin.GenerateFacetedMesh(prim, DetailLevel.Highest);
|
|
|
|
// Create a FaceData struct for each face that stores the 3D data
|
|
// in a Tao.OpenGL friendly format
|
|
for (int j = 0; j < mesh.Faces.Count; j++)
|
|
{
|
|
Face face = 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 (LoadTexture(tempPath, teFace.TextureID, ref data.Texture))
|
|
{
|
|
Bitmap bitmap = new Bitmap(data.Texture);
|
|
bitmap.RotateFlip(RotateFlipType.RotateNoneFlipY);
|
|
Rectangle rectangle = new Rectangle(0, 0, bitmap.Width, bitmap.Height);
|
|
BitmapData bitmapData = bitmap.LockBits(rectangle, ImageLockMode.ReadOnly, PixelFormat.Format24bppRgb);
|
|
|
|
Gl.glGenTextures(1, out data.TexturePointer);
|
|
Gl.glBindTexture(Gl.GL_TEXTURE_2D, data.TexturePointer);
|
|
|
|
Gl.glTexParameteri(Gl.GL_TEXTURE_2D, Gl.GL_TEXTURE_MIN_FILTER, Gl.GL_LINEAR_MIPMAP_LINEAR);
|
|
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);
|
|
|
|
Glu.gluBuild2DMipmaps(Gl.GL_TEXTURE_2D, Gl.GL_RGB8, bitmap.Width, bitmap.Height, Gl.GL_BGR, Gl.GL_UNSIGNED_BYTE, bitmapData.Scan0);
|
|
|
|
bitmap.UnlockBits(bitmapData);
|
|
bitmap.Dispose();
|
|
}
|
|
|
|
// Set the UserData for this face to our FaceData struct
|
|
face.UserData = data;
|
|
mesh.Faces[j] = face;
|
|
}
|
|
|
|
Prims.Add(mesh);
|
|
}
|
|
|
|
// Setup the dropdown list of prims
|
|
PopulatePrimCombobox();
|
|
|
|
glControl.Invalidate();
|
|
}
|
|
else
|
|
{
|
|
MessageBox.Show("Failed to load LLSD formatted primitive data from " + dialog.FileName);
|
|
}
|
|
|
|
Directory.Delete(tempPath);
|
|
}
|
|
}
|
|
|
|
private bool LoadTexture(string basePath, UUID textureID, ref System.Drawing.Image texture)
|
|
{
|
|
if (Textures.ContainsKey(textureID))
|
|
{
|
|
texture = Textures[textureID];
|
|
return true;
|
|
}
|
|
|
|
string texturePath = System.IO.Path.Combine(basePath, textureID.ToString());
|
|
|
|
if (File.Exists(texturePath + ".tga"))
|
|
{
|
|
try
|
|
{
|
|
texture = (Image)LoadTGAClass.LoadTGA(texturePath + ".tga");
|
|
Textures[textureID] = texture;
|
|
return true;
|
|
}
|
|
catch (Exception)
|
|
{
|
|
}
|
|
}
|
|
else if (File.Exists(texturePath + ".jp2"))
|
|
{
|
|
try
|
|
{
|
|
ManagedImage managedImage;
|
|
if (OpenJPEG.DecodeToImage(File.ReadAllBytes(texturePath + ".jp2"), out managedImage, out texture))
|
|
{
|
|
Textures[textureID] = texture;
|
|
return true;
|
|
}
|
|
}
|
|
catch (Exception)
|
|
{
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
private void textureToolStripMenuItem_Click(object sender, EventArgs e)
|
|
{
|
|
picTexture.Image = null;
|
|
TexturePointers[0] = 0;
|
|
|
|
OpenFileDialog dialog = new OpenFileDialog();
|
|
|
|
if (dialog.ShowDialog() == DialogResult.OK)
|
|
{
|
|
try
|
|
{
|
|
picTexture.Image = System.Drawing.Image.FromFile(dialog.FileName);
|
|
Bitmap bitmap = new Bitmap(picTexture.Image);
|
|
bitmap.RotateFlip(RotateFlipType.RotateNoneFlipY);
|
|
|
|
// Create the GL texture space
|
|
Gl.glGenTextures(1, TexturePointers);
|
|
Rectangle rectangle = new Rectangle(0, 0, bitmap.Width, bitmap.Height);
|
|
BitmapData bitmapData = bitmap.LockBits(rectangle, ImageLockMode.ReadOnly, PixelFormat.Format24bppRgb);
|
|
|
|
Gl.glBindTexture(Gl.GL_TEXTURE_2D, TexturePointers[0]);
|
|
|
|
Gl.glTexParameteri(Gl.GL_TEXTURE_2D, Gl.GL_TEXTURE_MIN_FILTER, Gl.GL_LINEAR_MIPMAP_LINEAR);
|
|
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_CLAMP_TO_EDGE);
|
|
Gl.glTexParameteri(Gl.GL_TEXTURE_2D, Gl.GL_TEXTURE_WRAP_T, Gl.GL_CLAMP_TO_EDGE);
|
|
Gl.glTexParameteri(Gl.GL_TEXTURE_2D, Gl.GL_GENERATE_MIPMAP, Gl.GL_TRUE);
|
|
|
|
Glu.gluBuild2DMipmaps(Gl.GL_TEXTURE_2D, Gl.GL_RGB8, bitmap.Width, bitmap.Height, Gl.GL_BGR, Gl.GL_UNSIGNED_BYTE, bitmapData.Scan0);
|
|
|
|
bitmap.UnlockBits(bitmapData);
|
|
bitmap.Dispose();
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
MessageBox.Show("Failed to load image from file " + dialog.FileName + ": " + ex.Message);
|
|
}
|
|
}
|
|
}
|
|
|
|
private void savePrimXMLToolStripMenuItem_Click(object sender, EventArgs e)
|
|
{
|
|
|
|
}
|
|
|
|
private void saveTextureToolStripMenuItem_Click(object sender, EventArgs e)
|
|
{
|
|
|
|
}
|
|
|
|
private void oBJToolStripMenuItem_Click(object sender, EventArgs e)
|
|
{
|
|
SaveFileDialog dialog = new SaveFileDialog();
|
|
dialog.Filter = "OBJ files (*.obj)|*.obj";
|
|
|
|
if (dialog.ShowDialog() == DialogResult.OK)
|
|
{
|
|
if (!MeshToOBJ.MeshesToOBJ(Prims, dialog.FileName))
|
|
{
|
|
MessageBox.Show("Failed to save file " + dialog.FileName +
|
|
". Ensure that you have permission to write to that file and it is currently not in use");
|
|
}
|
|
}
|
|
}
|
|
|
|
private void exitToolStripMenuItem_Click(object sender, EventArgs e)
|
|
{
|
|
Close();
|
|
}
|
|
|
|
private void aboutToolStripMenuItem_Click(object sender, EventArgs e)
|
|
{
|
|
MessageBox.Show(
|
|
"Written by John Hurliman <jhurliman@jhurliman.org> (http://www.jhurliman.org/)");
|
|
}
|
|
|
|
#endregion Menu Callbacks
|
|
|
|
#region Scrollbar Callbacks
|
|
|
|
private void scroll_ValueChanged(object sender, EventArgs e)
|
|
{
|
|
glControl.Invalidate();
|
|
}
|
|
|
|
private void scrollZoom_ValueChanged(object sender, EventArgs e)
|
|
{
|
|
glControl_Resize(null, null);
|
|
glControl.Invalidate();
|
|
}
|
|
|
|
#endregion Scrollbar Callbacks
|
|
|
|
#region PictureBox Callbacks
|
|
|
|
private void picTexture_MouseDown(object sender, MouseEventArgs e)
|
|
{
|
|
DraggingTexture = true;
|
|
}
|
|
|
|
private void picTexture_MouseUp(object sender, MouseEventArgs e)
|
|
{
|
|
DraggingTexture = false;
|
|
}
|
|
|
|
private void picTexture_MouseLeave(object sender, EventArgs e)
|
|
{
|
|
DraggingTexture = false;
|
|
}
|
|
|
|
private void picTexture_MouseMove(object sender, MouseEventArgs e)
|
|
{
|
|
if (DraggingTexture)
|
|
{
|
|
// What is the current action?
|
|
// None, DraggingEdge, DraggingCorner, DraggingWhole
|
|
}
|
|
else
|
|
{
|
|
// Check if the mouse is close to the edge or corner of a selection
|
|
// rectangle
|
|
|
|
// If so, change the cursor accordingly
|
|
}
|
|
}
|
|
|
|
private void picTexture_Paint(object sender, PaintEventArgs e)
|
|
{
|
|
// Draw the current selection rectangles
|
|
}
|
|
|
|
#endregion PictureBox Callbacks
|
|
|
|
private void cboPrim_SelectedIndexChanged(object sender, EventArgs e)
|
|
{
|
|
CurrentPrim = (FacetedMesh)cboPrim.Items[cboPrim.SelectedIndex];
|
|
PopulateFaceCombobox();
|
|
|
|
glControl.Invalidate();
|
|
}
|
|
|
|
private void cboFace_SelectedIndexChanged(object sender, EventArgs e)
|
|
{
|
|
CurrentFace = (ProfileFace)cboFace.Items[cboFace.SelectedIndex];
|
|
|
|
glControl.Invalidate();
|
|
}
|
|
|
|
private void PopulatePrimCombobox()
|
|
{
|
|
cboPrim.Items.Clear();
|
|
|
|
if (Prims != null)
|
|
{
|
|
for (int i = 0; i < Prims.Count; i++)
|
|
cboPrim.Items.Add(Prims[i]);
|
|
}
|
|
|
|
if (cboPrim.Items.Count > 0)
|
|
cboPrim.SelectedIndex = 0;
|
|
}
|
|
|
|
private void PopulateFaceCombobox()
|
|
{
|
|
cboFace.Items.Clear();
|
|
|
|
if (CurrentPrim != null)
|
|
{
|
|
for (int i = 0; i < CurrentPrim.Profile.Faces.Count; i++)
|
|
cboFace.Items.Add(CurrentPrim.Profile.Faces[i]);
|
|
}
|
|
|
|
if (cboFace.Items.Count > 0)
|
|
cboFace.SelectedIndex = 0;
|
|
}
|
|
|
|
private void wireframeToolStripMenuItem_Click(object sender, EventArgs e)
|
|
{
|
|
wireframeToolStripMenuItem.Checked = !wireframeToolStripMenuItem.Checked;
|
|
Wireframe = wireframeToolStripMenuItem.Checked;
|
|
|
|
glControl.Invalidate();
|
|
}
|
|
|
|
private void worldBrowserToolStripMenuItem_Click(object sender, EventArgs e)
|
|
{
|
|
frmBrowser browser = new frmBrowser();
|
|
browser.ShowDialog();
|
|
}
|
|
}
|
|
}
|