408 lines
15 KiB
C#
408 lines
15 KiB
C#
using System;
|
|
using System.Collections.Generic;
|
|
using System.Linq;
|
|
using System.Text;
|
|
using System.Windows;
|
|
using System.Windows.Controls;
|
|
using System.Windows.Data;
|
|
using System.Windows.Documents;
|
|
using System.Windows.Input;
|
|
using System.Windows.Media;
|
|
using System.Windows.Media.Imaging;
|
|
using System.Windows.Navigation;
|
|
using System.Windows.Shapes;
|
|
using Aiwaz.Resources;
|
|
using System.Windows.Threading;
|
|
using Aiwaz.Core;
|
|
using Aiwaz.Contracts;
|
|
using System.Runtime.InteropServices;
|
|
using System.Windows.Forms;
|
|
using SlimDX;
|
|
|
|
namespace Aiwaz.Editor.Controls
|
|
{
|
|
/// <summary>
|
|
/// Interaction logic for AiwazViewControl.xaml
|
|
/// </summary>
|
|
public partial class AiwazViewControl : System.Windows.Controls.UserControl
|
|
{
|
|
#region Win32
|
|
|
|
public static Point CorrectGetPosition(Visual relativeTo)
|
|
{
|
|
Win32Point w32Mouse = new Win32Point();
|
|
GetCursorPos(ref w32Mouse);
|
|
return relativeTo.PointFromScreen(new Point(w32Mouse.X, w32Mouse.Y));
|
|
}
|
|
|
|
[StructLayout(LayoutKind.Sequential)]
|
|
internal struct Win32Point
|
|
{
|
|
public Int32 X;
|
|
public Int32 Y;
|
|
};
|
|
|
|
[DllImport("user32.dll")]
|
|
[return: MarshalAs(UnmanagedType.Bool)]
|
|
internal static extern bool GetCursorPos(ref Win32Point pt);
|
|
|
|
#endregion
|
|
|
|
private DateTime lastPerformCalledTime = DateTime.UtcNow;
|
|
private bool rightButtonPressed;
|
|
private bool leftButtonPressed;
|
|
private bool moveThresholdReached;
|
|
private Point lastMousePosition;
|
|
private int desiredFPS = 30;
|
|
private DispatcherTimer renderTimer;
|
|
|
|
private RenderCommandNode mainRootNode;
|
|
private RenderCommandNode rootNode;
|
|
|
|
private Shader selectionShader;
|
|
private RenderCommandNode selectionMarkerNode;
|
|
private PickableResourceInfo selectedResourceInfo;
|
|
|
|
public delegate void PerformCalledDelegate(object sender, TimeSpan elapsedTime);
|
|
public delegate void SelectedResourceChangedDelegate(object sender, PickableResourceInfo newSelectedResourceInfo);
|
|
|
|
public event PerformCalledDelegate PerformCalled;
|
|
public event SelectedResourceChangedDelegate SelectedResourceChanged;
|
|
|
|
public AiwazViewControl()
|
|
{
|
|
InitializeComponent();
|
|
|
|
if (System.ComponentModel.DesignerProperties.GetIsInDesignMode(this))
|
|
return;
|
|
|
|
this.SwapChain = new SwapChain(RenderTarget.Handle);
|
|
this.SwapChain.HasDepthStencilBuffer = true;
|
|
|
|
MainRootNode.Children.Add(this.SwapChain);
|
|
MainRootNode.Children.Add(RootNode);
|
|
|
|
renderTimer = new DispatcherTimer();
|
|
renderTimer.Tick += new EventHandler(delegate(object s, EventArgs a)
|
|
{
|
|
this.Perform();
|
|
});
|
|
renderTimer.Interval = TimeSpan.FromMilliseconds(1000 / 30.0);
|
|
renderTimer.Start();
|
|
|
|
selectionMarkerNode = new RenderCommandNode();
|
|
selectionShader = new Shader(new ShaderParams() { FileName = "Resources/SelectionHighlight.fx" });
|
|
selectionShader.Priority = 128;
|
|
MainRootNode.Children.Add(selectionMarkerNode);
|
|
|
|
this.RenderTarget.MouseDown += new System.Windows.Forms.MouseEventHandler(RenderTarget_MouseButtonDown);
|
|
this.RenderTarget.MouseUp += new System.Windows.Forms.MouseEventHandler(RenderTarget_MouseButtonUp);
|
|
}
|
|
|
|
public ICamera MainCamera
|
|
{
|
|
get
|
|
{
|
|
return FindLastAvailableCamera(MainRootNode);
|
|
}
|
|
}
|
|
|
|
public PickableResourcesDataSource PickableResources { get; set; }
|
|
|
|
public RenderCommandNode MainRootNode
|
|
{
|
|
get
|
|
{
|
|
if (mainRootNode == null)
|
|
{
|
|
mainRootNode = new RenderCommandNode();
|
|
}
|
|
return mainRootNode;
|
|
}
|
|
set
|
|
{
|
|
mainRootNode = value;
|
|
}
|
|
}
|
|
|
|
public SwapChain SwapChain { get; protected set; }
|
|
|
|
public RenderCommandNode RootNode
|
|
{
|
|
get
|
|
{
|
|
if (rootNode == null)
|
|
{
|
|
rootNode = new RenderCommandNode();
|
|
}
|
|
return rootNode;
|
|
}
|
|
set
|
|
{
|
|
mainRootNode.Children.Remove(rootNode);
|
|
rootNode = value;
|
|
mainRootNode.Children.Add(value);
|
|
}
|
|
}
|
|
|
|
public TimeSpan RenderInterval
|
|
{
|
|
get { return renderTimer.Interval; }
|
|
set { renderTimer.Interval = value; }
|
|
}
|
|
|
|
public PickableResourceInfo SelectedResourceInfo
|
|
{
|
|
get
|
|
{
|
|
return selectedResourceInfo;
|
|
}
|
|
|
|
set
|
|
{
|
|
if (selectedResourceInfo != null)
|
|
{
|
|
selectionMarkerNode.Children.Clear();
|
|
selectionMarkerNode.MarkDirty();
|
|
}
|
|
|
|
selectedResourceInfo = value;
|
|
|
|
if (selectedResourceInfo != null)
|
|
{
|
|
selectionMarkerNode.Children.Add(selectionShader);
|
|
if (selectedResourceInfo.Transformation != null)
|
|
selectionMarkerNode.Children.Add(selectedResourceInfo.Transformation);
|
|
if (selectedResourceInfo.Resource != null)
|
|
selectionMarkerNode.Children.Add(selectedResourceInfo.Resource);
|
|
}
|
|
|
|
if (SelectedResourceChanged != null)
|
|
SelectedResourceChanged(this, selectedResourceInfo);
|
|
}
|
|
}
|
|
|
|
public int DesiredFPS
|
|
{
|
|
get
|
|
{
|
|
return desiredFPS;
|
|
}
|
|
set
|
|
{
|
|
desiredFPS = value;
|
|
if (desiredFPS <= 0)
|
|
renderTimer.Interval = TimeSpan.FromMilliseconds(0);
|
|
else
|
|
renderTimer.Interval = TimeSpan.FromMilliseconds(1000 / (double)desiredFPS);
|
|
}
|
|
}
|
|
|
|
private ICamera FindLastAvailableCamera(Resource rootNode)
|
|
{
|
|
ICamera lastFoundCamera = null;
|
|
if (rootNode.Children != null)
|
|
foreach (var child in rootNode.Children)
|
|
{
|
|
if (child is ICamera)
|
|
lastFoundCamera = child as ICamera;
|
|
if (child is Resource)
|
|
lastFoundCamera = this.FindLastAvailableCamera(child as Resource) ?? lastFoundCamera;
|
|
}
|
|
return lastFoundCamera;
|
|
}
|
|
|
|
protected void Perform()
|
|
{
|
|
mainRootNode.Update(false);
|
|
mainRootNode.ProcessCommands();
|
|
this.SwapChain.Present();
|
|
|
|
var deltaTime = DateTime.UtcNow - lastPerformCalledTime;
|
|
this.PerformCalledInternal(deltaTime);
|
|
if (PerformCalled != null)
|
|
PerformCalled(this, deltaTime);
|
|
|
|
lastPerformCalledTime = DateTime.UtcNow;
|
|
}
|
|
|
|
private void RenderTarget_Resize(object sender, EventArgs e)
|
|
{
|
|
if (System.ComponentModel.DesignerProperties.GetIsInDesignMode(this))
|
|
return;
|
|
this.SwapChain.Resize((int)this.Width, (int)this.Height);
|
|
}
|
|
|
|
private void RenderTarget_MouseButtonUp(object sender, System.Windows.Forms.MouseEventArgs e)
|
|
{
|
|
if (e.Button == MouseButtons.Right)
|
|
rightButtonPressed = false;
|
|
else if (e.Button == MouseButtons.Left)
|
|
leftButtonPressed = false;
|
|
|
|
lastMousePosition = CorrectGetPosition(this);
|
|
if (!moveThresholdReached)
|
|
{
|
|
if (e.Button == MouseButtons.Right)
|
|
{
|
|
if (this.ContextMenu != null)
|
|
this.ContextMenu.IsOpen = true;
|
|
}
|
|
else if (e.Button == MouseButtons.Left)
|
|
{
|
|
if (PickableResources != null)
|
|
{
|
|
var mainCamera = this.MainCamera;
|
|
|
|
int width = this.SwapChain.ViewPort.Width;
|
|
int height = this.SwapChain.ViewPort.Height;
|
|
|
|
var viewProj = mainCamera.ViewMatrix * mainCamera.ProjectionMatrix;
|
|
|
|
Vector3 ZNearPlane = Vector3.Unproject(new Vector3((float)lastMousePosition.X, (float)lastMousePosition.Y, 0), 0, 0, width, height,
|
|
this.SwapChain.ViewPort.MinDepth, this.SwapChain.ViewPort.MaxDepth, viewProj);
|
|
Vector3 ZFarPlane = Vector3.Unproject(new Vector3((float)lastMousePosition.X, (float)lastMousePosition.Y, 1), 0, 0, width, height,
|
|
this.SwapChain.ViewPort.MinDepth, this.SwapChain.ViewPort.MaxDepth, viewProj);
|
|
|
|
Vector3 direction = ZFarPlane - ZNearPlane;
|
|
direction.Normalize();
|
|
|
|
Ray ray = new Ray(ZNearPlane, direction);
|
|
|
|
PickableResourceInfo winnerResourceInfo = this.SelectedResourceInfo;
|
|
float distance = float.MaxValue;
|
|
foreach (var pickable in PickableResources)
|
|
{
|
|
float newDistance;
|
|
if (pickable.PickHull.TryPick(ray, pickable.Transformation.WorldMatrix, out newDistance)
|
|
&& newDistance < distance)
|
|
{
|
|
winnerResourceInfo = pickable;
|
|
distance = newDistance;
|
|
}
|
|
}
|
|
|
|
if (this.SelectedResourceInfo != null && winnerResourceInfo != null && this.SelectedResourceInfo.Resource == winnerResourceInfo.Resource)
|
|
winnerResourceInfo = null;
|
|
|
|
this.SelectedResourceInfo = winnerResourceInfo;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (!leftButtonPressed && !rightButtonPressed)
|
|
{
|
|
renderTimer.Interval = TimeSpan.FromMilliseconds(1000 / (double)desiredFPS);
|
|
moveThresholdReached = false;
|
|
}
|
|
}
|
|
|
|
private void RenderTarget_MouseButtonDown(object sender, System.Windows.Forms.MouseEventArgs e)
|
|
{
|
|
if (e.Button == MouseButtons.Right)
|
|
rightButtonPressed = true;
|
|
else if (e.Button == MouseButtons.Left)
|
|
leftButtonPressed = true;
|
|
|
|
if (leftButtonPressed || rightButtonPressed)
|
|
renderTimer.Interval = TimeSpan.FromMilliseconds(0);
|
|
|
|
lastMousePosition = CorrectGetPosition(this);
|
|
}
|
|
|
|
private void PerformCalledInternal(TimeSpan elapsedTime)
|
|
{
|
|
var currentMousePosition = CorrectGetPosition(this);
|
|
var deltaTime = Math.Max(1.0, elapsedTime.TotalSeconds);
|
|
var deltaMouse = currentMousePosition - lastMousePosition;
|
|
lastMousePosition = currentMousePosition;
|
|
|
|
var power = 1.7;
|
|
if (rightButtonPressed && !leftButtonPressed)
|
|
power = 1.25;
|
|
|
|
deltaMouse.X = Math.Pow(Math.Abs(deltaMouse.X), power) * Math.Sign(deltaMouse.X) * deltaTime;
|
|
deltaMouse.Y = Math.Pow(Math.Abs(deltaMouse.Y), power) * Math.Sign(deltaMouse.Y) * deltaTime;
|
|
|
|
if (!leftButtonPressed && !rightButtonPressed)
|
|
return;
|
|
|
|
moveThresholdReached = moveThresholdReached || deltaMouse.Length >= 3.0;
|
|
if (!moveThresholdReached)
|
|
return;
|
|
|
|
var mainCamera = this.MainCamera as Transformation;
|
|
|
|
if (rightButtonPressed && leftButtonPressed)
|
|
{
|
|
mainCamera.LocalPosition += mainCamera.LocalUpDirection * -(float)deltaMouse.Y * 0.005f +
|
|
mainCamera.LocalRightDirection * (float)deltaMouse.X * 0.005f;
|
|
}
|
|
else if (leftButtonPressed)
|
|
{
|
|
mainCamera.LocalPosition += mainCamera.LocalDirection * -(float)deltaMouse.Y * 0.005f +
|
|
mainCamera.LocalRightDirection * (float)deltaMouse.X * 0.005f;
|
|
}
|
|
else if (rightButtonPressed)
|
|
{
|
|
var yawPitchRoll = mainCamera.LocalRotationYPR;
|
|
yawPitchRoll.X += (float)deltaMouse.X * 0.005f;
|
|
yawPitchRoll.Y += (float)deltaMouse.Y * 0.005f;
|
|
if (yawPitchRoll.Y > Math.PI / 2)
|
|
yawPitchRoll.Y = (float)Math.PI / 2;
|
|
else if (yawPitchRoll.Y < -Math.PI / 2)
|
|
yawPitchRoll.Y = -(float)Math.PI / 2;
|
|
mainCamera.LocalRotationYPR = yawPitchRoll;
|
|
}
|
|
}
|
|
}
|
|
|
|
public class PickableResourcesDataSource : List<PickableResourceInfo>
|
|
{
|
|
public static PickableResourcesDataSource CreateFromResource(Resource rootNode)
|
|
{
|
|
var pickablesDataResource = new PickableResourcesDataSource();
|
|
|
|
List<KeyValuePair<Transformation, IGeometryBuffer>> pickables = new List<KeyValuePair<Transformation, IGeometryBuffer>>();
|
|
FindAllPickables(rootNode, null, pickables);
|
|
|
|
foreach (var pickable in pickables)
|
|
{
|
|
pickablesDataResource.Add(new PickableResourceInfo()
|
|
{
|
|
PickHull = pickable.Value.PickHull,
|
|
Transformation = pickable.Key,
|
|
Resource = pickable.Value as IResource
|
|
});
|
|
}
|
|
return pickablesDataResource;
|
|
}
|
|
|
|
private static void FindAllPickables(Resource rootNode, Transformation lastTransformation, List<KeyValuePair<Transformation, IGeometryBuffer>> pickables)
|
|
{
|
|
if (rootNode.Children != null)
|
|
foreach (var child in rootNode.Children)
|
|
{
|
|
if (child is Transformation)
|
|
lastTransformation = child as Transformation;
|
|
if (child is IGeometryBuffer && lastTransformation != null)
|
|
{
|
|
var geoBuffer = child as IGeometryBuffer;
|
|
if (geoBuffer.IsPickable)
|
|
pickables.Add(new KeyValuePair<Transformation, IGeometryBuffer>(lastTransformation, geoBuffer));
|
|
}
|
|
if (child is Resource)
|
|
FindAllPickables(child as Resource, lastTransformation, pickables);
|
|
}
|
|
}
|
|
|
|
}
|
|
|
|
public class PickableResourceInfo
|
|
{
|
|
public IResource Resource;
|
|
public Transformation Transformation;
|
|
public IPickHull PickHull;
|
|
}
|
|
}
|