EditorImport.as 20 KB

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