/////////////////////////////////////////////////////////////////////////////////
// Paint.NET //
// Copyright (C) Rick Brewster, Tom Jackson, and past contributors. //
// Portions Copyright (C) Microsoft Corporation. All Rights Reserved. //
// See src/Resources/Files/License.txt for full licensing and attribution //
// details. //
// . //
/////////////////////////////////////////////////////////////////////////////////
using System;
using System.ComponentModel;
using System.Drawing;
using System.Drawing.Drawing2D;
using System.Windows.Forms;
namespace PaintDotNet.Effects
{
public class RollControl
: System.Windows.Forms.UserControl
{
///
/// Required designer variable.
///
private System.ComponentModel.Container components = null;
private bool tracking = false;
private Point lastMouseXY;
private Bitmap renderSurface = null; // used for double-buffering
public event EventHandler ValueChanged;
protected virtual void OnValueChanged()
{
if (ValueChanged != null)
{
ValueChanged(this, EventArgs.Empty);
}
}
public double angle;
public double Angle
{
get
{
return angle;
}
set
{
double v = Math.IEEERemainder(value, 360);
if (angle != v)
{
angle = v;
OnValueChanged();
Invalidate();
}
}
}
//Direction at which to roll the image, in degrees
protected double rollDirection;
public double RollDirection
{
get
{
return rollDirection;
}
set
{
double v = Math.IEEERemainder(value, 360);
if (rollDirection != v)
{
rollDirection = v;
OnValueChanged();
Invalidate();
}
}
}
//Amount to roll the image, in degrees
protected double rollAmount;
public double RollAmount
{
get
{
return rollAmount;
}
set
{
double v = Math.IEEERemainder(value, 360);
if (v >= 90)
{
return;
}
if (rollAmount != v)
{
rollAmount = v;
OnValueChanged();
Invalidate();
}
}
}
private void DrawToGraphics(Graphics g)
{
g.Clear(this.BackColor);
#if DEBUG
string debug = "";
try
{
#endif
g.SmoothingMode = SmoothingMode.AntiAlias;
// Calculations
Rectangle ourRect = Rectangle.Inflate(ClientRectangle, -2, -2);
int diameter = Math.Min(ourRect.Width, ourRect.Height);
Point center = new Point(ourRect.X + (diameter / 2), ourRect.Y + (diameter / 2));
float radius1 = ((float)diameter / 3);
float radius2 = ((float)diameter / 2);
double theta = -((double)angle * 2 * Math.PI) / 360.0;
float cos = (float)Math.Cos(theta);
float sin = (float)Math.Sin(theta);
float rx = (float)(rollAmount * Math.Cos(rollDirection * Math.PI / 180) / 90);
float ry = (float)(rollAmount * Math.Sin(rollDirection * Math.PI / 180) / 90);
float phi = (float)(rx / (ry * ry < 0.99 ? Math.Sqrt(1 - ry * ry) : 1));
float rho = (float)(ry / (rx * rx < 0.99 ? Math.Sqrt(1 - rx * rx) : 1));
// Globe
g.TranslateTransform(center.X, center.Y);
int divs = 4;
double angleRadians = angle * Math.PI / 180;
Pen darkPen;
darkPen = onSphere ? Pens.Blue : Pens.Black;
Pen lightPen;
lightPen = Pens.Gray;
for (int i = -divs; i < divs; i++)
{
double u = -angleRadians + i * Math.PI / divs;
double v = -Math.PI / 2;
double ox = Math.Cos(u) * Math.Cos(v);
double oy = Math.Sin(u) * Math.Cos(v);
double oz = Math.Sin(v);
double x;
double y;
double z;
for (int j = -divs * 4; j <= 0; j++)
{
v = j * Math.PI / (divs * 8);
Pen p = (i % 2 == 0) ? lightPen : darkPen;
x = Math.Cos(u) * Math.Cos(v);
y = Math.Sin(u) * Math.Cos(v);
z = Math.Sin(v);
Draw3DLine(g, p, rx, -ry, radius1, ox, oy, oz, x, y, z);
ox = x;
oy = y;
oz = z;
}
}
for (int j = -divs / 2; j <= 0; j++)
{
double v = j * Math.PI / divs;
double u = -angleRadians + -Math.PI;
double ox = Math.Cos(u) * Math.Cos(v);
double oy = Math.Sin(u) * Math.Cos(v);
double oz = Math.Sin(v);
double x;
double y;
double z;
for (int i = -divs * 6; i <= divs * 6; i++)
{
u = -angleRadians + i * Math.PI / (divs * 6);
double cosv = Math.Cos(v);
double sinv = Math.Sin(v);
Pen p = (j == 0) ? lightPen : darkPen;
x = Math.Cos(u) * Math.Cos(v);
y = Math.Sin(u) * Math.Cos(v);
z = Math.Sin(v);
Draw3DLine(g, p, rx, -ry, radius1, ox, oy, oz, x, y, z);
ox = x;
oy = y;
oz = z;
}
}
g.ResetTransform();
// Ring
// Reference Theta line
g.DrawLine(SystemPens.ControlDark,
center.X + radius1, center.Y,
center.X + radius2, center.Y);
// Draw Theta-chooser ring
Pen outerRingDark = (Pen)SystemPens.ControlDarkDark.Clone();
outerRingDark.Width = 2.0f;
Pen outerRingLight = (Pen)SystemPens.ControlLightLight.Clone();
outerRingLight.Width = 2.0f;
g.DrawEllipse(outerRingDark, Utility.RectangleFromCenter(new Point(center.X - 1, center.Y - 1), radius1));
g.DrawEllipse(outerRingDark, Utility.RectangleFromCenter(new Point(center.X - 1, center.Y - 1), radius2));
g.DrawEllipse(outerRingLight, Utility.RectangleFromCenter(center, radius2));
g.DrawEllipse(outerRingLight, Utility.RectangleFromCenter(center, radius1));
outerRingDark.Dispose();
outerRingLight.Dispose();
// Draw actual theta line
Pen thetaLinePen;
if (mouseEntered && !onSphere)
{
thetaLinePen = Pens.Blue;
}
else
{
thetaLinePen = Pens.Black;
}
Pen useMePen = (Pen)thetaLinePen.Clone();
useMePen.Width = 3.0f;
g.DrawLine(useMePen,
center.X + radius1 * cos, center.Y + radius1 * sin,
center.X + radius2 * cos, center.Y + radius2 * sin);
useMePen.Dispose();
#if DEBUG
}
catch
{
g.DrawString(debug, new Font("Courier New", 10), SystemBrushes.WindowText, 0, 0);
}
#endif
}
void Draw3DLine(
Graphics g,
Pen p,
double rx,
double ry,
double scale,
double xs,
double ys,
double zs,
double xe,
double ye,
double ze)
{
double dist = Math.Sqrt(rx * rx + ry * ry);
if (dist != 0)
{
double rAngle = Math.Atan2(ry, rx);
double sinAngle = Math.Sin(rAngle);
double cosAngle = Math.Cos(rAngle);
Transform(sinAngle, cosAngle, dist, Math.Cos(Math.Asin(dist)), ref xs, ref ys, ref zs);
Transform(sinAngle, cosAngle, dist, Math.Cos(Math.Asin(dist)), ref xe, ref ye, ref ze);
}
xs *= scale;
xe *= scale;
ys *= scale;
ye *= scale;
if (ze < 0.03 && zs < 0.03)
{
g.DrawLine(p, (float)xs, (float)ys, (float)xe, (float)ye);
}
}
void Transform(double sinangle, double cosangle, double sinamt, double cosamt, ref double x, ref double y, ref double z)
{
double ox = x;
double oy = y;
double oz = z;
x = cosangle * ox - sinangle * oy;
y = sinangle * ox + cosangle * oy;
ox = x;
oy = y;
x = cosamt * ox - sinamt * oz;
z = sinamt * ox + cosamt * oz;
ox = x;
x = cosangle * ox + sinangle * oy;
y = -sinangle * ox + cosangle * oy;
}
private void CheckRenderSurface()
{
if (renderSurface != null && renderSurface.Size != Size)
{
renderSurface.Dispose();
renderSurface = null;
}
if (renderSurface == null)
{
renderSurface = new Bitmap(Width, Height);
using (Graphics g = Graphics.FromImage(renderSurface))
{
DrawToGraphics(g);
}
}
}
private void DoPaint(Graphics g)
{
CheckRenderSurface();
g.DrawImage(renderSurface, ClientRectangle, ClientRectangle, GraphicsUnit.Pixel);
}
protected override void OnPaint(PaintEventArgs e)
{
base.OnPaint (e);
renderSurface = null;
DoPaint(e.Graphics);
}
protected override void OnPaintBackground(PaintEventArgs pevent)
{
DoPaint(pevent.Graphics);
}
private bool onSphere = false;
private double startAngle;
private double startTheta;
private PointF startRoll;
private Point startPt;
private bool mouseEntered = false;
protected override void OnMouseEnter(EventArgs e)
{
mouseEntered = true;
onSphere = IsMouseOnSphere(Control.MousePosition.X, Control.MousePosition.Y);
Invalidate();
base.OnMouseEnter(e);
}
protected override void OnMouseLeave(EventArgs e)
{
mouseEntered = false;
onSphere = IsMouseOnSphere(Control.MousePosition.X, Control.MousePosition.Y);
Invalidate();
base.OnMouseLeave(e);
}
private bool IsMouseOnSphere(int x, int y)
{
Rectangle ourRect = Rectangle.Inflate(ClientRectangle, -2, -2);
int diameter = Math.Min(ourRect.Width, ourRect.Height);
float radius1 = ((float)diameter / 3);
Point center = new Point(ourRect.X + (diameter / 2), ourRect.Y + (diameter / 2));
Point dist = new Point(x - center.X, y - center.Y);
bool returnVal = (Math.Sqrt(dist.X * dist.X + dist.Y * dist.Y) <= radius1);
return returnVal;
}
protected override void OnMouseDown(MouseEventArgs e)
{
startPt = new Point(e.X, e.Y);
base.OnMouseDown (e);
tracking = true;
onSphere = IsMouseOnSphere(e.X, e.Y);
startAngle = angle;
startTheta = rollDirection;
startRoll = new PointF(
(float)(rollAmount * Math.Cos(rollDirection * Math.PI / 180)),
(float)(rollAmount * Math.Sin(rollDirection * Math.PI / 180)));
OnMouseMove(e);
}
protected override void OnMouseUp(MouseEventArgs e)
{
base.OnMouseUp(e);
tracking = false;
}
protected override void OnMouseMove(MouseEventArgs e)
{
base.OnMouseMove(e);
if (!tracking)
{
onSphere = IsMouseOnSphere(e.X, e.Y);
Invalidate();
}
Point preLastMouseXY = new Point(e.X, e.Y);
bool moved = (preLastMouseXY != lastMouseXY);
lastMouseXY = preLastMouseXY;
if (tracking && moved)
{
Rectangle ourRect = Rectangle.Inflate(ClientRectangle, -2, -2);
int diameter = Math.Min(ourRect.Width, ourRect.Height);
Point center = new Point(ourRect.X + (diameter / 2), ourRect.Y + (diameter / 2));
if (onSphere)
{
int dx = e.X - startPt.X;
int dy = e.Y - startPt.Y;
float mx = startRoll.X / 89.9f + 3.0f * dx / (diameter - 4);
float my = startRoll.Y / 89.9f + 3.0f * dy / (diameter - 4);
float dist = (float)Math.Sqrt(mx * mx + my * my);
float rad = (float)((dist > 1) ? 1 : dist);
if (dist == 0.0f)
{
mx = 0;
my = 0;
}
else
{
mx = mx * rad / dist;
my = my * rad / dist;
}
if (0 != (ModifierKeys & Keys.Shift))
{
if (mx * mx > my * my)
{
my = 0;
}
else
{
mx = 0;
}
}
this.rollDirection = 180 * Math.Atan2(my, mx) / Math.PI;
this.rollAmount = 89.94 * Math.Sqrt(mx * mx + my * my);
OnValueChanged();
Update();
}
else
{
int dx = e.X - center.X;
int dy = e.Y - center.Y;
double theta = Math.Atan2(-dy, dx);
if (0 != (ModifierKeys & Keys.Shift))
{
this.Angle = Math.Round(4 * theta / Math.PI) * 45;
}
else
{
this.Angle = (theta * 360) / (2 * Math.PI);
}
Update();
}
}
}
protected override void OnClick(EventArgs e)
{
base.OnClick (e);
tracking = true;
OnMouseMove(new MouseEventArgs(MouseButtons.Left, 1, lastMouseXY.X, lastMouseXY.Y, 0));
tracking = false;
}
protected override void OnDoubleClick(EventArgs e)
{
base.OnDoubleClick (e);
tracking = true;
OnMouseMove(new MouseEventArgs(MouseButtons.Left, 1, lastMouseXY.X, lastMouseXY.Y, 0));
tracking = false;
rollAmount = 0;
rollDirection = 0;
}
public RollControl()
{
// This call is required by the Windows.Forms Form Designer.
InitializeComponent();
this.ResizeRedraw = true;
}
///
/// Clean up any resources being used.
///
protected override void Dispose(bool disposing)
{
if (disposing)
{
if (components != null)
{
components.Dispose();
components = null;
}
}
base.Dispose(disposing);
}
#region Component Designer generated code
///
/// Required method for Designer support - do not modify
/// the contents of this method with the code editor.
///
private void InitializeComponent()
{
//
// RollControl
//
this.Name = "RollControl";
this.Size = new System.Drawing.Size(168, 144);
}
#endregion
}
}