1+ using UnityEngine ;
2+ using System . Collections . Generic ;
3+ using UnityEditor . Experimental . GraphView ;
4+ using System . Runtime . InteropServices . WindowsRuntime ;
5+ using System ;
6+ using UnityEditor ;
7+
8+ namespace ABCUnity
9+ {
10+ static class Grouping
11+ {
12+ const float ParaoblaMidpointScale = 0.3f ;
13+
14+ private enum SlurPosition { Above , Below } ;
15+
16+ public static LineRenderer Create ( VoiceLayout . ScoreLine . Element startElement , VoiceLayout . ScoreLine . Element endElement , Material material )
17+ {
18+ var elements = CollectElements ( startElement , endElement ) ;
19+ return CreateSingleScorelineSlur ( elements , material ) ;
20+ }
21+
22+ private static LineRenderer CreateSingleScorelineSlur ( List < VoiceLayout . ScoreLine . Element > elements , Material material )
23+ {
24+ var startElement = elements [ 0 ] ;
25+ var endElement = elements [ elements . Count - 1 ] ;
26+ var scoreLine = startElement . measure . scoreLine ;
27+
28+ var slurPosition = DetermineSlurPosition ( elements ) ;
29+ var startPos = startElement . container . transform . localPosition + startElement . measure . container . transform . localPosition ;
30+ Vector3 startAnchor , endAnchor ;
31+
32+ var endPos = endElement . container . transform . localPosition + endElement . measure . container . transform . localPosition ;
33+
34+ if ( slurPosition == SlurPosition . Below )
35+ {
36+ startPos += new Vector3 ( startElement . info . rootBounding . max . x , startElement . info . rootBounding . min . y , 0.0f ) ;
37+ startAnchor = startPos ;
38+
39+ startPos += new Vector3 ( 0.1f , - 0.1f , 0.0f ) ;
40+ startAnchor . x -= startElement . info . rootBounding . extents . x ;
41+
42+ endPos += endElement . info . rootBounding . min ;
43+ endAnchor = endPos ;
44+
45+ endPos += new Vector3 ( - 0.1f , - 0.1f , 0.0f ) ;
46+ endAnchor . x += endElement . info . rootBounding . extents . x ;
47+ }
48+ else
49+ {
50+ startPos += new Vector3 ( startElement . info . rootBounding . max . x , startElement . info . rootBounding . max . y , 0.0f ) ;
51+ startAnchor = startPos ;
52+
53+ startPos += new Vector3 ( 0.1f , 0.1f , 0.0f ) ;
54+ startAnchor . x -= startElement . info . rootBounding . extents . x ;
55+
56+ endPos += new Vector3 ( endElement . info . rootBounding . min . x , endElement . info . rootBounding . max . y , 0.0f ) ;
57+ endAnchor = endPos ;
58+
59+ endPos += new Vector3 ( - 0.1f , 0.1f , 0.0f ) ;
60+ endAnchor . x += endElement . info . rootBounding . extents . x ;
61+ }
62+
63+ var boundingY = GetSlurBoundingY ( elements , slurPosition ) ;
64+ Vector3 boundingPt1 = new Vector3 ( 0.0f , boundingY , 0.0f ) ;
65+ Vector3 boundingPt2 = new Vector3 ( 1.0f , boundingY , 0.0f ) ;
66+
67+ var lineMidpoint = ( startPos + endPos ) / 2.0f ;
68+ var anchorMidpoint = ( startAnchor + endAnchor ) / 2.0f ;
69+
70+
71+ var slurMidpoint = MathUtil . LineIntersect ( lineMidpoint , anchorMidpoint , boundingPt1 , boundingPt2 ) ;
72+ var direction = ( lineMidpoint - anchorMidpoint ) . normalized * ParaoblaMidpointScale ;
73+
74+ var slurLinePoints = CreatePoints ( startPos , slurMidpoint + direction , endPos ) ;
75+
76+ return CreateLineRenderer ( scoreLine , slurLinePoints , material ) ;
77+ }
78+
79+ static List < Vector3 > CreatePoints ( Vector3 startPos , Vector3 midpoint , Vector3 endPos )
80+ {
81+ var slurPositions = new List < Vector3 > ( ) ;
82+
83+ slurPositions . Add ( startPos ) ;
84+
85+ float [ , ] matrix = new float [ 3 , 4 ] {
86+ { startPos . x * startPos . x , startPos . x , 1 , startPos . y } ,
87+ { midpoint . x * midpoint . x , midpoint . x , 1 , midpoint . y } ,
88+ { endPos . x * endPos . x , endPos . x , 1 , endPos . y }
89+ } ;
90+
91+ matrix = MathUtil . ReducedRowEchelonForm ( matrix ) ;
92+ float a = matrix [ 0 , 3 ] ;
93+ float b = matrix [ 1 , 3 ] ;
94+ float c = matrix [ 2 , 3 ] ;
95+
96+ const int segmentCount = 20 ;
97+ float step = ( endPos . x - startPos . x ) / segmentCount ;
98+ float x = startPos . x ;
99+ for ( int i = 0 ; i < segmentCount ; i ++ ) {
100+ x += step ;
101+
102+ float y = a * ( x * x ) + b * x + c ;
103+
104+ slurPositions . Add ( new Vector3 ( x , y , 0 ) ) ;
105+ }
106+
107+ return slurPositions ;
108+ }
109+
110+ private static LineRenderer CreateLineRenderer ( VoiceLayout . ScoreLine scoreLine , List < Vector3 > slurPositions , Material material )
111+ {
112+ if ( scoreLine . slurs == null ) {
113+ scoreLine . slurs = new GameObject ( "Slurs" ) ;
114+ scoreLine . slurs . transform . SetParent ( scoreLine . container . transform , false ) ;
115+ }
116+
117+ var slur = new GameObject ( "Slur" ) ;
118+ slur . transform . SetParent ( scoreLine . slurs . transform , false ) ;
119+
120+ var lineRenderer = slur . AddComponent < LineRenderer > ( ) ;
121+ lineRenderer . positionCount = slurPositions . Count ;
122+ lineRenderer . SetPositions ( slurPositions . ToArray ( ) ) ;
123+
124+ lineRenderer . useWorldSpace = false ;
125+ lineRenderer . startWidth = 0.1f ;
126+ lineRenderer . endWidth = 0.1f ;
127+
128+ lineRenderer . material = material ;
129+
130+ return lineRenderer ;
131+ }
132+
133+ /// <summary>
134+ /// Returns a list containing all the elements between start and end elements inclusive.
135+ /// </summary>
136+
137+ private static List < VoiceLayout . ScoreLine . Element > CollectElements ( VoiceLayout . ScoreLine . Element startElement , VoiceLayout . ScoreLine . Element endElement )
138+ {
139+ List < VoiceLayout . ScoreLine . Element > elements = new List < VoiceLayout . ScoreLine . Element > ( ) ;
140+ var currentElement = startElement ;
141+ var elementIndex = currentElement . measure . elements . FindIndex ( e => e == startElement ) ;
142+
143+ var currentMeasure = currentElement . measure ;
144+ var measureIndex = currentMeasure . scoreLine . measures . FindIndex ( m => m == currentMeasure ) ;
145+
146+ var currentScoreLine = currentMeasure . scoreLine ;
147+ var scoreLineIndex = currentScoreLine . voiceLayout . scoreLines . FindIndex ( sl => sl == currentScoreLine ) ;
148+
149+ var voiceLayout = currentScoreLine . voiceLayout ;
150+
151+ while ( currentMeasure . elements [ elementIndex ] != endElement ) {
152+ elements . Add ( currentMeasure . elements [ elementIndex ++ ] ) ;
153+
154+ if ( elementIndex >= currentMeasure . elements . Count )
155+ {
156+ elementIndex = 0 ;
157+ measureIndex += 1 ;
158+ }
159+
160+ if ( measureIndex >= currentScoreLine . measures . Count ) {
161+ measureIndex = 0 ;
162+ scoreLineIndex += 1 ;
163+ }
164+
165+ currentScoreLine = voiceLayout . scoreLines [ scoreLineIndex ] ;
166+ currentMeasure = currentScoreLine . measures [ measureIndex ] ;
167+ currentElement = currentMeasure . elements [ elementIndex ] ;
168+ }
169+
170+ elements . Add ( endElement ) ;
171+
172+ return elements ;
173+ }
174+
175+ /// Determines the Position of the slur by looking at note directions.
176+ /// If the stems point up then the slur will be placed below.
177+ /// If the stems point down then the slur will be placed above.
178+ /// If the stems are mixed then the slur will be placed above
179+ private static SlurPosition DetermineSlurPosition ( List < VoiceLayout . ScoreLine . Element > elements )
180+ {
181+
182+ var clef = elements [ 0 ] . measure . scoreLine . voiceLayout . voice . clef ;
183+
184+ // get the initial direction
185+ int i = 0 ;
186+ var initialDirection = NoteCreator . NoteDirection . Unknown ;
187+
188+ for ( ; i < elements . Count ; i ++ )
189+ {
190+ if ( initialDirection != NoteCreator . NoteDirection . Unknown )
191+ break ;
192+
193+ initialDirection = NoteCreator . DetermineNoteDirection ( elements [ i ] . item , clef ) ;
194+ }
195+
196+ for ( ; i < elements . Count ; i ++ )
197+ {
198+ var direction = NoteCreator . DetermineNoteDirection ( elements [ i ] . item , clef ) ;
199+ if ( direction == NoteCreator . NoteDirection . Unknown )
200+ continue ;
201+
202+ if ( direction != initialDirection )
203+ return SlurPosition . Above ;
204+ }
205+
206+ return initialDirection == NoteCreator . NoteDirection . Up ? SlurPosition . Below : SlurPosition . Above ;
207+ }
208+
209+ private static float GetSlurBoundingY ( List < VoiceLayout . ScoreLine . Element > elements , SlurPosition slurPosition )
210+ {
211+ if ( slurPosition == SlurPosition . Above )
212+ {
213+ float max = float . MinValue ;
214+ foreach ( var element in elements )
215+ {
216+ max = Math . Max ( max , element . info . rootBounding . max . y ) ;
217+ }
218+
219+ return max ;
220+ }
221+ else
222+ {
223+ float min = float . MaxValue ;
224+ foreach ( var element in elements )
225+ {
226+ min = Math . Min ( min , element . info . rootBounding . min . y ) ;
227+ }
228+
229+ return min ;
230+ }
231+ }
232+ }
233+ }
0 commit comments