2009-04-19 18:07:22 +00:00
#region = = = = = = = = = = = = = = = = = = Copyright ( c ) 2007 Pascal vd Heiden
/ *
* Copyright ( c ) 2007 Pascal vd Heiden , www . codeimp . com
* This program is released under GNU General Public License
*
* This program is distributed in the hope that it will be useful ,
* but WITHOUT ANY WARRANTY ; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE . See the
* GNU General Public License for more details .
*
* /
#endregion
#region = = = = = = = = = = = = = = = = = = Namespaces
using System ;
using System.Collections.Generic ;
using System.Windows.Forms ;
using CodeImp.DoomBuilder.Windows ;
using CodeImp.DoomBuilder.Map ;
using CodeImp.DoomBuilder.Rendering ;
using CodeImp.DoomBuilder.Geometry ;
using System.Drawing ;
using CodeImp.DoomBuilder.Editing ;
using CodeImp.DoomBuilder.Actions ;
#endregion
namespace CodeImp.DoomBuilder.BuilderModes
{
[ EditMode ( DisplayName = "Drawing Mode" ,
SwitchAction = "drawlinesmode" ,
2011-12-03 14:49:53 +00:00
AllowCopyPaste = false ,
2009-04-19 18:07:22 +00:00
Volatile = true ,
UseByDefault = true ,
Optional = false ) ]
public class DrawGeometryMode : BaseClassicMode
{
#region = = = = = = = = = = = = = = = = = = Constants
2012-06-04 23:42:13 +00:00
protected const float LINE_THICKNESS = 0.8f ;
2009-04-19 18:07:22 +00:00
#endregion
#region = = = = = = = = = = = = = = = = = = Variables
// Drawing points
2013-09-11 09:47:53 +00:00
protected List < DrawnVertex > points ;
protected List < LineLengthLabel > labels ;
2009-04-19 18:07:22 +00:00
// Keep track of view changes
2013-09-11 09:47:53 +00:00
protected float lastoffsetx ;
protected float lastoffsety ;
protected float lastscale ;
2009-04-19 18:07:22 +00:00
// Options
2012-06-04 23:42:13 +00:00
protected bool snaptogrid ; // SHIFT to toggle
protected bool snaptonearest ; // CTRL to enable
2009-04-19 18:07:22 +00:00
#endregion
#region = = = = = = = = = = = = = = = = = = Properties
// Just keep the base mode button checked
public override string EditModeButtonName { get { return General . Editing . PreviousStableMode . Name ; } }
#endregion
#region = = = = = = = = = = = = = = = = = = Constructor / Disposer
// Constructor
public DrawGeometryMode ( )
{
// Initialize
points = new List < DrawnVertex > ( ) ;
labels = new List < LineLengthLabel > ( ) ;
// No selection in this mode
General . Map . Map . ClearAllSelected ( ) ;
General . Map . Map . ClearAllMarks ( false ) ;
// We have no destructor
GC . SuppressFinalize ( this ) ;
}
// Disposer
public override void Dispose ( )
{
// Not already disposed?
if ( ! isdisposed )
{
// Clean up
if ( labels ! = null )
foreach ( LineLengthLabel l in labels ) l . Dispose ( ) ;
// Done
base . Dispose ( ) ;
}
}
#endregion
#region = = = = = = = = = = = = = = = = = = Methods
// This checks if the view offset/zoom changed and updates the check
protected bool CheckViewChanged ( )
{
// View changed?
2013-09-05 13:03:17 +00:00
bool viewchanged = ( renderer . OffsetX ! = lastoffsetx | | renderer . OffsetY ! = lastoffsety | | renderer . Scale ! = lastscale ) ;
2009-04-19 18:07:22 +00:00
// Keep view information
lastoffsetx = renderer . OffsetX ;
lastoffsety = renderer . OffsetY ;
lastscale = renderer . Scale ;
// Return result
return viewchanged ;
}
// This updates the dragging
2012-06-04 23:42:13 +00:00
protected virtual void Update ( )
2009-04-19 18:07:22 +00:00
{
2013-09-11 09:47:53 +00:00
PixelColor stitchcolor = General . Colors . Highlight ;
2009-04-19 18:07:22 +00:00
PixelColor losecolor = General . Colors . Selection ;
PixelColor color ;
snaptogrid = General . Interface . ShiftState ^ General . Interface . SnapToGrid ;
snaptonearest = General . Interface . CtrlState ^ General . Interface . AutoMerge ;
DrawnVertex lastp = new DrawnVertex ( ) ;
DrawnVertex curp = GetCurrentPosition ( ) ;
2013-09-05 13:03:17 +00:00
float vsize = ( renderer . VertexSize + 1.0f ) / renderer . Scale ;
2009-04-19 18:07:22 +00:00
// The last label's end must go to the mouse cursor
if ( labels . Count > 0 ) labels [ labels . Count - 1 ] . End = curp . pos ;
// Render drawing lines
if ( renderer . StartOverlay ( true ) )
{
// Go for all points to draw lines
if ( points . Count > 0 )
{
// Render lines
lastp = points [ 0 ] ;
for ( int i = 1 ; i < points . Count ; i + + )
{
// Determine line color
if ( lastp . stitchline & & points [ i ] . stitchline ) color = stitchcolor ;
else color = losecolor ;
// Render line
renderer . RenderLine ( lastp . pos , points [ i ] . pos , LINE_THICKNESS , color , true ) ;
2012-09-02 23:37:17 +00:00
RenderLinedefDirectionIndicator ( lastp . pos , points [ i ] . pos , color ) ; //mxd
2009-04-19 18:07:22 +00:00
lastp = points [ i ] ;
}
// Determine line color
if ( lastp . stitchline & & snaptonearest ) color = stitchcolor ;
else color = losecolor ;
// Render line to cursor
renderer . RenderLine ( lastp . pos , curp . pos , LINE_THICKNESS , color , true ) ;
2012-09-02 23:37:17 +00:00
RenderLinedefDirectionIndicator ( lastp . pos , curp . pos , color ) ; //mxd
2009-04-19 18:07:22 +00:00
// Render vertices
for ( int i = 0 ; i < points . Count ; i + + )
{
// Determine vertex color
2013-09-05 13:03:17 +00:00
color = points [ i ] . stitch ? stitchcolor : losecolor ;
2009-04-19 18:07:22 +00:00
// Render vertex
renderer . RenderRectangleFilled ( new RectangleF ( points [ i ] . pos . x - vsize , points [ i ] . pos . y - vsize , vsize * 2.0f , vsize * 2.0f ) , color , true ) ;
}
}
// Determine point color
2013-09-05 13:03:17 +00:00
color = snaptonearest ? stitchcolor : losecolor ;
2009-04-19 18:07:22 +00:00
// Render vertex at cursor
renderer . RenderRectangleFilled ( new RectangleF ( curp . pos . x - vsize , curp . pos . y - vsize , vsize * 2.0f , vsize * 2.0f ) , color , true ) ;
// Go for all labels
foreach ( LineLengthLabel l in labels ) renderer . RenderText ( l . TextLabel ) ;
// Done
renderer . Finish ( ) ;
}
// Done
renderer . Present ( ) ;
}
2012-09-02 23:37:17 +00:00
//mxd
protected void RenderLinedefDirectionIndicator ( Vector2D start , Vector2D end , PixelColor color ) {
Vector2D delta = end - start ;
Vector2D middlePoint = new Vector2D ( start . x + delta . x / 2 , start . y + delta . y / 2 ) ;
Vector2D scaledPerpendicular = delta . GetPerpendicular ( ) . GetNormal ( ) . GetScaled ( 18f / renderer . Scale ) ;
renderer . RenderLine ( middlePoint , new Vector2D ( middlePoint . x - scaledPerpendicular . x , middlePoint . y - scaledPerpendicular . y ) , LINE_THICKNESS , color , true ) ;
}
2009-04-19 18:07:22 +00:00
// This returns the aligned and snapped draw position
public static DrawnVertex GetCurrentPosition ( Vector2D mousemappos , bool snaptonearest , bool snaptogrid , IRenderer2D renderer , List < DrawnVertex > points )
{
DrawnVertex p = new DrawnVertex ( ) ;
2013-07-30 09:25:27 +00:00
p . stitch = true ; //mxd. Setting these to false seems to be a good way to create invalid geometry...
p . stitchline = true ; //mxd
2010-08-13 18:32:21 +00:00
Vector2D vm = mousemappos ;
2009-05-20 15:03:08 +00:00
float vrange = BuilderPlug . Me . StitchRange / renderer . Scale ;
2009-04-19 18:07:22 +00:00
// Snap to nearest?
if ( snaptonearest )
{
// Go for all drawn points
foreach ( DrawnVertex v in points )
{
if ( Vector2D . DistanceSq ( mousemappos , v . pos ) < ( vrange * vrange ) )
{
p . pos = v . pos ;
2013-07-30 09:25:27 +00:00
//p.stitch = true;
//p.stitchline = true;
2009-04-19 18:07:22 +00:00
return p ;
}
}
// Try the nearest vertex
Vertex nv = General . Map . Map . NearestVertexSquareRange ( mousemappos , vrange ) ;
if ( nv ! = null )
{
p . pos = nv . Position ;
2013-07-30 09:25:27 +00:00
//p.stitch = true;
//p.stitchline = true;
2009-04-19 18:07:22 +00:00
return p ;
}
// Try the nearest linedef
2009-05-20 15:03:08 +00:00
Linedef nl = General . Map . Map . NearestLinedefRange ( mousemappos , BuilderPlug . Me . StitchRange / renderer . Scale ) ;
2009-04-19 18:07:22 +00:00
if ( nl ! = null )
{
// Snap to grid?
if ( snaptogrid )
{
// Get grid intersection coordinates
List < Vector2D > coords = nl . GetGridIntersections ( ) ;
// Find nearest grid intersection
2010-10-03 13:10:52 +00:00
bool found = false ;
2009-04-19 18:07:22 +00:00
float found_distance = float . MaxValue ;
Vector2D found_coord = new Vector2D ( ) ;
foreach ( Vector2D v in coords )
{
Vector2D delta = mousemappos - v ;
if ( delta . GetLengthSq ( ) < found_distance )
{
found_distance = delta . GetLengthSq ( ) ;
found_coord = v ;
2010-10-03 13:10:52 +00:00
found = true ;
2009-04-19 18:07:22 +00:00
}
}
2010-10-03 13:10:52 +00:00
if ( found )
{
// Align to the closest grid intersection
p . pos = found_coord ;
2013-07-30 09:25:27 +00:00
//p.stitch = true;
//p.stitchline = true;
2010-10-03 13:10:52 +00:00
return p ;
}
2009-04-19 18:07:22 +00:00
}
else
{
// Aligned to line
p . pos = nl . NearestOnLine ( mousemappos ) ;
2013-07-30 09:25:27 +00:00
//p.stitch = true;
//p.stitchline = true;
2009-04-19 18:07:22 +00:00
return p ;
}
}
}
else
{
// Always snap to the first drawn vertex so that the user can finish a complete sector without stitching
if ( points . Count > 0 )
{
if ( Vector2D . DistanceSq ( mousemappos , points [ 0 ] . pos ) < ( vrange * vrange ) )
{
p . pos = points [ 0 ] . pos ;
2013-07-30 09:25:27 +00:00
//p.stitch = true;
//p.stitchline = false;
2009-04-19 18:07:22 +00:00
return p ;
}
}
}
2010-08-13 18:32:21 +00:00
// if the mouse cursor is outside the map bondaries check if the line between the last set point and the
// mouse cursor intersect any of the boundary lines. If it does, set the position to this intersection
if ( points . Count > 0 & &
( mousemappos . x < General . Map . Config . LeftBoundary | | mousemappos . x > General . Map . Config . RightBoundary | |
mousemappos . y > General . Map . Config . TopBoundary | | mousemappos . y < General . Map . Config . BottomBoundary ) )
{
Line2D dline = new Line2D ( mousemappos , points [ points . Count - 1 ] . pos ) ;
bool foundintersection = false ;
float u = 0.0f ;
List < Line2D > blines = new List < Line2D > ( ) ;
// lines for left, top, right and bottom bondaries
blines . Add ( new Line2D ( General . Map . Config . LeftBoundary , General . Map . Config . BottomBoundary , General . Map . Config . LeftBoundary , General . Map . Config . TopBoundary ) ) ;
blines . Add ( new Line2D ( General . Map . Config . LeftBoundary , General . Map . Config . TopBoundary , General . Map . Config . RightBoundary , General . Map . Config . TopBoundary ) ) ;
blines . Add ( new Line2D ( General . Map . Config . RightBoundary , General . Map . Config . TopBoundary , General . Map . Config . RightBoundary , General . Map . Config . BottomBoundary ) ) ;
blines . Add ( new Line2D ( General . Map . Config . RightBoundary , General . Map . Config . BottomBoundary , General . Map . Config . LeftBoundary , General . Map . Config . BottomBoundary ) ) ;
// check for intersections with boundaries
for ( int i = 0 ; i < blines . Count ; i + + )
{
if ( ! foundintersection )
{
// only check for intersection if the last set point is not on the
// line we are checking against
if ( blines [ i ] . GetSideOfLine ( points [ points . Count - 1 ] . pos ) ! = 0.0f )
{
foundintersection = blines [ i ] . GetIntersection ( dline , out u ) ;
}
}
}
// if there was no intersection set the position to the last set point
if ( ! foundintersection )
vm = points [ points . Count - 1 ] . pos ;
else
vm = dline . GetCoordinatesAt ( u ) ;
}
2009-04-19 18:07:22 +00:00
// Snap to grid?
if ( snaptogrid )
{
// Aligned to grid
2010-08-13 18:32:21 +00:00
p . pos = General . Map . Grid . SnappedToGrid ( vm ) ;
// special handling
if ( p . pos . x > General . Map . Config . RightBoundary ) p . pos . x = General . Map . Config . RightBoundary ;
if ( p . pos . y < General . Map . Config . BottomBoundary ) p . pos . y = General . Map . Config . BottomBoundary ;
2013-07-30 09:25:27 +00:00
//p.stitch = snaptonearest;
//p.stitchline = snaptonearest;
2009-04-19 18:07:22 +00:00
return p ;
}
else
{
// Normal position
2010-08-13 18:32:21 +00:00
p . pos = vm ;
2013-07-30 09:25:27 +00:00
//p.stitch = snaptonearest;
//p.stitchline = snaptonearest;
2009-04-19 18:07:22 +00:00
return p ;
}
}
// This gets the aligned and snapped draw position
2012-06-04 23:42:13 +00:00
protected DrawnVertex GetCurrentPosition ( )
2009-04-19 18:07:22 +00:00
{
return GetCurrentPosition ( mousemappos , snaptonearest , snaptogrid , renderer , points ) ;
}
// This draws a point at a specific location
2013-09-11 09:47:53 +00:00
public bool DrawPointAt ( DrawnVertex p )
2009-04-19 18:07:22 +00:00
{
2010-08-13 18:32:21 +00:00
return DrawPointAt ( p . pos , p . stitch , p . stitchline ) ;
2009-04-19 18:07:22 +00:00
}
// This draws a point at a specific location
2013-09-11 09:47:53 +00:00
public virtual bool DrawPointAt ( Vector2D pos , bool stitch , bool stitchline )
2009-04-19 18:07:22 +00:00
{
2010-08-13 18:32:21 +00:00
if ( pos . x < General . Map . Config . LeftBoundary | | pos . x > General . Map . Config . RightBoundary | |
pos . y > General . Map . Config . TopBoundary | | pos . y < General . Map . Config . BottomBoundary )
return false ;
2009-04-19 18:07:22 +00:00
DrawnVertex newpoint = new DrawnVertex ( ) ;
newpoint . pos = pos ;
newpoint . stitch = stitch ;
newpoint . stitchline = stitchline ;
points . Add ( newpoint ) ;
2012-11-11 21:52:08 +00:00
labels . Add ( new LineLengthLabel ( true ) ) ;
2009-04-19 18:07:22 +00:00
labels [ labels . Count - 1 ] . Start = newpoint . pos ;
if ( labels . Count > 1 ) labels [ labels . Count - 2 ] . End = newpoint . pos ;
Update ( ) ;
// Check if point stitches with the first
if ( ( points . Count > 1 ) & & points [ points . Count - 1 ] . stitch )
{
Vector2D p1 = points [ 0 ] . pos ;
Vector2D p2 = points [ points . Count - 1 ] . pos ;
Vector2D delta = p1 - p2 ;
if ( ( Math . Abs ( delta . x ) < = 0.001f ) & & ( Math . Abs ( delta . y ) < = 0.001f ) )
{
2013-09-11 09:47:53 +00:00
//mxd. Seems... logical?
if ( points . Count = = 2 ) {
OnCancel ( ) ;
return true ;
}
// Finish drawing
2009-04-19 18:07:22 +00:00
FinishDraw ( ) ;
}
}
2010-08-13 18:32:21 +00:00
return true ;
2009-04-19 18:07:22 +00:00
}
2013-12-11 08:47:11 +00:00
//mxd. Setup hints for current editing mode
protected override void SetupHints ( ) {
2014-01-08 09:46:57 +00:00
hints = new [ ] { "Press <b>" + Actions . Action . GetShortcutKeyDesc ( "builder_classicselect" ) + "</b> to place a vertex" ,
"Press <b>" + Actions . Action . GetShortcutKeyDesc ( "buildermodes_removepoint" ) + "</b> to remove last vertex" ,
"Press <b>" + Actions . Action . GetShortcutKeyDesc ( "builder_acceptmode" ) + "</b> to accept" ,
"Press <b>" + Actions . Action . GetShortcutKeyDesc ( "builder_cancelmode" ) + "</b> or <b>" + Actions . Action . GetShortcutKeyDesc ( "builder_classicedit" ) + "</b> to cancel"
2013-12-11 08:47:11 +00:00
} ;
}
2009-04-19 18:07:22 +00:00
#endregion
#region = = = = = = = = = = = = = = = = = = Events
public override void OnHelp ( )
{
General . ShowHelp ( "e_drawgeometry.html" ) ;
}
// Engaging
public override void OnEngage ( )
{
base . OnEngage ( ) ;
EnableAutoPanning ( ) ;
renderer . SetPresentation ( Presentation . Standard ) ;
// Set cursor
General . Interface . SetCursor ( Cursors . Cross ) ;
}
// Disengaging
public override void OnDisengage ( )
{
base . OnDisengage ( ) ;
DisableAutoPanning ( ) ;
}
// Cancelled
public override void OnCancel ( )
{
// Cancel base class
base . OnCancel ( ) ;
// Return to original mode
General . Editing . ChangeMode ( General . Editing . PreviousStableMode . Name ) ;
}
// Accepted
public override void OnAccept ( )
{
Cursor . Current = Cursors . AppStarting ;
General . Settings . FindDefaultDrawSettings ( ) ;
// When points have been drawn
if ( points . Count > 0 )
{
// Make undo for the draw
General . Map . UndoRedo . CreateUndo ( "Line draw" ) ;
// Make an analysis and show info
string [ ] adjectives = new string [ ]
{ "beautiful" , "lovely" , "romantic" , "stylish" , "cheerful" , "comical" ,
"awesome" , "accurate" , "adorable" , "adventurous" , "attractive" , "cute" ,
"elegant" , "glamorous" , "gorgeous" , "handsome" , "magnificent" , "unusual" ,
"outstanding" , "mysterious" , "amusing" , "charming" , "fantastic" , "jolly" } ;
string word = adjectives [ points . Count % adjectives . Length ] ;
word = ( points . Count > adjectives . Length ) ? "very " + word : word ;
2013-04-15 10:54:55 +00:00
string a = ( ( word [ 0 ] = = 'a' ) | | ( word [ 0 ] = = 'e' ) | | ( word [ 0 ] = = 'o' ) | | ( word [ 0 ] = = 'u' ) ) ? "an " : "a " ;
2009-04-19 18:07:22 +00:00
General . Interface . DisplayStatus ( StatusType . Action , "Created " + a + word + " drawing." ) ;
// Make the drawing
2013-11-21 10:53:11 +00:00
if ( ! Tools . DrawLines ( points , true , BuilderPlug . Me . AutoAlignTextureOffsetsOnCreate ) ) //mxd
2010-08-15 19:43:00 +00:00
{
// Drawing failed
// NOTE: I have to call this twice, because the first time only cancels this volatile mode
General . Map . UndoRedo . WithdrawUndo ( ) ;
General . Map . UndoRedo . WithdrawUndo ( ) ;
return ;
}
2010-01-02 21:28:18 +00:00
// Snap to map format accuracy
General . Map . Map . SnapAllToAccuracy ( ) ;
2009-04-19 18:07:22 +00:00
// Clear selection
General . Map . Map . ClearAllSelected ( ) ;
// Update cached values
General . Map . Map . Update ( ) ;
// Edit new sectors?
List < Sector > newsectors = General . Map . Map . GetMarkedSectors ( true ) ;
if ( BuilderPlug . Me . EditNewSector & & ( newsectors . Count > 0 ) )
General . Interface . ShowEditSectors ( newsectors ) ;
// Update the used textures
General . Map . Data . UpdateUsedTextures ( ) ;
// Map is changed
General . Map . IsChanged = true ;
}
// Done
Cursor . Current = Cursors . Default ;
// Return to original mode
General . Editing . ChangeMode ( General . Editing . PreviousStableMode . Name ) ;
}
// This redraws the display
public override void OnRedrawDisplay ( )
{
renderer . RedrawSurface ( ) ;
// Render lines
if ( renderer . StartPlotter ( true ) )
{
renderer . PlotLinedefSet ( General . Map . Map . Linedefs ) ;
renderer . PlotVerticesSet ( General . Map . Map . Vertices ) ;
renderer . Finish ( ) ;
}
// Render things
if ( renderer . StartThings ( true ) )
{
renderer . RenderThingSet ( General . Map . Map . Things , 1.0f ) ;
renderer . Finish ( ) ;
}
// Normal update
Update ( ) ;
}
// Mouse moving
public override void OnMouseMove ( MouseEventArgs e )
{
base . OnMouseMove ( e ) ;
2013-07-29 08:50:50 +00:00
if ( panning ) return ; //mxd. Skip all this jass while panning
2009-04-19 18:07:22 +00:00
Update ( ) ;
}
// When a key is released
public override void OnKeyUp ( KeyEventArgs e )
{
base . OnKeyUp ( e ) ;
if ( ( snaptogrid ! = ( General . Interface . ShiftState ^ General . Interface . SnapToGrid ) ) | |
( snaptonearest ! = ( General . Interface . CtrlState ^ General . Interface . AutoMerge ) ) ) Update ( ) ;
}
// When a key is pressed
public override void OnKeyDown ( KeyEventArgs e )
{
base . OnKeyDown ( e ) ;
if ( ( snaptogrid ! = ( General . Interface . ShiftState ^ General . Interface . SnapToGrid ) ) | |
( snaptonearest ! = ( General . Interface . CtrlState ^ General . Interface . AutoMerge ) ) ) Update ( ) ;
}
#endregion
#region = = = = = = = = = = = = = = = = = = Actions
// Drawing a point
[BeginAction("drawpoint")]
2013-09-11 09:47:53 +00:00
public void DrawPoint ( )
2009-04-19 18:07:22 +00:00
{
// Mouse inside window?
if ( General . Interface . MouseInDisplay )
{
DrawnVertex newpoint = GetCurrentPosition ( ) ;
2010-08-13 18:32:21 +00:00
if ( ! DrawPointAt ( newpoint ) ) General . Interface . DisplayStatus ( StatusType . Warning , "Failed to draw point: outside of map boundaries." ) ;
2009-04-19 18:07:22 +00:00
}
}
// Remove a point
[BeginAction("removepoint")]
2013-09-11 09:47:53 +00:00
public virtual void RemovePoint ( )
2009-04-19 18:07:22 +00:00
{
if ( points . Count > 0 ) points . RemoveAt ( points . Count - 1 ) ;
if ( labels . Count > 0 )
{
labels [ labels . Count - 1 ] . Dispose ( ) ;
labels . RemoveAt ( labels . Count - 1 ) ;
}
Update ( ) ;
}
// Finish drawing
[BeginAction("finishdraw")]
public void FinishDraw ( )
{
// Accept the changes
General . Editing . AcceptMode ( ) ;
}
#endregion
}
}