2021-03-22 19:20:51 +08:00
using System ;
using Mapbox.Unity.Utilities ;
using System.Collections.Generic ;
using Mapbox.Unity.Map ;
using UnityEngine ;
using Mapbox.Unity.MeshGeneration.Data ;
namespace Mapbox.Unity.MeshGeneration.Modifiers
{
/// <summary>
/// Line Mesh Modifier creates line polygons from a list of vertices. It offsets the original vertices to both sides using Width parameter and triangulates them manually.
/// It also creates tiled UV mapping using the line length.
/// </summary>
[CreateAssetMenu(menuName = "Mapbox/Modifiers/Line Mesh Modifier")]
public class LineMeshModifier : MeshModifier
{
#region Constants
private readonly float _cosHalfSharpCorner = Mathf . Cos ( 75f / 2f * ( Mathf . PI / 180f ));
private readonly float _sharpCornerOffset = 15f ;
#endregion
#region Line Parameters
private LineGeometryOptions _options ;
private float _scaledWidth ;
#endregion
#region Mesh Generation Fields
private List < Vector3 > _vertexList ;
private List < Vector3 > _normalList ;
private List < int > _triangleList ;
private List < Vector2 > _uvList ;
private List < Vector4 > _tangentList ;
//triangle indices
private int _index1 = - 1 ;
private int _index2 = - 1 ;
private int _index3 = - 1 ;
private float _cornerOffsetA ;
private float _cornerOffsetB ;
private bool _startOfLine = true ;
private Vector3 _prevVertex ;
private Vector3 _currentVertex ;
private Vector3 _nextVertex ;
private Vector3 _prevNormal ;
private Vector3 _nextNormal ;
private float _distance = 0f ;
#endregion
#region Modifier Overrides
public override ModifierType Type
{
get { return ModifierType . Preprocess ; }
}
public override void Initialize ()
{
2021-04-02 19:17:23 +08:00
_scaledWidth = 1 ; //_options.Width;
2021-03-22 19:20:51 +08:00
_vertexList = new List < Vector3 >();
_normalList = new List < Vector3 >();
_triangleList = new List < int >();
_uvList = new List < Vector2 >();
_tangentList = new List < Vector4 >();
}
public override void SetProperties ( ModifierProperties properties )
{
_options = ( LineGeometryOptions ) properties ;
properties . PropertyHasChanged += UpdateModifier ;
}
public override void Run ( VectorFeatureUnity feature , MeshData md , float scale )
{
2021-04-02 19:17:23 +08:00
_scaledWidth = 1 * scale ;
2021-03-22 19:20:51 +08:00
ExtrudeLine ( feature , md );
}
public override void Run ( VectorFeatureUnity feature , MeshData md , UnityTile tile = null )
{
2021-04-02 19:17:23 +08:00
_scaledWidth = tile . TileScale * 10 ; // tile != null ? _options.Width * tile.TileScale : _options.Width;
2021-03-22 19:20:51 +08:00
ExtrudeLine ( feature , md );
}
#endregion
#region Mesh Generations
private void ExtrudeLine ( VectorFeatureUnity feature , MeshData md )
{
if ( feature . Points . Count < 1 )
{
return ;
}
foreach ( var roadSegment in feature . Points )
{
if ( roadSegment . Count < 2 )
continue ;
ResetFields ();
var roadSegmentCount = roadSegment . Count ;
for ( int i = 0 ; i < roadSegmentCount ; i ++)
{
_nextVertex = i != ( roadSegmentCount - 1 ) ? roadSegment [ i + 1 ] : Constants . Math . Vector3Unused ;
if ( _nextNormal != Constants . Math . Vector3Unused )
{
_prevNormal = _nextNormal ;
}
if ( _currentVertex != Constants . Math . Vector3Unused )
{
_prevVertex = _currentVertex ;
}
_currentVertex = roadSegment [ i ];
_nextNormal = ( _nextVertex != Constants . Math . Vector3Unused )
? ( _nextVertex - _currentVertex ). normalized . Perpendicular ()
: _prevNormal ;
if ( _prevNormal == Constants . Math . Vector3Unused )
{
_prevNormal = _nextNormal ;
}
var joinNormal = ( _prevNormal + _nextNormal ). normalized ;
/* joinNormal prevNormal
* ↖ ↑
* .________. prevVertex
* |
* nextNormal ← | currentVertex
* |
* nextVertex !
*
*/
var cosHalfAngle = joinNormal . x * _nextNormal . x + joinNormal . z * _nextNormal . z ;
var miterLength = cosHalfAngle != 0 ? 1 / cosHalfAngle : float . PositiveInfinity ;
var isSharpCorner = cosHalfAngle < _cosHalfSharpCorner && _prevVertex != Constants . Math . Vector3Unused && _nextVertex != Constants . Math . Vector3Unused ;
if ( isSharpCorner && i > 0 )
{
var prevSegmentLength = Vector3 . Distance ( _currentVertex , _prevVertex );
if ( prevSegmentLength > 2 * _sharpCornerOffset )
{
var dir = ( _currentVertex - _prevVertex );
var newPrevVertex = _currentVertex - ( dir * ( _sharpCornerOffset / prevSegmentLength ));
_distance += Vector3 . Distance ( newPrevVertex , _prevVertex );
AddCurrentVertex ( newPrevVertex , _distance , _prevNormal , md );
_prevVertex = newPrevVertex ;
}
}
var middleVertex = _prevVertex != Constants . Math . Vector3Unused && _nextVertex != Constants . Math . Vector3Unused ;
2021-03-28 18:17:15 +08:00
var currentJoin = JoinType . Fakeround ; //middleVertex ? _options.JoinType : _options.CapType;
2021-03-22 19:20:51 +08:00
if ( middleVertex && currentJoin == JoinType . Round )
{
if ( miterLength < _options . RoundLimit )
{
currentJoin = JoinType . Miter ;
}
else if ( miterLength <= 2 )
{
currentJoin = JoinType . Fakeround ;
}
}
if ( currentJoin == JoinType . Miter && miterLength > _options . MiterLimit )
{
currentJoin = JoinType . Bevel ;
}
if ( currentJoin == JoinType . Bevel )
{
// The maximum extrude length is 128 / 63 = 2 times the width of the line
// so if miterLength >= 2 we need to draw a different type of bevel here.
if ( miterLength > 2 )
{
currentJoin = JoinType . Flipbevel ;
}
// If the miterLength is really small and the line bevel wouldn't be visible,
// just draw a miter join to save a triangle.
if ( miterLength < _options . MiterLimit )
{
currentJoin = JoinType . Miter ;
}
}
if ( _prevVertex != Constants . Math . Vector3Unused )
{
_distance += Vector3 . Distance ( _currentVertex , _prevVertex );
}
if ( currentJoin == JoinType . Miter )
{
joinNormal *= miterLength ;
AddCurrentVertex ( _currentVertex , _distance , joinNormal , md );
}
else if ( currentJoin == JoinType . Flipbevel )
{
// miter is too big, flip the direction to make a beveled join
if ( miterLength > 100 )
{
// Almost parallel lines
joinNormal = _nextNormal * - 1 ;
}
else
{
var direction = ( _prevNormal . x * _nextNormal . z - _prevNormal . z * _nextNormal . x ) > 0 ? - 1 : 1 ;
var bevelLength = miterLength * ( _prevNormal + _nextNormal ). magnitude / ( _prevNormal - _nextNormal ). magnitude ;
joinNormal = joinNormal . Perpendicular () * ( bevelLength * direction );
}
AddCurrentVertex ( _currentVertex , _distance , joinNormal , md , 0 , 0 );
AddCurrentVertex ( _currentVertex , _distance , joinNormal * - 1 , md , 0 , 0 );
}
else if ( currentJoin == JoinType . Bevel || currentJoin == JoinType . Fakeround )
{
var lineTurnsLeft = ( _prevNormal . x * _nextNormal . z - _prevNormal . z * _nextNormal . x ) > 0 ;
var offset = ( float ) - Math . Sqrt ( miterLength * miterLength - 1 );
if ( lineTurnsLeft )
{
_cornerOffsetB = 0 ;
_cornerOffsetA = offset ;
}
else
{
_cornerOffsetA = 0 ;
_cornerOffsetB = offset ;
}
// Close previous segment with a bevel
if (! _startOfLine )
{
AddCurrentVertex ( _currentVertex , _distance , _prevNormal , md , _cornerOffsetA , _cornerOffsetB );
}
if ( currentJoin == JoinType . Fakeround )
{
// The join angle is sharp enough that a round join would be visible.
// Bevel joins fill the gap between segments with a single pie slice triangle.
// Create a round join by adding multiple pie slices. The join isn't actually round, but
// it looks like it is at the sizes we render lines at.
// Add more triangles for sharper angles.
// This math is just a good enough approximation. It isn't "correct".
var n = Mathf . Floor (( 0.5f - ( cosHalfAngle - 0.5f )) * 8 );
Vector3 approxFractionalJoinNormal ;
for ( var m = 0f ; m < n ; m ++)
{
approxFractionalJoinNormal = ( _nextNormal * (( m + 1f ) / ( n + 1f )) + ( _prevNormal )). normalized ;
AddPieSliceVertex ( _currentVertex , _distance , approxFractionalJoinNormal , lineTurnsLeft , md );
}
AddPieSliceVertex ( _currentVertex , _distance , joinNormal , lineTurnsLeft , md );
//change it to go -1, not sure if it's a good idea but it adds the last vertex in the corner,
//as duplicate of next road segment start
for ( var k = n - 1 ; k >= - 1 ; k --)
{
approxFractionalJoinNormal = ( _prevNormal * (( k + 1 ) / ( n + 1 )) + ( _nextNormal )). normalized ;
AddPieSliceVertex ( _currentVertex , _distance , approxFractionalJoinNormal , lineTurnsLeft , md );
}
//ending corner
_index1 = - 1 ;
_index2 = - 1 ;
}
if ( _nextVertex != Constants . Math . Vector3Unused )
{
AddCurrentVertex ( _currentVertex , _distance , _nextNormal , md , - _cornerOffsetA , - _cornerOffsetB );
}
}
else if ( currentJoin == JoinType . Butt )
{
if (! _startOfLine )
{
// Close previous segment with a butt
AddCurrentVertex ( _currentVertex , _distance , _prevNormal , md , 0 , 0 );
}
// Start next segment with a butt
if ( _nextVertex != Constants . Math . Vector3Unused )
{
AddCurrentVertex ( _currentVertex , _distance , _nextNormal , md , 0 , 0 );
}
}
else if ( currentJoin == JoinType . Square )
{
if (! _startOfLine )
{
// Close previous segment with a square cap
AddCurrentVertex ( _currentVertex , _distance , _prevNormal , md , 1 , 1 );
// The segment is done. Unset vertices to disconnect segments.
_index1 = _index2 = - 1 ;
}
// Start next segment
if ( _nextVertex != Constants . Math . Vector3Unused )
{
AddCurrentVertex ( _currentVertex , _distance , _nextNormal , md , - 1 , - 1 );
}
}
else if ( currentJoin == JoinType . Round )
{
if ( _startOfLine )
{
AddCurrentVertex ( _currentVertex , _distance , _prevNormal * 0.33f , md , - 2f , - 2f );
AddCurrentVertex ( _currentVertex , _distance , _prevNormal * 0.66f , md , -. 7f , -. 7f );
AddCurrentVertex ( _currentVertex , _distance , _prevNormal , md , 0 , 0 );
}
else if ( _nextVertex == Constants . Math . Vector3Unused )
{
AddCurrentVertex ( _currentVertex , _distance , _prevNormal , md , 0 , 0 );
AddCurrentVertex ( _currentVertex , _distance , _prevNormal * 0.66f , md , . 7f , . 7f );
AddCurrentVertex ( _currentVertex , _distance , _prevNormal * 0.33f , md , 2f , 2f );
_index1 = - 1 ;
_index2 = - 1 ;
}
else
{
AddCurrentVertex ( _currentVertex , _distance , _prevNormal , md , 0 , 0 );
AddCurrentVertex ( _currentVertex , _distance , _nextNormal , md , 0 , 0 );
}
}
if ( isSharpCorner && i < roadSegmentCount - 1 )
{
var nextSegmentLength = Vector3 . Distance ( _currentVertex , _nextVertex );
if ( nextSegmentLength > 2 * _sharpCornerOffset )
{
var newCurrentVertex = _currentVertex + (( _nextVertex - _currentVertex ) * ( _sharpCornerOffset / nextSegmentLength )); //._round()
_distance += Vector3 . Distance ( newCurrentVertex , _currentVertex );
AddCurrentVertex ( newCurrentVertex , _distance , _nextNormal , md );
_currentVertex = newCurrentVertex ;
}
}
_startOfLine = false ;
}
md . Edges . Add ( md . Vertices . Count );
md . Edges . Add ( md . Vertices . Count + 1 );
md . Edges . Add ( md . Vertices . Count + _vertexList . Count - 1 );
md . Edges . Add ( md . Vertices . Count + _vertexList . Count - 2 );
md . Vertices . AddRange ( _vertexList );
md . Normals . AddRange ( _normalList );
if ( md . Triangles . Count == 0 )
{
md . Triangles . Add ( new List < int >());
}
md . Triangles [ 0 ]. AddRange ( _triangleList );
md . Tangents . AddRange ( _tangentList );
md . UV [ 0 ]. AddRange ( _uvList );
}
}
private void ResetFields ()
{
_index1 = - 1 ;
_index2 = - 1 ;
_index3 = - 1 ;
_startOfLine = true ;
_cornerOffsetA = 0f ;
_cornerOffsetB = 0f ;
_vertexList . Clear ();
_normalList . Clear ();
_uvList . Clear ();
_tangentList . Clear ();
_triangleList . Clear ();
_prevVertex = Constants . Math . Vector3Unused ;
_currentVertex = Constants . Math . Vector3Unused ;
_nextVertex = Constants . Math . Vector3Unused ;
_prevNormal = Constants . Math . Vector3Unused ;
_nextNormal = Constants . Math . Vector3Unused ;
_distance = 0f ;
}
private void AddPieSliceVertex ( Vector3 vertexPosition , float dist , Vector3 normal , bool lineTurnsLeft , MeshData md )
{
var triIndexStart = md . Vertices . Count ;
var extrude = normal * ( lineTurnsLeft ? - 1 : 1 );
_vertexList . Add ( vertexPosition + extrude * _scaledWidth );
_normalList . Add ( Constants . Math . Vector3Up );
_uvList . Add ( new Vector2 ( 1 , dist ));
_tangentList . Add ( normal . Perpendicular () * - 1 );
_index3 = triIndexStart + _vertexList . Count - 1 ;
if ( _index1 >= 0 && _index2 >= 0 )
{
_triangleList . Add ( _index1 );
_triangleList . Add ( _index3 );
_triangleList . Add ( _index2 );
if (! lineTurnsLeft )
{
md . Edges . Add ( _index3 );
md . Edges . Add ( _index1 );
}
else
{
md . Edges . Add ( _index2 );
md . Edges . Add ( _index3 );
}
}
if ( lineTurnsLeft )
{
_index2 = _index3 ;
}
else
{
_index1 = _index3 ;
}
}
private void AddCurrentVertex ( Vector3 vertexPosition , float dist , Vector3 normal , MeshData md , float endLeft = 0 , float endRight = 0 )
{
var triIndexStart = md . Vertices . Count ;
var extrude = normal ;
if ( endLeft != 0 )
{
extrude -= ( normal . Perpendicular () * endLeft );
}
var vert = vertexPosition + extrude * _scaledWidth ;
_vertexList . Add ( vert );
_normalList . Add ( Constants . Math . Vector3Up );
_uvList . Add ( new Vector2 ( 1 , dist ));
_tangentList . Add ( normal . Perpendicular () * - 1 );
_index3 = triIndexStart + _vertexList . Count - 1 ;
if ( _index1 >= triIndexStart && _index2 >= triIndexStart )
{
_triangleList . Add ( _index1 );
_triangleList . Add ( _index3 );
_triangleList . Add ( _index2 );
md . Edges . Add ( triIndexStart + _vertexList . Count - 1 );
md . Edges . Add ( triIndexStart + _vertexList . Count - 3 );
}
_index1 = _index2 ;
_index2 = _index3 ;
extrude = normal * - 1 ;
if ( endRight != 0 )
{
extrude -= normal . Perpendicular () * endRight ;
}
_vertexList . Add ( vertexPosition + extrude * _scaledWidth );
_normalList . Add ( Constants . Math . Vector3Up );
_uvList . Add ( new Vector2 ( 0 , dist ));
_tangentList . Add ( normal . Perpendicular () * - 1 );
_index3 = triIndexStart + _vertexList . Count - 1 ;
if ( _index1 >= triIndexStart && _index2 >= triIndexStart )
{
_triangleList . Add ( _index1 );
_triangleList . Add ( _index2 );
_triangleList . Add ( _index3 );
md . Edges . Add ( triIndexStart + _vertexList . Count - 3 );
md . Edges . Add ( triIndexStart + _vertexList . Count - 1 );
}
_index1 = _index2 ;
_index2 = _index3 ;
}
#endregion
}
}