EditorImport.as 19 KB

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