394 lines
10 KiB
C#
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 );
|
|
}
|
|
}
|
|
} |