rj-action-library/Runtime/Procedural/Assets/Grass/GrassPatch.cs

394 lines
10 KiB
C#

using System.Collections;
using System.Collections.Generic;
using Godot;
using System;
namespace Rokojori
{
[Tool]
[GlobalClass, Icon("res://addons/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 bladeHeight = MathX.Curve( 0.4f );
[Export]
public Curve bladeInGround = MathX.Curve( 0.05f );
[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 positionJitter = MathX.Curve( 0.05f );
[Export]
public Curve scaleByDistanceX = MathX.Curve( 1f );
[Export]
public Curve scaleByDistanceZ = MathX.Curve( 1f );
[Export]
public Curve normalBlending = MathX.Curve( 0.5f );
[Export]
public Vector3 normalBlendingDirection = Vector3.Up;
[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 );
SerializedGodotObject _cached;
public override void _Process( double delta )
{
if ( ! ( update || updateAlways ) )
{
return;
}
update = false;
var current = SerializedGodotObject.Create( this );
var isEquals = _cached != null && _cached.Equals( current );
if ( _cached != null && _cached.Equals( current ) )
{
return;
}
CreatePatch();
_cached = current;
}
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 normalBlendingAmount = random.Sample( normalBlending );
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 );
MeshGeometry clonedBladeMG = null;
if ( createBackFaces )
{
clonedBladeMG = bladeMG.Clone();
}
bladeMG.BlendNormals( normalBlendingDirection, normalBlendingAmount );
mg.Add( bladeMG );
if ( createBackFaces )
{
clonedBladeMG.FlipNormalDirection();
clonedBladeMG.BlendNormals( normalBlendingDirection, normalBlendingAmount );
mg.Add( clonedBladeMG );
}
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 = Mathf.FloorToInt( index / uvSegmentColumns );
var xSize = 1f / uvSegmentColumns;
var ySize = 1f / uvSegmentRows;
uvMin.X = x * xSize;
uvMin.Y = y * ySize;
uvMax = uvMin + new Vector2( xSize, ySize );
// if ( X_numBlades < 6 )
// {
// RJLog.Log(
// " c,r:",uvSegmentColumns,uvSegmentRows,
// " index:", index,
// " x,y:", x,y,
// " xS,yS:",xSize,ySize,
// " uvMin:", uvMin,
// " uvMax:", uvMax
// );
// }
}
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 );
}
}
}