2024-12-01 17:07:41 +00:00
|
|
|
using System.Collections;
|
|
|
|
using System.Collections.Generic;
|
|
|
|
using Godot;
|
|
|
|
using System;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
namespace Rokojori
|
|
|
|
{
|
|
|
|
[Tool]
|
|
|
|
[GlobalClass]
|
|
|
|
public partial class TextureMerger:Node
|
|
|
|
{
|
|
|
|
public enum SourceMode
|
|
|
|
{
|
2025-01-03 12:09:23 +00:00
|
|
|
MultiBaker,
|
2024-12-01 17:07:41 +00:00
|
|
|
Viewports,
|
|
|
|
Textures
|
|
|
|
}
|
|
|
|
|
|
|
|
[Export]
|
|
|
|
public bool initialize;
|
|
|
|
|
|
|
|
[Export]
|
|
|
|
public bool createLayout;
|
|
|
|
|
|
|
|
|
|
|
|
[ExportGroup("Source")]
|
|
|
|
[Export]
|
|
|
|
public SourceMode sourceMode;
|
|
|
|
|
2025-01-03 12:09:23 +00:00
|
|
|
[ExportGroup("Source/MultiBaker")]
|
|
|
|
[Export]
|
|
|
|
public MultiBaker multiBaker = null;
|
|
|
|
|
2024-12-01 17:07:41 +00:00
|
|
|
|
|
|
|
[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 );
|
2025-01-03 12:09:23 +00:00
|
|
|
[Export]
|
|
|
|
public float alphaThreshold = 0.1f;
|
2024-12-01 17:07:41 +00:00
|
|
|
|
|
|
|
[Export]
|
|
|
|
public BaseMaterial3D.TransparencyEnum transparencyMode = BaseMaterial3D.TransparencyEnum.AlphaScissor;
|
|
|
|
|
|
|
|
[ExportGroup("Debugging")]
|
|
|
|
[Export]
|
|
|
|
public SubViewport X_textureMergerViewport;
|
|
|
|
|
|
|
|
[Export]
|
|
|
|
public Camera3D X_mergerCamera;
|
|
|
|
|
|
|
|
List<Texture2D> _textures = new List<Texture2D>();
|
|
|
|
|
|
|
|
public override void _Process( double delta )
|
|
|
|
{
|
|
|
|
if ( initialize )
|
|
|
|
{
|
|
|
|
initialize = false;
|
|
|
|
Initialize();
|
|
|
|
}
|
|
|
|
|
|
|
|
if ( createLayout )
|
|
|
|
{
|
|
|
|
createLayout = false;
|
|
|
|
CreateLayout();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
public void Initialize()
|
|
|
|
{
|
|
|
|
if ( outputTarget == null )
|
|
|
|
{
|
|
|
|
outputTarget = this;
|
|
|
|
}
|
|
|
|
|
2025-01-03 12:09:23 +00:00
|
|
|
if ( X_textureMergerViewport == null )
|
|
|
|
{
|
|
|
|
X_textureMergerViewport = outputTarget.CreateChild<SubViewport>( "Texture Merger Viewport" );
|
|
|
|
}
|
2024-12-01 17:07:41 +00:00
|
|
|
|
2025-01-03 12:09:23 +00:00
|
|
|
Nodes.RemoveAndDeleteChildren( X_textureMergerViewport );
|
2024-12-01 17:07:41 +00:00
|
|
|
|
|
|
|
X_mergerCamera = X_textureMergerViewport.CreateChild<Camera3D>( "Texture Merger Camera" );
|
2025-01-03 12:09:23 +00:00
|
|
|
X_mergerCamera.Projection = Camera3D.ProjectionType.Orthogonal;
|
|
|
|
X_mergerCamera.Size = 2;
|
2024-12-01 17:07:41 +00:00
|
|
|
|
|
|
|
X_textureMergerViewport.Size = (Vector2I) textureSize;
|
|
|
|
X_textureMergerViewport.OwnWorld3D = true;
|
|
|
|
X_textureMergerViewport.TransparentBg = true;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
void GrabTextures()
|
|
|
|
{
|
|
|
|
_textures = new List<Texture2D>();
|
|
|
|
|
|
|
|
if ( SourceMode.Textures == sourceMode )
|
|
|
|
{
|
|
|
|
_textures.AddRange( sourceTextures );
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2025-01-03 12:09:23 +00:00
|
|
|
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 );
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2024-12-01 17:07:41 +00:00
|
|
|
if ( SourceMode.Viewports == sourceMode )
|
|
|
|
{
|
|
|
|
if ( sourceViewportsContainer != null )
|
|
|
|
{
|
|
|
|
sourceViewports = Nodes.AllIn<SubViewport>( sourceViewportsContainer, null, false ).ToArray();
|
|
|
|
}
|
|
|
|
|
|
|
|
var vpTextures = Arrays.Map( sourceViewports,
|
|
|
|
s =>
|
|
|
|
{
|
|
|
|
var vt = new ViewportTexture();
|
|
|
|
vt.ViewportPath = s.GetPath();
|
|
|
|
return vt;
|
|
|
|
}
|
|
|
|
);
|
|
|
|
|
|
|
|
_textures.AddRange( vpTextures );
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2025-01-03 12:09:23 +00:00
|
|
|
public void CreateLayout()
|
2024-12-01 17:07:41 +00:00
|
|
|
{
|
|
|
|
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 );
|
|
|
|
|
2025-01-03 12:09:23 +00:00
|
|
|
// RJLog.Log( "Grid Alignment:", alignment );
|
|
|
|
|
2024-12-01 17:07:41 +00:00
|
|
|
Nodes.RemoveAndDeleteChildrenOfType<CsgMesh3D>( X_textureMergerViewport );
|
|
|
|
|
|
|
|
for ( int i = 0; i < _textures.Count; i++ )
|
|
|
|
{
|
|
|
|
var mesh = X_textureMergerViewport.CreateChild<CsgMesh3D>( "Texture " + ( i + 1 ) );
|
|
|
|
|
|
|
|
var uvRectangle = GetUVRectangle( alignment, i );
|
2025-01-03 12:09:23 +00:00
|
|
|
|
|
|
|
SetMeshCoordinates( mesh, uvRectangle, i );
|
2024-12-01 17:07:41 +00:00
|
|
|
|
|
|
|
var material = new StandardMaterial3D();
|
|
|
|
material.Transparency = transparencyMode;
|
2025-01-03 12:09:23 +00:00
|
|
|
material.AlphaScissorThreshold = alphaThreshold;
|
2024-12-01 17:07:41 +00:00
|
|
|
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<CsgMesh3D>( "Texture " + ( i + 1 ) );
|
|
|
|
|
2025-01-03 12:09:23 +00:00
|
|
|
SetMeshCoordinates( mesh, customPositions[ i ], customSizes[ i ], i );
|
2024-12-01 17:07:41 +00:00
|
|
|
|
|
|
|
var material = new StandardMaterial3D();
|
|
|
|
material.Transparency = transparencyMode;
|
|
|
|
material.ShadingMode = BaseMaterial3D.ShadingModeEnum.Unshaded;
|
2025-01-03 12:09:23 +00:00
|
|
|
material.AlphaScissorThreshold = alphaThreshold;
|
2024-12-01 17:07:41 +00:00
|
|
|
|
|
|
|
material.AlbedoTexture = _textures[ i ];
|
|
|
|
mesh.Material = material;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2025-01-03 12:09:23 +00:00
|
|
|
void SetMeshCoordinates( CsgMesh3D mesh, Box2 rectangle, int index )
|
2024-12-01 17:07:41 +00:00
|
|
|
{
|
2025-01-03 12:09:23 +00:00
|
|
|
SetMeshCoordinates( mesh, rectangle.min, rectangle.max, index );
|
2024-12-01 17:07:41 +00:00
|
|
|
}
|
|
|
|
|
2025-01-03 12:09:23 +00:00
|
|
|
void SetMeshCoordinates( CsgMesh3D mesh, Vector2 min, Vector2 max, int index )
|
2024-12-01 17:07:41 +00:00
|
|
|
{
|
2025-01-03 12:09:23 +00:00
|
|
|
var start = ConvertUVtoCameraSpace( min );
|
|
|
|
var end = ConvertUVtoCameraSpace( max );
|
2024-12-01 17:07:41 +00:00
|
|
|
|
2025-01-03 12:09:23 +00:00
|
|
|
var size = end - start;
|
2024-12-01 17:07:41 +00:00
|
|
|
|
|
|
|
var quadMesh = new QuadMesh();
|
|
|
|
quadMesh.Size = size;
|
|
|
|
mesh.Mesh = quadMesh;
|
|
|
|
|
2025-01-03 12:09:23 +00:00
|
|
|
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 );
|
2024-12-01 17:07:41 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
public static Vector2I ComputeTextureAlignment( int numElements )
|
|
|
|
{
|
|
|
|
var root = Mathf.Sqrt( numElements );
|
|
|
|
|
|
|
|
var ceiled = Mathf.CeilToInt( root );
|
|
|
|
var floored = ceiled - 1;
|
|
|
|
|
2025-01-03 12:09:23 +00:00
|
|
|
if ( ceiled * floored >= numElements )
|
|
|
|
{
|
|
|
|
return new Vector2I( ceiled, floored );
|
|
|
|
}
|
2024-12-01 17:07:41 +00:00
|
|
|
|
2025-01-03 12:09:23 +00:00
|
|
|
return new Vector2I( ceiled, ceiled );
|
|
|
|
|
2024-12-01 17:07:41 +00:00
|
|
|
}
|
|
|
|
|
2025-01-03 12:09:23 +00:00
|
|
|
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 )
|
2024-12-01 17:07:41 +00:00
|
|
|
{
|
|
|
|
var x = index % textureAlignment.X;
|
2025-01-03 12:09:23 +00:00
|
|
|
var y = index / textureAlignment.X;
|
2024-12-01 17:07:41 +00:00
|
|
|
|
|
|
|
var xs = 1f / textureAlignment.X;
|
|
|
|
var ys = 1f / textureAlignment.Y;
|
|
|
|
|
2025-01-03 12:09:23 +00:00
|
|
|
var size = new Vector2( xs, ys );
|
|
|
|
|
|
|
|
var min = new Vector2( x * xs, y * ys );
|
|
|
|
var max = min + size;
|
|
|
|
|
|
|
|
return new Box2( min, max );
|
2024-12-01 17:07:41 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
}
|
|
|
|
}
|