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; public string _fileExtension;
string _fullPath = null; 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>*/ /**<summary for="method get_fileName">Only fileName without fileExtension </summary>*/
public string fileName public string fileName
{ {
@ -40,12 +57,12 @@ namespace Rokojori
/**<summary for="method get_fullFileName">Combines fileName + fileExtension </summary>*/ /**<summary for="method get_fullFileName">Combines fileName + fileExtension </summary>*/
public string fullFileName => fileName + fileExtension; 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 public string fileExtension
{ {
get get
{ {
if ( _fileExtension == null) if ( _fileExtension == null )
{ {
_fileExtension = Path.GetExtension( path ); _fileExtension = Path.GetExtension( path );
} }
@ -92,12 +109,23 @@ namespace Rokojori
public static FilePath Create( string path, FilePathType type = FilePathType.Relative, FilePath parent = null ) public static FilePath Create( string path, FilePathType type = FilePathType.Relative, FilePath parent = null )
{ {
var rp = new FilePath(); var rp = new FilePath();
if ( path.EndsWith( "/" ) )
{
path = path.ReplaceEnd( "/" );
rp.isDirectory = Trillean.True;
}
path = path.ReplaceStart( "/" );
rp.type = type; rp.type = type;
rp.parent = parent; rp.parent = parent;
rp.path = path; rp.path = path;
return rp; return rp;
} }
@ -169,18 +197,79 @@ namespace Rokojori
return FilePath.Create( path, FilePathType.Relative, this ); 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 ); 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 ); return MakeRelative( relativePath );
} }

View File

@ -9,6 +9,18 @@ namespace Rokojori
{ {
public class FilesSync 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 ) public static void Delete( string path )
{ {
if ( string.IsNullOrWhiteSpace( path ) ) if ( string.IsNullOrWhiteSpace( path ) )
@ -47,6 +59,11 @@ namespace Rokojori
Directory.CreateDirectory( path ); Directory.CreateDirectory( path );
} }
public static void EnsureParentDirectoryExists( string path )
{
EnsureDirectoryExists( RegexUtility.ParentPath( path ) );
}
public static void EnsureDirectoryExists( string path ) public static void EnsureDirectoryExists( string path )
{ {
if ( DirectoryExists( path ) ) if ( DirectoryExists( path ) )

View File

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

View File

@ -407,8 +407,10 @@ namespace Rokojori
return bladeSegments * 2 * ComputeNumBlades(); 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) if ( blades == 0 && bladesX == 0 && bladesZ == 0)
{ {
return; return;
@ -433,7 +435,7 @@ namespace Rokojori
this.DestroyChildren(); 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; X_numTriangles = mg.indices.Count / 3;
output.Mesh = mg.GenerateMesh(); output.Mesh = mg.GenerateMesh();
output.Mesh.SurfaceSetMaterial( 0, material ); output.Mesh.SurfaceSetMaterial( 0, material );
output.Name = patchName;
} }
else else
{ {
@ -510,6 +514,8 @@ namespace Rokojori
output.Mesh = MeshGeometry.GenerateTrianglesLODMesh( mgs, lerpingData, false ); output.Mesh = MeshGeometry.GenerateTrianglesLODMesh( mgs, lerpingData, false );
output.Mesh.SurfaceSetMaterial( 0, material ); output.Mesh.SurfaceSetMaterial( 0, material );
output.Name = patchName;
} }
catch ( System.Exception e ) catch ( System.Exception e )

View File

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

View File

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

View File

@ -61,6 +61,26 @@ namespace Rokojori
return source; 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 namespace Rokojori
{ {
public class ReflectionHelper public static class ReflectionHelper
{ {
public static T Create<T>( Type type, params object[] args ) public static T Create<T>( Type type, params object[] args )
{ {
@ -364,8 +364,29 @@ namespace Rokojori
public static bool HasDataMember( object instance, string memberName, BindingFlags flags = ReflectionHelper.defaultBindings ) public static bool HasDataMember( object instance, string memberName, BindingFlags flags = ReflectionHelper.defaultBindings )
{ {
return GetDataMemberInfo( instance, memberName, flags ) != null; 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 ) public static T GetDataMemberValue<T>( object instance, MemberInfo info )
{ {

View File

@ -11,13 +11,94 @@ namespace Rokojori.Tools
{ {
public class GLTFExport 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 state = new GltfState();
var doc = new GltfDocument(); var doc = new GltfDocument();
doc.AppendFromScene( node, state ); 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; 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