using System.Collections; using System.Collections.Generic; using Godot; using System; namespace Rokojori { [Tool] [GlobalClass] public partial class TextureMerger:Node { public enum SourceMode { Viewports, Textures } [Export] public bool initialize; [Export] public bool createLayout; [ExportGroup("Source")] [Export] public SourceMode sourceMode; [ExportGroup("Source/Viewports")] [Export] public Node sourceViewportsContainer = null; [Export] public SubViewport[] sourceViewports; [ExportGroup("Source/Textures")] [Export] public Texture2D[] sourceTextures; [ExportGroup("Output")] [Export] public Node outputTarget; public enum LayoutMode { Grid, Custom } [Export] public LayoutMode layoutMode = LayoutMode.Grid; [ExportGroup("Output/Custom")] [Export] public Vector2[] customPositions; [Export] public Vector2[] customSizes; [ExportGroup("Viewport")] [Export] public Vector2 textureSize = new Vector2( 2048, 2048 ); [Export] public BaseMaterial3D.TransparencyEnum transparencyMode = BaseMaterial3D.TransparencyEnum.AlphaScissor; [ExportGroup("Debugging")] [Export] public SubViewport X_textureMergerViewport; [Export] public Camera3D X_mergerCamera; List _textures = new List(); public override void _Process( double delta ) { if ( initialize ) { initialize = false; Initialize(); } if ( createLayout ) { createLayout = false; CreateLayout(); } } public void Initialize() { if ( outputTarget == null ) { outputTarget = this; } Nodes.RemoveAndDeleteChildren( outputTarget ); X_textureMergerViewport = outputTarget.CreateChild( "Texture Merger Viewport" ); X_mergerCamera = X_textureMergerViewport.CreateChild( "Texture Merger Camera" ); X_mergerCamera.Projection = Camera3D.ProjectionType.Orthogonal; X_textureMergerViewport.Size = (Vector2I) textureSize; X_textureMergerViewport.OwnWorld3D = true; X_textureMergerViewport.TransparentBg = true; X_mergerCamera.Size = 1; } void GrabTextures() { _textures = new List(); if ( SourceMode.Textures == sourceMode ) { _textures.AddRange( sourceTextures ); return; } if ( SourceMode.Viewports == sourceMode ) { if ( sourceViewportsContainer != null ) { sourceViewports = Nodes.AllIn( sourceViewportsContainer, null, false ).ToArray(); } var vpTextures = Arrays.Map( sourceViewports, s => { var vt = new ViewportTexture(); vt.ViewportPath = s.GetPath(); return vt; } ); _textures.AddRange( vpTextures ); } } void CreateLayout() { GrabTextures(); if ( LayoutMode.Grid == layoutMode ) { CreateGridLayout(); } else if ( LayoutMode.Custom == layoutMode ) { CreateCustomLayout(); } } Vector2 ConvertUVtoCameraSpace( Vector2 uv ) { var scale = Vector2.One; var w = 1; var h = 1; if ( X_textureMergerViewport.Size.X != X_textureMergerViewport.Size.Y ) { if ( X_textureMergerViewport.Size.X > X_textureMergerViewport.Size.Y ) { w = X_textureMergerViewport.Size.X / X_textureMergerViewport.Size.Y; } else { h = X_textureMergerViewport.Size.Y / X_textureMergerViewport.Size.X; } } var x = Mathf.Remap( uv.X, 0, 1, -w, w ); var y = Mathf.Remap( uv.Y, 0, 1, -h, h ); return new Vector2( x, y ); } void CreateGridLayout() { var alignment = ComputeTextureAlignment( _textures.Count ); Nodes.RemoveAndDeleteChildrenOfType( X_textureMergerViewport ); for ( int i = 0; i < _textures.Count; i++ ) { var mesh = X_textureMergerViewport.CreateChild( "Texture " + ( i + 1 ) ); var uvRectangle = GetUVRectangle( alignment, i ); SetMeshCoordinates( mesh, uvRectangle ); var material = new StandardMaterial3D(); material.Transparency = transparencyMode; material.ShadingMode = BaseMaterial3D.ShadingModeEnum.Unshaded; material.AlbedoTexture = _textures[ i ]; mesh.Material = material; } } void CreateCustomLayout() { for ( int i = 0; i < _textures.Count; i++ ) { var mesh = outputTarget.CreateChild( "Texture " + ( i + 1 ) ); SetMeshCoordinates( mesh, customPositions[ i ], customSizes[ i ] ); var material = new StandardMaterial3D(); material.Transparency = transparencyMode; material.ShadingMode = BaseMaterial3D.ShadingModeEnum.Unshaded; material.AlbedoTexture = _textures[ i ]; mesh.Material = material; } } void SetMeshCoordinates( CsgMesh3D mesh, Rect2 rectangle ) { SetMeshCoordinates( mesh, rectangle.Position, rectangle.Size ); } void SetMeshCoordinates( CsgMesh3D mesh, Vector2 start, Vector2 size ) { start = ConvertUVtoCameraSpace( start ); var end = ConvertUVtoCameraSpace( start + size ); size = end - start; var quadMesh = new QuadMesh(); quadMesh.Size = size; mesh.Mesh = quadMesh; mesh.GlobalPosition = new Vector3( start.X - size.X , start.Y - size.Y, -1 ); } public static Vector2I ComputeTextureAlignment( int numElements ) { var root = Mathf.Sqrt( numElements ); var ceiled = Mathf.CeilToInt( root ); var floored = ceiled - 1; var weight = Mathf.RoundToInt( root % 1 ); var height = weight * ceiled + ( 1 - weight ) * floored; return new Vector2I( ceiled, height ); } public static Rect2 GetUVRectangle( Vector2I textureAlignment, int index ) { var x = index % textureAlignment.X; var y = index / textureAlignment.Y; var xs = 1f / textureAlignment.X; var ys = 1f / textureAlignment.Y; return new Rect2( x * xs, y * ys, new Vector2( xs, ys ) ); } } }