Urho3D/bin/Data/Scripts/Editor/EditorImport.as
2016-05-18 09:50:12 +02:00

577 lines
20 KiB
ActionScript

// Urho3D editor import functions
String importOptions = "-t";
class ParentAssignment
{
uint childID;
String parentName;
}
class AssetMapping
{
String assetName;
String fullAssetName;
}
Array<AssetMapping> assetMappings;
String assetImporterPath;
int ExecuteAssetImporter(Array<String>@ args)
{
if (assetImporterPath.empty)
{
String exeSuffix = "";
if (GetPlatform() == "Windows")
exeSuffix = ".exe";
// Try both with and without the tool directory; a packaged build may not have the tool directory
assetImporterPath = fileSystem.programDir + "tool/AssetImporter" + exeSuffix;
if (!fileSystem.FileExists(assetImporterPath))
assetImporterPath = fileSystem.programDir + "AssetImporter" + exeSuffix;
}
return fileSystem.SystemRun(assetImporterPath, args);
}
void ImportAnimation(const String&in fileName)
{
if (fileName.empty)
return;
ui.cursor.shape = CS_BUSY;
String modelName = "Models/" + GetFileName(fileName) + ".ani";
String outFileName = sceneResourcePath + modelName;
fileSystem.CreateDir(sceneResourcePath + "Models");
Array<String> args;
args.Push("anim");
args.Push("\"" + fileName + "\"");
args.Push("\"" + outFileName + "\"");
args.Push("-p \"" + sceneResourcePath + "\"");
Array<String> options = importOptions.Trimmed().Split(' ');
for (uint i = 0; i < options.length; ++i)
args.Push(options[i]);
if (ExecuteAssetImporter(args) == 0)
{
}
else
log.Error("Failed to execute AssetImporter to import model");
}
void ImportModel(const String&in fileName)
{
if (fileName.empty)
return;
ui.cursor.shape = CS_BUSY;
String modelName = "Models/" + GetFileName(fileName) + ".mdl";
String outFileName = sceneResourcePath + modelName;
fileSystem.CreateDir(sceneResourcePath + "Models");
Array<String> args;
args.Push("model");
args.Push("\"" + fileName + "\"");
args.Push("\"" + outFileName + "\"");
args.Push("-p \"" + sceneResourcePath + "\"");
Array<String> options = importOptions.Trimmed().Split(' ');
for (uint i = 0; i < options.length; ++i)
args.Push(options[i]);
// If material lists are to be applied, make sure the option to create them exists
if (applyMaterialList)
args.Push("-l");
if (ExecuteAssetImporter(args) == 0)
{
Node@ newNode = editorScene.CreateChild(GetFileName(fileName));
StaticModel@ newModel = newNode.CreateComponent("StaticModel");
newNode.position = GetNewNodePosition();
newModel.model = cache.GetResource("Model", modelName);
newModel.ApplyMaterialList(); // Setup default materials if possible
// Create an undo action for the create
CreateNodeAction action;
action.Define(newNode);
SaveEditAction(action);
SetSceneModified();
FocusNode(newNode);
}
else
log.Error("Failed to execute AssetImporter to import model");
}
void ImportScene(const String&in fileName)
{
if (fileName.empty)
return;
ui.cursor.shape = CS_BUSY;
// Handle Tundra scene files here in code, otherwise via AssetImporter
if (GetExtension(fileName) == ".txml")
ImportTundraScene(fileName);
else
{
// Export scene to a temp file, then load and delete it if successful
Array<String> options = importOptions.Trimmed().Split(' ');
bool isBinary = false;
for (uint i = 0; i < options.length; ++i)
if (options[i] == "-b")
isBinary = true;
String tempSceneName = sceneResourcePath + (isBinary ? TEMP_BINARY_SCENE_NAME : TEMP_SCENE_NAME);
Array<String> args;
args.Push("scene");
args.Push("\"" + fileName + "\"");
args.Push("\"" + tempSceneName + "\"");
args.Push("-p \"" + sceneResourcePath + "\"");
for (uint i = 0; i < options.length; ++i)
args.Push(options[i]);
if (applyMaterialList)
args.Push("-l");
if (ExecuteAssetImporter(args) == 0)
{
skipMruScene = true; // set to avoid adding tempscene to mru
LoadScene(tempSceneName);
fileSystem.Delete(tempSceneName);
UpdateWindowTitle();
}
else
log.Error("Failed to execute AssetImporter to import scene");
}
}
void ImportTundraScene(const String&in fileName)
{
fileSystem.CreateDir(sceneResourcePath + "Materials");
fileSystem.CreateDir(sceneResourcePath + "Models");
fileSystem.CreateDir(sceneResourcePath + "Textures");
XMLFile source;
source.Load(File(fileName, FILE_READ));
String filePath = GetPath(fileName);
XMLElement sceneElem = source.root;
XMLElement entityElem = sceneElem.GetChild("entity");
Array<String> convertedMaterials;
Array<String> convertedMeshes;
Array<ParentAssignment> parentAssignments;
// Read the scene directory structure recursively to get assetname to full assetname mappings
Array<String> fileNames = fileSystem.ScanDir(filePath, "*.*", SCAN_FILES, true);
for (uint i = 0; i < fileNames.length; ++i)
{
AssetMapping mapping;
mapping.assetName = GetFileNameAndExtension(fileNames[i]);
mapping.fullAssetName = fileNames[i];
assetMappings.Push(mapping);
}
// Clear old scene, then create a zone and a directional light first
ResetScene();
// Set standard gravity
editorScene.CreateComponent("PhysicsWorld");
editorScene.physicsWorld.gravity = Vector3(0, -9.81, 0);
// Create zone & global light
Node@ zoneNode = editorScene.CreateChild("Zone");
Zone@ zone = zoneNode.CreateComponent("Zone");
zone.boundingBox = BoundingBox(-1000, 1000);
zone.ambientColor = Color(0.364, 0.364, 0.364);
zone.fogColor = Color(0.707792, 0.770537, 0.831373);
zone.fogStart = 100.0;
zone.fogEnd = 500.0;
Node@ lightNode = editorScene.CreateChild("GlobalLight");
Light@ light = lightNode.CreateComponent("Light");
lightNode.rotation = Quaternion(60, 30, 0);
light.lightType = LIGHT_DIRECTIONAL;
light.color = Color(0.639, 0.639, 0.639);
light.castShadows = true;
light.shadowCascade = CascadeParameters(5, 15.0, 50.0, 0.0, 0.9);
// Loop through scene entities
while (!entityElem.isNull)
{
String nodeName;
String meshName;
String parentName;
Vector3 meshPos;
Vector3 meshRot;
Vector3 meshScale(1, 1, 1);
Vector3 pos;
Vector3 rot;
Vector3 scale(1, 1, 1);
bool castShadows = false;
float drawDistance = 0;
Array<String> materialNames;
int shapeType = -1;
float mass = 0.0f;
Vector3 bodySize;
bool trigger = false;
bool kinematic = false;
uint collisionLayer;
uint collisionMask;
String collisionMeshName;
XMLElement compElem = entityElem.GetChild("component");
while (!compElem.isNull)
{
String compType = compElem.GetAttribute("type");
if (compType == "EC_Mesh" || compType == "Mesh")
{
Array<String> coords = GetComponentAttribute(compElem, "Transform").Split(',');
meshPos = GetVector3FromStrings(coords, 0);
meshPos.z = -meshPos.z; // Convert to lefthanded
meshRot = GetVector3FromStrings(coords, 3);
meshScale = GetVector3FromStrings(coords, 6);
meshName = GetComponentAttribute(compElem, "Mesh ref");
castShadows = GetComponentAttribute(compElem, "Cast shadows").ToBool();
drawDistance = GetComponentAttribute(compElem, "Draw distance").ToFloat();
materialNames = GetComponentAttribute(compElem, "Mesh materials").Split(';');
ProcessRef(meshName);
for (uint i = 0; i < materialNames.length; ++i)
ProcessRef(materialNames[i]);
}
if (compType == "EC_Name" || compType == "Name")
nodeName = GetComponentAttribute(compElem, "name");
if (compType == "EC_Placeable" || compType == "Placeable")
{
Array<String> coords = GetComponentAttribute(compElem, "Transform").Split(',');
pos = GetVector3FromStrings(coords, 0);
pos.z = -pos.z; // Convert to lefthanded
rot = GetVector3FromStrings(coords, 3);
scale = GetVector3FromStrings(coords, 6);
parentName = GetComponentAttribute(compElem, "Parent entity ref");
}
if (compType == "EC_RigidBody" || compType == "RigidBody")
{
shapeType = GetComponentAttribute(compElem, "Shape type").ToInt();
mass = GetComponentAttribute(compElem, "Mass").ToFloat();
bodySize = GetComponentAttribute(compElem, "Size").ToVector3();
collisionMeshName = GetComponentAttribute(compElem, "Collision mesh ref");
trigger = GetComponentAttribute(compElem, "Phantom").ToBool();
kinematic = GetComponentAttribute(compElem, "Kinematic").ToBool();
collisionLayer = GetComponentAttribute(compElem, "Collision Layer").ToInt();
collisionMask = GetComponentAttribute(compElem, "Collision Mask").ToInt();
ProcessRef(collisionMeshName);
}
compElem = compElem.GetNext("component");
}
// If collision mesh not specified for the rigid body, assume same as the visible mesh
if ((shapeType == 4 || shapeType == 6) && collisionMeshName.Trimmed().empty)
collisionMeshName = meshName;
if (!meshName.empty || shapeType >= 0)
{
for (uint i = 0; i < materialNames.length; ++i)
ConvertMaterial(materialNames[i], filePath, convertedMaterials);
ConvertModel(meshName, filePath, convertedMeshes);
ConvertModel(collisionMeshName, filePath, convertedMeshes);
Node@ newNode = editorScene.CreateChild(nodeName);
// Calculate final transform in an Ogre-like fashion
Quaternion quat = GetTransformQuaternion(rot);
Quaternion meshQuat = GetTransformQuaternion(meshRot);
Quaternion finalQuat = quat * meshQuat;
Vector3 finalScale = scale * meshScale;
Vector3 finalPos = pos + quat * (scale * meshPos);
newNode.SetTransform(finalPos, finalQuat, finalScale);
// Create model
if (!meshName.empty)
{
StaticModel@ model = newNode.CreateComponent("StaticModel");
model.model = cache.GetResource("Model", GetOutModelName(meshName));
model.drawDistance = drawDistance;
model.castShadows = castShadows;
// Set default grey material to match Tundra defaults
model.material = cache.GetResource("Material", "Materials/DefaultGrey.xml");
// Then try to assign the actual materials
for (uint i = 0; i < materialNames.length; ++i)
{
Material@ mat = cache.GetResource("Material", GetOutMaterialName(materialNames[i]));
if (mat !is null)
model.materials[i] = mat;
}
}
// Create rigidbody & collision shape
if (shapeType >= 0)
{
RigidBody@ body = newNode.CreateComponent("RigidBody");
// If mesh has scaling, undo it for the collision shape
bodySize.x /= meshScale.x;
bodySize.y /= meshScale.y;
bodySize.z /= meshScale.z;
CollisionShape@ shape = newNode.CreateComponent("CollisionShape");
switch (shapeType)
{
case 0:
shape.SetBox(bodySize);
break;
case 1:
shape.SetSphere(bodySize.x);
break;
case 2:
shape.SetCylinder(bodySize.x, bodySize.y);
break;
case 3:
shape.SetCapsule(bodySize.x, bodySize.y);
break;
case 4:
shape.SetTriangleMesh(cache.GetResource("Model", GetOutModelName(collisionMeshName)), 0, bodySize);
break;
case 6:
shape.SetConvexHull(cache.GetResource("Model", GetOutModelName(collisionMeshName)), 0, bodySize);
break;
}
body.collisionLayer = collisionLayer;
body.collisionMask = collisionMask;
body.trigger = trigger;
body.mass = mass;
}
// Store pending parent assignment if necessary
if (!parentName.empty)
{
ParentAssignment assignment;
assignment.childID = newNode.id;
assignment.parentName = parentName;
parentAssignments.Push(assignment);
}
}
entityElem = entityElem.GetNext("entity");
}
// Process any parent assignments now
for (uint i = 0; i < parentAssignments.length; ++i)
{
Node@ childNode = editorScene.GetNode(parentAssignments[i].childID);
Node@ parentNode = editorScene.GetChild(parentAssignments[i].parentName, true);
if (childNode !is null && parentNode !is null)
childNode.parent = parentNode;
}
UpdateHierarchyItem(editorScene, true);
UpdateWindowTitle();
assetMappings.Clear();
}
String GetFullAssetName(const String& assetName)
{
for (uint i = 0; i < assetMappings.length; ++i)
{
if (assetMappings[i].assetName == assetName)
return assetMappings[i].fullAssetName;
}
return assetName;
}
Quaternion GetTransformQuaternion(Vector3 rotEuler)
{
// Convert rotation to lefthanded
Quaternion rotateX(-rotEuler.x, Vector3(1, 0, 0));
Quaternion rotateY(-rotEuler.y, Vector3(0, 1, 0));
Quaternion rotateZ(-rotEuler.z, Vector3(0, 0, -1));
return rotateZ * rotateY * rotateX;
}
String GetComponentAttribute(XMLElement compElem, const String&in name)
{
XMLElement attrElem = compElem.GetChild("attribute");
while (!attrElem.isNull)
{
if (attrElem.GetAttribute("name") == name)
return attrElem.GetAttribute("value");
attrElem = attrElem.GetNext("attribute");
}
return "";
}
Vector3 GetVector3FromStrings(Array<String>@ coords, uint startIndex)
{
return Vector3(coords[startIndex].ToFloat(), coords[startIndex + 1].ToFloat(), coords[startIndex + 2].ToFloat());
}
void ProcessRef(String& ref)
{
if (ref.StartsWith("local://"))
ref = ref.Substring(8);
if (ref.StartsWith("file://"))
ref = ref.Substring(7);
}
String GetOutModelName(const String&in ref)
{
return "Models/" + GetFullAssetName(ref).Replaced('/', '_').Replaced(".mesh", ".mdl");
}
String GetOutMaterialName(const String&in ref)
{
return "Materials/" + GetFullAssetName(ref).Replaced('/', '_').Replaced(".material", ".xml");
}
String GetOutTextureName(const String&in ref)
{
return "Textures/" + GetFullAssetName(ref).Replaced('/', '_');
}
void ConvertModel(const String&in modelName, const String&in filePath, Array<String>@ convertedModels)
{
if (modelName.Trimmed().empty)
return;
for (uint i = 0; i < convertedModels.length; ++i)
{
if (convertedModels[i] == modelName)
return;
}
String meshFileName = filePath + GetFullAssetName(modelName);
String xmlFileName = filePath + GetFullAssetName(modelName) + ".xml";
String outFileName = sceneResourcePath + GetOutModelName(modelName);
// Convert .mesh to .mesh.xml
String cmdLine = "ogrexmlconverter \"" + meshFileName + "\" \"" + xmlFileName + "\"";
if (!fileSystem.FileExists(xmlFileName))
fileSystem.SystemCommand(cmdLine.Replaced('/', '\\'));
if (!fileSystem.FileExists(outFileName))
{
// Convert .mesh.xml to .mdl
Array<String> args;
args.Push("\"" + xmlFileName + "\"");
args.Push("\"" + outFileName + "\"");
args.Push("-a");
fileSystem.SystemRun(fileSystem.programDir + "tool/OgreImporter", args);
}
convertedModels.Push(modelName);
}
void ConvertMaterial(const String&in materialName, const String&in filePath, Array<String>@ convertedMaterials)
{
if (materialName.Trimmed().empty)
return;
for (uint i = 0; i < convertedMaterials.length; ++i)
{
if (convertedMaterials[i] == materialName)
return;
}
String fileName = filePath + GetFullAssetName(materialName);
String outFileName = sceneResourcePath + GetOutMaterialName(materialName);
if (!fileSystem.FileExists(fileName))
return;
bool mask = false;
bool twoSided = false;
bool uvScaleSet = false;
String textureName;
Vector2 uvScale(1, 1);
Color diffuse(1, 1, 1, 1);
File file(fileName, FILE_READ);
while (!file.eof)
{
String line = file.ReadLine().Trimmed();
if (line.StartsWith("alpha_rejection") || line.StartsWith("scene_blend alpha_blend"))
mask = true;
if (line.StartsWith("cull_hardware none"))
twoSided = true;
// Todo: handle multiple textures per material
if (textureName.empty && line.StartsWith("texture "))
{
textureName = line.Substring(8);
ProcessRef(textureName);
}
if (!uvScaleSet && line.StartsWith("scale "))
{
uvScale = line.Substring(6).ToVector2();
uvScaleSet = true;
}
if (line.StartsWith("diffuse "))
diffuse = line.Substring(8).ToColor();
}
XMLFile outMat;
XMLElement rootElem = outMat.CreateRoot("material");
XMLElement techniqueElem = rootElem.CreateChild("technique");
if (twoSided)
{
XMLElement cullElem = rootElem.CreateChild("cull");
cullElem.SetAttribute("value", "none");
XMLElement shadowCullElem = rootElem.CreateChild("shadowcull");
shadowCullElem.SetAttribute("value", "none");
}
if (!textureName.empty)
{
techniqueElem.SetAttribute("name", mask ? "Techniques/DiffAlphaMask.xml" : "Techniques/Diff.xml");
String outTextureName = GetOutTextureName(textureName);
XMLElement textureElem = rootElem.CreateChild("texture");
textureElem.SetAttribute("unit", "diffuse");
textureElem.SetAttribute("name", outTextureName);
fileSystem.Copy(filePath + GetFullAssetName(textureName), sceneResourcePath + outTextureName);
}
else
techniqueElem.SetAttribute("name", "NoTexture.xml");
if (uvScale != Vector2(1, 1))
{
XMLElement uScaleElem = rootElem.CreateChild("parameter");
uScaleElem.SetAttribute("name", "UOffset");
uScaleElem.SetVector3("value", Vector3(1 / uvScale.x, 0, 0));
XMLElement vScaleElem = rootElem.CreateChild("parameter");
vScaleElem.SetAttribute("name", "VOffset");
vScaleElem.SetVector3("value", Vector3(0, 1 / uvScale.y, 0));
}
if (diffuse != Color(1, 1, 1, 1))
{
XMLElement diffuseElem = rootElem.CreateChild("parameter");
diffuseElem.SetAttribute("name", "MatDiffColor");
diffuseElem.SetColor("value", diffuse);
}
File outFile(outFileName, FILE_WRITE);
outMat.Save(outFile);
outFile.Close();
convertedMaterials.Push(materialName);
}