using System.Collections; using System.Collections.Generic; using Godot; using System; namespace Rokojori { [Tool] [GlobalClass, Icon("res://Scripts/Rokojori/Rokojori-Action-Library/Icons/Scatterer.svg") ] public partial class GrassPatch:Node3D { [Export] public MeshInstance3D output; [Export] public int seed = 1984; [Export] public bool update; [Export] public bool updateAlways; [Export( PropertyHint.Range, "0,100")] public int blades = 20; [Export( PropertyHint.Range, "0,100")] public int bladesX = 0; [Export( PropertyHint.Range, "0,100")] public int bladesZ = 0; [Export] public int X_numBlades; [Export] public float patchSize = 2; [Export] public float patchSizeX = 0; [Export] public float patchSizeZ = 0; [Export] public Vector2 patchOffsetPosition = new Vector2( 0.5f, 0.5f ); [Export( PropertyHint.Range, "1,20")] public int bladeSegments = 3; [Export] public bool createBackFaces = true; [Export] public int X_numTriangles; [Export] public Curve bladeSegmentMapping = MathX.Curve( 0, 1 ); [Export] public int uvSegmentColumns = 1; [Export] public int uvSegmentRows = 1; [Export] public Curve uvSegmentWeightsClose = MathX.Curve( 1f ); [Export] public Curve uvSegmentWeightsFar = null; [Export] public Curve bladeScale = MathX.Curve( 1f ); [Export] public Curve bladeWidth = MathX.Curve( 0.05f ); [Export] public Curve bladeWidth2 = null; [Export] public Curve bladeBending = MathX.Curve( 0f ); [Export] public Curve bladeBending2 = null; [Export] public Curve bladeHeight = MathX.Curve( 0.4f ); [Export] public Curve bladeInGround = MathX.Curve( 0.05f ); [Export] public Curve positionJitter = MathX.Curve( 0.05f ); [Export] public Curve scaleByDistanceX = MathX.Curve( 1f, 1f ); [Export] public Curve scaleByDistanceZ = MathX.Curve( 1f, 1f ); [Export] public Curve yawRotation = MathX.Curve( 0f, 1f ); [Export] public Curve randomRotation = MathX.Curve( 0f, 20f ); [Export (PropertyHint.Range, "0,1")] public float filterTreshold = 1; [Export] public Vector2 filterScale = new Vector2( 1, 1 ); [Export] public Vector2 filterOffset = new Vector2( 0, 0 ); [Export] public Vector2 positionToFilter = new Vector2( 0, 0 ); public override void _Process( double delta ) { if ( ! ( update || updateAlways ) ) { return; } update = false; CreatePatch(); } float _maxWidth = 0; Vector3 _patchOffset = Vector3.Zero; public void CreatePatch() { var mg = new MeshGeometry(); var cellSizeX = ( patchSizeX + patchSize ) / ( bladesX + blades ); var cellSizeZ = ( patchSizeZ + patchSize ) / ( bladesZ + blades ); var random = new LCG(); random.SetSeed( 1712 + seed ); _patchOffset = new Vector3( - ( patchSizeX + patchSize ) * patchOffsetPosition.X, 0, - ( patchSizeZ + patchSize ) * patchOffsetPosition.Y ); _maxWidth = MathX.CurveMaximum( bladeWidth ); X_numBlades = 0; var allBladesX = bladesX + blades; var allBladesZ = bladesZ + blades; for ( int i = 0; i < allBladesX; i++ ) { var x = ( i + 0.5f ) * cellSizeX; for ( int j = 0; j < allBladesZ; j++ ) { var z = ( j + 0.5f ) * cellSizeZ; random.SetSeed( i * 11223 + j *12895 + seed ); var position = new Vector3( x, 0, z ); var yaw = random.Sample( yawRotation ); var rotationY = yaw * Mathf.Pi * 2f; var maxRotation = random.Sample( randomRotation ); var rotationOther = random.Next() * Mathf.DegToRad( maxRotation ); var rotationX = random.Next() * rotationOther; var rotationZ = rotationOther - rotationX; var positionOffset = Math3D.OnCircleXZ( random.Next() * Mathf.Pi * 2f ) * random.Sample( positionJitter ); var worldPosition = position + _patchOffset + positionOffset; var filterValue = Noise.PerlinXZ( Math3D.XYasXZ( filterScale ) * worldPosition + Math3D.XYasXZ( filterOffset ) + -GlobalPosition * Math3D.XYasXZ( positionToFilter )); var trsf = new Transform3D( new Basis( Math3D.RotateY( rotationY ) * Math3D.RotateX( rotationX ) * Math3D.RotateZ( rotationZ )), worldPosition ); var bladeMG = CreateBlade( random, worldPosition ); if ( filterValue > filterTreshold ) { continue; } bladeMG.ApplyTransform( trsf ); mg.Add( bladeMG ); if ( createBackFaces ) { var blade2MG = bladeMG.Clone(); blade2MG.FlipNormalDirection(); mg.Add( blade2MG ); } X_numBlades ++; } } X_numTriangles = mg.indices.Count / 3; output.Mesh = mg.GenerateMesh(); } MeshGeometry CreateBlade( RandomEngine random, Vector3 position ) { var bmg = new MeshGeometry(); var inGround = random.Sample( bladeInGround ); var height = random.Sample( bladeHeight ); var size = height + inGround; var distancesToCenter = new Vector2( position.X / ( patchSizeX + patchSize ), position.Z / ( patchSizeZ + patchSize ) ); distancesToCenter = distancesToCenter.Abs(); var minDistance = distancesToCenter.Length(); var scaling = scaleByDistanceX.Sample( distancesToCenter.X ) * scaleByDistanceZ.Sample( distancesToCenter.Y ); scaling *= random.Sample( bladeScale ); size *= scaling; var firstIsTriangle = false; var lastIsTriangle = false; var bladeWidthLerp = random.Next(); var bladeBendLerp = random.Next(); var uvMin = new Vector2( 0, 0 ); var uvMax = new Vector2( 1, 1 ); var uvSegments = uvSegmentRows * uvSegmentColumns; if ( uvSegments > 1 ) { var index = random.IntegerExclusive( uvSegments ); if ( uvSegmentWeightsClose != null ) { var weightsClose = MathX.GetCurveWeights( uvSegmentWeightsClose, uvSegments ); if ( uvSegmentWeightsFar != null ) { var weightsFar = MathX.GetCurveWeights( uvSegmentWeightsFar, uvSegments ); var distanceFade = minDistance; for ( int i = 0; i < uvSegments; i++ ) { weightsClose[ i ] += distanceFade * ( weightsFar[ i ] - weightsClose[ i ] ); } } index = random.IndexFromUnnormalizedWeights( weightsClose ); } var x = index % uvSegmentColumns; var y = index / uvSegmentRows; var xSize = 1f / uvSegmentRows; var ySize = 1f / uvSegmentColumns; uvMin.X = x * xSize; uvMin.Y = y * ySize; uvMax = uvMin + new Vector2( xSize, ySize ); } for ( int i = 0; i <= bladeSegments; i++ ) { var t = (float)i / bladeSegments; t = bladeSegmentMapping.Sample( t ); var v = 1f - t; var y = size * t - inGround; var bw2 = bladeWidth2 == null ? bladeWidth : bladeWidth2; var bb2 = bladeBending2 == null ? bladeBending : bladeBending2; var width = Mathf.Lerp( bladeWidth.Sample( v ), bw2.Sample( v ), bladeWidthLerp ); width *= scaling; var bending = Mathf.Lerp( bladeBending.Sample( v ), bb2.Sample( v ), bladeBendLerp ) * Vector3.Forward.Z * scaling; var bendingNormalAngle = Mathf.LerpAngle( MathX.CurveAngle( bladeBending, v ), MathX.CurveAngle( bb2, v ), bladeBendLerp ); var bendingNormal = Math3D.NormalAngle( bendingNormalAngle ); if ( width < 0.005f && ( i == 0 || ( i == bladeSegments - 1 )) ) { bmg.vertices.Add( new Vector3( 0, y, bending ) ); bmg.normals.Add( bendingNormal ); bmg.uvs.Add( MapUV( new Vector2( 0.5f, v ), uvMin, uvMax ) ); if ( i == 0 ){ firstIsTriangle = true; } if ( i == bladeSegments - 1 ){ lastIsTriangle = true; } } else { bmg.vertices.Add( new Vector3( -width, y, bending ) ); bmg.vertices.Add( new Vector3( width, y, bending ) ); bmg.normals.Add( bendingNormal ); bmg.normals.Add( bendingNormal ); bmg.uvs.Add( MapUV( new Vector2( -width / _maxWidth, v ), uvMin, uvMax ) ); bmg.uvs.Add( MapUV( new Vector2( width / _maxWidth, v ), uvMin, uvMax ) ); } } for ( int i = 0; i < bladeSegments; i++ ) { var index = i * 2; if ( firstIsTriangle && index != 0 ) { index --; } if ( i == 0 && firstIsTriangle ) { bmg.AddTriangle( index + 0, index + 1, index + 2 ); } else if ( i == ( bladeSegments - 1 ) && lastIsTriangle ) { bmg.AddTriangle( index + 1, index + 0, index + 2 ); } else { bmg.AddQuad( index + 0, index + 1, index + 2, index + 3 ); } } return bmg; } Vector2 MapUV( Vector2 uv, Vector2 min, Vector2 max ) { uv = Math2D.Clamp01( uv ); return Math2D.Map( uv, Vector2.Zero, Vector2.One, min, max ); } } }