using System.Collections; using System.Collections.Generic; using Godot; using System; namespace Rokojori { [Tool] [GlobalClass] public partial class TextureMerger:Node { public enum SourceMode { MultiBaker, Viewports, Textures } [Export] public bool initialize; [Export] public bool createLayout; [ExportGroup("Source")] [Export] public SourceMode sourceMode; [ExportGroup("Source/MultiBaker")] [Export] public MultiBaker multiBaker = null; [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 float alphaThreshold = 0.1f; [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; } if ( X_textureMergerViewport == null ) { X_textureMergerViewport = outputTarget.CreateChild( "Texture Merger Viewport" ); } Nodes.RemoveAndDeleteChildren( X_textureMergerViewport ); X_mergerCamera = X_textureMergerViewport.CreateChild( "Texture Merger Camera" ); X_mergerCamera.Projection = Camera3D.ProjectionType.Orthogonal; X_mergerCamera.Size = 2; X_textureMergerViewport.Size = (Vector2I) textureSize; X_textureMergerViewport.OwnWorld3D = true; X_textureMergerViewport.TransparentBg = true; } void GrabTextures() { _textures = new List(); if ( SourceMode.Textures == sourceMode ) { _textures.AddRange( sourceTextures ); return; } if ( SourceMode.MultiBaker == sourceMode ) { var viewports = multiBaker.GetAllViewports().ToArray(); var vpTextures = Arrays.Map( viewports, s => { var vt = new ViewportTexture(); vt.ViewportPath = s.GetPath(); return vt; } ); _textures.AddRange( vpTextures ); } 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 ); } } public 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 ); // RJLog.Log( "Grid Alignment:", alignment ); 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, i ); var material = new StandardMaterial3D(); material.Transparency = transparencyMode; material.AlphaScissorThreshold = alphaThreshold; 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 ], i ); var material = new StandardMaterial3D(); material.Transparency = transparencyMode; material.ShadingMode = BaseMaterial3D.ShadingModeEnum.Unshaded; material.AlphaScissorThreshold = alphaThreshold; material.AlbedoTexture = _textures[ i ]; mesh.Material = material; } } void SetMeshCoordinates( CsgMesh3D mesh, Box2 rectangle, int index ) { SetMeshCoordinates( mesh, rectangle.min, rectangle.max, index ); } void SetMeshCoordinates( CsgMesh3D mesh, Vector2 min, Vector2 max, int index ) { var start = ConvertUVtoCameraSpace( min ); var end = ConvertUVtoCameraSpace( max ); var size = end - start; var quadMesh = new QuadMesh(); quadMesh.Size = size; mesh.Mesh = quadMesh; mesh.GlobalPosition = new Vector3( start.X + size.X/2, start.Y + size.Y/2, -1 ); // RJLog.Log( "Set Mesh", index, "Min:", min, ">>", start, "Max:", max, ">>", end ); } public static Vector2I ComputeTextureAlignment( int numElements ) { var root = Mathf.Sqrt( numElements ); var ceiled = Mathf.CeilToInt( root ); var floored = ceiled - 1; if ( ceiled * floored >= numElements ) { return new Vector2I( ceiled, floored ); } return new Vector2I( ceiled, ceiled ); } public static Box2 GetUVRectangle( Vector2I textureAlignment, int index, bool flipY ) { var b = GetUVRectangle( textureAlignment, index ); if ( ! flipY ) { return b; } var uvA = b.min; var uvB = b.max; uvA.Y = 1f - uvA.Y; uvB.Y = 1f - uvB.Y; b.min = uvA; b.max = uvB; return b; } public static Box2 GetUVRectangle( Vector2I textureAlignment, int index ) { var x = index % textureAlignment.X; var y = index / textureAlignment.X; var xs = 1f / textureAlignment.X; var ys = 1f / textureAlignment.Y; var size = new Vector2( xs, ys ); var min = new Vector2( x * xs, y * ys ); var max = min + size; return new Box2( min, max ); } } }