Skip to content

Commit 85e64a3

Browse files
pandaGaumeDrigax
andauthored
3DSMax bump map operation support (#962)
* Add Texture Operations Introduce Texture operation support, plus basic set of operations * add support for 3DSMAx normal map texture operation * finalize normal map operation support * comment fix * c#7.0 support * Avoid null pointer Old scene without the Custom attribute in bumb map may cause null pointer Co-authored-by: Nicholas Barlow <whoisdrigax@gmail.com>
1 parent 354f933 commit 85e64a3

File tree

11 files changed

+611
-53
lines changed

11 files changed

+611
-53
lines changed

3ds Max/Max2Babylon/Exporter/BabylonExporter.Material.cs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -83,6 +83,8 @@ public BabylonCustomAttributeDecorator(IIGameMaterial node) : base(node)
8383
/// </summary>
8484
partial class BabylonExporter
8585
{
86+
const string MaterialCustomBabylonAttributeName = "Babylon Attributes";
87+
8688
// use as scale for GetSelfIllumColor to convert [0,PI] to [O,1]
8789
private const float selfIllumScale = (float)(1.0 / Math.PI);
8890

3ds Max/Max2Babylon/Exporter/BabylonExporter.Texture.cs

Lines changed: 136 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -120,9 +120,7 @@ private BabylonTexture ExportSpecularTexture(IIGameMaterial materialNode, float[
120120

121121
RaiseMessage("Multiply specular color and level textures", 2);
122122

123-
string nameText = null;
124-
125-
nameText = (specularColorTexture != null ? Path.GetFileNameWithoutExtension(specularColorTexture.Map.FullFilePath) : TextureUtilities.ColorToStringName(specularColor)) +
123+
string nameText = (specularColorTexture != null ? Path.GetFileNameWithoutExtension(specularColorTexture.Map.FullFilePath) : TextureUtilities.ColorToStringName(specularColor)) +
126124
Path.GetFileNameWithoutExtension(specularLevelTexture.Map.FullFilePath) + "_specularColor";
127125

128126
var textureID = texture.GetGuid().ToString();
@@ -824,12 +822,12 @@ private BabylonTexture ExportEnvironmnentTexture(ITexmap texMap, BabylonScene ba
824822
// -------------------------
825823
// -- Export sub methods ---
826824
// -------------------------
827-
828-
private ITexmap _getSpecialTexmap(ITexmap texMap, out float amount)
825+
private ITexmap _getSpecialTexmap(ITexmap texMap, out float amount, out IEnumerable<TextureOperation> operations)
829826
{
827+
operations = null;
828+
amount = 0.0f;
830829
if (texMap == null)
831830
{
832-
amount = 0.0f;
833831
return null;
834832
}
835833

@@ -853,24 +851,8 @@ private ITexmap _getSpecialTexmap(ITexmap texMap, out float amount)
853851
RaiseError($"Only Normal Bump Texture in tangent space are supported.", 2);
854852
return null;
855853
}
856-
var flipR = block.GetInt(7, 0, 0); // Normal texture Red chanel Flip
857-
if (flipR != 0)
858-
{
859-
RaiseError($"Only Normal Bump Texture without R flip are supported.", 2);
860-
return null;
861-
}
862-
var flipG = block.GetInt(8, 0, 0); // Normal texture Green chanel Flip
863-
if (flipG != 0)
864-
{
865-
RaiseError($"Only Normal Bump Texture without G flip are supported.", 2);
866-
return null;
867-
}
868-
var swapRG = block.GetInt(9, 0, 0); // Normal texture swap R and G channels
869-
if (swapRG != 0)
870-
{
871-
RaiseError($"Only Normal Bump Texture without R and G swap are supported.", 2);
872-
return null;
873-
}
854+
855+
operations = GetNormalBump3DSMaxOperations(texMap, block);
874856

875857
var bumpAmount = block.GetFloat(1, 0, 0); // Bump texture Mult Spin
876858
var bumpMap = block.GetTexmap(3, 0, 0); // Bump texture
@@ -885,11 +867,126 @@ private ITexmap _getSpecialTexmap(ITexmap texMap, out float amount)
885867
}
886868
}
887869

888-
amount = 0.0f;
889870
RaiseError($"Texture type is not supported. Use a Bitmap or Normal Bump map instead.", 2);
890871
return null;
891872
}
892873

874+
/// <summary>
875+
/// Create the list of operation to transform the NormalMap, according Max and Babylon attributes
876+
/// </summary>
877+
/// <param name="texMap"></param>
878+
/// <param name="block"></param>
879+
/// <returns></returns>
880+
private IEnumerable<TextureOperation> GetNormalBump3DSMaxOperations(ITexmap texMap, IIParamBlock2 block)
881+
{
882+
var flipR = block.GetInt(7, 0, 0); // Normal texture Red chanel Flip
883+
var flipG = block.GetInt(8, 0, 0); // Normal texture Green chanel Flip
884+
var swapRG = block.GetInt(9, 0, 0); // Normal texture swap R and G channels
885+
886+
// try to retreive the custom Babylon Attributes
887+
IICustAttribContainer custAtt = texMap.CustAttribContainer;
888+
if( custAtt == null)
889+
{
890+
yield break;
891+
}
892+
var n = custAtt.NumCustAttribs;
893+
ICustAttrib att = null;
894+
for (int i = 0; i != n; i++)
895+
{
896+
var tmp = custAtt.GetCustAttrib(i);
897+
if (tmp.ClassName == MaterialCustomBabylonAttributeName)
898+
{
899+
att = tmp;
900+
break;
901+
}
902+
}
903+
904+
MaxNormalMapParameters parameters = new MaxNormalMapParameters();
905+
906+
IIParamBlock2 customBlock = att?.GetParamBlockByID(0);
907+
if (customBlock != null)
908+
{
909+
parameters.useMaxTransforms = customBlock.GetInt(0, 0, 0) != 0;
910+
parameters.Y = (NormalMapY)customBlock.GetInt(1, 0, 0);
911+
parameters.Coordinate = (MapCoordinate)customBlock.GetInt(2, 0, 0);
912+
}
913+
914+
bool mustFlipR = parameters.useMaxTransforms && flipR != 0;
915+
bool mustSwapRG = parameters.useMaxTransforms && swapRG != 0;
916+
917+
// flip green G = 255-G (individual pixel values into the texture)
918+
bool mustFlipG = parameters.useMaxTransforms && flipG != 0;
919+
switch (parameters.Y)
920+
{
921+
case NormalMapY.positiv:
922+
{
923+
// opengl norm
924+
if (isBabylonExported)
925+
{
926+
// texture source is OpenGL and babylon is DirectX, so we may flip G.
927+
// May void the Max operation if selected.
928+
mustFlipG = !mustFlipG;
929+
}
930+
break;
931+
}
932+
case NormalMapY.negativ:
933+
{
934+
// directx norm
935+
if (isGltfExported)
936+
{
937+
// texture source is DirectX and Gltf is OpenGL, so we may flip G.
938+
// May void the Max operation if selected.
939+
mustFlipG = !mustFlipG;
940+
}
941+
break;
942+
}
943+
}
944+
945+
if (mustFlipR)
946+
{
947+
yield return new FlipChannel(FlipChannel.ChannelRed);
948+
}
949+
if (mustFlipG)
950+
{
951+
yield return new FlipChannel(FlipChannel.ChannelGreen);
952+
}
953+
if (mustSwapRG)
954+
{
955+
yield return new SwapChannel(FlipChannel.ChannelRed, FlipChannel.ChannelGreen);
956+
}
957+
958+
// Invert Y coordinate Y = 1-Y (physical row layout into the texture)
959+
bool mustInvertY = false;
960+
961+
switch (parameters.Coordinate)
962+
{
963+
case MapCoordinate.right:
964+
{
965+
// opengl norm
966+
if (isBabylonExported)
967+
{
968+
// texture source is OpenGL and babylon is DirectX, so we may invert Y coordinate.
969+
mustInvertY = true;
970+
}
971+
break;
972+
}
973+
case MapCoordinate.left:
974+
{
975+
// directx norm
976+
if (isGltfExported)
977+
{
978+
// texture source is DirectX and Gltf is OpenGL, so we may invert Y coordinate.
979+
mustInvertY = true;
980+
}
981+
break;
982+
}
983+
}
984+
if (mustInvertY)
985+
{
986+
yield return new InvertY();
987+
}
988+
}
989+
893990
private BabylonTexture ExportTexture(ITexmap texMap, BabylonScene babylonScene, float amount = 1.0f, bool allowCube = false, bool forceAlpha = false)
894991
{
895992
if(texMap == null)
@@ -900,9 +997,10 @@ private BabylonTexture ExportTexture(ITexmap texMap, BabylonScene babylonScene,
900997
if (texture == null)
901998
{
902999
float specialAmount;
903-
var specialTexMap = _getSpecialTexmap(texMap, out specialAmount);
1000+
var specialTexMap = _getSpecialTexmap(texMap, out specialAmount, out var functions);
9041001
texture = _getBitmapTex(specialTexMap, false);
9051002
amount *= specialAmount;
1003+
return ExportBitmapTexture(texture, babylonScene, amount, allowCube, forceAlpha, functions);
9061004
}
9071005

9081006
return ExportBitmapTexture(texture, babylonScene, amount, allowCube, forceAlpha);
@@ -914,7 +1012,7 @@ private BabylonTexture ExportDiffuseTexture(ITexmap texMap, BabylonScene babylon
9141012
return ExportBitmapTexture(texture, babylonScene, amount, allowCube, forceAlpha);
9151013
}
9161014

917-
private BabylonTexture ExportBitmapTexture(IBitmapTex texture, BabylonScene babylonScene, float amount = 1.0f, bool allowCube = false, bool forceAlpha = false)
1015+
private BabylonTexture ExportBitmapTexture(IBitmapTex texture, BabylonScene babylonScene, float amount = 1.0f, bool allowCube = false, bool forceAlpha = false, IEnumerable<TextureOperation> transforms = null)
9181016
{
9191017
if (texture == null)
9201018
{
@@ -944,10 +1042,14 @@ private BabylonTexture ExportBitmapTexture(IBitmapTex texture, BabylonScene baby
9441042
return textureMap[textureID];
9451043
}
9461044
else
947-
{
1045+
{
1046+
TextureOperation[] operations = transforms != null? transforms.ToArray(): new TextureOperation[]{ } ;
1047+
// combine transform to the destination name.
1048+
var operationsCodeStr = operations.Length != 0 ? $"_{operations.EncodeName()}" : string.Empty;
1049+
9481050
var babylonTexture = new BabylonTexture(textureID)
9491051
{
950-
name = Path.GetFileNameWithoutExtension(texture.MapName) + "." + validImageFormat
1052+
name = $"{Path.GetFileNameWithoutExtension(texture.MapName)}{operationsCodeStr}.{validImageFormat}"
9511053
};
9521054
RaiseMessage($"texture id = {babylonTexture.Id}", 2);
9531055

@@ -984,13 +1086,17 @@ private BabylonTexture ExportBitmapTexture(IBitmapTex texture, BabylonScene baby
9841086
if (isBabylonExported)
9851087
{
9861088
var destPath = Path.Combine(babylonScene.OutputPath, babylonTexture.name);
987-
TextureUtilities.CopyTexture(sourcePath, destPath, exportParameters.txtQuality, this);
1089+
TextureUtilities.CopyTexture(sourcePath, operations, destPath, exportParameters.txtQuality, this);
9881090

9891091
// Is cube
9901092
_exportIsCube(Path.Combine(babylonScene.OutputPath, babylonTexture.name), babylonTexture, allowCube);
9911093
}
9921094
else
9931095
{
1096+
if (operations.Length != 0)
1097+
{
1098+
babylonTexture.bitmap = TextureUtilities.GetBitmap(sourcePath, operations, this);
1099+
}
9941100
babylonTexture.isCube = false;
9951101
}
9961102
babylonTexture.originalPath = sourcePath;

3ds Max/Max2Babylon/Exporter/MaxExportParameters.cs

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,14 @@
33

44
namespace Max2Babylon
55
{
6+
public class MaxNormalMapParameters : NormalMapParameters
7+
{
8+
public static MaxNormalMapParameters Default = new MaxNormalMapParameters();
9+
public const bool useMaxTransformsDefault = true;
10+
11+
public bool useMaxTransforms = useMaxTransformsDefault;
12+
}
13+
614
public enum BakeAnimationType
715
{
816
DoNotBakeAnimation,

SharedProjects/BabylonExport.Entities/ExportParameters.cs

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,38 @@
33

44
namespace BabylonExport.Entities
55
{
6+
public enum NormalMapFormat
7+
{
8+
unknown = 1,
9+
directx = 2,
10+
opengl = 3
11+
}
12+
13+
public enum NormalMapY
14+
{
15+
unknown = 1, positiv = 2, negativ = 3
16+
}
17+
18+
public enum MapCoordinate
19+
{
20+
unknown = 1, right = 2, left= 3
21+
}
22+
23+
24+
public class NormalMapParameters
25+
{
26+
public const NormalMapY YDefault = NormalMapY.unknown;
27+
public const MapCoordinate CoordinateDefault = MapCoordinate.unknown;
28+
29+
public NormalMapY Y = YDefault;
30+
public MapCoordinate Coordinate = CoordinateDefault;
31+
32+
public bool IsDirectX => Y == NormalMapY.negativ && Coordinate == MapCoordinate.left;
33+
public bool IsOpenGL => Y == NormalMapY.positiv && Coordinate == MapCoordinate.right;
34+
public bool IsBabylon => IsDirectX;
35+
public bool IsGLTF => IsOpenGL;
36+
}
37+
638
// Define the policy use to assign format to aggregated texture such ORM
739
public enum TextureFormatExportPolicy
840
{

SharedProjects/Utilities/Extensions.projitems

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,12 @@
2121
<Compile Include="$(MSBuildThisFileDirectory)JsonTextWriterOptimized.cs" />
2222
<Compile Include="$(MSBuildThisFileDirectory)MathUtilities.cs" />
2323
<Compile Include="$(MSBuildThisFileDirectory)PathUtilities.cs" />
24-
<Compile Include="$(MSBuildThisFileDirectory)TextureUtilities.cs" />
24+
<Compile Include="$(MSBuildThisFileDirectory)Texture\Operations\FlipChannel.cs" />
25+
<Compile Include="$(MSBuildThisFileDirectory)Texture\Operations\InvertY.cs" />
26+
<Compile Include="$(MSBuildThisFileDirectory)Texture\Operations\Normalize.cs" />
27+
<Compile Include="$(MSBuildThisFileDirectory)Texture\Operations\SwapChannel.cs" />
28+
<Compile Include="$(MSBuildThisFileDirectory)Texture\TextureOperation.cs" />
29+
<Compile Include="$(MSBuildThisFileDirectory)Texture\TextureUtilities.cs" />
2530
<Compile Include="$(MSBuildThisFileDirectory)TreeViewExtension.cs" />
2631
</ItemGroup>
2732
<ItemGroup>
Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
using System;
2+
using System.Drawing;
3+
using System.Drawing.Imaging;
4+
5+
namespace Utilities
6+
{
7+
public class FlipChannel : TextureOperation
8+
{
9+
public const int ChannelRed = 0;
10+
public const int ChannelGreen = 1;
11+
public const int ChannelBlue = 2;
12+
13+
int _channel;
14+
15+
/// <summary>
16+
/// Invert channel (R,G or B) using value = MAX_CHANNEL_VALUE - value.
17+
/// </summary>
18+
/// <param name="channel">R = 0, G = 1, B = 2</param>
19+
/// <param name="name"></param>
20+
/// <Exception name="ArgumentOutOfRangeException">channel value is invalid.</Exception>
21+
public FlipChannel(int channel, string name = null) : base(name ?? $"i{channel}")
22+
{
23+
if (channel < ChannelRed || channel > ChannelBlue)
24+
{
25+
throw new ArgumentOutOfRangeException(nameof(channel));
26+
}
27+
_channel = channel;
28+
}
29+
30+
public override void Apply(byte[] values, BitmapData infos)
31+
{
32+
int pixelSize = Image.GetPixelFormatSize(infos.PixelFormat) >> 3;
33+
int i = 0;
34+
int l = values.Length;
35+
if (i < l)
36+
{
37+
switch (infos.PixelFormat)
38+
{
39+
case PixelFormat.Canonical:
40+
case PixelFormat.Format24bppRgb:
41+
case PixelFormat.Format32bppRgb:
42+
{
43+
i += _channel;
44+
do
45+
{
46+
values[i] = (byte)(0xFF - values[i]);
47+
i += pixelSize;
48+
} while (i < l);
49+
break;
50+
}
51+
case PixelFormat.Format32bppArgb:
52+
case PixelFormat.Format32bppPArgb:
53+
{
54+
i += (_channel + 1);
55+
do
56+
{
57+
values[i] = (byte)(0xFF - values[i]);
58+
i += pixelSize;
59+
} while (i < l);
60+
break;
61+
}
62+
default:
63+
throw new NotSupportedException($"Pixel format not supported :{infos.PixelFormat}");
64+
}
65+
}
66+
}
67+
}
68+
}

0 commit comments

Comments
 (0)