CodeWalker/World/Widget.cs
2017-09-21 20:33:05 +10:00

942 lines
34 KiB
C#

using CodeWalker.Rendering;
using SharpDX;
using SharpDX.Direct3D11;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace CodeWalker.World
{
public abstract class Widget
{
public bool Visible { get; set; } = true;
public abstract void Update(Camera cam);
public abstract void Render(DeviceContext context, Camera cam, WidgetShader shader);
protected bool GetAxisRayHit(Vector3 ax1, Vector3 ax2, Vector3 camrel, Ray ray, out Vector3 pos)
{
//helper method for double sided ray/plane intersection
Vector3 pn = Vector3.Cross(ax1, ax2);
Plane p = new Plane(camrel, pn);
if (ray.Intersects(ref p, out pos))
{
return true;
}
else
{
p = new Plane(camrel, -pn);
if (ray.Intersects(ref p, out pos))
{
return true;
}
}
pos = Vector3.Zero;
return false;
}
protected int GetAxisIndex(WidgetAxis axis)
{
switch (axis)
{
default:
case WidgetAxis.X: return 0;
case WidgetAxis.Y: return 1;
case WidgetAxis.Z: return 2;
}
}
protected float GetWorldSize(float pxsize, float dist, Camera cam)
{
float sssize = pxsize / cam.Height;
float size = sssize * dist;
if (cam.IsMapView || cam.IsOrthographic)
{
size = sssize * cam.OrthographicSize;
}
return size;
}
}
public class TransformWidget : Widget
{
public DefaultWidget DefaultWidget { get; set; } = new DefaultWidget();
public PositionWidget PositionWidget { get; set; } = new PositionWidget();
public RotationWidget RotationWidget { get; set; } = new RotationWidget();
public ScaleWidget ScaleWidget { get; set; } = new ScaleWidget();
public bool ObjectSpace
{
get { return PositionWidget.ObjectSpace; }
set
{
DefaultWidget.ObjectSpace = value;
PositionWidget.ObjectSpace = value;
RotationWidget.ObjectSpace = value;
}
}
public Vector3 Position
{
get { return PositionWidget.Position; }
set
{
PositionWidget.Position = value;
RotationWidget.Position = value;
ScaleWidget.Position = value;
DefaultWidget.Position = value;
}
}
public Quaternion Rotation
{
get { return RotationWidget.Rotation; }
set
{
PositionWidget.Rotation = value;
RotationWidget.Rotation = value;
ScaleWidget.Rotation = value;
DefaultWidget.Rotation = value;
}
}
public Vector3 Scale
{
get { return ScaleWidget.Scale; }
set { ScaleWidget.Scale = value; }
}
public WidgetMode Mode { get; set; } = WidgetMode.Default;
public event WidgetPositionChangeHandler OnPositionChange;
public event WidgetRotationChangeHandler OnRotationChange;
public event WidgetScaleChangeHandler OnScaleChange;
public bool IsUnderMouse
{
get
{
switch (Mode)
{
default:
case WidgetMode.Default: return false;
case WidgetMode.Position: return (PositionWidget.MousedAxis != WidgetAxis.None);
case WidgetMode.Rotation: return (RotationWidget.MousedAxis != WidgetAxis.None);
case WidgetMode.Scale: return (ScaleWidget.MousedAxis != WidgetAxis.None);
}
}
}
public bool IsDragging
{
get
{
switch (Mode)
{
default:
case WidgetMode.Default: return false;
case WidgetMode.Position: return PositionWidget.IsDragging;
case WidgetMode.Rotation: return RotationWidget.IsDragging;
case WidgetMode.Scale: return ScaleWidget.IsDragging;
}
}
set
{
switch (Mode)
{
case WidgetMode.Position: PositionWidget.IsDragging = value; break;
case WidgetMode.Rotation: RotationWidget.IsDragging = value; break;
case WidgetMode.Scale: ScaleWidget.IsDragging = value; break;
}
}
}
public TransformWidget()
{
PositionWidget.OnPositionChange += PositionWidget_OnPositionChange;
RotationWidget.OnRotationChange += RotationWidget_OnRotationChange;
ScaleWidget.OnScaleChange += ScaleWidget_OnScaleChange;
}
private void PositionWidget_OnPositionChange(Vector3 newpos, Vector3 oldpos)
{
DefaultWidget.Position = newpos;
RotationWidget.Position = newpos;
ScaleWidget.Position = newpos;
OnPositionChange?.Invoke(newpos, oldpos);
}
private void RotationWidget_OnRotationChange(Quaternion newrot, Quaternion oldrot)
{
DefaultWidget.Rotation = newrot;
PositionWidget.Rotation = newrot;
ScaleWidget.Rotation = newrot;
OnRotationChange?.Invoke(newrot, oldrot);
}
private void ScaleWidget_OnScaleChange(Vector3 newscale, Vector3 oldscale)
{
OnScaleChange?.Invoke(newscale, oldscale);
}
public override void Update(Camera cam)
{
if (!Visible) return;
switch (Mode)
{
case WidgetMode.Position:
PositionWidget.Update(cam);
break;
case WidgetMode.Rotation:
RotationWidget.Update(cam);
break;
case WidgetMode.Scale:
ScaleWidget.Update(cam);
break;
case WidgetMode.Default:
DefaultWidget.Update(cam);
break;
}
}
public override void Render(DeviceContext context, Camera cam, WidgetShader shader)
{
if (!Visible) return;
switch (Mode)
{
case WidgetMode.Position:
PositionWidget.Render(context, cam, shader);
break;
case WidgetMode.Rotation:
RotationWidget.Render(context, cam, shader);
break;
case WidgetMode.Scale:
ScaleWidget.Render(context, cam, shader);
break;
case WidgetMode.Default:
DefaultWidget.Render(context, cam, shader);
break;
}
}
}
public class DefaultWidget : Widget
{
public Vector3 Position { get; set; }
public Quaternion Rotation { get; set; } = Quaternion.Identity;
public float Size { get; set; } = 70.0f;
public bool ObjectSpace { get; set; } = true;
public override void Render(DeviceContext context, Camera cam, WidgetShader shader)
{
if (!Visible) return;
Vector3 camrel = Position - cam.Position;
float dist = camrel.Length();
float size = GetWorldSize(Size, dist, cam);
var ori = ObjectSpace ? Rotation : Quaternion.Identity;
shader.DrawDefaultWidget(context, cam, camrel, ori, size);
}
public override void Update(Camera cam)
{
//nothing to update here...
}
}
public class PositionWidget : Widget
{
public Vector3 Position { get; set; }
public Quaternion Rotation { get; set; } = Quaternion.Identity;
public event WidgetPositionChangeHandler OnPositionChange;
public WidgetAxis MousedAxis { get; set; } = WidgetAxis.None;
public WidgetAxis DraggedAxis { get; set; } = WidgetAxis.None;
public Vector3 DraggedAxisDir { get; set; }
public Vector3 DraggedAxisSideDir { get; set; }
public Vector3 DragStartPosition { get; set; }
public bool IsDragging { get; set; }
private bool WasDragging = false;
private Vector3 DragStartVec; //projected onto the plane/axis being dragged
public float Size { get; set; } = 90.0f;
public bool ObjectSpace { get; set; } = true;
public PositionWidget()
{
}
public override void Render(DeviceContext context, Camera cam, WidgetShader shader)
{
if (!Visible) return;
Vector3 camrel = Position - cam.Position;
float dist = camrel.Length();
float size = GetWorldSize(Size, dist, cam);
WidgetAxis ax = IsDragging ? DraggedAxis : MousedAxis;
var ori = ObjectSpace ? Rotation : Quaternion.Identity;
shader.DrawPositionWidget(context, cam, camrel, ori, size, ax);
}
public override void Update(Camera cam)
{
if (!Visible) return;
var ori = ObjectSpace ? Rotation : Quaternion.Identity;
Vector3 xdir = Vector3.UnitX;
Vector3 ydir = Vector3.UnitY;
Vector3 zdir = Vector3.UnitZ;
Vector3[] axes = { xdir, ydir, zdir };
Vector3[] sides1 = { ydir, zdir, xdir };
Vector3[] sides2 = { zdir, xdir, ydir };
WidgetAxis[] sideax1 = { WidgetAxis.Y, WidgetAxis.Z, WidgetAxis.X };
WidgetAxis[] sideax2 = { WidgetAxis.Z, WidgetAxis.X, WidgetAxis.Y };
Quaternion iori = Quaternion.Invert(ori);
Vector3 camrel = iori.Multiply(Position - cam.Position);
Vector3 cdir = Vector3.Normalize(camrel);
Ray ray = cam.MouseRay;
ray.Position = iori.Multiply(ray.Position);
ray.Direction = iori.Multiply(ray.Direction);
float dist = camrel.Length();
float size = GetWorldSize(Size, dist, cam);
float linestart = 0.2f * size;
float lineend = 1.0f * size;
float sideval = 0.4f * size;
float arrowstart = 1.0f * size;
float arrowend = 1.33f * size;
float arrowrad = 0.06f * size;
float axhitrad = 0.07f * size;
float axhitstart = 0.2f * size;
float axhitend = 1.33f * size;
float sidehitend = 0.5f * size;
float sidehitstart = 0.25f * size;
float allhitrad = 0.07f * size;
//test for single and double axes hits
BoundingBox bb = new BoundingBox();
BoundingBox bb2 = new BoundingBox();
float hitd = float.MaxValue;
float d, d2;
WidgetAxis hitax = WidgetAxis.None;
for (int i = 0; i < 3; i++)
{
WidgetAxis ax = (WidgetAxis)(1 << i);
Vector3 s = sides1[i] * axhitrad + sides2[i] * axhitrad;
bb.Minimum = camrel - s + axes[i] * axhitstart;
bb.Maximum = camrel + s + axes[i] * axhitend;
if (ray.Intersects(ref bb, out d)) //single axis
{
if (d < hitd)
{
hitd = d;
hitax = ax;
}
}
for (int n = i + 1; n < 3; n++)
{
//double axis hit test - don't hit if in the central area (L shape hit area)
WidgetAxis ax2 = (WidgetAxis)(1 << n);
bb.Minimum = camrel;
bb.Maximum = camrel + axes[i] * sidehitend + axes[n] * sidehitend;
bb2.Minimum = camrel;
bb2.Maximum = camrel + axes[i] * sidehitstart + axes[n] * sidehitstart;
if (ray.Intersects(ref bb, out d) && !ray.Intersects(ref bb2, out d2))
{
if (d < hitd)
{
hitd = d;
hitax = ax | ax2;
}
}
}
}
//small box at the center for all axes hit.
Vector3 ss = (axes[0] + axes[1] + axes[2]) * allhitrad;
bb.Minimum = camrel - ss;
bb.Maximum = camrel + ss;
if (ray.Intersects(ref bb, out d))
{
if (d < hitd)
{
hitd = d;
hitax = WidgetAxis.XYZ;
}
}
MousedAxis = hitax;
if (IsDragging && !WasDragging)
{
//drag start. mark the start vector and axes
DraggedAxis = MousedAxis;
DragStartPosition = Position;
DraggedAxisDir = axes[0];
DraggedAxisSideDir = axes[1];
switch (DraggedAxis)
{
case WidgetAxis.XZ: DraggedAxisSideDir = axes[2]; break;
case WidgetAxis.YZ: DraggedAxisDir = axes[1]; DraggedAxisSideDir = axes[2]; break;
case WidgetAxis.Y: DraggedAxisDir = axes[1]; break;
case WidgetAxis.Z: DraggedAxisDir = axes[2]; break;
}
switch (DraggedAxis) //find the best second axis to use, for single axis motion only.
{
case WidgetAxis.X:
case WidgetAxis.Y:
case WidgetAxis.Z:
int curax = GetAxisIndex(DraggedAxis);
int minax = 0;
float mindp = float.MaxValue;
for (int i = 0; i < 3; i++)
{
if (i != curax)
{
float dp = Math.Abs(Vector3.Dot(cdir, axes[i]));
if (dp < mindp)
{
mindp = dp;
minax = i;
}
}
}
DraggedAxisSideDir = axes[minax];
break;
}
if (DraggedAxis == WidgetAxis.XYZ)
{
//all axes, move in the screen plane
float ad1 = Math.Abs(Vector3.Dot(cdir, Vector3.UnitY));
float ad2 = Math.Abs(Vector3.Dot(cdir, Vector3.UnitZ));
DraggedAxisDir = Vector3.Normalize(Vector3.Cross(cdir, (ad1 > ad2) ? Vector3.UnitY : Vector3.UnitZ));
DraggedAxisSideDir = Vector3.Normalize(Vector3.Cross(cdir, DraggedAxisDir));
}
bool hit = GetAxisRayHit(DraggedAxisDir, DraggedAxisSideDir, camrel, ray, out DragStartVec);
if ((MousedAxis == WidgetAxis.None) || !hit)
{
IsDragging = false;
}
}
else if (IsDragging)
{
//continue drag.
Vector3 newvec;
bool hit = GetAxisRayHit(DraggedAxisDir, DraggedAxisSideDir, camrel, ray, out newvec);
if (hit)
{
Vector3 diff = newvec - DragStartVec;
switch (DraggedAxis)
{
case WidgetAxis.X: diff.Y = 0; diff.Z = 0; break;
case WidgetAxis.Y: diff.X = 0; diff.Z = 0; break;
case WidgetAxis.Z: diff.X = 0; diff.Y = 0; break;
}
if (ObjectSpace)
{
diff = ori.Multiply(diff);
}
if (diff.Length() < 10000.0f) //limit movement in one go, to avoid losing the widget...
{
Vector3 oldpos = Position;
Position = DragStartPosition + diff;
if (Position != oldpos)
{
OnPositionChange?.Invoke(Position, oldpos);
}
}
}
}
WasDragging = IsDragging;
}
}
public class RotationWidget : Widget
{
public Vector3 Position { get; set; }
public Quaternion Rotation { get; set; } = Quaternion.Identity;
public event WidgetRotationChangeHandler OnRotationChange;
public WidgetAxis MousedAxis { get; set; } = WidgetAxis.None;
public WidgetAxis DraggedAxis { get; set; } = WidgetAxis.None;
public Vector3 DraggedAxisDir { get; set; }
public Vector3 DraggedAxisSideDir1 { get; set; }
public Vector3 DraggedAxisSideDir2 { get; set; }
public Quaternion DragStartRotation { get; set; }
public bool IsDragging { get; set; }
private bool WasDragging = false;
private Vector3 DragStartVec; //projected onto the plane/axis being dragged
public float Size { get; set; } = 100.0f;
public bool ObjectSpace { get; set; } = true;
public WidgetAxis EnableAxes { get; set; } = WidgetAxis.XYZ;
public RotationWidget()
{
}
public override void Render(DeviceContext context, Camera cam, WidgetShader shader)
{
if (!Visible) return;
Vector3 camrel = Position - cam.Position;
float dist = camrel.Length();
float size = GetWorldSize(Size, dist, cam);
WidgetAxis ax = IsDragging ? DraggedAxis : MousedAxis;
var ori = ObjectSpace ? Rotation : Quaternion.Identity;
shader.DrawRotationWidget(context, cam, camrel, ori, size, ax, EnableAxes);
}
public override void Update(Camera cam)
{
if (!Visible) return;
var ori = ObjectSpace ? Rotation : Quaternion.Identity;
Vector3 xdir = ori.Multiply(Vector3.UnitX);
Vector3 ydir = ori.Multiply(Vector3.UnitY);
Vector3 zdir = ori.Multiply(Vector3.UnitZ);
Vector3[] axes = { xdir, ydir, zdir };
Vector3[] sides1 = { ydir, zdir, xdir };
Vector3[] sides2 = { zdir, xdir, ydir };
Ray ray = cam.MouseRay;
Vector3 camrel = Position - cam.Position;
float dist = camrel.Length();
float size = GetWorldSize(Size, dist, cam);
float ocircsize = 1.0f * size; //outer ring radius
float icircsize = 0.75f * size; //inner ring radius
float icircthick = 0.2f * size; //inner ring hit width
float ocircthick = 0.13f * size;//outer ring hit width
float icirchiti = icircsize - icircthick;
float icirchito = icircsize + icircthick;
float ocirchiti = ocircsize - ocircthick;
float ocirchito = ocircsize + ocircthick;
//test for the main axes hits
float cullvalue = -0.18f;
float hitd = float.MaxValue;
Vector3 hitp = camrel;
Vector3 hitrel = Vector3.Zero;
WidgetAxis hitax = WidgetAxis.None;
Vector3 hitaxd = Vector3.UnitX;
Vector3 hitax1 = Vector3.UnitY;
Vector3 hitax2 = Vector3.UnitZ;
for (int i = 0; i < 3; i++)
{
WidgetAxis ax = (WidgetAxis)(1 << i);
if ((ax & EnableAxes) == 0) continue;
Vector3 s1 = sides1[i];
Vector3 s2 = sides2[i];
if (GetAxisRayHit(s1, s2, camrel, ray, out hitp))
{
float hitdist = hitp.Length();
float hitreld = (camrel.Length() - hitdist) / size;
if (hitreld < cullvalue) continue; //this hit was at the backside of the widget; ignore
Vector3 thitrel = hitp - camrel;
float hitrad = thitrel.Length();
if ((hitrad > icirchiti) && (hitrad < icirchito) && (hitdist < hitd))
{
hitd = hitdist;
hitax = ax;
hitax1 = s1;
hitax2 = s2;
hitrel = thitrel;
hitaxd = axes[i];
}
}
}
//test for the outer ring hit
if ((hitax == WidgetAxis.None) && (EnableAxes == WidgetAxis.XYZ))
{
Vector3 sdir = Vector3.Normalize(camrel);
//if (cam.IsMapView || cam.IsOrthographic)
//{
// sdir = cam.ViewDirection;
//}
float ad1 = Math.Abs(Vector3.Dot(sdir, Vector3.UnitY));
float ad2 = Math.Abs(Vector3.Dot(sdir, Vector3.UnitZ));
Vector3 ax1 = Vector3.Normalize(Vector3.Cross(sdir, (ad1 > ad2) ? Vector3.UnitY : Vector3.UnitZ));
Vector3 ax2 = Vector3.Normalize(Vector3.Cross(sdir, ax1));
if (GetAxisRayHit(ax1, ax2, camrel, ray, out hitp))
{
Vector3 thitrel = hitp - camrel;
float hitrad = thitrel.Length();
if ((hitrad > ocirchiti) && (hitrad < ocirchito))
{
hitax = WidgetAxis.XYZ;
hitax1 = ax1;
hitax2 = ax2;
hitrel = thitrel;
hitaxd = sdir;
}
}
}
MousedAxis = hitax;
if (IsDragging && !WasDragging)
{
//drag start. mark the start vector and axes
DraggedAxis = MousedAxis;
DragStartRotation = Rotation;
DraggedAxisDir = hitaxd;
DraggedAxisSideDir1 = hitax1;
DraggedAxisSideDir2 = hitax2;
bool hit = GetAxisRayHit(DraggedAxisSideDir1, DraggedAxisSideDir2, camrel, ray, out DragStartVec);
if ((MousedAxis == WidgetAxis.None) || !hit)
{
IsDragging = false;
}
}
else if (IsDragging)
{
//continue drag.
Vector3 newvec;
bool hit = GetAxisRayHit(DraggedAxisSideDir1, DraggedAxisSideDir2, camrel, ray, out newvec);
if (hit)
{
Vector3 diff = newvec - DragStartVec;
if (diff.Length() < 10000.0f) //put some limit to the plane intersection...
{
Vector3 nv = Vector3.Normalize(newvec - camrel);
Vector3 ov = Vector3.Normalize(DragStartVec - camrel);
float na = AngleOnAxes(nv, DraggedAxisSideDir1, DraggedAxisSideDir2);
float oa = AngleOnAxes(ov, DraggedAxisSideDir1, DraggedAxisSideDir2);
float a = na - oa;
Quaternion rot = Quaternion.RotationAxis(DraggedAxisDir, a);
Quaternion oldrot = Rotation;
Rotation = Quaternion.Normalize(Quaternion.Multiply(rot, DragStartRotation));
if (Rotation != oldrot)
{
OnRotationChange?.Invoke(Rotation, oldrot);
}
}
}
}
WasDragging = IsDragging;
}
private float AngleOnAxes(Vector3 v, Vector3 ax1, Vector3 ax2)
{
float d1 = Vector3.Dot(v, ax1);
float d2 = Vector3.Dot(v, ax2);
return (float)Math.Atan2(d2, d1);
}
}
public class ScaleWidget : Widget
{
public Vector3 Position { get; set; }
public Quaternion Rotation { get; set; } = Quaternion.Identity;
public Vector3 Scale { get; set; } = Vector3.One;
public event WidgetScaleChangeHandler OnScaleChange;
public WidgetAxis MousedAxis { get; set; } = WidgetAxis.None;
public WidgetAxis DraggedAxis { get; set; } = WidgetAxis.None;
public Vector3 DraggedAxisDir { get; set; }
public Vector3 DraggedAxisSideDir { get; set; }
public Vector3 DragStartScale { get; set; }
public bool IsDragging { get; set; }
private bool WasDragging = false;
private Vector3 DragStartVec; //projected onto the plane/axis being dragged
public bool LockXY { get; set; } = true;
public float Size { get; set; } = 90.0f;
public ScaleWidget()
{
}
public override void Render(DeviceContext context, Camera cam, WidgetShader shader)
{
if (!Visible) return;
Vector3 camrel = Position - cam.Position;
float dist = camrel.Length();
float size = GetWorldSize(Size, dist, cam);
WidgetAxis ax = IsDragging ? DraggedAxis : MousedAxis;
var ori = Rotation; //scale is always in object space.
shader.DrawScaleWidget(context, cam, camrel, ori, size, ax);
}
public override void Update(Camera cam)
{
if (!Visible) return;
var ori = Rotation;// : Quaternion.Identity;
Vector3 xdir = Vector3.UnitX;
Vector3 ydir = Vector3.UnitY;
Vector3 zdir = Vector3.UnitZ;
Vector3[] axes = { xdir, ydir, zdir };
Vector3[] sides1 = { ydir, zdir, xdir };
Vector3[] sides2 = { zdir, xdir, ydir };
WidgetAxis[] sideax1 = { WidgetAxis.Y, WidgetAxis.Z, WidgetAxis.X };
WidgetAxis[] sideax2 = { WidgetAxis.Z, WidgetAxis.X, WidgetAxis.Y };
Quaternion iori = Quaternion.Invert(ori);
Vector3 camrel = iori.Multiply(Position - cam.Position);
Vector3 cdir = Vector3.Normalize(camrel);
Ray ray = cam.MouseRay;
ray.Position = iori.Multiply(ray.Position);
ray.Direction = iori.Multiply(ray.Direction);
float dist = camrel.Length();
float size = GetWorldSize(Size, dist, cam);
float axhitrad = 0.09f * size;
float axhitstart = 0.4f * size;
float axhitend = 1.33f * size;
float innertri = 0.7f * size;
float outertri = 1.0f * size;
//test for single and double axes hits
BoundingBox bb = new BoundingBox();
float hitd = float.MaxValue;
float d;
Vector3 hitp;
WidgetAxis hitax = WidgetAxis.None;
for (int i = 0; i < 3; i++)
{
WidgetAxis ax = (WidgetAxis)(1 << i);
Vector3 s = sides1[i] * axhitrad + sides2[i] * axhitrad;
bb.Minimum = camrel - s + axes[i] * axhitstart;
bb.Maximum = camrel + s + axes[i] * axhitend;
if (ray.Intersects(ref bb, out d)) //single axis
{
if (d < hitd)
{
hitd = d;
hitax = ax;
}
}
Vector3 s1 = axes[i];
Vector3 s2 = sides1[i];
if (GetAxisRayHit(s1, s2, camrel, ray, out hitp))
{
//test if hitp is within the inner triangle - uniform scale
//test if hitp is within the outer triangle - 2 axes scale
float hitpl = hitp.Length();
if (hitpl > hitd) continue;
Vector3 hitrel = hitp - camrel;
float d1 = Vector3.Dot(hitrel, s1);
float d2 = Vector3.Dot(hitrel, s2);
if ((d1 > 0) && (d2 > 0))
{
if ((d1 < innertri) && (d2 < innertri) && ((d1 + d2) < innertri))
{
hitd = hitpl;
hitax = WidgetAxis.XYZ;
}
else if ((d1 < outertri) && (d2 < outertri) && ((d1 + d2) < outertri))
{
hitd = hitpl;
hitax = ax | sideax1[i];
}
}
}
}
if (LockXY)
{
switch (hitax)
{
case WidgetAxis.X:
case WidgetAxis.Y:
hitax = WidgetAxis.XY;
break;
case WidgetAxis.XZ:
case WidgetAxis.YZ:
hitax = WidgetAxis.XYZ;
break;
}
}
MousedAxis = hitax;
if (IsDragging && !WasDragging)
{
//drag start. mark the start vector and axes
DraggedAxis = MousedAxis;
DragStartScale = Scale;
DraggedAxisDir = axes[0];
DraggedAxisSideDir = axes[1];
switch (DraggedAxis)
{
case WidgetAxis.XZ: DraggedAxisSideDir = axes[2]; break;
case WidgetAxis.YZ: DraggedAxisDir = axes[1]; DraggedAxisSideDir = axes[2]; break;
case WidgetAxis.Y: DraggedAxisDir = axes[1]; break;
case WidgetAxis.Z: DraggedAxisDir = axes[2]; break;
}
switch (DraggedAxis) //find the best second axis to use, for single axis motion only.
{
case WidgetAxis.X:
case WidgetAxis.Y:
case WidgetAxis.Z:
int curax = GetAxisIndex(DraggedAxis);
int minax = 0;
float mindp = float.MaxValue;
for (int i = 0; i < 3; i++)
{
if (i != curax)
{
float dp = Math.Abs(Vector3.Dot(cdir, axes[i]));
if (dp < mindp)
{
mindp = dp;
minax = i;
}
}
}
DraggedAxisSideDir = axes[minax];
break;
}
if (DraggedAxis == WidgetAxis.XYZ)
{
//all axes, move in the screen plane
float ad1 = Math.Abs(Vector3.Dot(cdir, Vector3.UnitY));
float ad2 = Math.Abs(Vector3.Dot(cdir, Vector3.UnitZ));
DraggedAxisDir = Vector3.Normalize(Vector3.Cross(cdir, (ad1 > ad2) ? Vector3.UnitY : Vector3.UnitZ));
DraggedAxisSideDir = Vector3.Normalize(Vector3.Cross(cdir, DraggedAxisDir));
}
bool hit = GetAxisRayHit(DraggedAxisDir, DraggedAxisSideDir, camrel, ray, out DragStartVec);
if ((MousedAxis == WidgetAxis.None) || !hit)
{
IsDragging = false;
}
}
else if (IsDragging)
{
//continue drag.
Vector3 newvec;
bool hit = GetAxisRayHit(DraggedAxisDir, DraggedAxisSideDir, camrel, ray, out newvec);
if (hit)
{
Vector3 diff = newvec - DragStartVec;
switch (DraggedAxis)
{
case WidgetAxis.X: diff.Y = 0; diff.Z = 0; break;
case WidgetAxis.Y: diff.X = 0; diff.Z = 0; break;
case WidgetAxis.Z: diff.X = 0; diff.Y = 0; break;
}
//diff = ori.Multiply(diff);
Vector3 ods = DragStartVec - camrel;// ori.Multiply(DragStartVec);
float odl = Math.Max(ods.Length(), 0.0001f); //don't divide by 0
float ndl = Math.Max((ods + diff).Length(), 0.001f); //don't scale to 0 size
float dl = ndl / odl;
if (diff.Length() < 10000.0f) //limit movement in one go, to avoid crazy values...
{
Vector3 oldscale = Scale;
Vector3 sv = Vector3.One;
switch (DraggedAxis)
{
case WidgetAxis.X: sv = new Vector3(dl, 1, 1); break;
case WidgetAxis.Y: sv = new Vector3(1, dl, 1); break;
case WidgetAxis.Z: sv = new Vector3(1, 1, dl); break;
case WidgetAxis.XY: sv = new Vector3(dl, dl, 1); break;
case WidgetAxis.YZ: sv = new Vector3(1, dl, dl); break;
case WidgetAxis.XZ: sv = new Vector3(dl, 1, dl); break;
case WidgetAxis.XYZ: sv = new Vector3(dl); break;
}
Scale = DragStartScale * sv;
if (Scale != oldscale)
{
OnScaleChange?.Invoke(Scale, oldscale);
}
}
}
}
WasDragging = IsDragging;
}
}
public delegate void WidgetPositionChangeHandler(Vector3 newpos, Vector3 oldpos);
public delegate void WidgetRotationChangeHandler(Quaternion newrot, Quaternion oldrot);
public delegate void WidgetScaleChangeHandler(Vector3 newscale, Vector3 oldscale);
public enum WidgetMode
{
Default = 0,
Position = 1,
Rotation = 2,
Scale = 3,
}
public enum WidgetAxis
{
None = 0,
X = 1,
Y = 2,
Z = 4,
XY = 3,
XZ = 5,
YZ = 6,
XYZ = 7,
}
}