File IO/GLTF Export/Grass Patch

This commit is contained in:
Josef 2025-07-15 11:30:35 +02:00
parent 537f3d7e96
commit afdf5e115f
17 changed files with 348 additions and 15 deletions

View File

@ -23,6 +23,23 @@ namespace Rokojori
public string _fileExtension;
string _fullPath = null;
Trillean isDirectory = Trillean.Any;
public bool Exists()
{
return File.Exists( fullPath );
}
public void MarkAsFile()
{
isDirectory = Trillean.False;
}
public void MarkAsDirectory()
{
isDirectory = Trillean.True;
}
/**<summary for="method get_fileName">Only fileName without fileExtension </summary>*/
public string fileName
{
@ -40,7 +57,7 @@ namespace Rokojori
/**<summary for="method get_fullFileName">Combines fileName + fileExtension </summary>*/
public string fullFileName => fileName + fileExtension;
/**<summary for="method get_fileExtension>File extension including the dot, eg. ".svg", ".jpg", ".js"</summary>*/
/** <summary for="method get_fileExtension">File extension including the dot, eg. ".svg", ".jpg", ".js" </summary> */
public string fileExtension
{
get
@ -92,12 +109,23 @@ namespace Rokojori
public static FilePath Create( string path, FilePathType type = FilePathType.Relative, FilePath parent = null )
{
var rp = new FilePath();
if ( path.EndsWith( "/" ) )
{
path = path.ReplaceEnd( "/" );
rp.isDirectory = Trillean.True;
}
path = path.ReplaceStart( "/" );
rp.type = type;
rp.parent = parent;
rp.path = path;
return rp;
}
@ -169,17 +197,78 @@ namespace Rokojori
return FilePath.Create( path, FilePathType.Relative, this );
}
public FilePath MakeAbsolutePathRelative( string absolutePath )
public FilePath MakeAbsolutePathRelative( string otherAbsolutePath )
{
absolutePath = FilePath.Normalize( absolutePath );
otherAbsolutePath = FilePath.Normalize( otherAbsolutePath );
var ownAbsolutePath = FilePath.Normalize( fullPath );
if ( ! absolutePath.StartsWith( ownAbsolutePath ) )
if ( ! otherAbsolutePath.StartsWith( ownAbsolutePath ) )
{
return null;
// A/B/C/D.x => own
// A/B/E/F.x => other
// A/B => common
// E/F => common relative
// /.. => own to common
// /../E/F => own to other
var commonPath = ownAbsolutePath.ExtractCommonStart( otherAbsolutePath );
var commonFilePath = FilePath.Absolute( commonPath );
var commonRelativeFilePath = commonFilePath.MakeAbsolutePathRelative( otherAbsolutePath );
var ownIsDir = Trillean.True == isDirectory || Trillean.False != isDirectory && fileExtension == "";
var itPath = ownAbsolutePath;
if ( ! ownIsDir )
{
itPath = absoluteParentPath;
}
var relativePath = absolutePath.Substring( ownAbsolutePath.Length );
itPath = itPath.ReplaceStart( commonPath );
var splits = RegexUtility.SplitPaths( itPath );
var dirs = "";
for ( int i = 0; i < splits.Count; i++ )
{
if ( i != 0 )
{
dirs += "/" ;
}
dirs += "..";
}
if ( ! commonRelativeFilePath.path.StartsWith( "/" ) )
{
dirs += "/";
}
// RJLog.Log(
// "\nown:", ownAbsolutePath,
// "\nother:", otherAbsolutePath,
// "\ncommonPath:", commonPath,
// "\ncommonRelativeFilePath:", commonRelativeFilePath.fullPath,
// "\ncommonRelativeFilePath.path:", commonRelativeFilePath.path,
// "\ndirs", dirs,
// "\nRelative:", dirs + commonRelativeFilePath.path
// );
return MakeRelative( dirs + commonRelativeFilePath.path );
}
// A/B/C
// A/B/C/D
var relativePath = otherAbsolutePath.Substring( ownAbsolutePath.Length );
relativePath = relativePath.ReplaceStart( "/" );
return MakeRelative( relativePath );
}

View File

@ -9,6 +9,18 @@ namespace Rokojori
{
public class FilesSync
{
public static void Move( string fromPath, string toPath )
{
string? destinationDirectory = Path.GetDirectoryName( toPath );
if ( ! string.IsNullOrEmpty( destinationDirectory ) && ! Directory.Exists( destinationDirectory ) )
{
Directory.CreateDirectory(destinationDirectory);
}
File.Move( fromPath, toPath );
}
public static void Delete( string path )
{
if ( string.IsNullOrWhiteSpace( path ) )
@ -47,6 +59,11 @@ namespace Rokojori
Directory.CreateDirectory( path );
}
public static void EnsureParentDirectoryExists( string path )
{
EnsureDirectoryExists( RegexUtility.ParentPath( path ) );
}
public static void EnsureDirectoryExists( string path )
{
if ( DirectoryExists( path ) )

View File

@ -4,6 +4,7 @@ using System.Collections.Generic;
using System;
using System.Threading.Tasks;
using System.Reflection;
using Microsoft.VisualBasic;
namespace Rokojori
{
@ -29,8 +30,21 @@ namespace Rokojori
{
var memberInfos = ReflectionHelper.GetDataMemberInfos<T>( BindingFlags.Public | BindingFlags.Instance | BindingFlags.DeclaredOnly );
memberInfos = memberInfos.Filter(
( mi ) =>
{
if ( mi.IsMemberType<Callable>() )
{
return false;
}
return true;
}
);
var memberNames = Lists.Map( memberInfos, m => m.Name );
// RJLog.Log( "Trying to copy:", memberNames );
ReflectionHelper.CopyDataMembersFromTo( from, to, memberNames );
}

View File

@ -407,8 +407,10 @@ namespace Rokojori
return bladeSegments * 2 * ComputeNumBlades();
}
public async Task CreatePatch()
public async Task CreatePatch( string patchName = null )
{
RJLog.Log( "Create Patch:", patchName );
if ( blades == 0 && bladesX == 0 && bladesZ == 0)
{
return;
@ -433,7 +435,7 @@ namespace Rokojori
this.DestroyChildren();
this.output = this.CreateChild<MeshInstance3D>( "Grass Patch Mesh" );
this.output = this.CreateChild<MeshInstance3D>( patchName );
@ -452,6 +454,8 @@ namespace Rokojori
X_numTriangles = mg.indices.Count / 3;
output.Mesh = mg.GenerateMesh();
output.Mesh.SurfaceSetMaterial( 0, material );
output.Name = patchName;
}
else
{
@ -510,6 +514,8 @@ namespace Rokojori
output.Mesh = MeshGeometry.GenerateTrianglesLODMesh( mgs, lerpingData, false );
output.Mesh.SurfaceSetMaterial( 0, material );
output.Name = patchName;
}
catch ( System.Exception e )

View File

@ -549,14 +549,16 @@ namespace Rokojori
var fd = fovDistance.ComputeFOVDistance( size / 2f );
_cameraFOV = fd.X;
_cameraFOV = Mathf.Clamp( fd.X, 1, 179 );
_cameraDistance = fd.Y;
_outputScale = computeScale ? Cameras.ComputeCameraFittingScale( _cameraFOV, _cameraDistance ) : 1;
RJLog.Log( "Computed FOV Distance", _cameraFOV, _cameraDistance );
}
public float GetCameraFOV()
{
return _cameraFOV;
return Mathf.Clamp( _cameraFOV, 1, 179 );
}
public float GetCameraDistance()

View File

@ -14,11 +14,21 @@ namespace Rokojori
return jsonData.Stringify();
}
public static void Save( string path, JSONData data )
{
FilesSync.SaveUTF8( path, Stringify( data ) );
}
public static JSONData Parse( string jsonString )
{
return new JSONParser().Parse( jsonString );
}
public static JSONData Load( string path )
{
return JSON.Parse( FilesSync.LoadUTF8( path ) );
}
public static string StringifyObject( object value )
{
var serializer = new JSONSerializer( new JSONSerializationSettings() );

View File

@ -62,6 +62,26 @@ namespace Rokojori
return source;
}
public static string ExtractCommonStart( this string source, string other )
{
for ( int i = 0; i < source.Length && i < other.Length; i++ )
{
if ( source[ i ] != other[ i ] )
{
return source.Substring( 0, i );
}
}
if ( source.Length < other.Length )
{
return source;
}
return other;
}
}
}

View File

@ -7,7 +7,7 @@ using Godot;
namespace Rokojori
{
public class ReflectionHelper
public static class ReflectionHelper
{
public static T Create<T>( Type type, params object[] args )
{
@ -366,6 +366,27 @@ namespace Rokojori
return GetDataMemberInfo( instance, memberName, flags ) != null;
}
public static Type GetMemberType( this MemberInfo memberInfo )
{
if ( memberInfo is FieldInfo fi )
{
return fi.FieldType;
}
if ( memberInfo is PropertyInfo pi )
{
return pi.PropertyType;
}
return null;
}
public static bool IsMemberType<T>( this MemberInfo memberInfo )
{
return GetMemberType( memberInfo ) == typeof( T );
}
public static T GetDataMemberValue<T>( object instance, MemberInfo info )
{

View File

@ -11,13 +11,94 @@ namespace Rokojori.Tools
{
public class GLTFExport
{
public static void Save( Node3D node, string path, string temporaryPath = "res://--temporary--" )
static string temporaryPath = "res://.rokojori/cache/temp";
public static void Save( Node3D node, string path, string texturesPath = null, string binPath = null )
{
FilesSync.EnsureParentDirectoryExists( path );
if ( binPath != null )
{
FilesSync.EnsureDirectoryExists( binPath );
}
if ( texturesPath != null )
{
FilesSync.EnsureDirectoryExists( texturesPath );
}
var state = new GltfState();
var doc = new GltfDocument();
doc.AppendFromScene( node, state );
if ( texturesPath == null )
{
doc.WriteToFilesystem( state, path );
return;
}
var gltfFilePath = FilePath.Absolute( path ); // Defines file name and gltfPath;
var gltfParentFilePath = gltfFilePath.CreateAbsoluteParent();
var texturesDirPath = FilePath.Absolute( texturesPath ); // Only directory for textures
var binDirPath = FilePath.Absolute( binPath ); // Only directory for bins, name comes from gltf.
var temporaryFilePath = FilePath.Create( ProjectSettings.GlobalizePath( temporaryPath ) + "/" + gltfFilePath.fullFileName );
var temporaryDirPath = temporaryFilePath.CreateAbsoluteParent();
doc.WriteToFilesystem( state, temporaryFilePath.fullPath );
var earlyOut = false;
if ( earlyOut )
{
return;
}
var json = JSON.Load( temporaryFilePath.fullPath ).AsObject();
var images = json.GetArray( "images" );
for ( int i = 0; images != null && i < images.size; i++ )
{
var image = images.Get( i ).AsObject();
var uri = image.GetString( "uri" ).Replace( "%2F", "/" );
var textureFilePath = temporaryDirPath.MakeRelative( uri );
var newTexturesFilePath = texturesDirPath.MakeRelative( textureFilePath.fullFileName );
var newRelativeFilePath = gltfParentFilePath.MakeAbsolutePathRelative( newTexturesFilePath.fullPath );
// RJLog.Log( "Moving image", i, uri, newRelativeFilePath.path,"\n" + textureFilePath.fullPath, "\n" + newRelativeFilePath.fullPath );
image.Set( "uri", newRelativeFilePath.path.Replace( "/", "%2F" ) );
if ( ! newRelativeFilePath.Exists() )
{
FilesSync.Move( textureFilePath.fullPath, newTexturesFilePath.fullPath );
}
}
var buffers = json.GetArray( "buffers" );
for ( int i = 0; buffers != null && i < buffers.size; i++ )
{
var buffer = buffers.Get( i ).AsObject();
var uri = buffer.GetString( "uri" ).Replace( "%2F", "/" );
var bufferFilePath = temporaryDirPath.MakeRelative( uri );
var newBufferFilePath = binDirPath.MakeRelative( gltfFilePath.WithExtension( ".bin" ).fullFileName );
var newRelativeFilePath = gltfParentFilePath.MakeAbsolutePathRelative( newBufferFilePath.fullPath );
// RJLog.Log( "Moving buffer", i, uri, newRelativeFilePath.path, bufferFilePath.fullPath, newRelativeFilePath.fullPath );
buffer.Set( "uri", newRelativeFilePath.path.Replace( "/", "%2F" ) );
FilesSync.Move( bufferFilePath.fullPath, newBufferFilePath.fullPath );
}
JSON.Save( gltfFilePath.fullPath, json );
FilesSync.Delete( temporaryFilePath.fullPath );
return;
}

View File

@ -0,0 +1,18 @@
using System.Collections;
using System.Collections.Generic;
using Godot;
using System;
using System.Threading.Tasks;
namespace Rokojori.Tools
{
public class GLTFAssetInfo
{
public string generator;
public string version;
}
}

View File

@ -0,0 +1 @@
uid://4cwq20m8ihng

View File

@ -0,0 +1,17 @@
using System.Collections;
using System.Collections.Generic;
using Godot;
using System;
using System.Threading.Tasks;
namespace Rokojori.Tools
{
public class GLTFBufferReference
{
public int byteLength;
public string uri;
}
}

View File

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

View File

@ -0,0 +1,18 @@
using System.Collections;
using System.Collections.Generic;
using Godot;
using System;
using System.Threading.Tasks;
namespace Rokojori.Tools
{
public class GLTFFile
{
public GLTFAssetInfo asset;
public List<GLTFBufferReference> buffers;
public List<GLTFImageReference> images;
}
}

View File

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

View File

@ -0,0 +1,16 @@
using System.Collections;
using System.Collections.Generic;
using Godot;
using System;
using System.Threading.Tasks;
namespace Rokojori.Tools
{
public class GLTFImageReference
{
public string uri;
}
}

View File

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