#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.Drawing; using System.Windows.Forms; using CodeImp.DoomBuilder.Actions; using CodeImp.DoomBuilder.Geometry; using CodeImp.DoomBuilder.Map; using CodeImp.DoomBuilder.Rendering; using CodeImp.DoomBuilder.VisualModes; using CodeImp.DoomBuilder.Windows; #endregion namespace CodeImp.DoomBuilder.Editing { /// /// Provides specialized functionality for a classic (2D) Doom Builder editing mode. /// public abstract class ClassicMode : EditMode { #region ================== Constants private const float SCALE_MAX = 90f; private const float SCALE_MIN = 0.01f; private const float SELECTION_BORDER_SIZE = 2f; private const int SELECTION_ALPHA = 200; private const float CENTER_VIEW_PADDING = 0.06f; private const float AUTOPAN_BORDER_SIZE = 100.0f; #endregion #region ================== Variables // Cancelled? protected bool cancelled; // Graphics protected IRenderer2D renderer; private Renderer2D renderer2d; // Mouse status protected Vector2D mousepos; protected Vector2D mouselastpos; protected Vector2D mousemappos; protected Vector2D mousedownpos; protected Vector2D mousedownmappos; protected MouseButtons mousebuttons; protected bool mouseinside; protected MouseButtons mousedragging = MouseButtons.None; // Selection protected bool selecting; protected bool selectpressed; //mxd protected Vector2D selectstart; protected RectangleF selectionrect; protected MarqueSelectionMode marqueSelectionMode; //mxd // View panning protected bool panning; private bool autopanenabled; //mxd. used in "Play From Here" Action private Thing playerStart; private Vector3D playerStartPosition; #endregion #region ================== Properties // Mouse status public Vector2D MousePos { get { return mousepos; } } public Vector2D MouseLastPos { get { return mouselastpos; } } public Vector2D MouseMapPos { get { return mousemappos; } } public Vector2D MouseDownPos { get { return mousedownpos; } } public Vector2D MouseDownMapPos { get { return mousedownmappos; } } public MouseButtons MouseButtons { get { return mousebuttons; } } public bool IsMouseInside { get { return mouseinside; } } public MouseButtons MouseDragging { get { return mousedragging; } } // Selection public bool IsSelecting { get { return selecting; } } public Vector2D SelectionStart { get { return selectstart; } } public RectangleF SelectionRect { get { return selectionrect; } } // Panning public bool IsPanning { get { return panning; } } // Rendering public IRenderer2D Renderer { get { return renderer; } } #endregion #region ================== Constructor / Disposer /// /// Provides specialized functionality for a classic (2D) Doom Builder editing mode. /// protected ClassicMode() { // Initialize this.renderer = General.Map.Renderer2D; this.renderer2d = (Renderer2D)General.Map.Renderer2D; // If the current mode is a ClassicMode, copy mouse properties ClassicMode oldmode = General.Editing.Mode as ClassicMode; if(oldmode != null) { // Copy mouse properties mousepos = oldmode.mousepos; mousemappos = oldmode.mousemappos; mousedownpos = oldmode.mousedownpos; mousedownmappos = oldmode.mousedownmappos; mousebuttons = oldmode.mousebuttons; mouseinside = oldmode.mouseinside; mousedragging = oldmode.mousedragging; } } // Disposer public override void Dispose() { // Not already disposed? if(!isdisposed) { // Clean up // Dispose base base.Dispose(); } } #endregion #region ================== Scroll / Zoom // This scrolls the view north [BeginAction("scrollnorth", BaseAction = true)] public virtual void ScrollNorth() { // Scroll ScrollBy(0f, 100f / renderer2d.Scale); } // This scrolls the view south [BeginAction("scrollsouth", BaseAction = true)] public virtual void ScrollSouth() { // Scroll ScrollBy(0f, -100f / renderer2d.Scale); } // This scrolls the view west [BeginAction("scrollwest", BaseAction = true)] public virtual void ScrollWest() { // Scroll ScrollBy(-100f / renderer2d.Scale, 0f); } // This scrolls the view east [BeginAction("scrolleast", BaseAction = true)] public virtual void ScrollEast() { // Scroll ScrollBy(100f / renderer2d.Scale, 0f); } // This zooms in [BeginAction("zoomin", BaseAction = true)] public virtual void ZoomIn() { float z = 1.0f + General.Settings.ZoomFactor * 0.1f; // Zoom ZoomBy(z); } // This zooms out [BeginAction("zoomout", BaseAction = true)] public virtual void ZoomOut() { float z = 1.0f + General.Settings.ZoomFactor * 0.1f; // Zoom ZoomBy(1.0f / z); } // This scrolls anywhere private void ScrollBy(float deltax, float deltay) { // Scroll now renderer2d.PositionView(renderer2d.OffsetX + deltax, renderer2d.OffsetY + deltay); this.OnViewChanged(); // Redraw General.MainWindow.RedrawDisplay(); // Determine new unprojected mouse coordinates mousemappos = renderer2d.DisplayToMap(mousepos); General.MainWindow.UpdateCoordinates(mousemappos); } // This sets the view to be centered at x,y /*private void ScrollTo(float x, float y) { // Scroll now renderer2d.PositionView(x, y); this.OnViewChanged(); // Redraw General.MainWindow.RedrawDisplay(); // Determine new unprojected mouse coordinates mousemappos = renderer2d.DisplayToMap(mousepos); General.MainWindow.UpdateCoordinates(mousemappos); }*/ // This zooms private void ZoomBy(float deltaz) { Vector2D zoompos; // This will be the new zoom scale float newscale = renderer2d.Scale * deltaz; // Limit scale if(newscale > SCALE_MAX) newscale = SCALE_MAX; if(newscale < SCALE_MIN) newscale = SCALE_MIN; // Get the dimensions of the display Vector2D clientsize = new Vector2D(General.Map.Graphics.RenderTarget.ClientSize.Width, General.Map.Graphics.RenderTarget.ClientSize.Height); // When mouse is inside display if(mouseinside) { // Zoom into or from mouse position zoompos = (mousepos / clientsize) - new Vector2D(0.5f, 0.5f); } else { // Zoom into or from center zoompos = new Vector2D(0f, 0f); } // Calculate view position difference Vector2D diff = ((clientsize / newscale) - (clientsize / renderer2d.Scale)) * zoompos; // Zoom now renderer2d.PositionView(renderer2d.OffsetX - diff.x, renderer2d.OffsetY + diff.y); renderer2d.ScaleView(newscale); this.OnViewChanged(); //mxd. Change grid size? MatchGridSizeToDisplayScale(); // Redraw General.MainWindow.RedrawDisplay(); // Give a new mousemove event to update coordinates if(mouseinside) OnMouseMove(new MouseEventArgs(mousebuttons, 0, (int)mousepos.x, (int)mousepos.y, 0)); } //mxd. This changes current grid size based on current zoom level internal void MatchGridSizeToDisplayScale() { if(!General.Settings.DynamicGridSize) return; // Get the dimensions of the display Vector2D clientsize = new Vector2D(General.Map.Graphics.RenderTarget.ClientSize.Width, General.Map.Graphics.RenderTarget.ClientSize.Height); Vector2D clientscale = clientsize / renderer2d.Scale; int targetsize = (int)Math.Ceiling(Math.Min(clientscale.x, clientscale.y) / 32); // Convert to nearest power of 2 targetsize--; targetsize |= targetsize >> 1; targetsize |= targetsize >> 2; targetsize |= targetsize >> 4; targetsize |= targetsize >> 8; targetsize |= targetsize >> 16; targetsize++; // Apply changes General.Map.Grid.SetGridSize(targetsize); } // This zooms to a specific level public void SetZoom(float newscale) { // Zoom now renderer2d.ScaleView(newscale); this.OnViewChanged(); //mxd. Change grid size? MatchGridSizeToDisplayScale(); // Redraw //General.Map.Map.Update(); General.MainWindow.RedrawDisplay(); // Give a new mousemove event to update coordinates if(mouseinside) OnMouseMove(new MouseEventArgs(mousebuttons, 0, (int)mousepos.x, (int)mousepos.y, 0)); } // This zooms and scrolls to fit the map in the window [BeginAction("centerinscreen", BaseAction = true)] public void CenterInScreen() { float left = float.MaxValue; float top = float.MaxValue; float right = float.MinValue; float bottom = float.MinValue; bool anything = false; // Go for all vertices foreach(Vertex v in General.Map.Map.Vertices) { // Vertex used? if(v.Linedefs.Count > 0) { // Adjust boundaries by vertices if(v.Position.x < left) left = v.Position.x; if(v.Position.x > right) right = v.Position.x; if(v.Position.y < top) top = v.Position.y; if(v.Position.y > bottom) bottom = v.Position.y; anything = true; } } // Not already found something to center in view? if(!anything) { // Go for all things foreach(Thing t in General.Map.Map.Things) { // Adjust boundaries by vertices if(t.Position.x < left) left = t.Position.x; if(t.Position.x > right) right = t.Position.x; if(t.Position.y < top) top = t.Position.y; if(t.Position.y > bottom) bottom = t.Position.y; anything = true; } } // Anything found to center in view? if(anything) { RectangleF area = new RectangleF(left, top, (right - left), (bottom - top)); CenterOnArea(area, CENTER_VIEW_PADDING); } else { // Default view SetDefaultZoom(); } } // This zooms and moves to view the given area public void CenterOnArea(RectangleF area, float padding) { // Add size to the area for better overview area.Inflate(area.Width * padding, area.Height * padding); // Calculate scale to view map at float scalew = General.Map.Graphics.RenderTarget.ClientSize.Width / area.Width; float scaleh = General.Map.Graphics.RenderTarget.ClientSize.Height / area.Height; float scale = scalew < scaleh ? scalew : scaleh; //mxd. Change the view to see the whole map CenterOnCoordinates(new Vector2D(area.Left + area.Width * 0.5f, area.Top + area.Height * 0.5f), scale); } //mxd public void CenterOnCoordinates(Vector2D offset, float scale) { // Change the view renderer2d.ScaleView(scale); renderer2d.PositionView(offset.x, offset.y); this.OnViewChanged(); //mxd. Change grid size? MatchGridSizeToDisplayScale(); // Redraw General.MainWindow.RedrawDisplay(); // Give a new mousemove event to update coordinates if(mouseinside) OnMouseMove(new MouseEventArgs(mousebuttons, 0, (int)mousepos.x, (int)mousepos.y, 0)); } // This sets up the default view public void SetDefaultZoom() { // View middle of map at 50% zoom renderer2d.ScaleView(0.5f); renderer2d.PositionView(0.0f, 0.0f); this.OnViewChanged(); //mxd. Change grid size? MatchGridSizeToDisplayScale(); // Redraw General.MainWindow.RedrawDisplay(); // Give a new mousemove event to update coordinates if(mouseinside) OnMouseMove(new MouseEventArgs(mousebuttons, 0, (int)mousepos.x, (int)mousepos.y, 0)); } /// /// This is called when the view changes (scroll/zoom), before the display is redrawn. /// protected virtual void OnViewChanged() { } // This enabled automatic panning, if preferred protected void EnableAutoPanning() { if(General.Settings.AutoScrollSpeed > 0) { if(!autopanenabled) { autopanenabled = true; General.MainWindow.EnableProcessing(); } } } // This disabls automatic panning protected void DisableAutoPanning() { if(autopanenabled) { autopanenabled = false; General.MainWindow.DisableProcessing(); } } #endregion #region ================== Processing // Processing public override void OnProcess(long deltatime) { base.OnProcess(deltatime); if(autopanenabled) { Vector2D panamount = new Vector2D(); // How much to pan the view in X? if(mousepos.x < AUTOPAN_BORDER_SIZE) panamount.x = -AUTOPAN_BORDER_SIZE + mousepos.x; else if(mousepos.x > (General.MainWindow.Display.ClientSize.Width - AUTOPAN_BORDER_SIZE)) panamount.x = mousepos.x - (General.MainWindow.Display.ClientSize.Width - AUTOPAN_BORDER_SIZE); // How much to pan the view in Y? if(mousepos.y < AUTOPAN_BORDER_SIZE) panamount.y = AUTOPAN_BORDER_SIZE - mousepos.y; else if(mousepos.y > (General.MainWindow.Display.ClientSize.Height - AUTOPAN_BORDER_SIZE)) panamount.y = -(mousepos.y - (General.MainWindow.Display.ClientSize.Height - AUTOPAN_BORDER_SIZE)); // Do any panning? if(panamount.GetManhattanLength() > 0.0f) { // Scale and power this for nicer usability Vector2D pansign = panamount.GetSign(); panamount = (panamount * panamount) * pansign * 0.0001f * General.Settings.AutoScrollSpeed / renderer.Scale; // Multiply by delta time panamount.x *= deltatime; panamount.y *= deltatime; // Pan the view ScrollBy(panamount.x, panamount.y); } } } #endregion #region ================== Input // Mouse leaves the display public override void OnMouseLeave(EventArgs e) { // Mouse is outside the display mouseinside = false; mousepos = new Vector2D(float.NaN, float.NaN); mousemappos = mousepos; mousebuttons = MouseButtons.None; // Determine new unprojected mouse coordinates General.MainWindow.UpdateCoordinates(mousemappos); // Let the base class know base.OnMouseLeave(e); } // Mouse moved inside the display public override void OnMouseMove(MouseEventArgs e) { // Record last position mouseinside = true; mouselastpos = mousepos; mousepos = new Vector2D(e.X, e.Y); mousemappos = renderer2d.DisplayToMap(mousepos); mousebuttons = e.Button; // Update labels in main window General.MainWindow.UpdateCoordinates(mousemappos); // Holding a button? if(e.Button != MouseButtons.None) { // Not dragging? if(mousedragging == MouseButtons.None) { // Check if moved enough pixels for dragging Vector2D delta = mousedownpos - mousepos; if((Math.Abs(delta.x) > DRAG_START_MOVE_PIXELS) || (Math.Abs(delta.y) > DRAG_START_MOVE_PIXELS)) { // Dragging starts now mousedragging = e.Button; OnDragStart(e); } } } // Selecting? if(selecting) OnUpdateMultiSelection(); // Panning? if(panning) OnUpdateViewPanning(); // Let the base class know base.OnMouseMove(e); } // Mouse button pressed public override void OnMouseDown(MouseEventArgs e) { // Save mouse down position mousedownpos = mousepos; mousedownmappos = mousemappos; //mxd. Looks like in some cases (very detailed maps / slow CPUs) OnMouseUp is not fired // This is my attempt at fixing this... if(e.Button == mousedragging) mousedragging = MouseButtons.None; // Let the base class know base.OnMouseDown(e); } // Mouse button released public override void OnMouseUp(MouseEventArgs e) { // Releasing drag button? if(e.Button == mousedragging) { // No longer dragging OnDragStop(e); mousedragging = MouseButtons.None; } // Let the base class know base.OnMouseUp(e); } //mxd public override void OnKeyDown(KeyEventArgs e) { // Update marque color when modifier keys are pressed if(selecting && (e.Control || e.Shift) && marqueSelectionMode != GetMultiSelectionMode()) OnUpdateMultiSelection(); base.OnKeyDown(e); } //mxd public override void OnKeyUp(KeyEventArgs e) { // Update marque color when modifier keys are released if(selecting && (!e.Control || !e.Shift) && marqueSelectionMode != GetMultiSelectionMode()) OnUpdateMultiSelection(); base.OnKeyUp(e); } /// /// Automatically called when dragging operation starts. /// protected virtual void OnDragStart(MouseEventArgs e) { } /// /// Automatically called when dragging operation stops. /// protected virtual void OnDragStop(MouseEventArgs e) { } #endregion #region ================== Display // This just refreshes the display public override void OnPresentDisplay() { renderer2d.Present(); } // This sets the view mode private static void SetViewMode(ViewMode mode) { General.Map.CRenderer2D.SetViewMode(mode); General.MainWindow.UpdateInterface(); General.MainWindow.RedrawDisplay(); } #endregion #region ================== Methods /// /// Automatically called by the core when this editing mode is engaged. /// public override void OnEngage() { //mxd. Clear display overlay if(renderer.StartOverlay(true)) { //mxd. Center 2d view on camera position in 3d view if(General.Settings.GZSynchCameras && General.Editing.PreviousMode != null && General.Editing.PreviousMode.IsSubclassOf(typeof (VisualMode))) { Vector2D campos = new Vector2D(General.Map.VisualCamera.Position.x, General.Map.VisualCamera.Position.y); renderer2d.PositionView(campos.x, campos.y); } renderer.Finish(); } base.OnEngage(); } /// /// Called when the user requests to cancel this editing mode. /// public override void OnCancel() { cancelled = true; base.OnCancel(); } //mxd public override bool OnMapTestBegin(bool testFromCurrentPosition) { if(testFromCurrentPosition) { if(!mouseinside) { General.MainWindow.DisplayStatus(StatusType.Warning, "Can't test from current position: mouse is outside editing vindow!"); return false; } //find Single Player Start. Should be type 1 in all games Thing start = null; foreach(Thing t in General.Map.Map.Things) { if(t.SRB2Type == 1) { //store thing and position if(start == null) { start = t; } else if(t.Index > start.Index) { //if there are several Player Start 1 things, GZDoom uses one with the biggest index. start = t; } } } if(start == null) { General.MainWindow.DisplayStatus(StatusType.Warning, "Can't test from current position: no Player 1 start found!"); return false; } //now check if cursor is located inside a sector Sector s = General.Map.Map.GetSectorByCoordinates(mousemappos); if(s == null) { General.MainWindow.DisplayStatus(StatusType.Warning, "Can't test from current position: cursor is not inside sector!"); return false; } //41 = player's height in Doom. Is that so in all other games as well? if(s.CeilHeight - s.FloorHeight < 41) { General.MainWindow.DisplayStatus(StatusType.Warning, "Can't test from current position: sector is too low!"); return false; } //store initial position playerStart = start; playerStartPosition = start.Position; //everything should be valid, let's move player start here start.Move(new Vector3D(mousemappos.x, mousemappos.y, 0)); } return true; } public override void OnMapTestEnd(bool testFromCurrentPosition) { if(testFromCurrentPosition) { //restore position playerStart.Move(playerStartPosition); playerStart = null; } } /// /// This is called automatically when the Edit button is pressed. /// (in Doom Builder 1, this was always the right mousebutton) /// [BeginAction("classicedit", BaseAction = true)] protected virtual void OnEditBegin() { } /// /// This is called automatically when the Edit button is released. /// (in Doom Builder 1, this was always the right mousebutton) /// [EndAction("classicedit", BaseAction = true)] protected virtual void OnEditEnd() { } /// /// This is called automatically when the Select button is pressed. /// (in Doom Builder 1, this was always the left mousebutton) /// [BeginAction("classicselect", BaseAction = true)] protected virtual void OnSelectBegin() { selectpressed = true;//mxd } /// /// This is called automatically when the Select button is released. /// (in Doom Builder 1, this was always the left mousebutton) /// [EndAction("classicselect", BaseAction = true)] protected virtual void OnSelectEnd() { selectpressed = false;//mxd if(selecting) OnEndMultiSelection(); } /// /// This is called automatically when a rectangular multi-selection ends. /// protected virtual void OnEndMultiSelection() { selecting = false; General.Hints.ShowHints(this.GetType(), HintsManager.GENERAL); } /// /// Call this to initiate a rectangular multi-selection. /// protected virtual void StartMultiSelection() { selecting = true; selectstart = mousemappos; selectionrect = new RectangleF(selectstart.x, selectstart.y, 0, 0); //mxd General.Hints.ShowHints(this.GetType(), HintsManager.MULTISELECTION); } /// /// This is called automatically when a multi-selection is updated. /// protected virtual void OnUpdateMultiSelection() { marqueSelectionMode = GetMultiSelectionMode(); //mxd selectionrect.X = selectstart.x; selectionrect.Y = selectstart.y; selectionrect.Width = mousemappos.x - selectstart.x; selectionrect.Height = mousemappos.y - selectstart.y; if(selectionrect.Width < 0f) { selectionrect.Width = -selectionrect.Width; selectionrect.X -= selectionrect.Width; } if(selectionrect.Height < 0f) { selectionrect.Height = -selectionrect.Height; selectionrect.Y -= selectionrect.Height; } } //mxd protected virtual MarqueSelectionMode GetMultiSelectionMode() { return MarqueSelectionMode.SELECT; } /// /// Call this to draw the selection on the overlay layer. /// Must call renderer.StartOverlay first! /// protected virtual void RenderMultiSelection() { //mxd PixelColor marqueColor; switch(marqueSelectionMode) { case MarqueSelectionMode.SELECT: marqueColor = General.Colors.Selection.WithAlpha(SELECTION_ALPHA); break; case MarqueSelectionMode.ADD: marqueColor = General.Colors.Highlight.WithAlpha(SELECTION_ALPHA); break; case MarqueSelectionMode.SUBTRACT: marqueColor = General.Colors.Selection.WithAlpha(SELECTION_ALPHA).InverseKeepAlpha(); break; default: //should be Intersect marqueColor = General.Colors.Highlight.WithAlpha(SELECTION_ALPHA).InverseKeepAlpha(); break; } renderer.RenderRectangle(selectionrect, SELECTION_BORDER_SIZE, marqueColor, true); } /// /// This is called automatically when the mouse is moved while panning /// protected virtual void OnUpdateViewPanning() { // We can only drag the map when the mouse pointer is inside // otherwise we don't have coordinates where to drag the map to if(mouseinside && !float.IsNaN(mouselastpos.x) && !float.IsNaN(mouselastpos.y)) { // Get the map coordinates of the last mouse posision (before it moved) Vector2D lastmappos = renderer2d.DisplayToMap(mouselastpos); // Do the scroll ScrollBy(lastmappos.x - mousemappos.x, lastmappos.y - mousemappos.y); } } /// /// This selects given map element (mxd) /// public virtual void SelectMapElement(SelectableElement element) { element.Selected = true; } #endregion #region ================== Actions [BeginAction("gridsetup", BaseAction = true)] protected void ShowGridSetup() { GridSetup.ShowGridSetup(); } [BeginAction("pan_view", BaseAction = true)] protected virtual void BeginViewPan() { panning = true; } [EndAction("pan_view", BaseAction = true)] protected virtual void EndViewPan() { panning = false; } [BeginAction("viewmodenormal", BaseAction = true)] protected virtual void ViewModeNormal() { SetViewMode(ViewMode.Normal); } [BeginAction("viewmodebrightness", BaseAction = true)] protected virtual void ViewModeBrightness() { SetViewMode(ViewMode.Brightness); } [BeginAction("viewmodefloors", BaseAction = true)] protected virtual void ViewModeFloors() { SetViewMode(ViewMode.FloorTextures); } [BeginAction("viewmodeceilings", BaseAction = true)] protected virtual void ViewModeCeilings() { SetViewMode(ViewMode.CeilingTextures); } //mxd [BeginAction("centeroncoordinates", BaseAction = true)] protected virtual void CenterOnCoordinates() { //show form... CenterOnCoordinatesForm form = new CenterOnCoordinatesForm(); if(form.ShowDialog() == DialogResult.OK) { //center view renderer2d.PositionView(form.Coordinates.x, form.Coordinates.y); General.Interface.RedrawDisplay(); } } #endregion } //mxd public enum MarqueSelectionMode { SELECT, ADD, SUBTRACT, INTERSECT, } }