mirror of
https://mirror.ghproxy.com/https://github.com/dexyfex/CodeWalker
synced 2024-08-20 00:49:25 +08:00
957 lines
35 KiB
C#
957 lines
35 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 float SnapAngleDegrees
|
|
{
|
|
get { return RotationWidget.SnapAngleDegrees; }
|
|
set { RotationWidget.SnapAngleDegrees = 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; }
|
|
public bool WasDragging = false;
|
|
public 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 float SnapAngleDegrees { get; set; } = 5.0f;
|
|
|
|
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 = SnapAngle(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);
|
|
}
|
|
|
|
private float SnapAngle(float a)
|
|
{
|
|
if (SnapAngleDegrees <= 0.0f) return a;
|
|
float snaprad = SnapAngleDegrees * 0.0174532925f;
|
|
float ahalf = a + (snaprad * 0.5f);
|
|
float mod = ahalf % snaprad;
|
|
float snapped = ahalf - mod;
|
|
return snapped;
|
|
}
|
|
}
|
|
|
|
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,
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
}
|