ClipMap Update

This commit is contained in:
Josef 2025-03-27 16:05:28 +01:00
parent 76a4e8e87d
commit 560668edf8
15 changed files with 424 additions and 63 deletions

View File

@ -11,11 +11,16 @@ namespace Rokojori
public Vector3 center => ( max + min ) / 2f;
public static implicit operator Box3( Aabb aabb )
public static implicit operator Box3( Aabb aabb )
{
return Box3.Create( aabb.Position, aabb.End );
}
public static implicit operator Aabb( Box3 box )
{
return new Aabb( box.min, box.size );
}
public static Box3 FromPositionAndScale( Vector3 position, Vector3 scale )
{
var max = scale * 0.5f;
@ -33,6 +38,8 @@ namespace Rokojori
return b;
}
public Vector3 size => max - min;
public Box2 AsXZBox2()
{
@ -51,6 +58,12 @@ namespace Rokojori
return point;
}
public void EnsureYBounds( float minY, float maxY )
{
min.Y = Mathf.Min( minY, min.Y );
max.Y = Mathf.Max( maxY, max.Y );
}
public float maxDistance => ( max - min ).Length();
public static Vector3 Constrain( Vector3 point, Vector3 min, Vector3 max )

View File

@ -35,6 +35,39 @@ namespace Rokojori
}
public float y
{
get => position.Y;
set {
var p = position;
p.Y = value;
position = p;
}
}
public float x
{
get => position.X;
set {
var p = position;
p.X = value;
position = p;
}
}
public float z
{
get => position.Z;
set {
var p = position;
p.Z = value;
position = p;
}
}
Basis _basis;
void Update()

View File

@ -261,6 +261,14 @@ namespace Rokojori
return v;
}
public static Vector3 SnapRoundedXZ( Vector3 v, float snapX, float snapZ )
{
v.X = MathX.SnapRounded( v.X, snapX );
v.Z = MathX.SnapRounded( v.Z, snapZ );
return v;
}
public static Vector3 SnapCeiled( Vector3 v, Vector3 snapping )
{
v.X = MathX.SnapCeiled( v.X, snapping.X );
@ -325,6 +333,7 @@ namespace Rokojori
return new Vector3( 0, y, z );
}
public static Vector3 Lerp( Vector3 a, Vector3 b, float lerp )

View File

