EditorImport.as 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537
  1. // Urho3D editor import functions
  2. bool generateTangents = false;
  3. class ParentAssignment
  4. {
  5. uint childID;
  6. String parentName;
  7. }
  8. class AssetMapping
  9. {
  10. String assetName;
  11. String fullAssetName;
  12. }
  13. bool useLocalIDs = false;
  14. Array<AssetMapping> assetMappings;
  15. void ImportModel(const String&in fileName)
  16. {
  17. if (fileName.empty)
  18. return;
  19. String modelName = "Models/" + GetFileName(fileName) + ".mdl";
  20. String outFileName = sceneResourcePath + modelName;
  21. fileSystem.CreateDir(sceneResourcePath + "Models");
  22. Array<String> args;
  23. args.Push("model");
  24. args.Push("\"" + fileName + "\"");
  25. args.Push("\"" + outFileName + "\"");
  26. args.Push("-p\"" + sceneResourcePath + "\"");
  27. if (generateTangents)
  28. args.Push("-t");
  29. if (fileSystem.SystemRun(fileSystem.programDir + "AssetImporter", args) == 0)
  30. {
  31. Node@ newNode = editorScene.CreateChild(GetFileName(fileName));
  32. StaticModel@ newModel = newNode.CreateComponent("StaticModel");
  33. newNode.position = GetNewNodePosition();
  34. newModel.model = cache.GetResource("Model", modelName);
  35. ApplyMaterialList(newModel);
  36. UpdateAndFocusNode(newNode);
  37. }
  38. }
  39. void ApplyMaterialList(StaticModel@ newModel)
  40. {
  41. String materialListName;
  42. if (newModel.model !is null)
  43. materialListName = ReplaceExtension(newModel.model.name, ".txt");
  44. else
  45. return;
  46. if (!cache.Exists(materialListName))
  47. return;
  48. File@ list = cache.GetFile(materialListName);
  49. if (list !is null)
  50. {
  51. for (uint i = 0; i < newModel.numGeometries; ++i)
  52. {
  53. if (!list.eof)
  54. {
  55. Material@ newMat = cache.GetResource("Material", list.ReadLine());
  56. if (newMat !is null)
  57. newModel.materials[i] = newMat;
  58. }
  59. else
  60. break;
  61. }
  62. list.Close();
  63. }
  64. }
  65. void ImportScene(const String&in fileName)
  66. {
  67. if (fileName.empty)
  68. return;
  69. // Handle Tundra scene files here in code, otherwise via AssetImporter
  70. if (GetExtension(fileName) == ".txml")
  71. ImportTundraScene(fileName);
  72. else
  73. {
  74. // Export scene to a temp file, then load and delete it if successful
  75. String tempSceneName = sceneResourcePath + "_tempscene_.xml";
  76. Array<String> args;
  77. args.Push("scene");
  78. args.Push("\"" + fileName + "\"");
  79. args.Push("\"" + tempSceneName + "\"");
  80. args.Push("-p\"" + sceneResourcePath + "\"");
  81. if (generateTangents)
  82. args.Push("-t");
  83. if (useLocalIDs)
  84. args.Push("-i");
  85. if (fileSystem.SystemRun(fileSystem.programDir + "AssetImporter", args) == 0)
  86. {
  87. String currentFileName = sceneFileName;
  88. LoadScene(tempSceneName);
  89. fileSystem.Delete(tempSceneName);
  90. sceneFileName = currentFileName;
  91. UpdateWindowTitle();
  92. }
  93. }
  94. }
  95. void ImportTundraScene(const String&in fileName)
  96. {
  97. fileSystem.CreateDir(sceneResourcePath + "Materials");
  98. fileSystem.CreateDir(sceneResourcePath + "Models");
  99. fileSystem.CreateDir(sceneResourcePath + "Textures");
  100. XMLFile source;
  101. source.Load(File(fileName, FILE_READ));
  102. String filePath = GetPath(fileName);
  103. XMLElement sceneElem = source.root;
  104. XMLElement entityElem = sceneElem.GetChild("entity");
  105. Array<String> convertedMaterials;
  106. Array<String> convertedMeshes;
  107. Array<ParentAssignment> parentAssignments;
  108. // Read the scene directory structure recursively to get assetname to full assetname mappings
  109. Array<String> fileNames = fileSystem.ScanDir(filePath, "*.*", SCAN_FILES, true);
  110. for (uint i = 0; i < fileNames.length; ++i)
  111. {
  112. AssetMapping mapping;
  113. mapping.assetName = GetFileNameAndExtension(fileNames[i]);
  114. mapping.fullAssetName = fileNames[i];
  115. assetMappings.Push(mapping);
  116. }
  117. // Clear old scene, then create a zone and a directional light first
  118. CreateScene();
  119. // Set standard gravity
  120. editorScene.physicsWorld.gravity = Vector3(0, -9.81, 0);
  121. // Create zone & global light
  122. Node@ zoneNode = editorScene.CreateChild("Zone", useLocalIDs ? LOCAL : REPLICATED);
  123. Zone@ zone = zoneNode.CreateComponent("Zone");
  124. zone.boundingBox = BoundingBox(-1000, 1000);
  125. zone.ambientColor = Color(0.364, 0.364, 0.364);
  126. zone.fogColor = Color(0.707792, 0.770537, 0.831373);
  127. zone.fogStart = 100.0;
  128. zone.fogEnd = 500.0;
  129. Node@ lightNode = editorScene.CreateChild("GlobalLight", useLocalIDs ? LOCAL : REPLICATED);
  130. Light@ light = lightNode.CreateComponent("Light");
  131. lightNode.rotation = Quaternion(60, 30, 0);
  132. light.lightType = LIGHT_DIRECTIONAL;
  133. light.color = Color(0.639, 0.639, 0.639);
  134. light.castShadows = true;
  135. light.shadowCascade = CascadeParameters(5, 15.0, 50.0, 0.0, 0.9);
  136. // Loop through scene entities
  137. while (!entityElem.isNull)
  138. {
  139. String nodeName;
  140. String meshName;
  141. String parentName;
  142. Vector3 meshPos;
  143. Vector3 meshRot;
  144. Vector3 meshScale(1, 1, 1);
  145. Vector3 pos;
  146. Vector3 rot;
  147. Vector3 scale(1, 1, 1);
  148. bool castShadows = false;
  149. float drawDistance = 0;
  150. Array<String> materialNames;
  151. int shapeType = -1;
  152. float mass = 0.0f;
  153. Vector3 bodySize;
  154. bool phantom = false;
  155. bool kinematic = false;
  156. uint collisionLayer;
  157. uint collisionMask;
  158. String collisionMeshName;
  159. XMLElement compElem = entityElem.GetChild("component");
  160. while (!compElem.isNull)
  161. {
  162. String compType = compElem.GetAttribute("type");
  163. if (compType == "EC_Mesh")
  164. {
  165. Array<String> coords = GetComponentAttribute(compElem, "Transform").Split(',');
  166. meshPos = GetVector3FromStrings(coords, 0);
  167. meshPos.z = -meshPos.z; // Convert to lefthanded
  168. meshRot = GetVector3FromStrings(coords, 3);
  169. meshScale = GetVector3FromStrings(coords, 6);
  170. meshName = GetComponentAttribute(compElem, "Mesh ref");
  171. castShadows = GetComponentAttribute(compElem, "Cast shadows").ToBool();
  172. drawDistance = GetComponentAttribute(compElem, "Draw distance").ToFloat();
  173. materialNames = GetComponentAttribute(compElem, "Mesh materials").Split(';');
  174. ProcessRef(meshName);
  175. for (uint i = 0; i < materialNames.length; ++i)
  176. ProcessRef(materialNames[i]);
  177. }
  178. if (compType == "EC_Name")
  179. nodeName = GetComponentAttribute(compElem, "name");
  180. if (compType == "EC_Placeable")
  181. {
  182. Array<String> coords = GetComponentAttribute(compElem, "Transform").Split(',');
  183. pos = GetVector3FromStrings(coords, 0);
  184. pos.z = -pos.z; // Convert to lefthanded
  185. rot = GetVector3FromStrings(coords, 3);
  186. scale = GetVector3FromStrings(coords, 6);
  187. parentName = GetComponentAttribute(compElem, "Parent entity ref");
  188. }
  189. if (compType == "EC_RigidBody")
  190. {
  191. shapeType = GetComponentAttribute(compElem, "Shape type").ToInt();
  192. mass = GetComponentAttribute(compElem, "Mass").ToFloat();
  193. bodySize = GetComponentAttribute(compElem, "Size").ToVector3();
  194. collisionMeshName = GetComponentAttribute(compElem, "Collision mesh ref");
  195. phantom = GetComponentAttribute(compElem, "Phantom").ToBool();
  196. kinematic = GetComponentAttribute(compElem, "Kinematic").ToBool();
  197. collisionLayer = GetComponentAttribute(compElem, "Collision Layer").ToInt();
  198. collisionMask = GetComponentAttribute(compElem, "Collision Mask").ToInt();
  199. ProcessRef(collisionMeshName);
  200. }
  201. compElem = compElem.GetNext("component");
  202. }
  203. // If collision mesh not specified for the rigid body, assume same as the visible mesh
  204. if ((shapeType == 4 || shapeType == 6) && collisionMeshName.Trimmed().empty)
  205. collisionMeshName = meshName;
  206. if (!meshName.empty || shapeType >= 0)
  207. {
  208. for (uint i = 0; i < materialNames.length; ++i)
  209. ConvertMaterial(materialNames[i], filePath, convertedMaterials);
  210. ConvertModel(meshName, filePath, convertedMeshes);
  211. ConvertModel(collisionMeshName, filePath, convertedMeshes);
  212. Node@ newNode = editorScene.CreateChild(nodeName, useLocalIDs ? LOCAL : REPLICATED);
  213. // Calculate final transform in an Ogre-like fashion
  214. Quaternion quat = GetTransformQuaternion(rot);
  215. Quaternion meshQuat = GetTransformQuaternion(meshRot);
  216. Quaternion finalQuat = quat * meshQuat;
  217. Vector3 finalScale = scale * meshScale;
  218. Vector3 finalPos = pos + quat * (scale * meshPos);
  219. newNode.SetTransform(finalPos, finalQuat, finalScale);
  220. // Create model
  221. if (!meshName.empty)
  222. {
  223. StaticModel@ model = newNode.CreateComponent("StaticModel");
  224. model.model = cache.GetResource("Model", GetOutModelName(meshName));
  225. model.drawDistance = drawDistance;
  226. model.castShadows = castShadows;
  227. // Set default grey material to match Tundra defaults
  228. model.material = cache.GetResource("Material", "Materials/DefaultGrey.xml");
  229. // Then try to assign the actual materials
  230. for (uint i = 0; i < materialNames.length; ++i)
  231. {
  232. Material@ mat = cache.GetResource("Material", GetOutMaterialName(materialNames[i]));
  233. if (mat !is null)
  234. model.materials[i] = mat;
  235. }
  236. }
  237. // Create rigidbody & collision shape
  238. if (shapeType >= 0)
  239. {
  240. RigidBody@ body = newNode.CreateComponent("RigidBody");
  241. // If mesh has scaling, undo it for the collision shape
  242. bodySize.x /= meshScale.x;
  243. bodySize.y /= meshScale.y;
  244. bodySize.z /= meshScale.z;
  245. CollisionShape@ shape = newNode.CreateComponent("CollisionShape");
  246. switch (shapeType)
  247. {
  248. case 0:
  249. shape.SetBox(bodySize);
  250. break;
  251. case 1:
  252. shape.SetSphere(bodySize.x);
  253. break;
  254. case 2:
  255. shape.SetCylinder(bodySize.x, bodySize.y);
  256. break;
  257. case 3:
  258. shape.SetCapsule(bodySize.x, bodySize.y);
  259. break;
  260. case 4:
  261. shape.SetTriangleMesh(cache.GetResource("Model", GetOutModelName(collisionMeshName)), 0, bodySize);
  262. break;
  263. case 6:
  264. shape.SetConvexHull(cache.GetResource("Model", GetOutModelName(collisionMeshName)), 0, bodySize);
  265. break;
  266. }
  267. body.collisionLayer = collisionLayer;
  268. body.collisionMask = collisionMask;
  269. body.phantom = phantom;
  270. body.mass = mass;
  271. }
  272. // Store pending parent assignment if necessary
  273. if (!parentName.empty)
  274. {
  275. ParentAssignment assignment;
  276. assignment.childID = newNode.id;
  277. assignment.parentName = parentName;
  278. parentAssignments.Push(assignment);
  279. }
  280. }
  281. entityElem = entityElem.GetNext("entity");
  282. }
  283. // Process any parent assignments now
  284. for (uint i = 0; i < parentAssignments.length; ++i)
  285. {
  286. Node@ childNode = editorScene.GetNode(parentAssignments[i].childID);
  287. Node@ parentNode = editorScene.GetChild(parentAssignments[i].parentName, true);
  288. if (childNode !is null && parentNode !is null)
  289. childNode.parent = parentNode;
  290. }
  291. UpdateSceneWindow();
  292. UpdateWindowTitle();
  293. assetMappings.Clear();
  294. }
  295. String GetFullAssetName(const String& assetName)
  296. {
  297. for (uint i = 0; i < assetMappings.length; ++i)
  298. {
  299. if (assetMappings[i].assetName == assetName)
  300. return assetMappings[i].fullAssetName;
  301. }
  302. return assetName;
  303. }
  304. Quaternion GetTransformQuaternion(Vector3 rotEuler)
  305. {
  306. // Convert rotation to lefthanded
  307. Quaternion rotateX(-rotEuler.x, Vector3(1, 0, 0));
  308. Quaternion rotateY(-rotEuler.y, Vector3(0, 1, 0));
  309. Quaternion rotateZ(-rotEuler.z, Vector3(0, 0, -1));
  310. return rotateZ * rotateY * rotateX;
  311. }
  312. String GetComponentAttribute(XMLElement compElem, const String&in name)
  313. {
  314. XMLElement attrElem = compElem.GetChild("attribute");
  315. while (!attrElem.isNull)
  316. {
  317. if (attrElem.GetAttribute("name") == name)
  318. return attrElem.GetAttribute("value");
  319. attrElem = attrElem.GetNext("attribute");
  320. }
  321. return "";
  322. }
  323. Vector3 GetVector3FromStrings(Array<String>@ coords, uint startIndex)
  324. {
  325. return Vector3(coords[startIndex].ToFloat(), coords[startIndex + 1].ToFloat(), coords[startIndex + 2].ToFloat());
  326. }
  327. void ProcessRef(String& ref)
  328. {
  329. if (ref.StartsWith("local://"))
  330. ref = ref.Substring(8);
  331. if (ref.StartsWith("file://"))
  332. ref = ref.Substring(7);
  333. }
  334. String GetOutModelName(const String&in ref)
  335. {
  336. return "Models/" + GetFullAssetName(ref).Replaced('/', '_').Replaced(".mesh", ".mdl");
  337. }
  338. String GetOutMaterialName(const String&in ref)
  339. {
  340. return "Materials/" + GetFullAssetName(ref).Replaced('/', '_').Replaced(".material", ".xml");
  341. }
  342. String GetOutTextureName(const String&in ref)
  343. {
  344. return "Textures/" + GetFullAssetName(ref).Replaced('/', '_');
  345. }
  346. void ConvertModel(const String&in modelName, const String&in filePath, Array<String>@ convertedModels)
  347. {
  348. if (modelName.Trimmed().empty)
  349. return;
  350. for (uint i = 0; i < convertedModels.length; ++i)
  351. {
  352. if (convertedModels[i] == modelName)
  353. return;
  354. }
  355. String meshFileName = filePath + GetFullAssetName(modelName);
  356. String xmlFileName = filePath + GetFullAssetName(modelName) + ".xml";
  357. String outFileName = sceneResourcePath + GetOutModelName(modelName);
  358. // Convert .mesh to .mesh.xml
  359. String cmdLine = "ogrexmlconverter \"" + meshFileName + "\" \"" + xmlFileName + "\"";
  360. if (!fileSystem.FileExists(xmlFileName))
  361. fileSystem.SystemCommand(cmdLine.Replaced('/', '\\'));
  362. if (!fileSystem.FileExists(outFileName))
  363. {
  364. // Convert .mesh.xml to .mdl
  365. Array<String> args;
  366. args.Push("\"" + xmlFileName + "\"");
  367. args.Push("\"" + outFileName + "\"");
  368. args.Push("-a");
  369. fileSystem.SystemRun(fileSystem.programDir + "OgreImporter", args);
  370. }
  371. convertedModels.Push(modelName);
  372. }
  373. void ConvertMaterial(const String&in materialName, const String&in filePath, Array<String>@ convertedMaterials)
  374. {
  375. if (materialName.Trimmed().empty)
  376. return;
  377. for (uint i = 0; i < convertedMaterials.length; ++i)
  378. {
  379. if (convertedMaterials[i] == materialName)
  380. return;
  381. }
  382. String fileName = filePath + GetFullAssetName(materialName);
  383. String outFileName = sceneResourcePath + GetOutMaterialName(materialName);
  384. if (!fileSystem.FileExists(fileName))
  385. return;
  386. bool mask = false;
  387. bool twoSided = false;
  388. bool uvScaleSet = false;
  389. String textureName;
  390. Vector2 uvScale(1, 1);
  391. Color diffuse(1, 1, 1, 1);
  392. File file(fileName, FILE_READ);
  393. while (!file.eof)
  394. {
  395. String line = file.ReadLine().Trimmed();
  396. if (line.StartsWith("alpha_rejection") || line.StartsWith("scene_blend alpha_blend"))
  397. mask = true;
  398. if (line.StartsWith("cull_hardware none"))
  399. twoSided = true;
  400. // Todo: handle multiple textures per material
  401. if (textureName.empty && line.StartsWith("texture "))
  402. {
  403. textureName = line.Substring(8);
  404. ProcessRef(textureName);
  405. }
  406. if (!uvScaleSet && line.StartsWith("scale "))
  407. {
  408. uvScale = line.Substring(6).ToVector2();
  409. uvScaleSet = true;
  410. }
  411. if (line.StartsWith("diffuse "))
  412. diffuse = line.Substring(8).ToColor();
  413. }
  414. XMLFile outMat;
  415. XMLElement rootElem = outMat.CreateRoot("material");
  416. XMLElement techniqueElem = rootElem.CreateChild("technique");
  417. if (twoSided)
  418. {
  419. XMLElement cullElem = rootElem.CreateChild("cull");
  420. cullElem.SetAttribute("value", "none");
  421. XMLElement shadowCullElem = rootElem.CreateChild("shadowcull");
  422. shadowCullElem.SetAttribute("value", "none");
  423. }
  424. if (!textureName.empty)
  425. {
  426. techniqueElem.SetAttribute("name", mask ? "Techniques/DiffAlphaMask.xml" : "Techniques/Diff.xml");
  427. String outTextureName = GetOutTextureName(textureName);
  428. XMLElement textureElem = rootElem.CreateChild("texture");
  429. textureElem.SetAttribute("unit", "diffuse");
  430. textureElem.SetAttribute("name", outTextureName);
  431. fileSystem.Copy(filePath + GetFullAssetName(textureName), sceneResourcePath + outTextureName);
  432. }
  433. else
  434. techniqueElem.SetAttribute("name", "NoTexture.xml");
  435. if (uvScale != Vector2(1, 1))
  436. {
  437. XMLElement uScaleElem = rootElem.CreateChild("parameter");
  438. uScaleElem.SetAttribute("name", "UOffset");
  439. uScaleElem.SetVector3("value", Vector3(1 / uvScale.x, 0, 0));
  440. XMLElement vScaleElem = rootElem.CreateChild("parameter");
  441. vScaleElem.SetAttribute("name", "VOffset");
  442. vScaleElem.SetVector3("value", Vector3(0, 1 / uvScale.y, 0));
  443. }
  444. if (diffuse != Color(1, 1, 1, 1))
  445. {
  446. XMLElement diffuseElem = rootElem.CreateChild("parameter");
  447. diffuseElem.SetAttribute("name", "MatDiffColor");
  448. diffuseElem.SetColor("value", diffuse);
  449. }
  450. File outFile(outFileName, FILE_WRITE);
  451. outMat.Save(outFile);
  452. outFile.Close();
  453. convertedMaterials.Push(materialName);
  454. }