├── kn5 converter
├── app.config
├── Properties
│ └── AssemblyInfo.cs
├── kn5 converter.csproj
└── Program.cs
├── kn5 converter.sln
├── .gitattributes
└── .gitignore
/kn5 converter/app.config:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
--------------------------------------------------------------------------------
/kn5 converter.sln:
--------------------------------------------------------------------------------
1 |
2 | Microsoft Visual Studio Solution File, Format Version 11.00
3 | # Visual C# Express 2010
4 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "kn5 converter", "kn5 converter\kn5 converter.csproj", "{507C9510-A849-426B-9B8C-45F237DA7DE6}"
5 | EndProject
6 | Global
7 | GlobalSection(SolutionConfigurationPlatforms) = preSolution
8 | Debug|x86 = Debug|x86
9 | Release|x86 = Release|x86
10 | EndGlobalSection
11 | GlobalSection(ProjectConfigurationPlatforms) = postSolution
12 | {507C9510-A849-426B-9B8C-45F237DA7DE6}.Debug|x86.ActiveCfg = Debug|x86
13 | {507C9510-A849-426B-9B8C-45F237DA7DE6}.Debug|x86.Build.0 = Debug|x86
14 | {507C9510-A849-426B-9B8C-45F237DA7DE6}.Release|x86.ActiveCfg = Release|x86
15 | {507C9510-A849-426B-9B8C-45F237DA7DE6}.Release|x86.Build.0 = Release|x86
16 | EndGlobalSection
17 | GlobalSection(SolutionProperties) = preSolution
18 | HideSolutionNode = FALSE
19 | EndGlobalSection
20 | EndGlobal
21 |
--------------------------------------------------------------------------------
/kn5 converter/Properties/AssemblyInfo.cs:
--------------------------------------------------------------------------------
1 | using System.Reflection;
2 | using System.Runtime.CompilerServices;
3 | using System.Runtime.InteropServices;
4 |
5 | // General Information about an assembly is controlled through the following
6 | // set of attributes. Change these attribute values to modify the information
7 | // associated with an assembly.
8 | [assembly: AssemblyTitle("kn5 converter")]
9 | [assembly: AssemblyDescription("")]
10 | [assembly: AssemblyConfiguration("")]
11 | [assembly: AssemblyCompany("")]
12 | [assembly: AssemblyProduct("kn5 converter")]
13 | [assembly: AssemblyCopyright("Copyright © 2015")]
14 | [assembly: AssemblyTrademark("")]
15 | [assembly: AssemblyCulture("")]
16 |
17 | // Setting ComVisible to false makes the types in this assembly not visible
18 | // to COM components. If you need to access a type in this assembly from
19 | // COM, set the ComVisible attribute to true on that type.
20 | [assembly: ComVisible(false)]
21 |
22 | // The following GUID is for the ID of the typelib if this project is exposed to COM
23 | [assembly: Guid("49682c3a-5b7a-4988-80dd-3cb3ac6f1a66")]
24 |
25 | // Version information for an assembly consists of the following four values:
26 | //
27 | // Major Version
28 | // Minor Version
29 | // Build Number
30 | // Revision
31 | //
32 | // You can specify all the values or you can default the Build and Revision Numbers
33 | // by using the '*' as shown below:
34 | // [assembly: AssemblyVersion("1.0.*")]
35 | [assembly: AssemblyVersion("1.0.0.0")]
36 | [assembly: AssemblyFileVersion("1.0.0.0")]
37 |
--------------------------------------------------------------------------------
/kn5 converter/kn5 converter.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | Debug
5 | x86
6 | 8.0.30703
7 | 2.0
8 | {507C9510-A849-426B-9B8C-45F237DA7DE6}
9 | Exe
10 | Properties
11 | kn5_converter
12 | kn5conv
13 | v3.5
14 | Client
15 | 512
16 |
17 |
18 | x86
19 | true
20 | full
21 | false
22 | bin\Debug\
23 | DEBUG;TRACE
24 | prompt
25 | 4
26 |
27 |
28 | x86
29 | pdbonly
30 | true
31 | bin\Release\
32 | TRACE
33 | prompt
34 | 4
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
59 |
--------------------------------------------------------------------------------
/.gitattributes:
--------------------------------------------------------------------------------
1 | ###############################################################################
2 | # Set default behavior to automatically normalize line endings.
3 | ###############################################################################
4 | * text=auto
5 |
6 | ###############################################################################
7 | # Set default behavior for command prompt diff.
8 | #
9 | # This is need for earlier builds of msysgit that does not have it on by
10 | # default for csharp files.
11 | # Note: This is only used by command line
12 | ###############################################################################
13 | #*.cs diff=csharp
14 |
15 | ###############################################################################
16 | # Set the merge driver for project and solution files
17 | #
18 | # Merging from the command prompt will add diff markers to the files if there
19 | # are conflicts (Merging from VS is not affected by the settings below, in VS
20 | # the diff markers are never inserted). Diff markers may cause the following
21 | # file extensions to fail to load in VS. An alternative would be to treat
22 | # these files as binary and thus will always conflict and require user
23 | # intervention with every merge. To do so, just uncomment the entries below
24 | ###############################################################################
25 | #*.sln merge=binary
26 | #*.csproj merge=binary
27 | #*.vbproj merge=binary
28 | #*.vcxproj merge=binary
29 | #*.vcproj merge=binary
30 | #*.dbproj merge=binary
31 | #*.fsproj merge=binary
32 | #*.lsproj merge=binary
33 | #*.wixproj merge=binary
34 | #*.modelproj merge=binary
35 | #*.sqlproj merge=binary
36 | #*.wwaproj merge=binary
37 |
38 | ###############################################################################
39 | # behavior for image files
40 | #
41 | # image files are treated as binary by default.
42 | ###############################################################################
43 | #*.jpg binary
44 | #*.png binary
45 | #*.gif binary
46 |
47 | ###############################################################################
48 | # diff behavior for common document formats
49 | #
50 | # Convert binary document formats to text before diffing them. This feature
51 | # is only available from the command line. Turn it on by uncommenting the
52 | # entries below.
53 | ###############################################################################
54 | #*.doc diff=astextplain
55 | #*.DOC diff=astextplain
56 | #*.docx diff=astextplain
57 | #*.DOCX diff=astextplain
58 | #*.dot diff=astextplain
59 | #*.DOT diff=astextplain
60 | #*.pdf diff=astextplain
61 | #*.PDF diff=astextplain
62 | #*.rtf diff=astextplain
63 | #*.RTF diff=astextplain
64 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | ## Ignore Visual Studio temporary files, build results, and
2 | ## files generated by popular Visual Studio add-ons.
3 |
4 | # User-specific files
5 | *.suo
6 | *.user
7 | *.userosscache
8 | *.sln.docstates
9 |
10 | # User-specific files (MonoDevelop/Xamarin Studio)
11 | *.userprefs
12 |
13 | # Build results
14 | [Dd]ebug/
15 | [Dd]ebugPublic/
16 | [Rr]elease/
17 | [Rr]eleases/
18 | x64/
19 | x86/
20 | build/
21 | bld/
22 | [Bb]in/
23 | [Oo]bj/
24 |
25 | # Visual Studio 2015 cache/options directory
26 | .vs/
27 |
28 | # MSTest test Results
29 | [Tt]est[Rr]esult*/
30 | [Bb]uild[Ll]og.*
31 |
32 | # NUNIT
33 | *.VisualState.xml
34 | TestResult.xml
35 |
36 | # Build Results of an ATL Project
37 | [Dd]ebugPS/
38 | [Rr]eleasePS/
39 | dlldata.c
40 |
41 | # DNX
42 | project.lock.json
43 | artifacts/
44 |
45 | *_i.c
46 | *_p.c
47 | *_i.h
48 | *.ilk
49 | *.meta
50 | *.obj
51 | *.pch
52 | *.pdb
53 | *.pgc
54 | *.pgd
55 | *.rsp
56 | *.sbr
57 | *.tlb
58 | *.tli
59 | *.tlh
60 | *.tmp
61 | *.tmp_proj
62 | *.log
63 | *.vspscc
64 | *.vssscc
65 | .builds
66 | *.pidb
67 | *.svclog
68 | *.scc
69 |
70 | # Chutzpah Test files
71 | _Chutzpah*
72 |
73 | # Visual C++ cache files
74 | ipch/
75 | *.aps
76 | *.ncb
77 | *.opensdf
78 | *.sdf
79 | *.cachefile
80 |
81 | # Visual Studio profiler
82 | *.psess
83 | *.vsp
84 | *.vspx
85 |
86 | # TFS 2012 Local Workspace
87 | $tf/
88 |
89 | # Guidance Automation Toolkit
90 | *.gpState
91 |
92 | # ReSharper is a .NET coding add-in
93 | _ReSharper*/
94 | *.[Rr]e[Ss]harper
95 | *.DotSettings.user
96 |
97 | # JustCode is a .NET coding add-in
98 | .JustCode
99 |
100 | # TeamCity is a build add-in
101 | _TeamCity*
102 |
103 | # DotCover is a Code Coverage Tool
104 | *.dotCover
105 |
106 | # NCrunch
107 | _NCrunch_*
108 | .*crunch*.local.xml
109 |
110 | # MightyMoose
111 | *.mm.*
112 | AutoTest.Net/
113 |
114 | # Web workbench (sass)
115 | .sass-cache/
116 |
117 | # Installshield output folder
118 | [Ee]xpress/
119 |
120 | # DocProject is a documentation generator add-in
121 | DocProject/buildhelp/
122 | DocProject/Help/*.HxT
123 | DocProject/Help/*.HxC
124 | DocProject/Help/*.hhc
125 | DocProject/Help/*.hhk
126 | DocProject/Help/*.hhp
127 | DocProject/Help/Html2
128 | DocProject/Help/html
129 |
130 | # Click-Once directory
131 | publish/
132 |
133 | # Publish Web Output
134 | *.[Pp]ublish.xml
135 | *.azurePubxml
136 | ## TODO: Comment the next line if you want to checkin your
137 | ## web deploy settings but do note that will include unencrypted
138 | ## passwords
139 | #*.pubxml
140 |
141 | *.publishproj
142 |
143 | # NuGet Packages
144 | *.nupkg
145 | # The packages folder can be ignored because of Package Restore
146 | **/packages/*
147 | # except build/, which is used as an MSBuild target.
148 | !**/packages/build/
149 | # Uncomment if necessary however generally it will be regenerated when needed
150 | #!**/packages/repositories.config
151 |
152 | # Windows Azure Build Output
153 | csx/
154 | *.build.csdef
155 |
156 | # Windows Store app package directory
157 | AppPackages/
158 |
159 | # Visual Studio cache files
160 | # files ending in .cache can be ignored
161 | *.[Cc]ache
162 | # but keep track of directories ending in .cache
163 | !*.[Cc]ache/
164 |
165 | # Others
166 | ClientBin/
167 | [Ss]tyle[Cc]op.*
168 | ~$*
169 | *~
170 | *.dbmdl
171 | *.dbproj.schemaview
172 | *.pfx
173 | *.publishsettings
174 | node_modules/
175 | orleans.codegen.cs
176 |
177 | # RIA/Silverlight projects
178 | Generated_Code/
179 |
180 | # Backup & report files from converting an old project file
181 | # to a newer Visual Studio version. Backup files are not needed,
182 | # because we have git ;-)
183 | _UpgradeReport_Files/
184 | Backup*/
185 | UpgradeLog*.XML
186 | UpgradeLog*.htm
187 |
188 | # SQL Server files
189 | *.mdf
190 | *.ldf
191 |
192 | # Business Intelligence projects
193 | *.rdl.data
194 | *.bim.layout
195 | *.bim_*.settings
196 |
197 | # Microsoft Fakes
198 | FakesAssemblies/
199 |
200 | # Node.js Tools for Visual Studio
201 | .ntvs_analysis.dat
202 |
203 | # Visual Studio 6 build log
204 | *.plg
205 |
206 | # Visual Studio 6 workspace options file
207 | *.opt
208 |
209 | # LightSwitch generated files
210 | GeneratedArtifacts/
211 | _Pvt_Extensions/
212 | ModelManifest.xml
213 |
--------------------------------------------------------------------------------
/kn5 converter/Program.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Linq;
4 | using System.Text;
5 | using System.IO;
6 |
7 | namespace kn5_converter
8 | {
9 | class Program
10 | {
11 | public class kn5Model
12 | {
13 | public string modelDir;
14 | public string modelName;
15 |
16 | public int version;
17 | public List textures = new List();
18 | public List usedTex = new List();
19 | public List materials = new List();
20 | public List nodes = new List();
21 | }
22 |
23 | public class kn5Material
24 | {
25 | public string name = "Default";
26 | public string shader = "";
27 | public float ksAmbient = 0.6f;
28 | public float ksDiffuse = 0.6f;
29 | public float ksSpecular = 0.9f;
30 | public float ksSpecularEXP = 1.0f;
31 | public float diffuseMult = 1.0f;
32 | public float normalMult = 1.0f;
33 | public float useDetail = 0.0f;
34 | public float detailUVMultiplier = 1.0f;
35 |
36 | public string txDiffuse;
37 | public string txNormal;
38 | public string txDetail;
39 |
40 | public string shaderProps = "";
41 | }
42 |
43 | public class kn5Texture
44 | {
45 | public string filename;
46 | public float UVScaling = 1.0f;
47 | }
48 |
49 | public class kn5Node
50 | {
51 | public int type = 1;
52 | public string name = "Default";
53 |
54 | public float[,] tmatrix = new float[4, 4] { { 1.0f, 0.0f, 0.0f, 0.0f }, { 0.0f, 1.0f, 0.0f, 0.0f }, { 0.0f, 0.0f, 1.0f, 0.0f }, { 0.0f, 0.0f, 0.0f, 1.0f } };
55 | public float[,] hmatrix = new float[4, 4] { { 1.0f, 0.0f, 0.0f, 0.0f }, { 0.0f, 1.0f, 0.0f, 0.0f }, { 0.0f, 0.0f, 1.0f, 0.0f }, { 0.0f, 0.0f, 0.0f, 1.0f } };
56 |
57 | public float[] translation = new float[3] { 0.0f, 0.0f, 0.0f };
58 | public float[] rotation = new float[3] { 0.0f, 0.0f, 0.0f };
59 | public float[] scaling = new float[3] { 1.0f, 1.0f, 1.0f };
60 |
61 | public int vertexCount;
62 | public float[] position;
63 | public float[] normal;
64 | public float[] texture0;
65 |
66 | public ushort[] indices;
67 |
68 | public int materialID = -1;
69 |
70 | //public List children; //do I really wanna do this? no
71 | public int parentID = -1;
72 | }
73 |
74 | static string[] outputTypes = new string[3] {"fbx", "obj", "objZMhack"};
75 | //static string currentPath = AppDomain.CurrentDomain.BaseDirectory;
76 |
77 | static void Main(string[] args)
78 | {
79 | System.Threading.Thread.CurrentThread.CurrentCulture = new System.Globalization.CultureInfo("en-US");
80 | SearchOption recurse = SearchOption.AllDirectories;
81 |
82 | //string input = @"D:\Assetto Corsa\content\";
83 | string input = "";
84 |
85 | switch (args.Length)
86 | {
87 | case 0:
88 | input = AppDomain.CurrentDomain.BaseDirectory;
89 | recurse = SearchOption.TopDirectoryOnly;
90 | goto default;
91 | case 1:
92 | if (GetOutputTypes(args[0])) { goto case 0; }
93 | else { input = args[0]; }
94 | break;
95 | case 2:
96 | if (GetOutputTypes(args[0])) { input = args[1]; }
97 | else if (GetOutputTypes(args[1])) { input = args[0]; } //maybe the user can't follow instructions
98 | break;
99 | default:
100 | Console.WriteLine("Assetto Corsa kn5 model converter\nby Chipicao - KotsChopShop.com\n\nUsage: kn5conv.exe [-fbx|-obj|-objZMhack] [input_file/folder]");
101 | break;
102 | }
103 |
104 | if (File.Exists(input))
105 | {
106 | input = Path.GetFullPath(input); //in case only filename is entered
107 |
108 | string currentModel = MakeRelative(input, (Path.GetDirectoryName(input) + "\\"));
109 | Console.WriteLine("Reading {0}", currentModel);
110 |
111 | var theModel = readKN5(input);
112 | //Console.WriteLine("\rConverting {0}", currentModel);
113 |
114 | foreach (string format in outputTypes)
115 | {
116 | switch (format.ToLower())
117 | {
118 | case "fbx":
119 | ExportFBX(theModel);
120 | break;
121 | case "obj":
122 | ExportOBJ(theModel, false);
123 | break;
124 | case "objzmhack":
125 | ExportOBJ(theModel, true);
126 | break;
127 | }
128 | }
129 | }
130 | else if (Directory.Exists(input))
131 | {
132 | input = Path.GetFullPath(input + "\\");
133 | string[] inputFiles = Directory.GetFiles(input, "*.kn5", recurse);
134 | Console.WriteLine("Found {0} files.", inputFiles.Length);
135 |
136 | foreach (var inputFile in inputFiles)
137 | {
138 | string currentModel = MakeRelative(inputFile, input);
139 | Console.WriteLine("Reading {0}", currentModel);
140 |
141 | var theModel = readKN5(inputFile);
142 | //Console.WriteLine("\rConverting {0}", currentModel);
143 |
144 | foreach (string format in outputTypes)
145 | {
146 | switch (format.ToLower())
147 | {
148 | case "fbx":
149 | ExportFBX(theModel);
150 | break;
151 | case "obj":
152 | ExportOBJ(theModel, false);
153 | break;
154 | case "objzmhack":
155 | ExportOBJ(theModel, true);
156 | break;
157 | }
158 | }
159 | }
160 |
161 | Console.WriteLine("Finished. Press any key to exit...");
162 | Console.ReadKey();
163 | }
164 | else { Console.WriteLine("Invalid input file/folder: {0}\n\nUsage: M3G2FBX.exe [-u] [input_file/folder]", input); }
165 | }
166 |
167 | private static bool GetOutputTypes(string arg)
168 | {
169 | string[] outputArgs = arg.Split(new char[1] { '-' });
170 |
171 | //don't just split the types, also check if they are valid
172 | //TODO make case insensitive
173 | string[] newOutputTypes = outputArgs.Intersect(outputTypes, StringComparer.OrdinalIgnoreCase).ToArray();
174 |
175 | if (newOutputTypes.Length > 0)
176 | {
177 | outputTypes = newOutputTypes;
178 | return true;
179 | }
180 | else { return false; }
181 | }
182 |
183 |
184 | private static kn5Model readKN5(string kn5File)
185 | {
186 | using (BinaryReader binStream = new BinaryReader(File.OpenRead(kn5File)))
187 | {
188 | string magicNumber = ReadStr(binStream, 6);
189 | if (magicNumber == "sc6969")
190 | {
191 | kn5Model newModel = new kn5Model();
192 | newModel.modelDir = Path.GetDirectoryName(kn5File) + "\\";
193 | newModel.modelName = Path.GetFileNameWithoutExtension(kn5File);
194 |
195 | newModel.version = binStream.ReadInt32();
196 | if (newModel.version > 5) { int unknownNo = binStream.ReadInt32(); } //673425
197 |
198 | #region extract textures
199 | Directory.CreateDirectory(newModel.modelDir + "texture");
200 | int texCount = binStream.ReadInt32();
201 | for (int t = 0; t < texCount; t++)
202 | {
203 | int texType = binStream.ReadInt32();
204 | string texName = ReadStr(binStream, binStream.ReadInt32());
205 | int texSize = binStream.ReadInt32();
206 | newModel.textures.Add(texName);
207 |
208 | if (File.Exists(newModel.modelDir + "texture\\" + texName))
209 | {
210 | binStream.BaseStream.Position += texSize;
211 | }
212 | else
213 | {
214 | byte[] texBuffer = binStream.ReadBytes(texSize);
215 | using (BinaryWriter texWriter = new BinaryWriter(File.Create(newModel.modelDir + "texture\\" + texName)))
216 | {
217 | texWriter.Write(texBuffer);
218 | }
219 | }
220 | }
221 | #endregion
222 |
223 | #region read materials
224 | int matCount = binStream.ReadInt32();
225 | for (int m = 0; m < matCount; m++)
226 | {
227 | kn5Material newMaterial = new kn5Material();
228 |
229 | newMaterial.name = ReadStr(binStream, binStream.ReadInt32());
230 | newMaterial.shader = ReadStr(binStream, binStream.ReadInt32());
231 | short ashort = binStream.ReadInt16();
232 | if (newModel.version > 4) { int azero = binStream.ReadInt32(); }
233 |
234 | int propCount = binStream.ReadInt32();
235 | for (int p = 0; p < propCount; p++)
236 | {
237 | string propName = ReadStr(binStream, binStream.ReadInt32());
238 | float propValue = binStream.ReadSingle();
239 | newMaterial.shaderProps += propName + " = " + propValue.ToString() + "&cr;&lf;";
240 |
241 | switch (propName)
242 | {
243 | case "ksAmbient":
244 | newMaterial.ksAmbient = propValue;
245 | break;
246 | case "ksDiffuse":
247 | newMaterial.ksDiffuse = propValue;
248 | break;
249 | case "ksSpecular":
250 | newMaterial.ksSpecular = propValue;
251 | break;
252 | case "ksSpecularEXP":
253 | newMaterial.ksSpecularEXP = propValue;
254 | break;
255 | case "diffuseMult":
256 | newMaterial.diffuseMult = propValue;
257 | break;
258 | case "normalMult":
259 | newMaterial.normalMult = propValue;
260 | break;
261 | case "useDetail":
262 | newMaterial.useDetail = propValue;
263 | break;
264 | case "detailUVMultiplier":
265 | newMaterial.detailUVMultiplier = propValue;
266 | break;
267 | }
268 |
269 | binStream.BaseStream.Position += 36;
270 | }
271 |
272 | int textures = binStream.ReadInt32();
273 | for (int t = 0; t < textures; t++)
274 | {
275 | string sampleName = ReadStr(binStream, binStream.ReadInt32());
276 | int sampleSlot = binStream.ReadInt32();
277 | string texName = ReadStr(binStream, binStream.ReadInt32());
278 |
279 | newMaterial.shaderProps += sampleName + " = " + texName + "&cr;&lf;";
280 |
281 | switch (sampleName)
282 | {
283 | case "txDiffuse":
284 | newMaterial.txDiffuse = texName;
285 | break;
286 | case "txNormal":
287 | newMaterial.txNormal = texName;
288 | break;
289 | case "txDetail":
290 | newMaterial.txDetail = texName;
291 | break;
292 | }
293 | }
294 |
295 | newModel.materials.Add(newMaterial);
296 | }
297 | #endregion
298 |
299 | readNodes(binStream, newModel.nodes, -1); //recursive
300 |
301 | return newModel;
302 | }
303 | else
304 | {
305 | Console.WriteLine("Unknown file type.");
306 | return null;
307 | }
308 | }
309 | }
310 |
311 | private static void readNodes(BinaryReader modelStream, List nodeList, int parentID)
312 | {
313 | kn5Node newNode = new kn5Node();
314 | newNode.parentID = parentID;
315 |
316 | newNode.type = modelStream.ReadInt32();
317 | newNode.name = ReadStr(modelStream, modelStream.ReadInt32());
318 | int childrenCount = modelStream.ReadInt32();
319 | byte abyte = modelStream.ReadByte();
320 |
321 | switch (newNode.type)
322 | {
323 | #region dummy node
324 | case 1: //dummy
325 | {
326 | newNode.tmatrix[0, 0] = modelStream.ReadSingle();
327 | newNode.tmatrix[0, 1] = modelStream.ReadSingle();
328 | newNode.tmatrix[0, 2] = modelStream.ReadSingle();
329 | newNode.tmatrix[0, 3] = modelStream.ReadSingle();
330 | newNode.tmatrix[1, 0] = modelStream.ReadSingle();
331 | newNode.tmatrix[1, 1] = modelStream.ReadSingle();
332 | newNode.tmatrix[1, 2] = modelStream.ReadSingle();
333 | newNode.tmatrix[1, 3] = modelStream.ReadSingle();
334 | newNode.tmatrix[2, 0] = modelStream.ReadSingle();
335 | newNode.tmatrix[2, 1] = modelStream.ReadSingle();
336 | newNode.tmatrix[2, 2] = modelStream.ReadSingle();
337 | newNode.tmatrix[2, 3] = modelStream.ReadSingle();
338 | newNode.tmatrix[3, 0] = modelStream.ReadSingle();
339 | newNode.tmatrix[3, 1] = modelStream.ReadSingle();
340 | newNode.tmatrix[3, 2] = modelStream.ReadSingle();
341 | newNode.tmatrix[3, 3] = modelStream.ReadSingle();
342 |
343 | newNode.translation = new float[3] { newNode.tmatrix[3, 0], newNode.tmatrix[3, 1], newNode.tmatrix[3, 2] };
344 | newNode.rotation = MatrixToEuler(newNode.tmatrix);
345 | newNode.scaling = ScaleFromMatrix(newNode.tmatrix);
346 |
347 | break;
348 | }
349 | #endregion
350 | #region mesh node
351 | case 2: //mesh
352 | {
353 | byte bbyte = modelStream.ReadByte();
354 | byte cbyte = modelStream.ReadByte();
355 | byte dbyte = modelStream.ReadByte();
356 |
357 | newNode.vertexCount = modelStream.ReadInt32();
358 | newNode.position = new float[newNode.vertexCount * 3];
359 | newNode.normal = new float[newNode.vertexCount * 3];
360 | newNode.texture0 = new float[newNode.vertexCount * 2];
361 |
362 | for (int v = 0; v < newNode.vertexCount; v++)
363 | {
364 | newNode.position[v * 3] = modelStream.ReadSingle();
365 | newNode.position[v * 3 + 1] = modelStream.ReadSingle();
366 | newNode.position[v * 3 + 2] = modelStream.ReadSingle();
367 |
368 | newNode.normal[v * 3] = modelStream.ReadSingle();
369 | newNode.normal[v * 3 + 1] = modelStream.ReadSingle();
370 | newNode.normal[v * 3 + 2] = modelStream.ReadSingle();
371 |
372 | newNode.texture0[v * 2] = modelStream.ReadSingle();
373 | newNode.texture0[v * 2 + 1] = 1 - modelStream.ReadSingle();
374 |
375 | modelStream.BaseStream.Position += 12; //tangents
376 | }
377 |
378 | int indexCount = modelStream.ReadInt32();
379 | newNode.indices = new ushort[indexCount];
380 | for (int i = 0; i < indexCount; i++)
381 | {
382 | newNode.indices[i] = modelStream.ReadUInt16();
383 | }
384 |
385 | newNode.materialID = modelStream.ReadInt32();
386 | modelStream.BaseStream.Position += 29;
387 |
388 | break;
389 | }
390 | #endregion
391 | #region animated mesh
392 | case 3: //animated mesh
393 | {
394 | byte bbyte = modelStream.ReadByte();
395 | byte cbyte = modelStream.ReadByte();
396 | byte dbyte = modelStream.ReadByte();
397 |
398 | int boneCount = modelStream.ReadInt32();
399 | for (int b = 0; b < boneCount; b++)
400 | {
401 | string boneName = ReadStr(modelStream, modelStream.ReadInt32());
402 | modelStream.BaseStream.Position += 64; //transformation matrix
403 | }
404 |
405 | newNode.vertexCount = modelStream.ReadInt32();
406 | newNode.position = new float[newNode.vertexCount * 3];
407 | newNode.normal = new float[newNode.vertexCount * 3];
408 | newNode.texture0 = new float[newNode.vertexCount * 2];
409 |
410 | for (int v = 0; v < newNode.vertexCount; v++)
411 | {
412 | newNode.position[v * 3] = modelStream.ReadSingle();
413 | newNode.position[v * 3 + 1] = modelStream.ReadSingle();
414 | newNode.position[v * 3 + 2] = modelStream.ReadSingle();
415 |
416 | newNode.normal[v * 3] = modelStream.ReadSingle();
417 | newNode.normal[v * 3 + 1] = modelStream.ReadSingle();
418 | newNode.normal[v * 3 + 2] = modelStream.ReadSingle();
419 |
420 | newNode.texture0[v * 2] = modelStream.ReadSingle();
421 | newNode.texture0[v * 2 + 1] = 1 - modelStream.ReadSingle();
422 |
423 | modelStream.BaseStream.Position += 44; //tangents & weights
424 | }
425 |
426 | int indexCount = modelStream.ReadInt32();
427 | newNode.indices = new ushort[indexCount];
428 | for (int i = 0; i < indexCount; i++)
429 | {
430 | newNode.indices[i] = modelStream.ReadUInt16();
431 | }
432 |
433 | newNode.materialID = modelStream.ReadInt32();
434 | modelStream.BaseStream.Position += 12;
435 |
436 | break;
437 | }
438 | #endregion
439 | }
440 |
441 | if (parentID < 0) { newNode.hmatrix = newNode.tmatrix; }
442 | else { newNode.hmatrix = matrixMult(newNode.tmatrix, nodeList[parentID].hmatrix); }
443 |
444 | nodeList.Add(newNode);
445 | int currentID = nodeList.IndexOf(newNode);
446 |
447 | for (int c = 0; c < childrenCount; c++)
448 | {
449 | readNodes(modelStream, nodeList, currentID);
450 | }
451 | }
452 |
453 | private static float[,] matrixMult(float[,] ma, float[,] mb)
454 | {
455 | float[,] mm = new float[4, 4];
456 |
457 | for (int i = 0; i < 4; i++)
458 | {
459 | for (int j = 0; j < 4; j++)
460 | {
461 | mm[i, j] = ma[i, 0] * mb[0, j] + ma[i, 1] * mb[1, j] + ma[i, 2] * mb[2, j] + ma[i, 3] * mb[3, j];
462 | }
463 | }
464 |
465 | /*
466 | mm[0, 0] = ma00*mb00 + ma01*mb10 + ma02*mb20 + ma03*mb30
467 | mm[0, 1] = ma00*mb01 + ma01*mb11 + ma02*mb21 + ma03*mb31
468 | mm[0, 2] = ma00*mb02 + ma01*mb12 + ma02*mb22 + ma03*mb32
469 | mm[0, 3] = ma00*mb03 + ma01*mb13 + ma02*mb23 + ma03*mb33
470 |
471 | mm[1, 1] = ma10*mb00 + ma11*mb10 + ma12*mb20 + ma13*mb30
472 | mm[1, 1] = ma10*mb01 + ma11*mb11 + ma12*mb21 + ma13*mb31
473 | mm[1, 2] = ma10*mb02 + ma11*mb12 + ma12*mb22 + ma13*mb32
474 | mm[1, 3] = ma10*mb03 + ma11*mb13 + ma12*mb23 + ma13*mb33
475 |
476 | mm[2, 0] = ma20*mb00 + ma21*mb10 + ma22*mb20 + ma23*mb30
477 | mm[2, 1] = ma20*mb01 + ma21*mb11 + ma22*mb21 + ma23*mb31
478 | mm[2, 2] = ma20*mb02 + ma21*mb12 + ma22*mb22 + ma23*mb32
479 | mm[2, 3] = ma20*mb03 + ma21*mb13 + ma22*mb23 + ma23*mb33
480 |
481 | mm[3, 0] = ma30*mb00 + ma31*mb10 + ma32*mb20 + ma33*mb30
482 | mm[3, 1] = ma30*mb01 + ma31*mb11 + ma32*mb21 + ma33*mb31
483 | mm[3, 2] = ma30*mb02 + ma31*mb12 + ma32*mb22 + ma33*mb32
484 | mm[3, 3] = ma30*mb03 + ma31*mb13 + ma32*mb23 + ma33*mb33*/
485 |
486 | return mm;
487 | }
488 |
489 | private static float[] MatrixToEuler(float[,] transf)
490 | {
491 | double heading = 0;
492 | double attitude = 0;
493 | double bank = 0;
494 | //original code by Martin John Baker for right-handed coordinate system
495 | /*if (transf[0, 1] > 0.998)
496 | { // singularity at north pole
497 | heading = Math.Atan2(transf[0, 2], transf[2, 2]);
498 | attitude = Math.PI / 2;
499 | bank = 0;
500 | }
501 | if (transf[0, 1] < -0.998)
502 | { // singularity at south pole
503 | heading = Math.Atan2(transf[0, 2], transf[2, 2]);
504 | attitude = -Math.PI / 2;
505 | bank = 0;
506 | }
507 |
508 | heading = Math.Atan2(-transf[2, 0], transf[0, 0]);
509 | bank = Math.Atan2(-transf[1, 2], transf[1, 1]);
510 | attitude = Math.Asin(transf[1, 0]);*/
511 |
512 | //left handed
513 | if (transf[0, 1] > 0.998)
514 | { // singularity at north pole
515 | heading = Math.Atan2(-transf[1, 0], transf[1, 1]);
516 | attitude = -Math.PI / 2;
517 | bank = 0;
518 | }
519 | else if (transf[0, 1] < -0.998)
520 | { // singularity at south pole
521 | heading = Math.Atan2(-transf[1, 0], transf[1, 1]);
522 | attitude = Math.PI / 2;
523 | bank = 0;
524 | }
525 | else
526 | {
527 | heading = Math.Atan2(transf[0, 1], transf[0, 0]);
528 | bank = Math.Atan2(transf[1, 2], transf[2, 2]);
529 | attitude = Math.Asin(-transf[0, 2]);
530 | }
531 |
532 |
533 | //alternative code by Mike Day, Insomniac Games
534 | /*bank = Math.Atan2(transf[1, 2], transf[2, 2]);
535 |
536 | double c2 = Math.Sqrt(transf[0, 0] * transf[0, 0] + transf[0, 1] * transf[0, 1]);
537 | attitude = Math.Atan2(-transf[0, 2], c2);
538 |
539 | double s1 = Math.Sin(bank);
540 | double c1 = Math.Cos(bank);
541 | heading = Math.Atan2(s1 * transf[2, 0] - c1 * transf[1, 0], c1 * transf[1, 1] - s1 * transf[2, 1]);*/
542 |
543 | attitude *= 180 / Math.PI;
544 | heading *= 180 / Math.PI;
545 | bank *= 180 / Math.PI;
546 |
547 | return new float[3] { (float)bank, (float)attitude, (float)heading };
548 | }
549 |
550 | private static float[] ScaleFromMatrix(float[,] transf)
551 | {
552 | double scaleX = Math.Sqrt(transf[0, 0] * transf[0, 0] + transf[1, 0] * transf[1, 0] + transf[2, 0] * transf[2, 0]);
553 | double scaleY = Math.Sqrt(transf[0, 1] * transf[0, 1] + transf[1, 1] * transf[1, 1] + transf[2, 1] * transf[2, 1]);
554 | double scaleZ = Math.Sqrt(transf[0, 2] * transf[0, 2] + transf[1, 2] * transf[1, 2] + transf[2, 2] * transf[2, 2]);
555 |
556 | return new float[3] { (float)scaleX, (float)scaleY, (float)scaleZ };
557 | }
558 |
559 | private static string ReadStr(BinaryReader str, int len)
560 | {
561 | //int len = str.ReadInt32();
562 | byte[] stringData = new byte[len];
563 | str.Read(stringData, 0, len);
564 | var result = System.Text.Encoding.UTF8.GetString(stringData);
565 | return result;
566 | }
567 |
568 |
569 | private static void ExportOBJ(kn5Model srcModel, bool ZMhack)
570 | {
571 | string modelFilename = srcModel.modelName;
572 | if (ZMhack) { modelFilename += "_ZMhack"; }
573 |
574 | if (!File.Exists(srcModel.modelDir + modelFilename + ".obj"))
575 | {
576 | Console.WriteLine("Exporting {0}.obj", modelFilename);
577 |
578 | #region write MTL
579 | using (StreamWriter MTLwriter = new StreamWriter(File.Create(srcModel.modelDir + modelFilename + ".mtl")))
580 | {
581 | StringBuilder sb = new StringBuilder();
582 |
583 | foreach (var srcMat in srcModel.materials)
584 | {
585 | sb.AppendFormat("newmtl {0}\r\n", srcMat.name.Replace(' ', '_'));
586 | sb.AppendFormat("Ka {0} {0} {0}\r\n", srcMat.ksAmbient);
587 | sb.AppendFormat("Kd {0} {0} {0}\r\n", srcMat.ksDiffuse);
588 | sb.AppendFormat("Ks {0} {0} {0}\r\n", srcMat.ksSpecular);
589 | sb.AppendFormat("Ns {0}\r\n", srcMat.ksSpecularEXP);
590 | sb.AppendFormat("illum 2\r\n", srcMat.ksSpecular);
591 | //add function to search for textures and get relative path
592 | if (srcMat.useDetail == 1.0f && srcMat.txDetail != null)
593 | {
594 | sb.AppendFormat("map_Kd texture\\{0}\r\n", srcMat.txDetail);
595 | if (srcMat.txDiffuse != null) { sb.AppendFormat("map_Ks texture\\{0}\r\n", srcMat.txDiffuse); }
596 | }
597 | else if (srcMat.txDiffuse != null) { sb.AppendFormat("map_Kd texture\\{0}\r\n", srcMat.txDiffuse); }
598 | if (srcMat.txNormal != null) { sb.AppendFormat("bump texture\\{0}\r\n", srcMat.txNormal); }
599 | sb.Append("\r\n");
600 | }
601 |
602 | MTLwriter.Write(sb);
603 | }
604 | #endregion
605 |
606 | #region write OBJ
607 | using (StreamWriter OBJwriter = new StreamWriter(File.Create(srcModel.modelDir + modelFilename + ".obj")))
608 | {
609 | StringBuilder sb = new StringBuilder();
610 | sb.AppendFormat("# Assetto Corsa model\r\n# Exported with kn5 Converter by Chipicao on {0}\r\n", DateTime.Now);
611 | sb.AppendFormat("\r\nmtllib {0}.mtl\r\n", modelFilename);
612 |
613 | int vertexPad = 1;
614 |
615 | foreach (var srcNode in srcModel.nodes)
616 | {
617 | switch (srcNode.type)
618 | {
619 | case 1:
620 | {
621 | if (ZMhack)
622 | {
623 | //create dummy box
624 | srcNode.vertexCount = 24;
625 | srcNode.position = new float[72] { 0.05f, -0.05f, 0.05f, 0.05f, 0.05f, 0.05f, -0.05f, 0.05f, 0.05f, -0.05f, -0.05f, 0.05f, 0.05f, 0.05f, -0.05f, 0.05f, 0.05f, 0.05f, 0.05f, -0.05f, 0.05f, 0.05f, -0.05f, -0.05f, -0.05f, 0.05f, 0.05f, 0.05f, 0.05f, 0.05f, 0.05f, 0.05f, -0.05f, -0.05f, 0.05f, -0.05f, -0.05f, 0.05f, -0.05f, 0.05f, 0.05f, -0.05f, 0.05f, -0.05f, -0.05f, -0.05f, -0.05f, -0.05f, -0.05f, -0.05f, 0.05f, -0.05f, 0.05f, 0.05f, -0.05f, 0.05f, -0.05f, -0.05f, -0.05f, -0.05f, 0.05f, -0.05f, -0.05f, 0.05f, -0.05f, 0.05f, -0.05f, -0.05f, 0.05f, -0.05f, -0.05f, -0.05f };
626 | srcNode.normal = new float[72] { -0f, 0f, 1f, -0f, 0f, 1f, -0f, 0f, 1f, -0f, 0f, 1f, 1f, 0f, 0f, 1f, 0f, 0f, 1f, 0f, 0f, 1f, 0f, 0f, -0f, 1f, 0f, -0f, 1f, 0f, -0f, 1f, 0f, -0f, 1f, 0f, -0f, 0f, -1f, -0f, 0f, -1f, -0f, 0f, -1f, -0f, 0f, -1f, -1f, 0f, 0f, -1f, 0f, 0f, -1f, 0f, 0f, -1f, 0f, 0f, -0f, -1f, 0f, -0f, -1f, 0f, -0f, -1f, 0f, -0f, -1f, 0f };
627 | srcNode.texture0 = new float[48] { 0f, 1f, 1f, 1f, 1f, 0f, 0f, 0f, 0f, 1f, 1f, 1f, 1f, 0f, 0f, 0f, 0f, 1f, 1f, 1f, 1f, 0f, 0f, 0f, 0f, 1f, 1f, 1f, 1f, 0f, 0f, 0f, 0f, 1f, 1f, 1f, 1f, 0f, 0f, 0f, 1f, 0f, 1f, 1f, 0f, 1f, 0f, 0f };
628 | srcNode.indices = new ushort[36] { 0, 1, 2, 0, 2, 3, 4, 5, 6, 4, 6, 7, 8, 9, 10, 8, 10, 11, 12, 13, 14, 12, 14, 15, 16, 17, 18, 16, 18, 19, 20, 21, 22, 20, 22, 23 };
629 | goto case 3;
630 | }
631 | break;
632 | }
633 | case 2:
634 | case 3:
635 | {
636 | sb.AppendFormat("\r\ng {0}", srcNode.name.Replace(' ', '_'));
637 | if (ZMhack && srcNode.parentID >= 0) { sb.AppendFormat(" {0}", srcModel.nodes[srcNode.parentID].name.Replace(' ', '_')); }
638 | sb.AppendFormat("\r\n");
639 |
640 | for (int v = 0; v < srcNode.vertexCount; v++)
641 | {
642 | var x = srcNode.position[v * 3];
643 | var y = srcNode.position[v * 3 + 1];
644 | var z = srcNode.position[v * 3 + 2];
645 |
646 | float vx = srcNode.hmatrix[0, 0] * x + srcNode.hmatrix[1, 0] * y + srcNode.hmatrix[2, 0] * z + srcNode.hmatrix[3, 0];
647 | float vy = srcNode.hmatrix[0, 1] * x + srcNode.hmatrix[1, 1] * y + srcNode.hmatrix[2, 1] * z + srcNode.hmatrix[3, 1];
648 | float vz = srcNode.hmatrix[0, 2] * x + srcNode.hmatrix[1, 2] * y + srcNode.hmatrix[2, 2] * z + srcNode.hmatrix[3, 2];
649 |
650 | sb.AppendFormat("v {0} {1} {2}\r\n", vx, vy, vz);
651 | }
652 | OBJwriter.Write(sb);
653 | sb.Length = 0;
654 |
655 | for (int v = 0; v < srcNode.vertexCount; v++)
656 | {
657 | var x = srcNode.normal[v * 3];
658 | var y = srcNode.normal[v * 3 + 1];
659 | var z = srcNode.normal[v * 3 + 2];
660 |
661 | //transforming normal vectors requires the transposed inverse matrix
662 | //transformation matrices SHOULD be normalized (assumption, mother of all fuckups)
663 | //so in theory, the inverse of a normalized matrix is the transposed matrix, which transposed again is the matrix itself
664 | float nx = srcNode.hmatrix[0, 0] * x + srcNode.hmatrix[1, 0] * y + srcNode.hmatrix[2, 0] * z;
665 | float ny = srcNode.hmatrix[0, 1] * x + srcNode.hmatrix[1, 1] * y + srcNode.hmatrix[2, 1] * z;
666 | float nz = srcNode.hmatrix[0, 2] * x + srcNode.hmatrix[1, 2] * y + srcNode.hmatrix[2, 2] * z;
667 |
668 | sb.AppendFormat("vn {0} {1} {2}\r\n", nx, ny, nz);
669 | }
670 | OBJwriter.Write(sb);
671 | sb.Length = 0;
672 |
673 | float UVmult = 1.0f;
674 | if (srcNode.materialID >= 0)
675 | {
676 | if (srcModel.materials[srcNode.materialID].useDetail == 0.0f) { UVmult = srcModel.materials[srcNode.materialID].diffuseMult; }
677 | else { UVmult = srcModel.materials[srcNode.materialID].detailUVMultiplier; }
678 | }
679 |
680 | for (int v = 0; v < srcNode.vertexCount; v++)
681 | {
682 | var tx = srcNode.texture0[v * 2] * UVmult;
683 | var ty = srcNode.texture0[v * 2 + 1] * UVmult;
684 |
685 | sb.AppendFormat("vt {0} {1}\r\n", tx, ty);
686 | }
687 | OBJwriter.Write(sb);
688 | sb.Length = 0;
689 |
690 | if (srcNode.materialID >= 0) { sb.AppendFormat("\r\nusemtl {0}\r\n", srcModel.materials[srcNode.materialID].name.Replace(' ', '_')); }
691 | else { sb.AppendFormat("\r\nusemtl Default\r\n"); }
692 | for (int i = 0; i < srcNode.indices.Length / 3; i++)
693 | {
694 | var i1 = srcNode.indices[i * 3] + vertexPad;
695 | var i2 = srcNode.indices[i * 3 + 1] + vertexPad;
696 | var i3 = srcNode.indices[i * 3 + 2] + vertexPad;
697 |
698 | sb.AppendFormat("f {0}/{0}/{0} {1}/{1}/{1} {2}/{2}/{2}\r\n", i1, i2, i3);
699 | }
700 | OBJwriter.Write(sb);
701 | sb.Length = 0;
702 |
703 | vertexPad += srcNode.vertexCount;
704 | break;
705 | }
706 | }
707 | }
708 | }
709 | #endregion
710 | }
711 | else { Console.WriteLine("File already exists: {0}.obj", modelFilename); }
712 | }
713 |
714 | private static void ExportFBX(kn5Model srcModel)
715 | {
716 | if (!File.Exists(srcModel.modelDir + srcModel.modelName + ".fbx"))
717 | {
718 | Console.WriteLine("Exporting {0}.fbx", srcModel.modelName);
719 |
720 | using (StreamWriter FBXwriter = new StreamWriter(File.Create(srcModel.modelDir + srcModel.modelName + ".fbx")))
721 | {
722 | StringBuilder fbx = new StringBuilder();
723 | var timestamp = DateTime.Now;
724 |
725 | StringBuilder ob = new StringBuilder(); //objects builder
726 | //ob.Append("\nObjects: {");
727 |
728 | StringBuilder cb = new StringBuilder(); //connections builder
729 | cb.Append("\n}\n");//Objects end
730 | cb.Append("\nConnections: {");
731 |
732 | int FBXgeometryCount = 0;
733 | foreach (var srcNode in srcModel.nodes.Where(n => n.type > 1)) { FBXgeometryCount++; }
734 |
735 | #region build materials first to get used texture count
736 | for (int m = 0; m < srcModel.materials.Count; m++)
737 | {
738 | var srcMat = srcModel.materials[m];
739 |
740 | ob.Append("\n\tMaterial: " + (400000 + m) + ", \"Material::" + srcMat.name + "\", \"\" {");
741 | ob.Append("\n\t\tVersion: 102");
742 | ob.Append("\n\t\tShadingModel: \"phong\"");
743 | ob.Append("\n\t\tMultiLayer: 0");
744 | ob.Append("\n\t\tProperties70: {");
745 | ob.Append("\n\t\t\tP: \"ShadingModel\", \"KString\", \"\", \"\", \"phong\"");
746 | ob.AppendFormat("\n\t\t\tP: \"AmbientColor\", \"Color\", \"\", \"A\",{0},{0},{0}", srcMat.ksAmbient);
747 | ob.AppendFormat("\n\t\t\tP: \"DiffuseColor\", \"Color\", \"\", \"A\",{0},{0},{0}", srcMat.ksDiffuse);
748 | ob.AppendFormat("\n\t\t\tP: \"SpecularColor\", \"Color\", \"\", \"A\",{0},{0},{0}", srcMat.ksSpecular);
749 | ob.AppendFormat("\n\t\t\tP: \"SpecularFactor\", \"Number\", \"\", \"A\",{0}", srcMat.ksSpecularEXP / 100f);
750 | //ob.AppendFormat("\n\t\t\tP: \"ShininessExponent\", \"Number\", \"\", \"A\",{0}", srcMat.ksSpecularEXP);
751 | //ob.AppendFormat("\n\t\t\tP: \"Shininess\", \"double\", \"Number\", \"\",{0}", srcMat.ksSpecularEXP);
752 | ob.Append("\n\t\t}");
753 | ob.Append("\n\t}");
754 |
755 | //try to provide some level of texture instancing by using a custom class to match file and UV scaling
756 |
757 | int txDiffuseID = srcModel.usedTex.FindIndex(s => s.filename == srcMat.txDiffuse && s.UVScaling == srcMat.diffuseMult);
758 | if (txDiffuseID < 0 && srcMat.txDiffuse != null) //add texture to instance list
759 | {
760 | srcModel.usedTex.Add(new kn5Texture() { filename = srcMat.txDiffuse, UVScaling = srcMat.diffuseMult });
761 | txDiffuseID = srcModel.usedTex.Count - 1;
762 | }
763 |
764 | if (srcMat.useDetail == 1.0f && srcMat.txDetail != null)
765 | {
766 | int txDetailID = srcModel.usedTex.FindIndex(s => s.filename == srcMat.txDetail && s.UVScaling == srcMat.detailUVMultiplier);
767 | if (txDetailID < 0)
768 | {
769 | srcModel.usedTex.Add(new kn5Texture() { filename = srcMat.txDetail, UVScaling = srcMat.detailUVMultiplier });
770 | txDetailID = srcModel.usedTex.Count - 1;
771 | }
772 |
773 | cb.Append("\n\tC: \"OP\"," + (500000 + txDetailID) + "," + (400000 + m) + ", \"DiffuseColor\"");
774 |
775 | //use txDiffuse(AO) as specular map
776 | cb.Append("\n\tC: \"OP\"," + (500000 + txDiffuseID) + "," + (400000 + m) + ", \"SpecularColor\"");
777 | }
778 | else
779 | {
780 | cb.Append("\n\tC: \"OP\"," + (500000 + txDiffuseID) + "," + (400000 + m) + ", \"DiffuseColor\"");
781 |
782 | cb.Append("\n\tC: \"OP\"," + (500000 + txDiffuseID) + "," + (400000 + m) + ", \"TransparentColor\"");
783 | }
784 |
785 | if (srcMat.txNormal != null)
786 | {
787 | int txNormalID = srcModel.usedTex.FindIndex(s => s.filename == srcMat.txNormal && s.UVScaling == srcMat.normalMult);
788 | if (txNormalID < 0)
789 | {
790 | srcModel.usedTex.Add(new kn5Texture() { filename = srcMat.txNormal, UVScaling = srcMat.normalMult });
791 | txNormalID = srcModel.usedTex.Count - 1;
792 | }
793 |
794 | cb.Append("\n\tC: \"OP\"," + (500000 + txNormalID) + "," + (400000 + m) + ", \"NormalMap\"");
795 | }
796 | }
797 | #endregion
798 |
799 | #region write generic FBX data
800 | fbx.Append("; FBX 7.1.0 project file");
801 | fbx.Append("\nFBXHeaderExtension: {\n\tFBXHeaderVersion: 1003\n\tFBXVersion: 7100\n\tCreationTimeStamp: {\n\t\tVersion: 1000");
802 | fbx.Append("\n\t\tYear: " + timestamp.Year);
803 | fbx.Append("\n\t\tMonth: " + timestamp.Month);
804 | fbx.Append("\n\t\tDay: " + timestamp.Day);
805 | fbx.Append("\n\t\tHour: " + timestamp.Hour);
806 | fbx.Append("\n\t\tMinute: " + timestamp.Minute);
807 | fbx.Append("\n\t\tSecond: " + timestamp.Second);
808 | fbx.Append("\n\t\tMillisecond: " + timestamp.Millisecond);
809 | fbx.Append("\n\t}\n\tCreator: \"kn5 converter by Chipicao\"\n}\n");
810 |
811 | fbx.Append("\nGlobalSettings: {");
812 | fbx.Append("\n\tVersion: 1000");
813 | fbx.Append("\n\tProperties70: {");
814 | fbx.Append("\n\t\tP: \"UpAxis\", \"int\", \"Integer\", \"\",1");
815 | fbx.Append("\n\t\tP: \"UpAxisSign\", \"int\", \"Integer\", \"\",1");
816 | fbx.Append("\n\t\tP: \"FrontAxis\", \"int\", \"Integer\", \"\",2");
817 | fbx.Append("\n\t\tP: \"FrontAxisSign\", \"int\", \"Integer\", \"\",1");
818 | fbx.Append("\n\t\tP: \"CoordAxis\", \"int\", \"Integer\", \"\",0");
819 | fbx.Append("\n\t\tP: \"CoordAxisSign\", \"int\", \"Integer\", \"\",1");
820 | fbx.Append("\n\t\tP: \"OriginalUpAxis\", \"int\", \"Integer\", \"\",1");
821 | fbx.Append("\n\t\tP: \"OriginalUpAxisSign\", \"int\", \"Integer\", \"\",1");
822 | fbx.Append("\n\t\tP: \"UnitScaleFactor\", \"double\", \"Number\", \"\",1");
823 | fbx.Append("\n\t\tP: \"OriginalUnitScaleFactor\", \"double\", \"Number\", \"\",1");
824 | //sb.Append("\n\t\tP: \"AmbientColor\", \"ColorRGB\", \"Color\", \"\",0,0,0");
825 | //sb.Append("\n\t\tP: \"DefaultCamera\", \"KString\", \"\", \"\", \"Producer Perspective\"");
826 | //sb.Append("\n\t\tP: \"TimeMode\", \"enum\", \"\", \"\",6");
827 | //sb.Append("\n\t\tP: \"TimeProtocol\", \"enum\", \"\", \"\",2");
828 | //sb.Append("\n\t\tP: \"SnapOnFrameMode\", \"enum\", \"\", \"\",0");
829 | //sb.Append("\n\t\tP: \"TimeSpanStart\", \"KTime\", \"Time\", \"\",0");
830 | //sb.Append("\n\t\tP: \"TimeSpanStop\", \"KTime\", \"Time\", \"\",153953860000");
831 | //sb.Append("\n\t\tP: \"CustomFrameRate\", \"double\", \"Number\", \"\",-1");
832 | //sb.Append("\n\t\tP: \"TimeMarker\", \"Compound\", \"\", \"\"");
833 | //sb.Append("\n\t\tP: \"CurrentTimeMarker\", \"int\", \"Integer\", \"\",-1");
834 | fbx.Append("\n\t}\n}\n");
835 |
836 | fbx.Append("\nDocuments: {");
837 | fbx.Append("\n\tCount: 1");
838 | fbx.Append("\n\tDocument: 1234567890, \"\", \"Scene\" {");
839 | fbx.Append("\n\t\tProperties70: {");
840 | fbx.Append("\n\t\t\tP: \"SourceObject\", \"object\", \"\", \"\"");
841 | fbx.Append("\n\t\t\tP: \"ActiveAnimStackName\", \"KString\", \"\", \"\", \"\"");
842 | fbx.Append("\n\t\t}");
843 | fbx.Append("\n\t\tRootNode: 0");
844 | fbx.Append("\n\t}\n}\n");
845 | fbx.Append("\nReferences: {\n}\n");
846 |
847 | fbx.Append("\nDefinitions: {");
848 | fbx.Append("\n\tVersion: 100");
849 | fbx.AppendFormat("\n\tCount: {0}", 1 + srcModel.nodes.Count + FBXgeometryCount + srcModel.materials.Count + srcModel.usedTex.Count * 2);
850 |
851 | fbx.Append("\n\tObjectType: \"GlobalSettings\" {");
852 | fbx.Append("\n\t\tCount: 1");
853 | fbx.Append("\n\t}");
854 |
855 | fbx.Append("\n\tObjectType: \"Model\" {");
856 | fbx.Append("\n\t\tCount: " + srcModel.nodes.Count);
857 | fbx.Append("\n\t}");
858 |
859 | fbx.Append("\n\tObjectType: \"Geometry\" {");
860 | fbx.Append("\n\t\tCount: " + FBXgeometryCount);
861 | fbx.Append("\n\t}");
862 |
863 | fbx.Append("\n\tObjectType: \"Material\" {");
864 | fbx.Append("\n\t\tCount: " + srcModel.materials.Count);
865 | fbx.Append("\n\t}");
866 |
867 | fbx.Append("\n\tObjectType: \"Texture\" {");
868 | fbx.Append("\n\t\tCount: " + srcModel.usedTex.Count);
869 | fbx.Append("\n\t}");
870 |
871 | fbx.Append("\n\tObjectType: \"Video\" {");
872 | fbx.Append("\n\t\tCount: " + srcModel.usedTex.Count);
873 | fbx.Append("\n\t}");
874 | fbx.Append("\n}\n");
875 | fbx.Append("\nObjects: {");
876 |
877 | FBXwriter.Write(fbx);
878 | fbx.Length = 0;
879 | //write previously built materials
880 | FBXwriter.Write(ob);
881 | ob.Length = 0;
882 | #endregion
883 |
884 | #region write Texture & Video data
885 | for (int t = 0; t < srcModel.usedTex.Count; t++)
886 | {
887 | string textureName = srcModel.usedTex[t].filename;
888 | string textureFile = srcModel.modelDir + "texture\\" + textureName;
889 | string relativePath = "texture\\" + textureName;
890 |
891 | //search for texture if doesn't exist
892 | //later
893 |
894 | ob.Append("\n\tTexture: " + (500000 + t) + ", \"Texture::" + textureName + "\", \"\" {");
895 | ob.Append("\n\t\tType: \"TextureVideoClip\"");
896 | ob.Append("\n\t\tVersion: 202");
897 | ob.Append("\n\t\tTextureName: \"Texture::" + textureName + "\"");
898 | ob.Append("\n\t\tProperties70: {");
899 | ob.AppendFormat("\n\t\t\tP: \"Translation\", \"Vector\", \"\", \"A\",{0},{0},1", 0.5f * (1 - srcModel.usedTex[t].UVScaling));
900 | ob.AppendFormat("\n\t\t\tP: \"Scaling\", \"Vector\", \"\", \"A\",{0},{0},1", srcModel.usedTex[t].UVScaling);
901 | ob.Append("\n\t\t\tP: \"UVSet\", \"KString\", \"\", \"\", \"UVChannel_1\"");
902 | ob.Append("\n\t\t\tP: \"UseMaterial\", \"bool\", \"\", \"\",1");
903 | ob.Append("\n\t\t}");
904 | ob.Append("\n\t\tMedia: \"Video::" + textureName + "\"");
905 | ob.Append("\n\t\tFileName: \"" + textureFile + "\"");
906 | ob.Append("\n\t\tRelativeFilename: \"" + relativePath + "\"");
907 | ob.Append("\n\t\tTexture_Alpha_Source: \"Alpha_Black\"");
908 | ob.Append("\n\t}");
909 |
910 | ob.Append("\n\tVideo: " + (600000 + t) + ", \"Video::" + textureName + "\", \"Clip\" {");
911 | ob.Append("\n\t\tType: \"Clip\"");
912 | ob.Append("\n\t\tProperties70: {");
913 | ob.Append("\n\t\t\tP: \"Path\", \"KString\", \"XRefUrl\", \"\", \"" + textureFile + "\"");
914 | ob.Append("\n\t\t}");
915 | ob.Append("\n\t\tFileName: \"" + textureFile + "\"");
916 | ob.Append("\n\t\tRelativeFilename: \"" + relativePath + "\"");
917 | ob.Append("\n\t}");
918 |
919 | //connect video to texture
920 | cb.Append("\n\tC: \"OO\"," + (600000 + t) + "," + (500000 + t));
921 | }
922 |
923 | FBXwriter.Write(ob);
924 | ob.Length = 0;
925 | #endregion
926 |
927 | #region write Model & Geometry data
928 | for (int n = 0; n < srcModel.nodes.Count; n++)
929 | {
930 | var srcNode = srcModel.nodes[n];
931 |
932 | #region if Mesh node
933 | if (srcNode.type > 1)
934 | {
935 | StringBuilder vb = new StringBuilder();
936 | StringBuilder ib = new StringBuilder();
937 |
938 | //write Geometry
939 | ob.Append("\n\tGeometry: " + (100000 + n) + ", \"Geometry::\", \"Mesh\" {");
940 | ob.Append("\n\t\tProperties70: {");
941 | var randomColor = RandomColorGenerator((100000 + n).ToString());
942 | ob.AppendFormat("\n\t\t\tP: \"Color\", \"ColorRGB\", \"Color\", \"\",{0},{1},{2}", ((float)randomColor[0] / 255), ((float)randomColor[1] / 255), ((float)randomColor[2] / 255));
943 | ob.Append("\n\t\t}");
944 |
945 | ob.AppendFormat("\n\t\tVertices: *{0} {{\n\t\t\ta: ", (srcNode.vertexCount * 3));
946 | foreach (var v in srcNode.position) { vb.AppendFormat("{0},", v); }
947 | vb.Length -= 1; //remove last ,
948 | ob.Append(SplitLine(vb.ToString()));
949 | ob.Append("\n\t\t}");
950 | vb.Length = 0;
951 |
952 | ob.AppendFormat("\n\t\tPolygonVertexIndex: *{0} {{\n\t\t\ta: ", srcNode.indices.Length);
953 | for (int f = 0; f < (srcNode.indices.Length / 3); f++)
954 | {
955 | ib.Append(srcNode.indices[f * 3]);
956 | ib.Append(',');
957 | ib.Append(srcNode.indices[f * 3 + 1]);
958 | ib.Append(',');
959 | ib.Append(-1 - srcNode.indices[f * 3 + 2]);
960 | ib.Append(',');
961 | }
962 | ib.Length -= 1; //remove last ,
963 | ob.Append(SplitLine(ib.ToString()));
964 | ob.Append("\n\t\t}");
965 | ib.Length = 0;
966 | ob.Append("\n\t\tGeometryVersion: 124");
967 |
968 | ob.Append("\n\t\tLayerElementNormal: 0 {");
969 | ob.Append("\n\t\t\tVersion: 101");
970 | ob.Append("\n\t\t\tName: \"\"");
971 | ob.Append("\n\t\t\tMappingInformationType: \"ByVertice\"");
972 | ob.Append("\n\t\t\tReferenceInformationType: \"Direct\"");
973 | ob.AppendFormat("\n\t\t\tNormals: *{0} {{\n\t\t\ta: ", (srcNode.vertexCount * 3));
974 | foreach (var v in srcNode.normal) { vb.AppendFormat("{0},", v); }
975 | vb.Length -= 1; //remove last ,
976 | ob.Append(SplitLine(vb.ToString()));
977 | ob.Append("\n\t\t\t}\n\t\t}");
978 | vb.Length = 0;
979 |
980 | ob.Append("\n\t\tLayerElementUV: 0 {");
981 | ob.Append("\n\t\t\tVersion: 101");
982 | ob.Append("\n\t\t\tName: \"UVChannel_0\"");
983 | ob.Append("\n\t\t\tMappingInformationType: \"ByVertice\"");
984 | ob.Append("\n\t\t\tReferenceInformationType: \"Direct\"");
985 | ob.AppendFormat("\n\t\t\tUV: *{0} {{\n\t\t\ta: ", (srcNode.vertexCount * 2));
986 | foreach (var v in srcNode.texture0) { vb.AppendFormat("{0},", v); }
987 | vb.Length -= 1; //remove last ,
988 | ob.Append(SplitLine(vb.ToString()));
989 | ob.Append("\n\t\t\t}\n\t\t}");
990 | vb.Length = 0;
991 |
992 | ob.Append("\n\t\tLayerElementMaterial: 0 {");
993 | ob.Append("\n\t\t\tVersion: 101");
994 | ob.Append("\n\t\t\tName: \"\"");
995 | ob.Append("\n\t\t\tMappingInformationType: \"AllSame\"");
996 | ob.Append("\n\t\t\tReferenceInformationType: \"IndexToDirect\"");
997 | ob.Append("\n\t\t\tMaterials: *1 {");
998 | ob.Append("\n\t\t\t\t0");
999 | ob.Append("\n\t\t\t}");
1000 | ob.Append("\n\t\t}");
1001 |
1002 | ob.Append("\n\t\tLayer: 0 {");
1003 | ob.Append("\n\t\t\tVersion: 100");
1004 | ob.Append("\n\t\t\tLayerElement: {");
1005 | ob.Append("\n\t\t\t\tType: \"LayerElementNormal\"");
1006 | ob.Append("\n\t\t\t\tTypedIndex: 0");
1007 | ob.Append("\n\t\t\t}");
1008 | ob.Append("\n\t\t\tLayerElement: {");
1009 | ob.Append("\n\t\t\t\tType: \"LayerElementMaterial\"");
1010 | ob.Append("\n\t\t\t\tTypedIndex: 0");
1011 | ob.Append("\n\t\t\t}");
1012 | ob.Append("\n\t\t\tLayerElement: {");
1013 | ob.Append("\n\t\t\t\tType: \"LayerElementTexture\"");
1014 | ob.Append("\n\t\t\t\tTypedIndex: 0");
1015 | ob.Append("\n\t\t\t}");
1016 | ob.Append("\n\t\t\tLayerElement: {");
1017 | ob.Append("\n\t\t\t\tType: \"LayerElementBumpTextures\"");
1018 | ob.Append("\n\t\t\t\tTypedIndex: 0");
1019 | ob.Append("\n\t\t\t}");
1020 | ob.Append("\n\t\t\tLayerElement: {");
1021 | ob.Append("\n\t\t\t\tType: \"LayerElementUV\"");
1022 | ob.Append("\n\t\t\t\tTypedIndex: 0");
1023 | ob.Append("\n\t\t\t}");
1024 | ob.Append("\n\t\t}"); //Layer 0 end
1025 | ob.Append("\n\t}"); //Geometry end
1026 |
1027 | //connect Geometry to Model
1028 | cb.Append("\n\tC: \"OO\"," + (100000 + n) + "," + (200000 + n));
1029 | //connect Material to Model
1030 | if (srcNode.materialID > -1) { cb.Append("\n\tC: \"OO\"," + (400000 + srcNode.materialID) + "," + (200000 + n)); }
1031 |
1032 | ob.Append("\n\tModel: " + (200000 + n) + ", \"Model::" + srcNode.name + "\", \"Mesh\" {");
1033 | }
1034 | #endregion
1035 | else { ob.Append("\n\tModel: " + (200000 + n) + ", \"Model::" + srcNode.name + "\", \"Null\" {"); }
1036 |
1037 | ob.Append("\n\t\tVersion: 232");
1038 | ob.Append("\n\t\tProperties70: {");
1039 | ob.Append("\n\t\t\tP: \"InheritType\", \"enum\", \"\", \"\",1");
1040 | ob.Append("\n\t\t\tP: \"ScalingMax\", \"Vector3D\", \"Vector\", \"\",0,0,0");
1041 | ob.Append("\n\t\t\tP: \"DefaultAttributeIndex\", \"int\", \"Integer\", \"\",0");
1042 | ob.Append("\n\t\t\tP: \"Lcl Translation\", \"Lcl Translation\", \"\", \"A\"," + srcNode.translation[0] + "," + srcNode.translation[1] + "," + srcNode.translation[2]);
1043 | ob.Append("\n\t\t\tP: \"Lcl Rotation\", \"Lcl Rotation\", \"\", \"A\"," + srcNode.rotation[0] + "," + srcNode.rotation[1] + "," + srcNode.rotation[2]);
1044 | ob.Append("\n\t\t\tP: \"Lcl Scaling\", \"Lcl Scaling\", \"\", \"A\"," + srcNode.scaling[0] + "," + srcNode.scaling[1] + "," + srcNode.scaling[2]);
1045 | //ob.Append("\n\t\t\tP: \"UDP3DSMAX\", \"KString\", \"\", \"U\", \"Exported_with = kn5 converter by Chipicao&cr;&lf;\"");
1046 | if (srcNode.type > 1 && srcNode.materialID > -1)
1047 | {
1048 | var srcMat = srcModel.materials[srcNode.materialID];
1049 | /*ob.Append("\n\t\t\tP: \"UDP3DSMAX\", \"KString\", \"\", \"U\", \"");
1050 | ob.AppendFormat("diffuseMult = {0}&cr;&lf;", srcMat.diffuseMult);
1051 | ob.AppendFormat("normalMult = {0}&cr;&lf;", srcMat.normalMult);
1052 | ob.AppendFormat("useDetail = {0}&cr;&lf;", srcMat.useDetail);
1053 | ob.AppendFormat("detailUVMultiplier = {0}&cr;&lf;", srcMat.detailUVMultiplier);
1054 | ob.Append("\"");*/
1055 | ob.AppendFormat("\n\t\t\tP: \"UDP3DSMAX\", \"KString\", \"\", \"U\", \"{0}\"", srcMat.shaderProps);
1056 | }
1057 | //ob.Append("\n\t\t\tP: \"MaxHandle\", \"int\", \"Integer\", \"UH\"," + (j + 2 + pmodel.nodeList.Count));
1058 | ob.Append("\n\t\t}");
1059 | ob.Append("\n\t\tShading: T");
1060 | ob.Append("\n\t\tCulling: \"CullingOff\"");
1061 | ob.Append("\n\t}"); //Model end
1062 |
1063 | //connect Model to parent
1064 | if (srcNode.parentID < 0) { cb.Append("\n\tC: \"OO\"," + (200000 + n) + ",0"); }
1065 | else { cb.Append("\n\tC: \"OO\"," + (200000 + n) + "," + (200000 + srcNode.parentID)); }
1066 |
1067 | FBXwriter.Write(ob);
1068 | ob.Length = 0;
1069 | }
1070 | #endregion
1071 |
1072 | cb.Append("\n}");//Connections end
1073 | FBXwriter.Write(cb);
1074 | }
1075 | }
1076 | else { Console.WriteLine("File already exists: {0}.fbx", srcModel.modelName); }
1077 | }
1078 |
1079 | private static byte[] RandomColorGenerator(string name)
1080 | {
1081 | int nameHash = name.GetHashCode();
1082 | Random r = new Random(nameHash);
1083 | //Random r = new Random(DateTime.Now.Millisecond);
1084 |
1085 | byte red = (byte)r.Next(0, 255);
1086 | byte green = (byte)r.Next(0, 255);
1087 | byte blue = (byte)r.Next(0, 255);
1088 |
1089 | return new byte[3] { red, green, blue };
1090 | }
1091 |
1092 | private static string SplitLine(string inputLine) //for FBX 2011
1093 | {
1094 | string outputLines = inputLine;
1095 | int vbSplit = 0;
1096 | for (int v = 0; v < inputLine.Length / 2048; v++)
1097 | {
1098 | vbSplit += 2048;
1099 | if (vbSplit < outputLines.Length)
1100 | {
1101 | vbSplit = outputLines.IndexOf(",", vbSplit) + 1;
1102 | if (vbSplit > 0) { outputLines = outputLines.Insert(vbSplit, "\n"); }
1103 | }
1104 | }
1105 | return outputLines;
1106 | }
1107 |
1108 | private static string MakeRelative(string filePath, string referencePath)
1109 | {
1110 | if (filePath != "" && referencePath != "")
1111 | {
1112 | var fileUri = new Uri(filePath);
1113 | var referenceUri = new Uri(referencePath);
1114 | return referenceUri.MakeRelativeUri(fileUri).ToString().Replace('/', Path.DirectorySeparatorChar);
1115 | }
1116 | else
1117 | {
1118 | return "";
1119 | }
1120 | }
1121 |
1122 | }
1123 | }
1124 |
--------------------------------------------------------------------------------