@ -119,7 +119,7 @@ namespace Rokojori
}
public const float DegreesToRadians = Mathf.Pi / 180f;
public const float RadiansToDegreens = 180f / Mathf.Pi;
public const float RadiansToDegrees = 180f / Mathf.Pi;
public static float AngleDelta( float degreesA, float degreesB)
{

View File

@ -0,0 +1 @@
uid://vbas64yqk13v

View File

@ -9,6 +9,7 @@ namespace Rokojori
{
public class MeshGeometry
{
public string name = "";
public List<Vector3> vertices = new List<Vector3>();
public List<int> indices = new List<int>();
@ -432,6 +433,12 @@ namespace Rokojori
Initialize( normals, uvs, colors, uvs2 );
}
public MeshGeometry( string name, bool normals = true, bool uvs = true, bool colors = false, bool uvs2 = false )
{
this.name = name;
Initialize( normals, uvs, colors, uvs2 );
}
public static MeshGeometry BillboardQuad( float size = 1 )
{
var hs = size / 2f;

View File

@ -7,6 +7,12 @@ using System;
namespace Rokojori
{
public enum ClipMapCellConstraints
{
Rounded_Integer_Divisions,
Only_MinLevel_PowersOfTwos
}
[Tool]
[GlobalClass]
public partial class ClipMapPlaneMeshType:__PlaneMeshType__
@ -18,54 +24,232 @@ namespace Rokojori
public float minCellSize = 10;
[Export]
public Curve cellSizeOverDistance = MathX.Curve( 0, 1 );
public float centerMeshRadius = 100;
public override MeshGeometry GetMeshGeometry( float sizeX, float sizeZ )
[Export]
public ClipMapCellConstraints cellConstrains = ClipMapCellConstraints.Rounded_Integer_Divisions;
[Export]
public int meshDivisions = 8;
[Export]
public Curve cellSizeOverDistance = MathX.Curve( 0, 1 );
float xUnits;
float zUnits;
float sizeX;
float sizeZ;
Vector2 size;
Vector2 offset;
float maxDistance;
List<MeshGeometry> geometries;
public override List<MeshGeometry> GetMeshGeometries( float sizeX, float sizeZ )
{
var xUnits = Mathf.Ceil( ( sizeX / 2 ) / maxCellSize ) * 2;
var zUnits = Mathf.Ceil( ( sizeZ / 2 ) / maxCellSize ) * 2;
geometries = new List<MeshGeometry>();
var mg = new MeshGeometry();
geometries.Add( new MeshGeometry( "Center" ) );
var size = new Vector2( xUnits * maxCellSize, zUnits * maxCellSize );
var offset = size / -2f;
var maxDistance = offset.Length();
for ( int i = 0; i < meshDivisions; i++ )
{
float angle = ( (float) i / meshDivisions ) * 360f;
geometries.Add( new MeshGeometry( "Angle " + angle ) );
}
this.sizeX = sizeX;
this.sizeZ = sizeZ;
xUnits = Mathf.Ceil( ( sizeX / 2 ) / maxCellSize ) * 2;
zUnits = Mathf.Ceil( ( sizeZ / 2 ) / maxCellSize ) * 2;
size = new Vector2( xUnits * maxCellSize, zUnits * maxCellSize );
offset = size / -2;
maxDistance = Mathf.Min( sizeX, sizeZ ) / 2f;
this.LogInfo( xUnits, zUnits );
for ( int i = 0; i < xUnits; i++ )
for ( int x = 0; x < xUnits; x++ )
{
for ( int j = 0; j < zUnits; j++ )
for ( int z = 0; z < zUnits; z++ )
{
var c = offset + new Vector2( i * maxCellSize, j * maxCellSize );
var s = c.Length() / maxDistance;
if ( cellSizeOverDistance != null )
{
s = cellSizeOverDistance.Sample( s );
}
s = Mathf.Lerp( minCellSize, maxCellSize, s );
Add( mg, c, s );
Add( x, z );
}
}
return mg;
var g = geometries;
geometries = null;
return g;
}
void Add( MeshGeometry mg, Vector2 center, float cellSize )
static float edgeTreshold = 0.000001f;
bool InRange( float value, float target )
{
var numDivisions = Mathf.CeilToInt( maxCellSize / cellSize );
var e = edgeTreshold;
return Range.Contains( -e + target, e + target, value );
}
bool IsEdge( float value )
{
return InRange( value, 0 ) || InRange( value, 1 );
}
Vector2 CellOffset( int x, int z )
{
var c = offset + new Vector2( x * maxCellSize, z * maxCellSize );
return c;
}
float CellSize( int x, int z )
{
var c = CellOffset( x, z );
var cL = c + new Vector2( 1, 1 ) * maxCellSize / 2;
var s = cL.Length() / maxDistance;
if ( cellSizeOverDistance != null )
{
s = cellSizeOverDistance.Sample( s );
}
s = Mathf.Lerp( minCellSize, maxCellSize, s );
return s;
}
int CellDivisions( int x, int z )
{
var cellSize = CellSize( x, z );
var divisions = Mathf.CeilToInt( maxCellSize / cellSize );
if ( ClipMapCellConstraints.Rounded_Integer_Divisions == cellConstrains )
{
return divisions;
}
if ( divisions == 1 || divisions == 2 )
{
return divisions;
}
var v = MathX.NextPowerOfTwo( divisions );
return v * v;
}
bool IsEdge( Vector2 value )
{
return IsEdge( value.X ) || IsEdge( value.Y );
}
static float SnapToClosest( float value, int lowDivs )
{
float stepSize = 1.0f / lowDivs;
float snappedValue = Mathf.Round( value / stepSize ) * stepSize;
return snappedValue;
}
MeshGeometry GetMeshGeometry( Vector2 center )
{
if ( center.Length() < centerMeshRadius )
{
return geometries[ 0 ];
}
var angle = MathX.RadiansToDegrees * center.Angle();
var index = Mathf.RoundToInt( angle / 360f * meshDivisions -0.5f );
index = MathX.Repeat( index, meshDivisions );
return geometries[ index + 1 ];
}
void Add( int x, int z )
{
var center = CellOffset( x, z );
var cellSize = CellSize( x, z );
var mg = GetMeshGeometry( center );
var numDivisions = CellDivisions( x, z );
var leftDivisions = x == 0 ? -1 : CellDivisions( x - 1, z );
var rightDivisions = x == xUnits - 1 ? -1 : CellDivisions( x + 1, z );
var topDivisions = z == 0 ? -1 : CellDivisions( x, z - 1 );
var bottomDivisions = z == zUnits - 1 ? -1 : CellDivisions( x, z + 1 );
var snapToLeft = leftDivisions != -1 && leftDivisions < numDivisions;
var snapToRight = rightDivisions != -1 && rightDivisions < numDivisions;
var snapToTop = topDivisions != -1 && topDivisions < numDivisions;
var snapToBottom = bottomDivisions != -1 && bottomDivisions < numDivisions;
// RJLog.Log( snapToLeft, snapToRight, snapToBottom, snapToTop );
var sectionMG = MeshGeometry.CreateFromUVFunction(
( uv ) =>
{
var pose = new Pose();
pose.position = Math3D.XYasXZ( uv ) * maxCellSize + Math3D.XYasXZ( center );
var isEdge = IsEdge( uv );
if ( ! isEdge )
{
return pose;
}
var before = pose.position;
var snappedUV = uv;
if ( ! ( InRange( uv.Y, 0 ) || InRange( uv.Y, 1 ) ) )
{
if ( snapToLeft && InRange( uv.X, 0 ) )
{
snappedUV.Y = SnapToClosest( snappedUV.Y, leftDivisions );
}
if ( snapToRight && InRange( uv.X, 1 ) )
{
snappedUV.Y = SnapToClosest( snappedUV.Y, rightDivisions );
}
}
if ( ! ( InRange( uv.X, 0 ) || InRange( uv.X, 1 ) ) )
{
if ( snapToTop && InRange( uv.Y, 0 ) )
{
snappedUV.X = SnapToClosest( snappedUV.X, topDivisions );
}
if ( snapToBottom && InRange( uv.Y, 1 ) )
{
snappedUV.X = SnapToClosest( snappedUV.X, bottomDivisions );
}
}
pose.position = Math3D.XYasXZ( snappedUV ) * maxCellSize + Math3D.XYasXZ( center );
// this.LogInfo( "Snapped", before, ">>", pose.position );
return pose;

View File

@ -8,67 +8,104 @@ namespace Rokojori
[Tool]
[GlobalClass]
public partial class Plane:Node3D
{
{
[ExportToolButton( "Update Mesh")]
public Callable UpdateMeshButton => Callable.From( () => UpdateMesh() );
public readonly EventProperty<float> _width = new EventProperty<float>();
[Export]
public float width { get => _width; set { _width = value; UpdateMesh(); } }
float _width = 200;
public float width { get => _width.value; set => _width.value = value; }
public readonly EventProperty<float> _height = new EventProperty<float>();
[Export]
public float height { get => _height; set { _height = value; UpdateMesh(); } }
float _height = 200;
public float height { get => _height.value; set => _height.value = value; }
[Export]
public __PlaneMeshType__ type { get => _type; set { _type = value; UpdateMesh(); } }
__PlaneMeshType__ _type;
public bool extendBoundingBox = true;
[Export]
public float boundingBoxMinY = 0;
[Export]
public float boundingBoxMaxY = 1;
public readonly EventProperty<__PlaneMeshType__> _type = new EventProperty<__PlaneMeshType__>();
[Export]
public __PlaneMeshType__ type { get => _type.value; set { _type.value = value; } }
[Export]
public Material material;
[Export]
public MeshInstance3D outputMesh;
public MeshInstance3D[] meshes;
[Export]
public bool initialized
{
get => _initialized;
set { if ( _initialized ) { return; } _initialized = true; UpdateMesh(); }
}
bool _initialized;
public bool snapXZ = true;
void UpdateMesh()
[Export]
public float snappingDistance = 100;
[Export]
public Vector3 snapOffset = Vector3.Zero;
[ExportGroup( "Debug Info")]
[Export]
public int numTriangles;
public override void _Process( double delta )
{
if ( ! _initialized )
if ( ! snapXZ )
{
return;
}
GlobalPosition = Math3D.SnapRoundedXZ( GlobalPosition, snappingDistance, snappingDistance );
}
void UpdateMesh()
{
if ( _type == null )
{
return;
}
numTriangles = 0;
this.LogInfo( "Creating mesh" );
var mg = _type.GetMeshGeometry( width, height );
var mgs = _type.value.GetMeshGeometries( width, height );
if ( outputMesh == null )
meshes = new MeshInstance3D[ mgs.Count ];
Nodes.DestroyChildren( this );
for ( int i = 0; i < mgs.Count; i++ )
{
outputMesh = this.CreateChild<MeshInstance3D>();
var mg = mgs[ i ];
var outputMesh = this.CreateChild<MeshInstance3D>( mg.name );
outputMesh.Mesh = mg.GenerateMesh();
if ( extendBoundingBox )
{
var extendedBounds = (Box3) outputMesh.Mesh.GetAabb();
extendedBounds.EnsureYBounds( boundingBoxMinY, boundingBoxMaxY );
outputMesh.CustomAabb = extendedBounds;
}
meshes[ i ] = outputMesh;
numTriangles += mg.numTriangles;
if ( material != null )
{
Materials.Set( outputMesh, material );
}
}
outputMesh.Mesh = mg.GenerateMesh();
if ( material != null )
{
Materials.Set( outputMesh, material );
}
this.LogInfo( material, outputMesh, outputMesh.Mesh );
}
}

View File

@ -11,7 +11,7 @@ namespace Rokojori
[GlobalClass]
public partial class __PlaneMeshType__:Resource
{
public virtual MeshGeometry GetMeshGeometry( float width, float height )
public virtual List<MeshGeometry> GetMeshGeometries( float width, float height )
{
return null;
}

View File

@ -0,0 +1 @@
uid://ff6fmiujno23

View File

@ -8,3 +8,66 @@ vec4 triplanarTexture( sampler2D sampler, vec3 weights, vec3 triplanerPosition )
return sample;
}
vec2 heightMapUV( vec3 worldPosition, vec3 terrainSize, vec3 terrainOffset )
{
vec3 inTerrain = worldPosition - terrainOffset;
vec3 terrainUV = inTerrain / terrainSize;
return vec2( terrainUV.x, terrainUV.z );
}
vec4 fromHeightMap( sampler2D sampler, vec3 worldPosition, vec3 terrainSize, vec3 terrainOffset )
{
vec3 inTerrain = worldPosition - terrainOffset;
vec3 terrainUV = inTerrain / terrainSize;
return texture( sampler, vec2( terrainUV.x, terrainUV.z ) );
}
vec4 fromHeightMapSmoothed( sampler2D terrain, vec2 terrainUV, vec2 kernelSize, float weightSmoothing )
{
vec4 output = vec4( 0, 0, 0, 0 );
float sumW = 0.0;
for ( int i = -1; i < 2; i++ )
{
for ( int j= -1; j < 2; j++ )
{
vec2 offset = vec2( kernelSize.x * float(i), kernelSize.y * float(j) );
float w = 1.0 / ( weightSmoothing + length( offset ) );
output += texture( terrain, terrainUV + offset ) * w;
sumW += w;
}
}
return output / sumW;
}
vec3 heightMapDirection( sampler2D terrain, vec2 terrainUV, vec2 kernelSize, float normalScale )
{
float hL = texture( terrain, terrainUV - vec2( kernelSize.x, 0 ) ).r * normalScale;
float hR = texture( terrain, terrainUV + vec2( kernelSize.x, 0 ) ).r * normalScale;
float hD = texture( terrain, terrainUV - vec2( 0, kernelSize.y ) ).r * normalScale;
float hU = texture( terrain, terrainUV + vec2( 0, kernelSize.y ) ).r * normalScale;
return vec3( hL - hR, hD - hU, 2.0 );
}
vec3 heightMapNormal( sampler2D terrain, vec2 terrainUV, vec2 kernelSize, float normalScale )
{
return normalize( heightMapDirection( terrain, terrainUV, kernelSize, normalScale ) );
}
vec3 heightMapNormalSmoothed( sampler2D terrain, vec2 terrainUV, vec2 kernelSize, float normalScale, float kernelSpread )
{
vec3 n0 = heightMapDirection( terrain, terrainUV, kernelSize, normalScale );
vec3 n1 = heightMapDirection( terrain, terrainUV, kernelSize * kernelSpread, normalScale ) * 0.5;
vec3 n2 = heightMapDirection( terrain, terrainUV, kernelSize * kernelSpread * kernelSpread, normalScale ) * 0.25;
return normalize( n0 + n1 + n2 );
}

View File

@ -47,6 +47,16 @@ vec3 worldToLocalDirection( vec3 _VERTEX, mat4 _MODEL_MATRIX )
return ( mw * vec4( _VERTEX, 1.0 ) ).xyz;
}
vec3 worldToViewDirection( vec3 direction, mat4 _VIEW_MATRIX )
{
mat4 mw = _VIEW_MATRIX;
mw[ 3 ][ 0 ] = 0.0;
mw[ 3 ][ 1 ] = 0.0;
mw[ 3 ][ 2 ] = 0.0;
mw[ 3 ][ 3 ] = 1.0;
return ( mw * vec4( direction, 1.0 ) ).xyz;
}
vec3 extractScale( mat3 _MODEL_NORMAL_MATRIX )
{

View File

@ -0,0 +1 @@
uid://ctpdwmseds08

View File

@ -0,0 +1 @@
uid://bmyf1o4fx8en2

View File

@ -0,0 +1 @@
uid://dbbl2m0i06ysm