/* * CurveEditor.cs * Copyright (c) 2007-2009 kbinani * * This file is part of LipSync. * * LipSync is free software; you can redistribute it and/or * modify it under the terms of the BSD License. * * LipSync 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. */ using System; using System.Collections.Generic; using System.Drawing; using System.IO; using System.Runtime.Serialization; using System.Windows.Forms; using Boare.Lib.AppUtil; using LipSync; namespace CurveEditor { public partial class CurveEditor : UserControl { public delegate void CurveEditedEventHandler(); public enum MouseDownMode { Nothing, MovePoint, } private class ScaleAndOffset { public float Scale; public float Offset; public ScaleAndOffset( float scale, float offset ) { Scale = scale; Offset = offset; } } Dictionary m_list = new Dictionary(); Dictionary m_list_minmax = new Dictionary(); bool m_drag_ = false; bool m_drag_with_space_key = false; Point m_drag_start; float m_old_xoffset; float m_old_yoffset; const int LABEL_WIDTH = 15; const int LIST_WIDTH = 60; string m_selected = ""; int m_picked_point_id = -1; PickedSide m_picked_side; bool m_scale_locked = false; ControlType m_last_added_type = ControlType.None; List m_commands = new List(); int m_command_position = -1; PointF m_before_edit; //bool m_moved = false; //bool m_mouse_upped = true; bool m_change_xscale = false; bool m_change_yscale = false; int m_change_origin; float m_old_scale; float CHANGE_SCALE_ORDER = 70f; bool m_spacekey_down = false; Cursor HAND; bool _number_visible = false; PointF _number; Font _font; Point _mouse_position; ContextMenuStrip _cmenu; ToolStripMenuItem _cmenuNumericInput; bool m_rescaley_enabled = true; XLabel m_xlabel = XLabel.None; YLabel m_ylabel = YLabel.None; bool m_change_xscale_with_wheel = true; bool m_change_yscale_with_wheel = true; bool m_show_list = true; float m_max_xscale = 100f; float m_min_xscale = 1e-4f; float m_max_yscale = 100f; float m_min_yscale = 1e-4f; Color m_origin_scale_line = Color.FromArgb( 44, 44, 44 ); Color m_scale_line = Color.FromArgb( 94, 94, 94 ); Color m_sub_scale_line = Color.FromArgb( 110, 110, 110 ); Color m_cHandle_master = Color.FromArgb( 240, 144, 160 ); Color m_cControl_master = Color.FromArgb( 255, 130, 0 ); Color m_cHandle_normal = Color.FromArgb( 255, 130, 0 ); Color m_cControl_normal = Color.FromArgb( 51, 192, 64 ); Color m_data_point = Color.Black; Color m_data_point_hilight = Color.Red; int m_data_point_size = 2; int m_control_point_size = 2; PointType m_data_point_type; PointType m_control_point_type; Color m_label_back = Color.FromArgb( 172, 172, 172 ); Color m_list_back = Color.FromArgb( 143, 143, 143 ); float m_xscale = 1f; float m_yscale = 1f; float m_xoffset = 0f; float m_yoffset = 0f; int m_place_count_x = 1; int m_place_count_y = 1; bool m_scroll_enabled = true; bool m_mouse_moved = false; MouseDownMode m_mouse_down_mode = MouseDownMode.Nothing; /// /// カーブのデータ点・制御点などが編集された時に発生します /// public event CurveEditedEventHandler CurveEdited; /// /// パブリックコンストラクタ /// public CurveEditor() { InitializeComponent(); this.MouseWheel += new MouseEventHandler( CurveEditor_MouseWheel ); this.SetStyle( ControlStyles.DoubleBuffer, true ); this.SetStyle( ControlStyles.UserPaint, true ); this.SetStyle( ControlStyles.AllPaintingInWmPaint, true ); byte[] foo = new byte[] { 0x0, 0x0, 0x2, 0x0, 0x1, 0x0, 0x20, 0x20, 0x0, 0x0, 0x10, 0x0, 0x10, 0x0, 0xe8, 0x2, 0x0, 0x0, 0x16, 0x0, 0x0, 0x0, 0x28, 0x0, 0x0, 0x0, 0x20, 0x0, 0x0, 0x0, 0x40, 0x0, 0x0, 0x0, 0x1, 0x0, 0x4, 0x0, 0x0, 0x0, 0x0, 0x0, 0x80, 0x2, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x80, 0x0, 0x0, 0x0, 0x0, 0x80, 0x0, 0x0, 0x80, 0x80, 0x0, 0x0, 0x0, 0x0, 0x80, 0x0, 0x80, 0x0, 0x80, 0x0, 0x0, 0x80, 0x80, 0x0, 0xc0, 0xc0, 0xc0, 0x0, 0x80, 0x80, 0x80, 0x0, 0xff, 0x0, 0x0, 0x0, 0x0, 0xff, 0x0, 0x0, 0xff, 0xff, 0x0, 0x0, 0x0, 0x0, 0xff, 0x0, 0xff, 0x0, 0xff, 0x0, 0x0, 0xff, 0xff, 0x0, 0xff, 0xff, 0xff, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0xf, 0xf0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0xff, 0xff, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0xff, 0xff, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0xff, 0x0, 0x0, 0x0, 0x0, 0xff, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0xf, 0xff, 0x0, 0x0, 0x0, 0x0, 0xff, 0xf0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0xf, 0xff, 0x0, 0x0, 0x0, 0x0, 0xff, 0xf0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0xff, 0x0, 0x0, 0x0, 0x0, 0xff, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0xff, 0xff, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0xff, 0xff, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0xf, 0xf0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xfe, 0x7f, 0xff, 0xff, 0xfc, 0x3f, 0xff, 0xff, 0xf8, 0x1f, 0xff, 0xff, 0xf8, 0x1f, 0xff, 0xff, 0xfc, 0x3f, 0xff, 0xff, 0xcf, 0xf3, 0xff, 0xff, 0x87, 0xe1, 0xff, 0xff, 0x7, 0xe0, 0xff, 0xff, 0x7, 0xe0, 0xff, 0xff, 0x87, 0xe1, 0xff, 0xff, 0xcf, 0xf3, 0xff, 0xff, 0xfc, 0x3f, 0xff, 0xff, 0xf8, 0x1f, 0xff, 0xff, 0xf8, 0x1f, 0xff, 0xff, 0xfc, 0x3f, 0xff, 0xff, 0xfe, 0x7f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, }; using ( MemoryStream ms = new MemoryStream( foo ) ) { HAND = new Cursor( ms ); } _font = new Font( "MS UI Gothic", 10 ); } public bool GetYScaleAndYOffset( string ID, out float y_scale, out float y_offset ) { if ( m_list_minmax.ContainsKey( ID ) ) { y_scale = m_list_minmax[ID].Scale; y_offset = m_list_minmax[ID].Offset; return true; } else { y_scale = 1f; y_offset = 0f; return false; } } public void SetYScaleAndYOffset( string ID, float y_scale, float y_offset ) { if ( m_list_minmax.ContainsKey( ID ) ) { m_list_minmax[ID].Scale = y_scale; m_list_minmax[ID].Offset = y_offset; this.Invalidate(); } } public static string _( string s ) { return Messaging.GetMessage( s ); } public Size GraphSize { get { int width, height; if ( XLabel != XLabel.None ) { height = this.Height - LABEL_WIDTH; } else { height = this.Height; } if ( YLabel != YLabel.None ) { width = this.Width - LABEL_WIDTH; } else { width = this.Width; } if ( ShowList ) { width -= LIST_WIDTH; } return new Size( width, height ); } } /// /// 縦軸スケールの変更を許可するかどうかを取得または設定します /// public bool RescaleYEnabled { get { return m_rescaley_enabled; } set { m_rescaley_enabled = value; } } bool Drag { get { return m_drag_; } set { m_drag_ = value; if ( m_drag_ ) { this.Cursor = HAND; } else { this.Cursor = Cursors.Default; } } } /// /// 現在カーブエディタに設定されているデータを全て破棄し,初期化します /// public void Clear() { m_list.Clear(); m_list_minmax.Clear(); m_commands.Clear(); m_command_position = -1; m_selected = ""; m_picked_point_id = -1; } /// /// Undo, Redo用のバッファを全て破棄し,初期化します /// public void ClearBuffer() { m_commands.Clear(); m_command_position = -1; } /// /// 横軸の目盛の種類を取得または設定します /// public XLabel XLabel { get { return m_xlabel; } set { m_xlabel = value; } } /// /// 縦軸の目盛の種類を取得または設定します /// public YLabel YLabel { get { return m_ylabel; } set { m_ylabel = value; } } /// /// マウスホイールで横軸のスケールを変更するかどうかを取得または設定します /// public bool ChangeXScaleWithWheel { get { return m_change_xscale_with_wheel; } set { m_change_xscale_with_wheel = value; } } /// /// マウスホイールで縦軸のスケールを変更するかどうかを取得または設定します /// public bool ChangeYScaleWithWheel { get { return m_change_yscale_with_wheel; } set { m_change_yscale_with_wheel = value; } } /// /// 編集対象となるカーブのリストを,画面右側に表示するかどうかを取得または設定します /// public bool ShowList { get { return m_show_list; } set { m_show_list = value; } } /// /// 横軸のスケールとして設定できる最大値を取得または設定します /// public float MaxXScale { get { return m_max_xscale; } set { if ( value > 0 && value > m_min_xscale ) { m_max_xscale = value; } } } /// /// 横軸のスケールとして設定できる最小値を取得または設定します /// public float MinXScale { get { return m_min_xscale; } set { if ( value > 0 && value < m_max_xscale ) { m_min_xscale = value; } } } /// /// 縦軸のスケールとして設定できる最大値を取得または設定します /// public float MaxYScale { get { return m_max_yscale; } set { if ( value > 0 && value > m_min_yscale ) { m_max_yscale = value; } } } /// /// 縦軸のスケールとして設定できる最小値を取得または設定します /// public float MinYScale { get { return m_min_yscale; } set { if ( value > 0 && value < m_max_yscale ) { m_min_yscale = value; } } } void CurveEditor_MouseWheel( object sender, MouseEventArgs e ) { Keys modifier = Control.ModifierKeys; if ( modifier == Keys.Control ) { float dx = e.Delta / XScale; XOffset -= dx; this.Invalidate(); } else if ( modifier == Keys.Shift ) { float dy = e.Delta / YScale; YOffset -= dy; if ( m_selected != "" ) { m_list_minmax[m_selected].Offset = YOffset; } this.Invalidate(); } else if ( modifier == Keys.None ) { float count = e.Delta / 120f; float n_xscale; float n_yscale; float order; if ( count < 0 ) { order = (float)Math.Pow( 0.83, Math.Abs( count ) ); } else { order = (float)(1f / Math.Pow( 0.83, Math.Abs( count ) )); } if ( m_change_xscale_with_wheel ) { n_xscale = m_xscale * order; } else { n_xscale = m_xscale; } if ( m_change_yscale_with_wheel ) { n_yscale = m_yscale * order; } else { n_yscale = m_yscale; } if ( m_xscale != n_xscale || m_yscale != n_yscale ) { bool changed = false; if ( MinXScale <= n_xscale && n_xscale <= MaxXScale ) { XOffset = e.X * (1f / n_xscale - 1f / XScale) + XOffset; XScale = n_xscale; changed = true; } if ( MinYScale <= n_yscale && n_yscale <= MaxYScale ) { YOffset = (this.Height - e.Y) * (1f / n_yscale - 1f / YScale) + YOffset; YScale = n_yscale; changed = true; } if ( changed ) { this.Invalidate(); } } } } /// /// 編集対象となるカーブを,このコントロールに追加します /// /// /// /*public void Add( string ID, Color curve ) { m_list.Add( ID, new BezierChain( curve ) ); }*/ public void Add( string ID, BezierChain chain ) { m_list.Add( ID, chain ); m_list_minmax.Add( ID, new ScaleAndOffset( 1f, 0f ) ); } //todo:動かしうるxの範囲を探知できるようにする?pt1-4それぞれが動く場合。 /// /// 4つの制御点からなるベジエ曲線が、x軸について陰かどうかを判定する /// /// 始点 /// 制御点1 /// 制御点2 /// 終点 /// public static bool IsBezierImplicit( float pt1, float pt2, float pt3, float pt4 ) { double a = pt4 - 3 * pt3 + 3 * pt2 - pt1; double b = 2 * pt3 - 4 * pt2 + 2 * pt1; double c = pt2 - pt1; if ( a == 0 ) { if ( c >= 0 && b + c >= 0 ) { return true; } else { return false; } } else if ( a > 0 ) { if ( -b / (2 * a) <= 0 ) { if ( c >= 0 ) { return true; } else { return false; } } else if ( 1 <= -b / (2 * a) ) { if ( a + b + c >= 0 ) { return true; } else { return false; } } else { if ( c - b * b / (4 * a) >= 0 ) { return true; } else { return false; } } } else { if ( -b / (2 * a) <= 0.5 ) { if ( a + b + c >= 0 ) { return true; } else { return false; } } else { if ( c >= 0 ) { return true; } else { return false; } } } } #region 描画設定 public Color MainScaleLine { get { return m_origin_scale_line; } set { m_origin_scale_line = value; } } public Color ScaleLine { get { return m_scale_line; } set { m_scale_line = value; } } public Color SubScaleLine { get { return m_sub_scale_line; } set { m_sub_scale_line = value; } } public Color HandleMaster { get { return m_cHandle_master; } set { m_cHandle_master = value; } } public Color ControlMaster { get { return m_cControl_master; } set { m_cControl_master = value; } } public Color HandleNormal { get { return m_cHandle_normal; } set { m_cHandle_normal = value; } } public Color ControlNormal { get { return m_cControl_normal; } set { m_cControl_normal = value; } } /// /// データポイントの描画色を取得または設定します /// public Color DataPoint { get { return m_data_point; } set { m_data_point = value; } } /// /// 選択されたデータポイントの描画色を取得または設定します /// public Color DataPointHilight { get { return m_data_point_hilight; } set { m_data_point_hilight = value; } } public int DataPointSize { get { return m_data_point_size; } set { m_data_point_size = value; } } public int ControlPointSize { get { return m_control_point_size; } set { m_control_point_size = value; } } public PointType DataPointType { get { return m_data_point_type; } set { m_data_point_type = value; } } public PointType ControlPointType { get { return m_control_point_type; } set { m_control_point_type = value; } } public Color LabelBackground { get { return m_label_back; } set { m_label_back = value; } } public Color ListBackground { get { return m_list_back; } set { m_list_back = value; } } #endregion /// /// 横軸のスケールを取得または設定します[pixel/(任意の単位)] /// public float XScale { get { return m_xscale; } set { if ( !m_scale_locked && MinXScale <= value && value <= MaxXScale ) { m_xscale = value; } } } /// /// 縦軸のスケールを取得または設定します[pixel/(任意の単位)] /// public float YScale { get { return m_yscale; } set { if ( !m_scale_locked && MinYScale <= value && value <= MaxYScale ) { m_yscale = value; } } } /// /// 画面左端におけるx軸の値を取得または設定します /// public float XOffset { get { return m_xoffset; } set { m_xoffset = value; } } /// /// /// public float YOffset { get { return m_yoffset; } set { m_yoffset = value; } } /// /// グラフ上のX座標を,コントロール上の座標(pixel)に換算します /// /// /// private int cX( float sX ) { return (int)((sX + XOffset) * XScale); } /// /// グラフ上のY座標を,コントロール上の座標(pixel)に換算します /// /// /// private int cY( float sY ) { return this.Height - (int)((sY + YOffset) * YScale); } private Point cPoint( PointF sPoint ) { return new Point( cX( sPoint.X ), cY( sPoint.Y ) ); } private float sX( int cX ) { return cX / XScale - XOffset; } private float sY( int cY ) { return (this.Height - cY) / YScale - YOffset; } private PointF sPoint( Point cPoint ) { return new PointF( sX( cPoint.X ), sY( cPoint.Y ) ); } private float m_place_x { get { int n1 = (int)Math.Log10( 400 / XScale ); int n2 = (int)Math.Log10( 200 / XScale ); int n5 = (int)Math.Log10( 80 / XScale ); float d1 = (float)Math.Pow( 10, n1 ); float d2 = (float)Math.Pow( 10, n2 ); float d5 = (float)Math.Pow( 10, n5 ); if ( d1 <= 2 * d2 && d1 <= 5 * d5 ) { m_place_count_x = 1; return d1; } else if ( 2 * d2 <= d1 && 2 * d2 <= 5 * d5 ) { m_place_count_x = 2; return d2; } else { m_place_count_x = 5; return d5; } } } private float m_place_y { get { int n1 = (int)Math.Log10( 400 / YScale ); int n2 = (int)Math.Log10( 200 / YScale ); int n5 = (int)Math.Log10( 80 / YScale ); float d1 = (float)Math.Pow( 10, n1 ); float d2 = (float)Math.Pow( 10, n2 ); float d5 = (float)Math.Pow( 10, n5 ); if ( d1 <= 2 * d2 && d1 <= 5 * d5 ) { m_place_count_y = 1; return d1; } else if ( 2 * d2 <= d1 && 2 * d2 <= 5 * d5 ) { m_place_count_y = 2; return d2; } else { m_place_count_y = 5; return d5; } } } public BezierChain this[string ID] { get { return m_list[ID]; } set { m_list[ID] = value; } } public float this[string ID, float x] { get { return m_list[ID].GetValue( x ); } } public bool ScrollEnabled { get { return m_scroll_enabled; } set { m_scroll_enabled = value; } } /// /// /// /// /// /// 関数を抜けるとき確実にm_scale_locked = falseとすること!! private void CurveEditor_Paint( object sender, PaintEventArgs e ) { m_scale_locked = true; //グラフ内のメモリを描画 int o_x = cX( 0f ); int o_y = cY( 0f ); e.Graphics.DrawLine( new Pen( MainScaleLine ), new Point( 0, o_y ), new Point( this.Width, o_y ) ); e.Graphics.DrawLine( new Pen( MainScaleLine ), new Point( o_x, 0 ), new Point( o_x, this.Height ) ); #if DEBUG //MessageBox.Show( "place_x=" + place_x ); #endif float place_x = m_place_count_x * m_place_x; int start_x = (int)(sX( 0 ) / place_x) - 1; int end_x = (int)(sX( this.Width ) / place_x) + 1; for ( int i = start_x; i <= end_x; i++ ) { float sx; int px; if ( i != 0 ) { sx = i * place_x; px = cX( sx ); e.Graphics.DrawLine( new Pen( ScaleLine ), new Point( px, 0 ), new Point( px, this.Height ) ); } sx = (i + 0.5f) * place_x; px = cX( sx ); e.Graphics.DrawLine( new Pen( SubScaleLine ), new Point( px, 0 ), new Point( px, this.Height ) ); } float place_y = m_place_count_y * m_place_y; int start_y = (int)(sY( this.Height ) / place_y) - 1; int end_y = (int)(sY( 0 ) / place_y) + 1; for ( int i = start_y; i <= end_y; i++ ) { float sy; int py; if ( i != 0 ) { sy = i * place_y; py = cY( sy ); e.Graphics.DrawLine( new Pen( ScaleLine ), new Point( 0, py ), new Point( this.Width, py ) ); } sy = (i + 0.5f) * place_y; py = cY( sy ); e.Graphics.DrawLine( new Pen( SubScaleLine ), new Point( 0, py ), new Point( this.Width, py ) ); } //foreach ( BezierChain chain in m_list.Values ) { foreach ( string ID in m_list.Keys ) { BezierChain chain = m_list[ID]; BezierPoint last; if ( chain.Count >= 2 ) { last = chain.List[0]; } else { int default_y = cY( chain.Default ); e.Graphics.DrawLine( new Pen( chain.Color ), new Point( 0, default_y ), new Point( this.Width, default_y ) ); if ( ID == m_selected && chain.Count >= 1 ) { int width2 = m_data_point_size; switch ( DataPointType ) { case PointType.Circle: if ( chain.List[0].ID == m_picked_point_id ) { e.Graphics.FillEllipse( new SolidBrush( DataPointHilight ), new Rectangle( cX( chain.List[0].Base.X ) - width2, cY( chain.List[0].Base.Y ) - width2, 2 * width2, 2 * width2 ) ); } else { e.Graphics.FillEllipse( new SolidBrush( DataPoint ), new Rectangle( cX( chain.List[0].Base.X ) - width2, cY( chain.List[0].Base.Y ) - width2, 2 * width2, 2 * width2 ) ); } break; case PointType.Rectangle: if ( chain.List[0].ID == m_picked_point_id ) { e.Graphics.FillRectangle( new SolidBrush( DataPointHilight ), new Rectangle( cX( chain.List[0].Base.X ) - width2, cY( chain.List[0].Base.Y ) - width2, 2 * width2, 2 * width2 ) ); } else { e.Graphics.FillRectangle( new SolidBrush( DataPoint ), new Rectangle( cX( chain.List[0].Base.X ) - width2, cY( chain.List[0].Base.Y ) - width2, 2 * width2, 2 * width2 ) ); } break; } } continue; } int width = m_data_point_size; if ( ID == m_selected ) { switch ( DataPointType ) { case PointType.Circle: if ( chain.List[0].ID == m_picked_point_id ) { e.Graphics.FillEllipse( new SolidBrush( DataPointHilight ), new Rectangle( cX( chain.List[0].Base.X ) - width, cY( chain.List[0].Base.Y ) - width, 2 * width, 2 * width ) ); } else { e.Graphics.FillEllipse( new SolidBrush( DataPoint ), new Rectangle( cX( chain.List[0].Base.X ) - width, cY( chain.List[0].Base.Y ) - width, 2 * width, 2 * width ) ); } break; case PointType.Rectangle: if ( chain.List[0].ID == m_picked_point_id ) { e.Graphics.FillRectangle( new SolidBrush( DataPointHilight ), new Rectangle( cX( chain.List[0].Base.X ) - width, cY( chain.List[0].Base.Y ) - width, 2 * width, 2 * width ) ); } else { e.Graphics.FillRectangle( new SolidBrush( DataPoint ), new Rectangle( cX( chain.List[0].Base.X ) - width, cY( chain.List[0].Base.Y ) - width, 2 * width, 2 * width ) ); } break; } } //デフォルト値(左側)の描画 if ( chain.Count >= 1 ) { int default_y = cY( chain.Default ); int x = cX( chain.List[0].Base.X ); int y = cY( chain.List[0].Base.Y ); e.Graphics.DrawLine( new Pen( chain.Color ), new Point( x, default_y ), new Point( -1, default_y ) ); e.Graphics.DrawLine( new Pen( chain.Color ), new Point( x, default_y ), new Point( x, y ) ); } //for ( int i = 1; i < chain.Count; i++ ) { bool first = true; Size sz = new Size( 2 * width, 2 * width ); for( int i = 0; i < chain.List.Count; i++ ){ BezierPoint bp = chain.List[i]; if ( first ) { last = bp; first = false; continue; } //if ( last.ControlRightType != ControlType.None && chain[i].ControlLeftType != ControlType.None ) { e.Graphics.SmoothingMode = System.Drawing.Drawing2D.SmoothingMode.HighQuality; e.Graphics.DrawBezier( new Pen( chain.Color ), cPoint( last.Base ), cPoint( last.ControlRight ), cPoint( bp.ControlLeft ), cPoint( bp.Base ) ); e.Graphics.SmoothingMode = System.Drawing.Drawing2D.SmoothingMode.Default; //制御ハンドル用の線 if ( ID == m_selected ) { if ( bp.ControlLeftType == ControlType.Master ) { e.Graphics.DrawLine( new Pen( HandleMaster ), cPoint( bp.Base ), cPoint( bp.ControlLeft ) ); } else if ( bp.ControlLeftType == ControlType.Normal ) { e.Graphics.DrawLine( new Pen( HandleNormal ), cPoint( bp.Base ), cPoint( bp.ControlLeft ) ); } if ( last.ControlRightType == ControlType.Master ) { e.Graphics.DrawLine( new Pen( HandleMaster ), cPoint( last.Base ), cPoint( last.ControlRight ) ); } else if ( last.ControlRightType == ControlType.Normal ) { e.Graphics.DrawLine( new Pen( HandleNormal ), cPoint( last.Base ), cPoint( last.ControlRight ) ); } //データ点 width = m_data_point_size; Point data_point = new Point( cX( bp.Base.X ) - width, cY( bp.Base.Y ) - width ); switch ( DataPointType ) { case PointType.Circle: if ( bp.ID == m_picked_point_id ) { e.Graphics.FillEllipse( new SolidBrush( DataPointHilight ), new Rectangle( data_point, sz ) ); } else { e.Graphics.FillEllipse( new SolidBrush( DataPoint ), new Rectangle( data_point, sz ) ); } break; case PointType.Rectangle: if ( bp.ID == m_picked_point_id ) { e.Graphics.FillRectangle( new SolidBrush( DataPointHilight ), new Rectangle( data_point, sz ) ); } else { e.Graphics.FillRectangle( new SolidBrush( DataPoint ), new Rectangle( data_point, sz ) ); } break; } //制御ハンドル点 width = m_control_point_size; Color cLeft = Color.Black; Color cRight = Color.Black; if ( bp.ControlLeftType == ControlType.Master ) { cLeft = ControlMaster; } else if ( bp.ControlLeftType == ControlType.Normal ) { cLeft = ControlNormal; } if ( last.ControlRightType == ControlType.Master ) { cRight = ControlMaster; } else if ( last.ControlRightType == ControlType.Normal ) { cRight = ControlNormal; } if ( bp.ControlLeftType != ControlType.None && (bp.ControlLeft.X != bp.Base.X || bp.ControlLeft.Y != bp.Base.Y) ) { Point ctrl_left = new Point( cX( bp.ControlLeft.X ) - width, cY( bp.ControlLeft.Y ) - width ); switch ( ControlPointType ) { case PointType.Circle: e.Graphics.FillEllipse( new SolidBrush( cLeft ), new Rectangle( ctrl_left, sz ) ); break; case PointType.Rectangle: e.Graphics.FillRectangle( new SolidBrush( cLeft ), new Rectangle( ctrl_left, sz ) ); break; } } if ( last.ControlRightType != ControlType.None && (last.ControlRight.X != last.Base.X || last.ControlRight.Y != last.Base.Y) ) { Point ctrl_right = new Point( cX( last.ControlRight.X ) - width, cY( last.ControlRight.Y ) - width ); switch ( ControlPointType ) { case PointType.Circle: e.Graphics.FillEllipse( new SolidBrush( cRight ), new Rectangle( ctrl_right, sz ) ); break; case PointType.Rectangle: e.Graphics.FillRectangle( new SolidBrush( cRight ), new Rectangle( ctrl_right, sz ) ); break; } } } last = bp; } //デフォルト値(右側)の描画 if ( chain.Count >= 1 ) { int default_y = cY( chain.Default ); int x = cX( last.Base.X ); int y = cY( last.Base.Y ); e.Graphics.DrawLine( new Pen( chain.Color ), new Point( x, default_y ), new Point( this.Width + 1, default_y ) ); e.Graphics.DrawLine( new Pen( chain.Color ), new Point( x, default_y ), new Point( x, y ) ); } } // マウス位置の数値を描画 if ( _number_visible ) { int x = cX( _number.X ); int y = cY( _number.Y ); e.Graphics.DrawString( "x : " + _number.X, _font, Brushes.Black, new PointF( x, y - 24 ) ); e.Graphics.DrawString( "y : " + _number.Y, _font, Brushes.Black, new PointF( x, y - 12 ) ); } else { float x = sX( _mouse_position.X ); float y = sY( _mouse_position.Y ); e.Graphics.DrawString( "x : " + x, _font, Brushes.Black, new PointF( _mouse_position.X, _mouse_position.Y - 24 ) ); e.Graphics.DrawString( "y : " + y, _font, Brushes.Black, new PointF( _mouse_position.X, _mouse_position.Y - 12 ) ); } int label_y = 0; using ( Font font = new Font( "MS UI Gothic", 9 ) ) { //ラベルを描画。(必要なら) switch ( XLabel ) { case XLabel.Top: e.Graphics.FillRectangle( new SolidBrush( LabelBackground ), new Rectangle( 0, 0, this.Width, LABEL_WIDTH ) ); break; case XLabel.Bottom: e.Graphics.FillRectangle( new SolidBrush( LabelBackground ), new Rectangle( 0, this.Height - LABEL_WIDTH, this.Width, LABEL_WIDTH ) ); label_y = this.Height - LABEL_WIDTH; break; } if ( XLabel != XLabel.None ) { for ( int i = start_x; i <= end_x; i++ ) { float sx = i * place_x; int px = cX( sx ); e.Graphics.DrawString( sx.ToString(), font, Brushes.Black, new PointF( px, label_y ) ); } } int label_x = 0; switch ( YLabel ) { case YLabel.Left: e.Graphics.FillRectangle( new SolidBrush( LabelBackground ), new Rectangle( 0, 0, LABEL_WIDTH, this.Height ) ); break; case YLabel.Right: if ( ShowList ) { label_x = this.Width - LABEL_WIDTH - LIST_WIDTH; } else { label_x = this.Width - LABEL_WIDTH; } e.Graphics.FillRectangle( new SolidBrush( LabelBackground ), new Rectangle( label_x, 0, LABEL_WIDTH, this.Height ) ); break; } if ( YLabel != YLabel.None ) { for ( int i = start_y; i <= end_y; i++ ) { float sy = i * place_y; int py = cY( sy ); e.Graphics.DrawString( sy.ToString(), font, Brushes.Black, new PointF( label_x, py ) ); } } //リストを描く if ( ShowList ) { e.Graphics.FillRectangle( new SolidBrush( ListBackground ), new Rectangle( this.Width - LIST_WIDTH, 0, LIST_WIDTH, this.Height ) ); e.Graphics.DrawLine( Pens.Black, new Point( this.Width - LIST_WIDTH, 0 ), new Point( this.Width - LIST_WIDTH, this.Height ) ); int count = 0; using ( Font labelfont = new Font( "Arial", 12, FontStyle.Bold, GraphicsUnit.Pixel ) ) { foreach ( string name in m_list.Keys ) { e.Graphics.SmoothingMode = System.Drawing.Drawing2D.SmoothingMode.AntiAlias; if ( name == m_selected ) { e.Graphics.DrawString( name, labelfont, Brushes.White, new PointF( this.Width - 40, count * 17 + 2 ) ); } else { e.Graphics.DrawString( name, labelfont, Brushes.Black, new PointF( this.Width - 40, count * 17 + 2 ) ); } e.Graphics.SmoothingMode = System.Drawing.Drawing2D.SmoothingMode.Default; e.Graphics.FillRectangle( new SolidBrush( m_list[name].Color ), new Rectangle( this.Width - 55, count * 17 + 2, 10, 15 ) ); count++; } } } } m_scale_locked = false; } private void CurveEditor_MouseDown( object sender, MouseEventArgs e ) { m_mouse_moved = false; if ( e.Button == MouseButtons.Middle || (m_spacekey_down && e.Button == MouseButtons.Left ) ) { if ( m_spacekey_down && ((e.Button & MouseButtons.Left) == MouseButtons.Left) ) { m_drag_with_space_key = true; } #region MouseButtons.Middle if ( ScrollEnabled ) { switch ( XLabel ) { case XLabel.Top: if ( e.Y <= LABEL_WIDTH ) { return; } break; case XLabel.Bottom: if ( this.Height - LABEL_WIDTH <= e.Y ) { return; } break; } switch ( YLabel ) { case YLabel.Left: if ( e.X <= LABEL_WIDTH ) { return; } if ( ShowList && this.Width - LIST_WIDTH <= e.X ) { return; } break; case YLabel.Right: if ( ShowList ) { if ( this.Width - LIST_WIDTH - LABEL_WIDTH <= e.X && e.X <= this.Width - LIST_WIDTH ) { // 軸ラベルの部分でマウスダウン return; } else if ( this.Width - LIST_WIDTH <= e.X ) { // リストの部分でマウスダウン return; } } else { if ( this.Width - LABEL_WIDTH <= e.X ) { return; } } break; } m_drag_start = e.Location; m_old_xoffset = m_xoffset; m_old_yoffset = m_yoffset; Drag = true; } #endregion } else if ( e.Button == MouseButtons.Left ) { #region MouseButtons.Left #region リスト // 右のリストの部分がクリックされたかどうかを検査 if ( ShowList && this.Width - LIST_WIDTH <= e.X ) { int count = 0; foreach ( string name in m_list.Keys ) { Rectangle active = new Rectangle( this.Width - 55, count * 17, 50, 16 ); if ( active.X <= e.X && e.X <= active.X + active.Width && active.Y <= e.Y && e.Y <= active.Y + active.Height ) { m_selected = name; if ( RescaleYEnabled ) { /*float min, max; m_list[m_selected].GetMinMax( out min, out max ); #if DEBUG LipSync.Common.DebugWriteLine( "min,max=" + min + "," + max ); #endif if ( min != max ) { float dif = max - min; YScale = this.GraphSize.Height / (dif * 1.1f); float new_offset; if ( XLabel == XLabel.Bottom ) { new_offset = -(min - dif * 0.05f - LABEL_WIDTH / YScale); } else { new_offset = -min + dif * 0.05f; } YOffset = new_offset; }*/ YScale = m_list_minmax[m_selected].Scale; YOffset = m_list_minmax[m_selected].Offset; } this.Invalidate(); return; } count++; } m_selected = ""; this.Invalidate(); return; } #endregion #region スケール部分 if ( XLabel == XLabel.Top ) { if ( 0 <= e.Y && e.Y <= LABEL_WIDTH ) { m_change_xscale = true; m_change_origin = e.X; m_old_scale = XScale; return; } } else if ( XLabel == XLabel.Bottom ) { if ( this.Height - LABEL_WIDTH <= e.Y && e.Y <= this.Height ) { m_change_xscale = true; m_change_origin = e.X; m_old_scale = XScale; return; } } if ( YLabel == YLabel.Left ) { if ( 0 <= e.X && e.X <= LABEL_WIDTH ) { m_change_yscale = true; m_change_origin = e.Y; m_old_scale = YScale; return; } } else if ( YLabel == YLabel.Right ) { if ( ShowList ) { if ( this.Width - LIST_WIDTH - LABEL_WIDTH <= e.X && e.X <= this.Width - LIST_WIDTH ) { m_change_yscale = true; m_change_origin = e.Y; m_old_scale = YScale; return; } } else { if ( this.Width - LABEL_WIDTH <= e.X && e.X <= this.Width ) { m_change_yscale = true; m_change_origin = e.Y; m_old_scale = YScale; return; } } } #endregion #region マウス位置のデータ点を検索 DetectSelectedPoint( e.Location, out m_picked_point_id, out m_picked_side ); #if DEBUG Common.DebugWriteLine( "CureveEditor_MouseDown" ); Common.DebugWriteLine( " m_picked_point_id=" + m_picked_point_id ); Common.DebugWriteLine( " m_picked_side=" + m_picked_side ); #endif if ( m_picked_point_id >= 0 ) { if ( m_picked_side == PickedSide.Base ) { m_before_edit = m_list[m_selected][m_picked_point_id].Base; } else if ( m_picked_side == PickedSide.Left ) { m_before_edit = m_list[m_selected][m_picked_point_id].ControlLeft; } else { m_before_edit = m_list[m_selected][m_picked_point_id].ControlRight; } _number_visible = true; m_mouse_down_mode = MouseDownMode.MovePoint; Invalidate(); } else { m_mouse_down_mode = MouseDownMode.Nothing; } #endregion #endregion } } /// /// カーブエディタ画面上の指定された点にあるデータ点を調べます。 /// /// /// /// private void DetectSelectedPoint( Point e, out int picked_point_id, out PickedSide picked_side ) { picked_point_id = -1; picked_side = PickedSide.Base; if ( m_selected != "" ) { Rectangle active; Point pt; for ( int i = 0; i < m_list[m_selected].List.Count; i++ ) { pt = cPoint( m_list[m_selected].List[i].Base ); int width = m_data_point_size; pt = new Point( pt.X - width - 1, pt.Y - width - 1 ); active = new Rectangle( pt, new Size( width * 2 + 2, width * 2 + 2 ) ); if ( active.X <= e.X && e.X <= active.X + active.Width && active.Y <= e.Y && e.Y <= active.Y + active.Height ) { picked_point_id = m_list[m_selected].List[i].ID; picked_side = PickedSide.Base; //m_before_edit = m_list[m_selected][i].Base; //m_mouse_upped = false; //_number_visible = true; return; } pt = cPoint( m_list[m_selected].List[i].ControlLeft ); width = m_control_point_size; pt = new Point( pt.X - width - 1, pt.Y - width - 1 ); active = new Rectangle( pt, new Size( width * 2 + 2, width * 2 + 2 ) ); if ( active.X <= e.X && e.X <= active.X + active.Width && active.Y <= e.Y && e.Y <= active.Y + active.Height ) { picked_point_id = m_list[m_selected].List[i].ID; picked_side = PickedSide.Left; //m_before_edit = m_list[m_selected][i].ControlLeft; //m_mouse_upped = false; //_number_visible = true; return; } pt = cPoint( m_list[m_selected].List[i].ControlRight ); width = m_control_point_size; pt = new Point( pt.X - width - 1, pt.Y - width - 1 ); active = new Rectangle( pt, new Size( width * 2 + 2, width * 2 + 2 ) ); if ( active.X <= e.X && e.X <= active.X + active.Width && active.Y <= e.Y && e.Y <= active.Y + active.Height ) { picked_point_id = m_list[m_selected].List[i].ID; picked_side = PickedSide.Right; //m_before_edit = m_list[m_selected][i].ControlRight; //m_mouse_upped = false; //_number_visible = true; return; } } //m_picked_point = -1; //m_mouse_upped = true; } } private void CurveEditor_MouseUp( object sender, MouseEventArgs e ) { Drag = false; m_drag_with_space_key = false; if ( m_selected != "" && m_mouse_down_mode == MouseDownMode.MovePoint && m_picked_point_id >= 0 && m_mouse_moved ) { PointF new_pt = m_list[m_selected][m_picked_point_id].GetPosition( m_picked_side ); Command run = Command.GCommandEditPosition( m_selected, m_picked_point_id, m_picked_side, new_pt ); m_list[m_selected][m_picked_point_id].SetPosition( m_picked_side, m_before_edit ); Register( Execute( run ) ); this.Invalidate(); } m_mouse_down_mode = MouseDownMode.Nothing; m_change_xscale = false; m_change_yscale = false; _number_visible = false; } private void CurveEditor_MouseMove( object sender, MouseEventArgs e ) { m_mouse_moved = true; _mouse_position = e.Location; if ( Drag ) { bool drag_ok = false; if ( m_drag_with_space_key ) { if ( m_spacekey_down ) { drag_ok = true; } } else { drag_ok = true; } if ( drag_ok && m_selected != "" ) { int dx = e.X - m_drag_start.X; int dy = m_drag_start.Y - e.Y; XOffset = m_old_xoffset + dx / m_xscale; YOffset = m_old_yoffset + dy / m_yscale; m_list_minmax[m_selected].Offset = YOffset; this.Invalidate(); } } else if ( m_picked_point_id >= 0 && m_mouse_down_mode == MouseDownMode.MovePoint ) { PointF new_pt = sPoint( e.Location ); BezierPoint bp = m_list[m_selected][m_picked_point_id]; int centre = m_list[m_selected].GetIndexFromId( m_picked_point_id ); int left_id = m_list[m_selected].GetIdFromIndex( centre - 1 ); int right_id = m_list[m_selected].GetIdFromIndex( centre + 1 ); switch ( m_picked_side ) { case PickedSide.Base: #region PickedSide.Base if ( 1 <= centre ) { if ( new_pt.X < m_list[m_selected][left_id].Base.X ) { new_pt.X = m_list[m_selected][left_id].Base.X; } if ( bp.ControlLeftType != ControlType.None ) { float x1 = m_list[m_selected][left_id].Base.X; float x2 = m_list[m_selected][left_id].ControlRight.X; float x3 = new_pt.X + (bp.ControlLeft.X - bp.Base.X); float x4 = new_pt.X; if ( !IsBezierImplicit( x1, x2, x3, x4 ) ) { bp.Base = new PointF( bp.Base.X, new_pt.Y ); _number = bp.Base; this.Invalidate(); return; } } } if ( centre < m_list[m_selected].Count - 1 ) { if ( m_list[m_selected][right_id].Base.X < new_pt.X ) { new_pt.X = m_list[m_selected][right_id].Base.X; } if ( bp.ControlRightType != ControlType.None ) { float x1 = new_pt.X; float x2 = new_pt.X + (bp.ControlRight.X - bp.Base.X); float x3 = m_list[m_selected][right_id].ControlLeft.X; float x4 = m_list[m_selected][right_id].Base.X; if ( !IsBezierImplicit( x1, x2, x3, x4 ) ) { bp.Base = new PointF( bp.Base.X, new_pt.Y ); _number = bp.Base; this.Invalidate(); return; } } } bp.Base = new_pt; _number = bp.Base; #endregion break; case PickedSide.Right: #region PickedSide.Right if ( centre < m_list[m_selected].Count - 1 ) { float x1 = bp.Base.X; float x2 = new_pt.X; float x3 = m_list[m_selected][right_id].ControlLeft.X; float x4 = m_list[m_selected][right_id].Base.X; bool is_right = IsBezierImplicit( x1, x2, x3, x4 ); bool is_left = true; float dx = new_pt.X - bp.Base.X; float dy = new_pt.Y - bp.Base.Y; float k = 1f; //if ( bp.ControlRightType == ControlType.Master && m_picked_point >= 1 ) { if ( bp.ControlRightType != ControlType.None && centre >= 1 ) { x1 = m_list[m_selected][left_id].Base.X; x2 = m_list[m_selected][left_id].ControlRight.X; float dx1 = (bp.ControlLeft.X - bp.Base.X) * XScale; float dy1 = (bp.ControlLeft.Y - bp.Base.Y) * YScale; float length = (float)Math.Sqrt( dx1 * dx1 + dy1 * dy1 ); float tdx = dx * XScale; float tdy = dy * YScale; k = length / (float)Math.Sqrt( tdx * tdx + tdy * tdy ); x3 = bp.Base.X - dx * k; x4 = bp.Base.X; is_left = IsBezierImplicit( x1, x2, x3, x4 ); } if ( is_right && is_left ) { bp.ControlRight = new_pt; _number = bp.ControlRight; if ( bp.ControlRightType == ControlType.Master && centre >= 1 && bp.ControlLeftType != ControlType.None ) { bp.ControlLeft = new PointF( bp.Base.X - dx * k, bp.Base.Y - dy * k ); } this.Invalidate(); return; } else { if ( centre == 0 ) { bp.ControlRight = new PointF( bp.ControlRight.X, new_pt.Y ); _number = bp.ControlRight; this.Invalidate(); return; } else { //とりあえずnew_ptのyだけ替えてみて再評価してみる new_pt = new PointF( bp.ControlRight.X, new_pt.Y ); dx = new_pt.X - bp.Base.X; dy = new_pt.Y - bp.Base.Y; x1 = bp.Base.X; x2 = new_pt.X; x3 = m_list[m_selected][right_id].ControlLeft.X; x4 = m_list[m_selected][right_id].Base.X; is_right = IsBezierImplicit( x1, x2, x3, x4 ); is_left = true; if ( bp.ControlRightType == ControlType.Master ) { x1 = m_list[m_selected][left_id].Base.X; x2 = m_list[m_selected][left_id].ControlRight.X; float dx2 = (bp.ControlLeft.X - bp.Base.X) * XScale; float dy2 = (bp.ControlLeft.Y - bp.Base.Y) * YScale; float length = (float)Math.Sqrt( dx2 * dx2 + dy2 * dy2 ); float tdx = dx * XScale; float tdy = dy * YScale; k = length / (float)Math.Sqrt( tdx * tdx + tdy * tdy ); x3 = bp.Base.X - dx * k; x4 = bp.Base.X; is_left = IsBezierImplicit( x1, x2, x3, x4 ); } if ( is_right && is_left ) { bp.ControlRight = new PointF( bp.ControlRight.X, new_pt.Y ); _number = bp.ControlRight; if ( bp.ControlRightType == ControlType.Master ) { bp.ControlLeft = new PointF( bp.Base.X - dx * k, bp.Base.Y - dy * k ); } this.Invalidate(); return; } } } } else { bp.ControlRight = new_pt; _number = bp.ControlRight; } #endregion break; case PickedSide.Left: #region PickedSide.Left if ( centre >= 1 ) { float x1 = m_list[m_selected][left_id].Base.X; float x2 = m_list[m_selected][left_id].ControlRight.X; float x3 = new_pt.X; float x4 = bp.Base.X; bool is_left = IsBezierImplicit( x1, x2, x3, x4 ); bool is_right = true; float dx = new_pt.X - bp.Base.X; float dy = new_pt.Y - bp.Base.Y; float k = 1f; if ( bp.ControlLeftType != ControlType.None && centre < m_list[m_selected].Count - 1 ) { //if ( bp.ControlLeftType == ControlType.Master && m_picked_point < m_list[m_selected].Count - 1 ) { float dx1 = (bp.ControlRight.X - bp.Base.X) * XScale; float dy1 = (bp.ControlRight.Y - bp.Base.Y) * YScale; float length = (float)Math.Sqrt( dx1 * dx1 + dy1 * dy1 ); float tdx = dx * XScale; float tdy = dy * YScale; k = length / (float)Math.Sqrt( tdx * tdx + tdy * tdy ); x1 = bp.Base.X; x2 = bp.Base.X - dx * k; x3 = m_list[m_selected][right_id].ControlLeft.X; x4 = m_list[m_selected][right_id].Base.X; is_right = IsBezierImplicit( x1, x2, x3, x4 ); } if ( is_right && is_left ) { bp.ControlLeft = new_pt; _number = bp.ControlLeft; if ( bp.ControlLeftType == ControlType.Master && centre >= 1 && bp.ControlRightType != ControlType.None ) { bp.ControlRight = new PointF( bp.Base.X - dx * k, bp.Base.Y - dy * k ); } this.Invalidate(); return; } else { if ( centre == m_list[m_selected].Count - 1 ) { bp.ControlLeft = new PointF( bp.ControlLeft.X, new_pt.Y ); _number = bp.ControlLeft; this.Invalidate(); return; } else { //とりあえずnew_ptのyだけ替えてみて再評価してみる new_pt = new PointF( bp.ControlLeft.X, new_pt.Y ); dx = new_pt.X - bp.Base.X; dy = new_pt.Y - bp.Base.Y; x1 = m_list[m_selected][left_id].Base.X; x2 = m_list[m_selected][left_id].ControlRight.X; x3 = new_pt.X; x4 = bp.Base.X; is_left = IsBezierImplicit( x1, x2, x3, x4 ); is_right = true; if ( bp.ControlLeftType == ControlType.Master ) { float dx2 = (bp.ControlRight.X - bp.Base.X) * XScale; float dy2 = (bp.ControlRight.Y - bp.Base.Y) * YScale; float length = (float)Math.Sqrt( dx2 * dx2 + dy2 * dy2 ); float tdx = dx * XScale; float tdy = dy * YScale; k = length / (float)Math.Sqrt( tdx * tdx + tdy * tdy ); x1 = bp.Base.X; x2 = bp.Base.X - dx * k; x3 = m_list[m_selected][right_id].ControlLeft.X; x4 = m_list[m_selected][right_id].Base.X; is_right = IsBezierImplicit( x1, x2, x3, x4 ); } if ( is_right && is_left ) { bp.ControlLeft = new PointF( bp.ControlLeft.X, new_pt.Y ); _number = bp.ControlLeft; if ( bp.ControlLeftType == ControlType.Master ) { bp.ControlRight = new PointF( bp.Base.X - dx * k, bp.Base.Y - dy * k ); } this.Invalidate(); return; } } } } else { bp.ControlLeft = new_pt; _number = bp.ControlLeft; } //昔の=> /*if ( m_picked_point >= 1 ) { float x1 = m_list[m_selected][m_picked_point - 1].Base.X; float x2 = m_list[m_selected][m_picked_point - 1].ControlRight.X; float x3 = new_pt.X; float x4 = bp.Base.X; if ( !IsBezierImplicit( x1, x2, x3, x4 ) ) { bp.ControlLeft = new PointF( bp.ControlLeft.X, new_pt.Y ); this.Invalidate(); return; } } bp.ControlLeft = new_pt;*/ //<=ここまで #endregion break; } this.Invalidate(); } else if ( m_change_xscale ) { float new_scale = m_old_scale * (float)Math.Pow( 10, (e.X - m_change_origin) / CHANGE_SCALE_ORDER ); if ( new_scale < MinXScale ) { new_scale = MinXScale; } else if ( MaxXScale < new_scale ) { new_scale = MaxXScale; } if ( new_scale != XScale ) { XOffset = (m_change_origin) * (1f / new_scale - 1f / XScale) + XOffset; XScale = new_scale; this.Invalidate(); } } else if ( m_change_yscale ) { float new_scale = m_old_scale * (float)Math.Pow( 10, (e.Y - m_change_origin) / CHANGE_SCALE_ORDER ); if ( new_scale < MinYScale ) { new_scale = MinYScale; } else if ( MaxYScale < new_scale ) { new_scale = MaxYScale; } if ( new_scale != YScale ) { YOffset = m_change_origin * (1f / new_scale - 1f / YScale) + YOffset; YScale = new_scale; m_list_minmax[m_selected].Offset = YOffset; m_list_minmax[m_selected].Scale = YScale; this.Invalidate(); } } this.Invalidate(); } private void CurveEditor_Resize( object sender, EventArgs e ) { this.Invalidate(); } private void CurveEditor_MouseDoubleClick( object sender, MouseEventArgs e ) { if ( m_selected == "" ) { return; } float x = sX( e.X ); float y = sY( e.Y ); int picked_id; PickedSide picked_side; DetectSelectedPoint( e.Location, out picked_id, out picked_side ); if ( picked_id >= 0 ) { m_picked_point_id = picked_id; m_picked_side = picked_side; m_before_edit = m_list[m_selected][m_picked_point_id].GetPosition( m_picked_side ); using ( LipSync.SetSize dlg = new LipSync.SetSize( _( "Numeric entry" ), "x", "y", m_before_edit.X, m_before_edit.Y ) ) { if ( dlg.ShowDialog() == DialogResult.OK ) { SizeF res = new SizeF( dlg.ResultWidth, dlg.ResultHeight ); PointF new_pt = new PointF( res.Width, res.Height ); Command run = Command.GCommandEditPosition( m_selected, m_picked_point_id, m_picked_side, new_pt ); m_list[m_selected][m_picked_point_id].SetPosition( m_picked_side, m_before_edit ); Register( Execute( run ) ); this.Invalidate(); } } } else { float handle_length; float slope = 0f; if ( m_list[m_selected].Count == 0 ) { handle_length = x * 0.5f; } else { int right_point = -1; //右側の点を検索 for ( int i = 0; i < m_list[m_selected].List.Count; i++ ) { if ( x == m_list[m_selected].List[i].Base.X ) { //xが等しくなる位置にはデータ点を追加できない仕様。 return; } if ( x < m_list[m_selected].List[i].Base.X ) { right_point = i; break; } } if ( right_point == -1 ) { // 最も右 float dx = Math.Abs( x - m_list[m_selected].List[m_list[m_selected].List.Count - 1].Base.X ); handle_length = dx / 2; } else if ( right_point == 0 ) { float dx = Math.Abs( m_list[m_selected].List[0].Base.X - x ); handle_length = dx / 2; } else { float dx_r = Math.Abs( m_list[m_selected].List[right_point].Base.X - x ); float dx_l = Math.Abs( x - m_list[m_selected].List[right_point - 1].Base.X ); handle_length = Math.Min( dx_r, dx_l ) / 2; slope = (m_list[m_selected].List[right_point].Base.Y - m_list[m_selected].List[right_point - 1].Base.Y) / (m_list[m_selected].List[right_point].Base.X - m_list[m_selected].List[right_point - 1].Base.X); } } PointF p_left, p_right; p_right = new PointF( x + handle_length, y + handle_length * slope ); p_left = new PointF( x - handle_length, y - handle_length * slope ); BezierPoint bp = new BezierPoint( new PointF( x, y ), p_left, p_right ); bp.ControlLeftType = m_last_added_type; bp.ControlRightType = m_last_added_type; Command run = Command.GCommandAdd( m_selected, bp ); Register( Execute( run ) ); m_picked_side = PickedSide.Base; for ( int i = 0; i < m_list[m_selected].List.Count; i++ ) { BezierPoint bpoint = m_list[m_selected].List[i]; if ( x == bpoint.Base.X ) { m_picked_point_id = bpoint.ID; break; } } } this.Invalidate(); } private void CurveEditor_PreviewKeyDown( object sender, PreviewKeyDownEventArgs e ) { if ( m_selected != "" && m_picked_point_id >= 0 ) { if ( m_picked_side != PickedSide.Base ) { switch ( e.KeyCode ) { case Keys.H: if ( m_list[m_selected][m_picked_point_id].GetControlType( m_picked_side ) != ControlType.Master ) { Command run = Command.GCommandChangeType( m_selected, m_picked_point_id, m_picked_side, ControlType.Master ); Register( Execute( run ) ); } break; case Keys.V: if ( m_list[m_selected][m_picked_point_id].GetControlType( m_picked_side ) != ControlType.Normal ) { Command run = Command.GCommandChangeType( m_selected, m_picked_point_id, m_picked_side, ControlType.Normal ); Register( Execute( run ) ); } break; case Keys.Delete: if ( m_list[m_selected][m_picked_point_id].GetControlType( m_picked_side ) != ControlType.None ) { Command run = Command.GCommandChangeType( m_selected, m_picked_point_id, m_picked_side, ControlType.None ); Register( Execute( run ) ); } break; } this.Invalidate(); } else { ControlType target; switch ( e.KeyCode ) { case Keys.H: target = ControlType.Master; break; case Keys.V: target = ControlType.Normal; break; case Keys.Delete: Command run = Command.GCommandDelete( m_selected, m_picked_point_id ); Register( Execute( run ) ); m_picked_point_id = -1; this.Invalidate(); return; default: return; } BezierPoint bpoint = m_list[m_selected][m_picked_point_id]; if ( bpoint != null ) { if ( m_list[m_selected][m_picked_point_id].ControlLeftType != target || m_list[m_selected][m_picked_point_id].ControlRightType != target ) { BezierPoint bp = m_list[m_selected][m_picked_point_id].Clone(); bp.ControlLeftType = target; bp.ControlRightType = target; Command run = Command.GCommandEdit( m_selected, m_picked_point_id, bp ); Register( Execute( run ) ); this.Invalidate(); } } } } } private Command Execute( Command run ) { #if DEBUG Common.DebugWriteLine( "CurveEditor.Execute" ); /*Common.DebugWriteLine( " before" ); for ( int i = 0; i < m_list[m_selected].List.Count; i++ ) { BezierPoint bp = m_list[m_selected].List[i]; Common.DebugWriteLine( " Base.X=" + bp.Base.X + ", ID=" + bp.ID ); }*/ #endif Command ret = null; switch ( run.Type ) { case CommandType.Position: switch ( run.Side ) { case PickedSide.Base: ret = Command.GCommandEditPosition( run.ID, run.PointID, run.Side, m_list[run.ID][run.PointID].Base ); break; case PickedSide.Left: ret = Command.GCommandEditPosition( run.ID, run.PointID, run.Side, m_list[run.ID][run.PointID].ControlLeft ); break; case PickedSide.Right: ret = Command.GCommandEditPosition( run.ID, run.PointID, run.Side, m_list[run.ID][run.PointID].ControlRight ); break; } #if DEBUG LipSync.Common.DebugWriteLine( " before;Position=" + m_list[run.ID][run.PointID].GetPosition( PickedSide.Base ) ); #endif m_list[run.ID][run.PointID].SetPosition( run.Side, run.Position ); #if DEBUG for ( int i = 0; i < AppManager.SaveData.m_telop_ex2.Count; i++ ) { if ( AppManager.SaveData.m_telop_ex2[i].Text == run.ID ) { } } #endif break; case CommandType.Type: switch ( run.Side ) { case PickedSide.Left: ret = Command.GCommandChangeType( run.ID, run.PointID, run.Side, m_list[run.ID][run.PointID].ControlLeftType ); m_list[run.ID][run.PointID].ControlLeftType = run.ControlType; break; case PickedSide.Right: ret = Command.GCommandChangeType( run.ID, run.PointID, run.Side, m_list[run.ID][run.PointID].ControlRightType ); m_list[run.ID][run.PointID].ControlRightType = run.ControlType; break; } break; case CommandType.Add: BezierPoint bp = run.BezierPoint.Clone(); bp.ID = m_list[run.ID].GetNextID(); ret = Command.GCommandDelete( run.ID, bp.ID ); m_list[run.ID].Add( bp ); break; case CommandType.Delete: ret = Command.GCommandAdd( run.ID, m_list[run.ID][run.PointID] ); m_list[run.ID].RemoveAt( run.PointID ); break; case CommandType.Edit: ret = Command.GCommandEdit( run.ID, run.PointID, m_list[run.ID][run.PointID] ); m_list[run.ID][run.PointID] = run.BezierPoint.Clone(); break; default: return null; } if ( this.CurveEdited != null ) { CurveEdited(); } #if DEBUG /*Common.DebugWriteLine( " after" ); for ( int i = 0; i < m_list[m_selected].List.Count; i++ ) { BezierPoint bp = m_list[m_selected].List[i]; Common.DebugWriteLine( " Base.X=" + bp.Base.X + ", ID=" + bp.ID ); }*/ #endif return ret; } /// /// アンドゥ処理行います /// public void Undo() { if ( IsUndoAvailable ) { Command run = m_commands[m_command_position].Clone(); m_commands[m_command_position] = Execute( run ); m_command_position--; this.Invalidate(); } } /// /// リドゥ処理行います /// public void Redo() { if ( IsRedoAvailable ) { Command run = m_commands[m_command_position + 1].Clone(); m_commands[m_command_position + 1] = Execute( run ); m_command_position++; this.Invalidate(); } } /// /// リドゥ操作が可能かどうかを表す値を取得します /// public bool IsRedoAvailable { get { if ( m_command_position + 1 < m_commands.Count ) { return true; } else { return false; } } } /// /// アンドゥ操作が可能かどうかを表す値を取得します /// public bool IsUndoAvailable { get { if ( 0 > m_command_position ) { return false; } else { return true; } } } /// /// コマンドバッファに指定されたコマンドを登録します /// /// void Register( Command command ) { if ( m_command_position == m_commands.Count - 1 ) { // 新しいコマンドバッファを追加する場合 m_commands.Add( command.Clone() ); m_command_position = m_commands.Count - 1; } else { // 既にあるコマンドバッファを上書きする場合 m_commands[m_command_position + 1].Dispose(); m_commands[m_command_position + 1] = command.Clone(); for ( int i = m_commands.Count - 1; i >= m_command_position + 2; i-- ) { m_commands.RemoveAt( i ); } m_command_position++; } } private void CurveEditor_KeyDown( object sender, KeyEventArgs e ) { if ( e.KeyCode == Keys.Space ) { m_spacekey_down = true; this.Cursor = HAND; } else { m_spacekey_down = false; this.Cursor = Cursors.Default; } } private void CurveEditor_KeyUp( object sender, KeyEventArgs e ) { m_spacekey_down = false; this.Cursor = Cursors.Default; } void _num_input_FormClosing( object sender, FormClosingEventArgs e ) { e.Cancel = true; } /// /// データ点の数値入力用のコンテキストメニューを初期化します /// private void InitializeContextMenu() { if( _cmenu != null ) { _cmenu.Dispose(); } if( _cmenuNumericInput != null ) { _cmenuNumericInput.Dispose(); } _cmenu = new ContextMenuStrip(); _cmenu.ShowCheckMargin = false; _cmenu.ShowImageMargin = false; _cmenuNumericInput = new ToolStripMenuItem(); _cmenu.Items.AddRange( new ToolStripItem[] { _cmenuNumericInput} ); _cmenu.Name = "cmenu"; _cmenu.Size = new System.Drawing.Size( 135, 26 ); _cmenu.Font = this.Font; _cmenuNumericInput.Name = "cmenuNumericInput"; _cmenuNumericInput.Size = new System.Drawing.Size( 134, 22 ); _cmenuNumericInput.Text = _( "Numeric entry" ) + "(&N)"; _cmenuNumericInput.Click += new EventHandler( _cmenuNumericInput_Click ); } void _cmenuNumericInput_Click( object sender, EventArgs e ) { m_before_edit = m_list[m_selected][m_picked_point_id].GetPosition( m_picked_side ); using ( LipSync.SetSize dlg = new LipSync.SetSize( _( "Numeric entry" ), "x", "y", m_before_edit.X, m_before_edit.Y ) ) { if ( dlg.ShowDialog() == DialogResult.OK ) { SizeF res = new SizeF( dlg.ResultWidth, dlg.ResultHeight ); PointF new_pt = new PointF( res.Width, res.Height ); Command run = Command.GCommandEditPosition( m_selected, m_picked_point_id, m_picked_side, new_pt ); m_list[m_selected][m_picked_point_id].SetPosition( m_picked_side, m_before_edit ); Register( Execute( run ) ); this.Invalidate(); } } } private void CurveEditor_MouseClick( object sender, MouseEventArgs e ) { if ( (e.Button & MouseButtons.Right) == MouseButtons.Right ) { DetectSelectedPoint( e.Location, out m_picked_point_id, out m_picked_side ); #if DEBUG Common.DebugWriteLine( "CureveEditor_MouseClick" ); Common.DebugWriteLine( " m_picked_point_id=" + m_picked_point_id ); Common.DebugWriteLine( " m_picked_side=" + m_picked_side ); #endif if ( m_picked_point_id >= 0 ) { InitializeContextMenu(); _cmenu.Show( this, e.Location ); _number_visible = false; #if DEBUG LipSync.Common.DebugWriteLine( "MouseClick, m_picked_point_id=" + m_picked_point_id ); #endif } } } private void CurveEditor_FontChanged( object sender, EventArgs e ) { if ( _cmenu != null ) { _cmenu.Font = this.Font; } } } internal class Command /*: ICloneable*/ { string m_id; //int m_picked_index; PickedSide m_picked_side; PointF m_new_position; CommandType m_command_type; ControlType m_control_type; BezierPoint m_bp; //float m_x; int m_pid; public override string ToString() { return "{ID=" + ID + ", PointID=" + PointID + ", Side=" + Side + ", CommandType=" + Type + ", Position=" + Position + "}"; } public Command Clone() { Command result = new Command(); result.m_id = this.m_id; //result.m_picked_index = this.m_picked_index; result.m_new_position = this.m_new_position; result.m_command_type = this.m_command_type; result.m_control_type = this.m_control_type; if ( this.m_bp != null ) { result.m_bp = this.m_bp.Clone(); } result.m_pid = this.m_pid; result.m_picked_side = this.m_picked_side; return result; } public void Dispose(){ m_bp = null; } public static Command GCommandEditPosition( string ID, int picked_id, PickedSide picked_side, PointF new_position ) { Command ret = new Command(); ret.m_id = ID; ret.m_pid = picked_id; ret.m_picked_side = picked_side; ret.m_new_position = new_position; ret.m_command_type = CommandType.Position; ret.m_control_type = ControlType.None; return ret; } public static Command GCommandChangeType( string ID, int picked_id, PickedSide picked_side, ControlType control_type ){ Command ret = new Command(); ret.m_id = ID; ret.m_pid = picked_id; ret.m_picked_side = picked_side; ret.m_command_type = CommandType.Type; ret.m_control_type = control_type; return ret; } public static Command GCommandAdd( string ID, BezierPoint point ) { Command ret = new Command(); ret.m_id = ID; if ( point != null ) { ret.m_bp = (BezierPoint)point.Clone(); } ret.m_command_type = CommandType.Add; return ret; } public static Command GCommandDelete( string ID, /*float*/int pid ) { Command ret = new Command(); ret.m_id = ID; //this.m_x = x; ret.m_pid = pid; ret.m_command_type = CommandType.Delete; return ret; } public static Command GCommandNothing() { return new Command(); } private Command() { this.m_command_type = CommandType.None; } public static Command GCommandEdit( string ID, int picked_id, BezierPoint point ) { Command ret = new Command(); ret.m_id = ID; ret.m_pid = picked_id; if ( point != null ) { ret.m_bp = (BezierPoint)point.Clone(); } ret.m_command_type = CommandType.Edit; return ret; } public int PointID { get { return m_pid; } } /*public float X { get { return m_x; } }*/ public BezierPoint BezierPoint { get { return m_bp; } } public CommandType Type{ get{ return m_command_type; } } public ControlType ControlType { get { return m_control_type; } } public string ID{ get{ return m_id; } } public PickedSide Side{ get{ return m_picked_side; } } public PointF Position{ get{ return m_new_position; } } } internal enum CommandType { Position,//単に位置を変更する Type,//制御点のタイプを変更する Add, Delete, None, Edit, } public enum PickedSide { Right, Base, Left, } public enum PointType { Circle, Rectangle, } public enum XLabel { None, Top, Bottom, } public enum YLabel { None, Left, Right, } [Serializable] public class BezierChain : IDisposable, ICloneable { private List list; private float m_default = 0f; private Color m_color; public bool GetKeyMinMax( out float min, out float max ) { if ( list.Count == 0 ) { min = 0f; max = 0f; return false; } min = float.MaxValue; max = float.MinValue; for ( int i = 0; i < list.Count; i++ ) { min = Math.Min( min, list[i].Base.X ); max = Math.Max( max, list[i].Base.X ); } return true; } public int GetIndexFromId( int id ) { for ( int i = 0; i < list.Count; i++ ) { if ( list[i].ID == id ) { return i; } } return -1; } public int GetIdFromIndex( int index ) { if ( 0 <= index && index < list.Count ) { return list[index].ID; } return -1; } public List List { get { return list; } set { list = value; } } [OnDeserialized] void onDeserialized( StreamingContext sc ) { for ( int i = 0; i < list.Count; i++ ) { list[i].ID = i; list[i].Order = i; } } public void Sort() { //list.Sort( new BezierChainOrderIgnoaringComparator() ); list.Sort(); for ( int i = 0; i < list.Count; i++ ) { list[i].Order = i; } } public void Dispose() { if ( list != null ) { list.Clear(); } } public int GetNextID() { int max = -1; for ( int i = 0; i < list.Count; i++ ) { max = Math.Max( max, list[i].ID ); } return max + 1; } public void GetValueMinMax( out float min, out float max ){ //todo: ベジエが有効なときに、曲線の描く最大値、最小値も考慮 min = Default; max = Default; foreach ( BezierPoint bp in list ) { min = Math.Min( min, bp.Base.Y ); max = Math.Max( max, bp.Base.Y ); } } public object Clone() { BezierChain result = new BezierChain( this.m_color ); foreach ( BezierPoint bp in list ) { result.list.Add( bp.Clone() ); } result.m_default = this.m_default; return result; } public float Default { get { return m_default; } set { m_default = value; } } public BezierChain( Color curve ) { list = new List(); m_color = curve; } public BezierPoint this[int id] { get { for ( int i = 0; i < list.Count; i++ ) { if ( list[i].ID == id ) { return list[i]; } } return null; } set { for ( int i = 0; i < list.Count; i++ ) { if ( list[i].ID == id ) { list[i] = value; return; } } throw new Exception( "invalid point id" ); } } public Color Color { get { return m_color; } set { m_color = value; } } public void Add( BezierPoint bp ) { if ( list == null ) { list = new List(); m_color = Color.Black; } #if DEBUG Common.DebugWriteLine( "BezierChain.Add" ); Common.DebugWriteLine( " before" ); for ( int i = 0; i < list.Count; i++ ) { Common.DebugWriteLine( " Base.X=" + list[i].Base.X + ", Order=" + list[i].Order ); } #endif bool found = false; for ( int i = 0; i < list.Count - 1; i++ ) { if ( list[i].Base.X <= bp.Base.X && bp.Base.X < list[i + 1].Base.X ) { bp.Order = list[i].Order + 1; for ( int j = i + 1; j < list.Count; j++ ) { list[j].Order = list[j].Order + 1; } found = true; break; } } if ( !found ) { if ( list.Count == 0 ){ bp.Order = 0; }else{ bp.Order = list[list.Count - 1].Order + 1; } } list.Add( bp ); Sort(); #if DEBUG Common.DebugWriteLine( "BezierChain.Add" ); Common.DebugWriteLine( " after" ); for ( int i = 0; i < list.Count; i++ ) { Common.DebugWriteLine( " Base.X=" + list[i].Base.X + ", Order=" + list[i].Order ); } #endif } public void RemoveAt( int id ) { for ( int i = 0; i < list.Count; i++ ) { if ( list[i].ID == id ) { list.RemoveAt( i ); Sort(); return; } } } /*public void RemoveAt( float x ) { for ( int i = 0; i < list.Count; i++ ) { if ( list[i].Base.X == x ) { list.RemoveAt( i ); break; } } }*/ public int Count { get { if ( list == null ) { return 0; } return list.Count; } } public float GetValue( float x ) { for ( int i = 0; i < list.Count - 1; i++ ) { if ( list[i].Base.X <= x && x <= list[i + 1].Base.X ) { if ( list[i].ControlRightType == ControlType.None && list[i + 1].ControlLeftType == ControlType.None ) { PointF p1 = list[i].Base; PointF p2 = list[i + 1].Base; float slope = (p2.Y - p1.Y) / (p2.X - p1.X); return p1.Y + slope * (x - p1.X); } else { float x1 = list[i].Base.X; float x2 = list[i].ControlRight.X; float x3 = list[i + 1].ControlLeft.X; float x4 = list[i + 1].Base.X; float a3 = x4 - 3 * x3 + 3 * x2 - x1; float a2 = 3 * x3 - 6 * x2 + 3 * x1; float a1 = 3 * (x2 - x1); float a0 = x1; if ( x1 == x ) { return list[i].Base.Y; } else if ( x4 == x ) { return list[i + 1].Base.Y; } else { float t = SolveCubicEquation( a3, a2, a1, a0, x ); x1 = list[i].Base.Y; x2 = list[i].ControlRight.Y; x3 = list[i + 1].ControlLeft.Y; x4 = list[i + 1].Base.Y; a3 = x4 - 3 * x3 + 3 * x2 - x1; a2 = 3 * x3 - 6 * x2 + 3 * x1; a1 = 3 * (x2 - x1); a0 = x1; return ((a3 * t + a2) * t + a1) * t + a0; } } } } return m_default; } /// /// 3次方程式a3*x^3 + a2*x^2 + a1*x + a0 = ansの解をニュートン法を使って計算します。ただし、単調増加である必要がある。 /// /// /// /// /// /// /// /// private static float SolveCubicEquation( float a3, float a2, float a1, float a0, float ans ) { double EPSILON = 1e-9; double suggested_t = 0.4; double a3_3 = a3 * 3.0; double a2_2 = a2 * 2.0; while ( (a3_3 * suggested_t + a2_2) * suggested_t + a1 == 0.0 ) { suggested_t += 0.1; } double x = suggested_t; double new_x = suggested_t; for( int i = 0; i < 5000; i++ ){ new_x = x - (((a3 * x + a2) * x + a1) * x + a0 - ans) / ((a3_3 * x + a2_2) * x + a1); if ( Math.Abs( new_x - x ) < EPSILON * new_x ) { break; } x = new_x; } return (float)new_x; } } public enum ControlType { None, Normal, Master, } /// /// ベジエ曲線を構成するデータ点。 /// [Serializable] public class BezierPoint : IComparable { PointF m_base; internal PointF m_control_left; internal PointF m_control_right; ControlType m_type_left; ControlType m_type_right; [NonSerialized] int m_id; [OptionalField] public int Order; public int ID { get { return m_id; } internal set { m_id = value; } } public override string ToString() { return "m_base=" + m_base.X + "," + m_base.Y + "\n" + "m_control_left=" + m_control_left.X + "," + m_control_left.Y + "\n" + "m_control_right=" + m_control_right.X + "," + m_control_right.Y + "\n" + "m_type_left=" + m_type_left + "\n" + "m_type_right=" + m_type_right + "\n"; } public BezierPoint( PointF p1, PointF left, PointF right ) { m_base = p1; m_control_left = new PointF( left.X - m_base.X, left.Y - m_base.Y ); m_control_right = new PointF( right.X - m_base.X, right.Y - m_base.Y ); m_type_left = ControlType.None; m_type_right = ControlType.None; } public BezierPoint Clone() { BezierPoint result = new BezierPoint( this.Base, this.ControlLeft, this.ControlRight ); result.m_control_left = this.m_control_left; result.m_control_right = this.m_control_right; result.m_type_left = this.m_type_left; result.m_type_right = this.m_type_right; result.Order = this.Order; result.m_id = this.m_id; return result; } public int CompareTo( BezierPoint item ) { if ( this.Base.X > item.Base.X ) { return 1; } else if ( this.Base.X < item.Base.X ) { return -1; } else { return this.Order - item.Order; /*if ( this.ID > item.ID ) { return 1; } else if ( this.ID < item.ID ) { return -1; } else { return 0; }*/ } } public PointF Base { get { return m_base; } set { m_base = value; } } public void SetPosition( PickedSide picked_side, PointF new_position ) { if ( picked_side == PickedSide.Base ) { this.Base = new_position; } else if ( picked_side == PickedSide.Left ) { this.m_control_left = new PointF( new_position.X - this.Base.X, new_position.Y - this.Base.Y); } else { this.m_control_right = new PointF( new_position.X - this.Base.X, new_position.Y - this.Base.Y ); } } public PointF GetPosition( PickedSide picked_side ) { if ( picked_side == PickedSide.Base ) { return this.Base; } else if ( picked_side == PickedSide.Left ) { return this.ControlLeft; } else { return this.ControlRight; } } public ControlType GetControlType( PickedSide picked_side ) { if ( picked_side == PickedSide.Left ) { return this.ControlLeftType; } else if ( picked_side == PickedSide.Right ) { return this.ControlRightType; } else { return ControlType.None; } } public PointF ControlLeft { get { if ( m_type_left != ControlType.None ) { return new PointF( m_base.X + m_control_left.X, m_base.Y + m_control_left.Y ); } else { return m_base; } } set { m_control_left = new PointF( value.X - m_base.X, value.Y - m_base.Y ); } } public PointF ControlRight { get { if ( m_type_right != ControlType.None ) { return new PointF( m_base.X + m_control_right.X, m_base.Y + m_control_right.Y ); } else { return m_base; } } set { m_control_right = new PointF( value.X - m_base.X, value.Y - m_base.Y ); } } public ControlType ControlLeftType { get { return m_type_left; } set { m_type_left = value; } } public ControlType ControlRightType { get { return m_type_right; } set { m_type_right = value; } } } }