EditorImport.as 18 KB

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