Files
libremetaverse/applications/sceneviewer/sceneviewer.cs
John Hurliman 056492f394 * Fixes for some of the type class comparison operators
* More unit tests
* Fixed water height in sceneviewer and brought it up to speed with the latest libsecondlife API

git-svn-id: http://libopenmetaverse.googlecode.com/svn/trunk@553 52acb1d6-8a22-11de-b505-999d5b087335
2006-11-10 14:50:00 +00:00

773 lines
28 KiB
C#

/*
* Copyright (c) 2006, Second Life Reverse Engineering Team
* All rights reserved.
*
* - Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* - Redistributions of source code must retain the above copyright notice, this
* list of conditions and the following disclaimer.
* - Neither the name of the Second Life Reverse Engineering Team nor the names
* of its contributors may be used to endorse or promote products derived from
* this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
*/
#region Using Statements
using System;
using System.Collections.Generic;
using System.Threading;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Audio;
using Microsoft.Xna.Framework.Content;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework.Input;
using Microsoft.Xna.Framework.Storage;
using libsecondlife;
using sceneviewer.Prims;
#endregion
namespace sceneviewer
{
/// <summary>
/// This is the main type for your game
/// </summary>
public class sceneviewer : Microsoft.Xna.Framework.Game
{
GraphicsDeviceManager Graphics;
ContentManager Content;
const short WATER_ROWS = 50;
const short WATER_COLS = 50;
const float WATER_WIDTH = 256.0f;
const float WATER_HEIGHT = 256.0f;
// 3d world
private Camera Camera;
private BoundingFrustum Frustum;
private Matrix World;
private Matrix ViewMatrix;
private Matrix ViewProjectionMatrix;
private Matrix ViewInverseMatrix;
// Shaders
private Effect EffectBasicPrim;
private Effect EffectWater;
// Input
KeyboardState CurrentKeyboardState;
MouseState CurrentMouseState;
Keys[] KeysHeldDown;
List<Keys> KeysPressedThisFrame;
List<Keys> KeysReleasedThisFrame;
// Second Life
private SecondLife Client;
// Prims
private Dictionary<uint, PrimVisual> Prims;
private VertexDeclaration PrimVertexDeclaration;
// Water
private VertexPosTexNormalTanBitan[] WaterVertexArray;
private VertexBuffer WaterVertexBuffer;
private IndexBuffer WaterIndexBuffer;
private VertexDeclaration WaterVertexDeclaration;
private Texture2D WaterNormalMap;
private TextureCube WaterReflectionCubemap;
private float WaterHeight;
// Timer related
private Timer CameraUpdateTimer;
private float deltaFPSTime = 0;
// State tracking
bool Wireframe = false;
bool FPSCounter = false;
/// <summary>
///
/// </summary>
public sceneviewer()
{
Quaternion a = new Quaternion(0.19134f, -0.46194f, 0.19134f, 0.84462f);
Quaternion b = new Quaternion(0.00000f, 0.00000f, 1.00000f, 0.00000f);
Quaternion c = a * b;
Console.WriteLine(c.ToString());
Graphics = new GraphicsDeviceManager(this);
Graphics.MinimumPixelShaderProfile = ShaderProfile.PS_2_0;
Graphics.PreferMultiSampling = false;
Graphics.SynchronizeWithVerticalRetrace = false;
Content = new ContentManager(Services);
KeysPressedThisFrame = new List<Keys>();
KeysReleasedThisFrame = new List<Keys>();
Prims = new Dictionary<uint, PrimVisual>();
Client = new SecondLife();
this.IsMouseVisible = true;
Window.AllowUserResizing = true;
CurrentKeyboardState = Keyboard.GetState();
CurrentMouseState = Mouse.GetState();
KeysHeldDown = Keyboard.GetState().GetPressedKeys();
Window.ClientSizeChanged += new EventHandler(Window_ClientSizeChanged);
this.Exiting += new EventHandler(sceneviewer_Exiting);
Camera = new Camera(this.Window, new Vector3(-10, -10, 40), new Vector3(255, 255, 40));
}
/// <summary>
/// Allows the game to perform any initialization it needs to before starting to run.
/// This is where it can query for any required services and load any non-graphic
/// related content. Calling base.Initialize will enumerate through any components
/// and initialize them as well.
/// </summary>
protected override void Initialize()
{
// Register libsl callbacks
Client.Objects.RequestAllObjects = true;
Client.Objects.OnNewPrim += new ObjectManager.NewPrimCallback(OnNewPrim);
Client.Objects.OnPrimMoved += new ObjectManager.PrimMovedCallback(OnPrimMoved);
Client.Objects.OnObjectKilled += new ObjectManager.KillObjectCallback(OnObjectKilled);
if (!Client.Network.Login("Ron", "Hubbard", "radishman", "sceneviewer", "jhurliman@wsu.edu"))
{
Exit();
}
// Wait for basic information to be retrieved from the current sim
while (Client.Network.CurrentSim.Region.Name == "")
{
System.Threading.Thread.Sleep(10);
}
WaterHeight = Client.Network.CurrentSim.Region.WaterHeight;
// Initialize the engine
InitializeTransform();
InitializeScene();
InitializeWater();
// Start the timer
CameraUpdateTimer = new Timer(new TimerCallback(SendCameraUpdate), null, 0, 500);
base.Initialize();
}
/// <summary>
///
/// </summary>
private void InitializeTransform()
{
World = Matrix.CreateTranslation(Vector3.Zero);
}
/// <summary>
///
/// </summary>
private void InitializeScene()
{
PrimVertexDeclaration = new VertexDeclaration(Graphics.GraphicsDevice, VertexPositionColor.VertexElements);
//WaterVertexDeclaration = new VertexDeclaration(Graphics.GraphicsDevice, VertexPositionTexture.VertexElements);
WaterVertexDeclaration = new VertexDeclaration(Graphics.GraphicsDevice, VertexPosTexNormalTanBitan.VertexElements);
}
/// <summary>
///
/// </summary>
private void InitializeWater()
{
int i = 0;
short p = 0;
short[] indices = new short[WATER_ROWS * WATER_COLS * 6];
WaterVertexArray = new VertexPosTexNormalTanBitan[(WATER_ROWS + 1) * (WATER_COLS + 1)];
//WaterVertexArray = new VertexPositionTexture[(WATER_ROWS + 1) * (WATER_COLS + 1)];
WaterIndexBuffer = new IndexBuffer(Graphics.GraphicsDevice, typeof(short), WATER_ROWS * WATER_COLS * 6,
ResourceUsage.WriteOnly, ResourceManagementMode.Automatic);
for (int y = 0; y <= WATER_COLS; y++)
{
for (int x = 0; x <= WATER_ROWS; x++)
{
WaterVertexArray[p] = new VertexPosTexNormalTanBitan(
new Vector3(((float)x / WATER_ROWS) * WATER_WIDTH, ((float)y / WATER_COLS) * WATER_HEIGHT, 0),
new Vector2(((float)x / WATER_ROWS), (float)(WATER_COLS - y) / WATER_COLS),
Vector3.UnitZ,
Vector3.UnitX,
Vector3.UnitY
);
if (y != WATER_COLS && x != WATER_ROWS)
{
indices[i++] = p;
indices[i++] = (short)(p + 1);
indices[i++] = (short)(p + WATER_COLS + 1);
indices[i++] = (short)(p + WATER_COLS + 1);
indices[i++] = (short)(p + WATER_COLS + 2);
indices[i++] = (short)(p + 1);
}
p++;
}
}
WaterVertexBuffer = new VertexBuffer(Graphics.GraphicsDevice, typeof(VertexPosTexNormalTanBitan),
WATER_ROWS * WATER_COLS * 6, ResourceUsage.WriteOnly, ResourceManagementMode.Automatic);
WaterVertexBuffer.SetData<VertexPosTexNormalTanBitan>(WaterVertexArray);
WaterIndexBuffer.SetData<short>(indices);
}
/// <summary>
/// Load your graphics content. If loadAllContent is true, you should
/// load content from both ResourceManagementMode pools. Otherwise, just
/// load ResourceManagementMode.Manual content.
/// </summary>
/// <param name="loadAllContent">Which type of content to load.</param>
protected override void LoadGraphicsContent(bool loadAllContent)
{
if (loadAllContent)
{
EffectBasicPrim = Content.Load<Effect>("Shaders/basicprim");
EffectWater = Content.Load<Effect>("Shaders/ocean");
WaterNormalMap = Content.Load<Texture2D>("Textures/wavenormalmap");
WaterReflectionCubemap = Content.Load<TextureCube>("Textures/cubemap");
EffectWater.Parameters["normalMap"].SetValue(WaterNormalMap);
EffectWater.Parameters["cubeMap"].SetValue(WaterReflectionCubemap);
EffectWater.Parameters["bumpHeight"].SetValue(1.4f);
}
// Load any ResourceManagementMode.Manual content here
}
/// <summary>
/// Unload your graphics content. If unloadAllContent is true, you should
/// unload content from both ResourceManagementMode pools. Otherwise, just
/// unload ResourceManagementMode.Manual content. Manual content will get
/// Disposed by the GraphicsDevice during a Reset.
/// </summary>
/// <param name="unloadAllContent">Which type of content to unload.</param>
protected override void UnloadGraphicsContent(bool unloadAllContent)
{
if (unloadAllContent == true)
{
Content.Unload();
}
}
/// <summary>
/// Allows the game to run logic such as updating the world,
/// checking for collisions, gathering input and playing audio.
/// </summary>
/// <param name="gameTime">Provides a snapshot of timing values.</param>
protected override void Update(GameTime gameTime)
{
float elapsed = (float)gameTime.ElapsedRealTime.TotalSeconds;
// Allows the default game to exit on Xbox 360 and Windows
if (GamePad.GetState(PlayerIndex.One).Buttons.Back == ButtonState.Pressed)
this.Exit();
// Update the keyboard and mouse state
UpdateInput();
// Check for keypresses and keys that are currently held down
HandleInput();
// Update the camera matrices and bounding frustum
ViewMatrix = Camera.ViewMatrix;
ViewInverseMatrix = Matrix.Invert(ViewMatrix);
ViewProjectionMatrix = Camera.ViewProjectionMatrix;
Frustum = Camera.Frustum;
if (FPSCounter)
{
// Update the FPS counter
float fps = 1.0f / elapsed;
deltaFPSTime += elapsed;
if (deltaFPSTime > 0.5f)
{
Window.Title = fps.ToString() + " FPS";
deltaFPSTime -= 0.1f;
}
}
base.Update(gameTime);
}
/// <summary>
///
/// </summary>
void UpdateInput()
{
CurrentKeyboardState = Keyboard.GetState();
CurrentMouseState = Mouse.GetState();
// Clear our pressed and released lists.
KeysPressedThisFrame.Clear();
KeysReleasedThisFrame.Clear();
// Interpret pressed key data between arrays to
// figure out just-pressed and just-released keys.
Keys[] currentKeys = CurrentKeyboardState.GetPressedKeys();
// First loop, looking for keys just pressed.
for (int currentKey = 0; currentKey < currentKeys.Length; currentKey++)
{
bool found = false;
for (int previousKey = 0; previousKey < KeysHeldDown.Length; previousKey++)
{
if (currentKeys[currentKey] == KeysHeldDown[previousKey])
{
// The key was pressed both this frame and last; ignore.
found = true;
break;
}
}
if (!found)
{
// The key was pressed this frame, but not last frame; it was just pressed.
KeysPressedThisFrame.Add(currentKeys[currentKey]);
}
}
// Second loop, looking for keys just released.
for (int previousKey = 0; previousKey < KeysHeldDown.Length; previousKey++)
{
bool found = false;
for (int currentKey = 0; currentKey < currentKeys.Length; currentKey++)
{
if (KeysHeldDown[previousKey] == currentKeys[currentKey])
{
// The key was pressed both this frame and last; ignore.
found = true;
break;
}
}
if (!found)
{
// The key was pressed last frame, but not this frame; it was just released.
KeysReleasedThisFrame.Add(KeysHeldDown[previousKey]);
}
}
// Set the held state to the current state.
KeysHeldDown = currentKeys;
}
/// <summary>
///
/// </summary>
protected void HandleInput()
{
//
// Mouse
//
if (CurrentMouseState.LeftButton == ButtonState.Pressed)
{
// Test for intersections with objects in the grid
Ray pickRay = GetPickRay();
// TODO: Can we optimize this, insted of looping through every object there is?
lock (Prims)
{
foreach (PrimVisual prim in Prims.Values)
{
Nullable<float> result = pickRay.Intersects(prim.BoundBox);
if (result.HasValue)
{
// TODO: This should be added to a temporary list of prims that will be
// depth sorted and possibly checked for per-face intersection
prim.Select();
}
}
}
// TODO: If there were no object intersections, test for intersections
// with the water and terrain
}
//
// Keyboard
//
if (CurrentKeyboardState.IsKeyDown(Keys.W))
{
Camera.Translate(new Vector3(0, 0.8f, 0));
}
if (CurrentKeyboardState.IsKeyDown(Keys.A))
{
Camera.Rotate(0.05f);
}
if (CurrentKeyboardState.IsKeyDown(Keys.S))
{
Camera.Translate(new Vector3(0, -1, 0));
}
if (CurrentKeyboardState.IsKeyDown(Keys.D))
{
Camera.Rotate(-0.05f);
}
if (CurrentKeyboardState.IsKeyDown(Keys.PageUp))
{
Camera.Translate(new Vector3(0, 0, 1));
}
if (CurrentKeyboardState.IsKeyDown(Keys.PageDown))
{
Camera.Translate(new Vector3(0, 0, -1));
}
if (KeyPressedThisFrame(Keys.D1))
{
Wireframe = !Wireframe;
}
if (KeyPressedThisFrame(Keys.D2))
{
FPSCounter = !FPSCounter;
}
}
/// <summary>
/// This is called when the game should draw itself.
/// </summary>
/// <param name="gameTime">Provides a snapshot of timing values.</param>
protected override void Draw(GameTime gameTime)
{
Graphics.GraphicsDevice.Clear(Color.CornflowerBlue);
Graphics.GraphicsDevice.RenderState.CullMode = CullMode.CullClockwiseFace;
Graphics.GraphicsDevice.RenderState.FillMode = (Wireframe) ? FillMode.WireFrame : FillMode.Solid;
//graphics.GraphicsDevice.RenderState.MultiSampleAntiAlias = true;
RenderWater(gameTime);
RenderBasicPrims();
base.Draw(gameTime);
}
/// <summary>
///
/// </summary>
protected void RenderWater(GameTime gameTime)
{
Matrix worldOffset = Matrix.CreateTranslation(new Vector3(0, 0, WaterHeight));
Graphics.GraphicsDevice.VertexDeclaration = WaterVertexDeclaration;
Graphics.GraphicsDevice.Vertices[0].SetSource(WaterVertexBuffer, 0, VertexPosTexNormalTanBitan.SizeInBytes);
Graphics.GraphicsDevice.Indices = WaterIndexBuffer;
EffectWater.Begin();
EffectWater.Parameters["worldMatrix"].SetValue(worldOffset);
EffectWater.Parameters["wvpMatrix"].SetValue(worldOffset * ViewProjectionMatrix);
EffectWater.Parameters["worldViewMatrix"].SetValue(worldOffset * ViewMatrix);
EffectWater.Parameters["viewInverseMatrix"].SetValue(ViewInverseMatrix);
EffectWater.Parameters["time"].SetValue((float)gameTime.TotalGameTime.TotalSeconds);
EffectWater.CommitChanges();
foreach (EffectPass pass in EffectWater.CurrentTechnique.Passes)
{
pass.Begin();
Graphics.GraphicsDevice.DrawIndexedPrimitives(PrimitiveType.TriangleList, 0, 0,
(WATER_ROWS + 1) * (WATER_COLS + 1),
0, WATER_COLS * WATER_ROWS * 2);
pass.End();
}
EffectWater.End();
}
/// <summary>
///
/// </summary>
/// <param name="ViewProjectionMatrix"></param>
protected void RenderBasicPrims()
{
Graphics.GraphicsDevice.VertexDeclaration = PrimVertexDeclaration;
EffectBasicPrim.Begin();
lock (Prims)
{
foreach (EffectPass pass in EffectBasicPrim.CurrentTechnique.Passes)
{
pass.Begin();
foreach (PrimVisual prim in Prims.Values)
{
if (prim.Prim.ParentID != 0)
{
if (!Prims.ContainsKey(prim.Prim.ParentID))
{
// We don't have the base position for this child prim, can't render it
continue;
}
else
{
// Child prim in a linkset
// Add the base position of the parent prim and the offset position of this child
LLVector3 llBasePosition = Prims[prim.Prim.ParentID].Prim.Position;
LLQuaternion llBaseRotation = Prims[prim.Prim.ParentID].Prim.Rotation;
Vector3 basePosition = new Vector3(llBasePosition.X, llBasePosition.Y, llBasePosition.Z);
Matrix worldOffset = Matrix.CreateTranslation(basePosition);
Matrix rootRotation = Matrix.CreateFromQuaternion(new Quaternion(llBaseRotation.X,
llBaseRotation.Y, llBaseRotation.Z, llBaseRotation.W));
EffectBasicPrim.Parameters["WorldViewProj"].SetValue(prim.Matrix * rootRotation * worldOffset * ViewProjectionMatrix);
EffectBasicPrim.CommitChanges();
}
}
else
{
// FIXME: We only do this test on unlinked objects for now, since child bounding boxes are
// still broken
//ContainmentType contain = Frustum.Contains(prim.BoundBox);
//if (contain == ContainmentType.Disjoint)
//{
// continue;
//}
// Root prim or not part of a linkset
EffectBasicPrim.Parameters["WorldViewProj"].SetValue(prim.Matrix * ViewProjectionMatrix);
EffectBasicPrim.CommitChanges();
}
Graphics.GraphicsDevice.DrawUserPrimitives<VertexPositionColor>(PrimitiveType.TriangleList,
prim.VertexArray, 0, prim.VertexArray.Length / 3);
}
pass.End();
}
}
EffectBasicPrim.End();
}
/// <summary>
///
/// </summary>
/// <returns></returns>
Ray GetPickRay()
{
int mouseX = CurrentMouseState.X;
int mouseY = CurrentMouseState.Y;
float nearClip = Camera.NearClip;
float farClip = Camera.FarClip;
//System.Console.WriteLine("mouseState.X: " + mouseX + ", mouseState.Y: " + mouseY);
// Determine the mouse position in screen space.
double screenSpaceX = ((float)mouseX / ((float)Window.ClientBounds.Width / 2.0f) - 1.0f) *
((float)Window.ClientBounds.Width / (float)Window.ClientBounds.Height);
double screenSpaceY = (1.0f - (float)mouseY / ((float)Window.ClientBounds.Height / 2.0f));
//System.Console.WriteLine("ScreenSpaceX: " + screenSpaceX + ", ScreenSpaceY: " + screenSpaceY);
// Calculating the tangent in this method is for clarity. Normally, the
// tangent would be calculated only once at start up and recalculated
// if the camera field of view changes.
double viewRatio = Math.Tan(Camera.FOV / 2.0f);
screenSpaceX = screenSpaceX * viewRatio;
screenSpaceY = screenSpaceY * viewRatio;
// Determine the mouse position in camera space on the near clip plane.
Vector3 cameraSpaceNear = new Vector3((float)(screenSpaceX * nearClip),
(float)(screenSpaceY * nearClip), (float)(-nearClip));
// Deetermine the mouse position in camera space on the far clip plane.
Vector3 cameraSpaceFar = new Vector3((float)(screenSpaceX * farClip),
(float)(screenSpaceY * farClip), (float)(-farClip));
Vector3 worldSpaceNear = Vector3.Transform(cameraSpaceNear, ViewInverseMatrix);
Vector3 worldSpaceFar = Vector3.Transform(cameraSpaceFar, ViewInverseMatrix);
// Create a ray from the near clip plane to the far clip plane.
Ray pickRay = new Ray(worldSpaceNear, worldSpaceFar - worldSpaceNear);
return pickRay;
}
/// <summary>
///
/// </summary>
/// <param name="key"></param>
/// <returns></returns>
bool KeyPressedThisFrame(Keys key)
{
if (KeysPressedThisFrame.Contains(key))
{
return true;
}
return false;
}
/// <summary>
///
/// </summary>
/// <param name="key"></param>
/// <returns></returns>
bool KeyReleasedThisFrame(Keys key)
{
if (KeysReleasedThisFrame.Contains(key))
{
return true;
}
return false;
}
/// <summary>
///
/// </summary>
/// <returns></returns>
Keys[] KeysHeldDownThisFrame()
{
return KeysHeldDown;
}
/// <summary>
///
/// </summary>
/// <param name="simulator"></param>
/// <param name="prim"></param>
/// <param name="regionHandle"></param>
/// <param name="timeDilation"></param>
void OnNewPrim(Simulator simulator, PrimObject prim, ulong regionHandle, ushort timeDilation)
{
PrimVisual primVisual = PrimVisual.BuildPrimVisual(prim);
if (primVisual != null &&
(primVisual.GetType() == typeof(PrimVisualBox) ||
primVisual.GetType() == typeof(PrimVisualCylinder)))
{
lock (Prims)
{
if (Prims.ContainsKey(prim.LocalID))
{
Prims.Remove(prim.LocalID);
}
Prims.Add(prim.LocalID, primVisual);
}
}
}
/// <summary>
///
/// </summary>
/// <param name="simulator"></param>
/// <param name="primUpdate"></param>
/// <param name="regionHandle"></param>
/// <param name="timeDilation"></param>
void OnPrimMoved(Simulator simulator, PrimUpdate primUpdate, ulong regionHandle, ushort timeDilation)
{
if (Prims.ContainsKey(primUpdate.LocalID))
{
Prims[primUpdate.LocalID].Update(primUpdate);
}
else
{
Client.Objects.RequestObject(simulator, primUpdate.LocalID);
}
}
/// <summary>
///
/// </summary>
/// <param name="simulator"></param>
/// <param name="localID"></param>
void OnObjectKilled(Simulator simulator, uint localID)
{
lock (Prims)
{
Prims.Remove(localID);
}
}
/// <summary>
///
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
void sceneviewer_Exiting(object sender, EventArgs e)
{
if (Client.Network.Connected)
{
Client.Network.Logout();
}
}
/// <summary>
///
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
void Window_ClientSizeChanged(object sender, EventArgs e)
{
Camera.UpdateProjection(this.Window);
}
/// <summary>
///
/// </summary>
/// <param name="obj"></param>
void SendCameraUpdate(object obj)
{
//Client.Self.UpdateCamera(false);
}
}
}