From afdf5e115fd83bdfb8db8be7908d313029ff7d25 Mon Sep 17 00:00:00 2001 From: Josef Date: Tue, 15 Jul 2025 11:30:35 +0200 Subject: [PATCH] File IO/GLTF Export/Grass Patch --- Runtime/Files/FilePath.cs | 103 ++++++++++++++++-- Runtime/Files/FilesSync.cs | 17 +++ Runtime/Godot/Nodes.cs | 14 +++ Runtime/Procedural/Assets/Grass/GrassPatch.cs | 10 +- .../Baking/MultiBaker/MultiBaker.cs | 6 +- Runtime/Text/JSON/JSON.cs | 10 ++ Runtime/Text/Text.cs | 20 ++++ Runtime/Tools/ReflectionHelper.cs | 23 +++- Tools/gltf-export/GLTFExport.cs | 87 ++++++++++++++- Tools/gltf-export/gltf-file/GLTFAssetInfo.cs | 18 +++ .../gltf-file/GLTFAssetInfo.cs.uid | 1 + .../gltf-file/GLTFBufferReference.cs | 17 +++ .../gltf-file/GLTFBufferReference.cs.uid | 1 + Tools/gltf-export/gltf-file/GLTFFile.cs | 18 +++ Tools/gltf-export/gltf-file/GLTFFile.cs.uid | 1 + .../gltf-file/GLTFImageReference.cs | 16 +++ .../gltf-file/GLTFImageReference.cs.uid | 1 + 17 files changed, 348 insertions(+), 15 deletions(-) create mode 100644 Tools/gltf-export/gltf-file/GLTFAssetInfo.cs create mode 100644 Tools/gltf-export/gltf-file/GLTFAssetInfo.cs.uid create mode 100644 Tools/gltf-export/gltf-file/GLTFBufferReference.cs create mode 100644 Tools/gltf-export/gltf-file/GLTFBufferReference.cs.uid create mode 100644 Tools/gltf-export/gltf-file/GLTFFile.cs create mode 100644 Tools/gltf-export/gltf-file/GLTFFile.cs.uid create mode 100644 Tools/gltf-export/gltf-file/GLTFImageReference.cs create mode 100644 Tools/gltf-export/gltf-file/GLTFImageReference.cs.uid diff --git a/Runtime/Files/FilePath.cs b/Runtime/Files/FilePath.cs index 9b9df97..dd8cf53 100644 --- a/Runtime/Files/FilePath.cs +++ b/Runtime/Files/FilePath.cs @@ -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; + } + /**Only fileName without fileExtension */ public string fileName { @@ -40,12 +57,12 @@ namespace Rokojori /**Combines fileName + fileExtension */ public string fullFileName => fileName + fileExtension; - /***/ + /** File extension including the dot, eg. ".svg", ".jpg", ".js" */ public string fileExtension { get { - if ( _fileExtension == null) + if ( _fileExtension == null ) { _fileExtension = Path.GetExtension( path ); } @@ -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,18 +197,79 @@ 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; + } + + 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 ); } - var relativePath = absolutePath.Substring( ownAbsolutePath.Length ); + // A/B/C + // A/B/C/D + var relativePath = otherAbsolutePath.Substring( ownAbsolutePath.Length ); + relativePath = relativePath.ReplaceStart( "/" ); + + return MakeRelative( relativePath ); } diff --git a/Runtime/Files/FilesSync.cs b/Runtime/Files/FilesSync.cs index db37ede..3da4c55 100644 --- a/Runtime/Files/FilesSync.cs +++ b/Runtime/Files/FilesSync.cs @@ -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 ) ) diff --git a/Runtime/Godot/Nodes.cs b/Runtime/Godot/Nodes.cs index 26bb136..e24b465 100644 --- a/Runtime/Godot/Nodes.cs +++ b/Runtime/Godot/Nodes.cs @@ -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( BindingFlags.Public | BindingFlags.Instance | BindingFlags.DeclaredOnly ); + memberInfos = memberInfos.Filter( + ( mi ) => + { + if ( mi.IsMemberType() ) + { + return false; + } + + return true; + } + ); var memberNames = Lists.Map( memberInfos, m => m.Name ); + // RJLog.Log( "Trying to copy:", memberNames ); + ReflectionHelper.CopyDataMembersFromTo( from, to, memberNames ); } diff --git a/Runtime/Procedural/Assets/Grass/GrassPatch.cs b/Runtime/Procedural/Assets/Grass/GrassPatch.cs index 2a12ce3..a1e1efe 100644 --- a/Runtime/Procedural/Assets/Grass/GrassPatch.cs +++ b/Runtime/Procedural/Assets/Grass/GrassPatch.cs @@ -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( "Grass Patch Mesh" ); + this.output = this.CreateChild( 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 ) diff --git a/Runtime/Procedural/Baking/MultiBaker/MultiBaker.cs b/Runtime/Procedural/Baking/MultiBaker/MultiBaker.cs index c019893..9781784 100644 --- a/Runtime/Procedural/Baking/MultiBaker/MultiBaker.cs +++ b/Runtime/Procedural/Baking/MultiBaker/MultiBaker.cs @@ -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() diff --git a/Runtime/Text/JSON/JSON.cs b/Runtime/Text/JSON/JSON.cs index 5612e1a..348ec57 100644 --- a/Runtime/Text/JSON/JSON.cs +++ b/Runtime/Text/JSON/JSON.cs @@ -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() ); diff --git a/Runtime/Text/Text.cs b/Runtime/Text/Text.cs index aa3d984..117bd18 100644 --- a/Runtime/Text/Text.cs +++ b/Runtime/Text/Text.cs @@ -61,6 +61,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; + } + + } diff --git a/Runtime/Tools/ReflectionHelper.cs b/Runtime/Tools/ReflectionHelper.cs index 26fd282..1be7002 100644 --- a/Runtime/Tools/ReflectionHelper.cs +++ b/Runtime/Tools/ReflectionHelper.cs @@ -7,7 +7,7 @@ using Godot; namespace Rokojori { - public class ReflectionHelper + public static class ReflectionHelper { public static T Create( Type type, params object[] args ) { @@ -364,8 +364,29 @@ namespace Rokojori public static bool HasDataMember( object instance, string memberName, BindingFlags flags = ReflectionHelper.defaultBindings ) { 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( this MemberInfo memberInfo ) + { + return GetMemberType( memberInfo ) == typeof( T ); + } + + public static T GetDataMemberValue( object instance, MemberInfo info ) { diff --git a/Tools/gltf-export/GLTFExport.cs b/Tools/gltf-export/GLTFExport.cs index 5a968a8..6f5024b 100644 --- a/Tools/gltf-export/GLTFExport.cs +++ b/Tools/gltf-export/GLTFExport.cs @@ -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 ); - doc.WriteToFilesystem( state, path ); + + 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; } diff --git a/Tools/gltf-export/gltf-file/GLTFAssetInfo.cs b/Tools/gltf-export/gltf-file/GLTFAssetInfo.cs new file mode 100644 index 0000000..309cafe --- /dev/null +++ b/Tools/gltf-export/gltf-file/GLTFAssetInfo.cs @@ -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; + + } +} diff --git a/Tools/gltf-export/gltf-file/GLTFAssetInfo.cs.uid b/Tools/gltf-export/gltf-file/GLTFAssetInfo.cs.uid new file mode 100644 index 0000000..8d33b23 --- /dev/null +++ b/Tools/gltf-export/gltf-file/GLTFAssetInfo.cs.uid @@ -0,0 +1 @@ +uid://4cwq20m8ihng diff --git a/Tools/gltf-export/gltf-file/GLTFBufferReference.cs b/Tools/gltf-export/gltf-file/GLTFBufferReference.cs new file mode 100644 index 0000000..e78b2df --- /dev/null +++ b/Tools/gltf-export/gltf-file/GLTFBufferReference.cs @@ -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; + } +} diff --git a/Tools/gltf-export/gltf-file/GLTFBufferReference.cs.uid b/Tools/gltf-export/gltf-file/GLTFBufferReference.cs.uid new file mode 100644 index 0000000..c4384ae --- /dev/null +++ b/Tools/gltf-export/gltf-file/GLTFBufferReference.cs.uid @@ -0,0 +1 @@ +uid://ctku4smqws1lv diff --git a/Tools/gltf-export/gltf-file/GLTFFile.cs b/Tools/gltf-export/gltf-file/GLTFFile.cs new file mode 100644 index 0000000..af528fb --- /dev/null +++ b/Tools/gltf-export/gltf-file/GLTFFile.cs @@ -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 buffers; + public List images; + } +} diff --git a/Tools/gltf-export/gltf-file/GLTFFile.cs.uid b/Tools/gltf-export/gltf-file/GLTFFile.cs.uid new file mode 100644 index 0000000..05c4b6f --- /dev/null +++ b/Tools/gltf-export/gltf-file/GLTFFile.cs.uid @@ -0,0 +1 @@ +uid://dvu7j1ysw4yw7 diff --git a/Tools/gltf-export/gltf-file/GLTFImageReference.cs b/Tools/gltf-export/gltf-file/GLTFImageReference.cs new file mode 100644 index 0000000..5e4bb95 --- /dev/null +++ b/Tools/gltf-export/gltf-file/GLTFImageReference.cs @@ -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; + } +} diff --git a/Tools/gltf-export/gltf-file/GLTFImageReference.cs.uid b/Tools/gltf-export/gltf-file/GLTFImageReference.cs.uid new file mode 100644 index 0000000..f2cec59 --- /dev/null +++ b/Tools/gltf-export/gltf-file/GLTFImageReference.cs.uid @@ -0,0 +1 @@ +uid://bgwmjjymklkrj