Browse Source

Added LOD combine mode to AssetImporter.
Documentation update.

Lasse Öörni 15 years ago
parent
commit
88a24c5bd0

+ 1 - 1
Engine/Renderer/Model.h

@@ -89,7 +89,7 @@ public:
     bool setGeometry(unsigned index, unsigned lodLevel, Geometry* geometry);
     bool setGeometry(unsigned index, unsigned lodLevel, Geometry* geometry);
     //! Set skeleton
     //! Set skeleton
     void setSkeleton(const Skeleton& skeleton);
     void setSkeleton(const Skeleton& skeleton);
-    //! Set bone mappings when model has more bones than skinning shader can handle
+    //! Set bone mappings when model has more bones than the skinning shader can handle
     void setGeometryBoneMappings(const std::vector<std::vector<unsigned> >& mappings);
     void setGeometryBoneMappings(const std::vector<std::vector<unsigned> >& mappings);
     //! Set vertex morphs
     //! Set vertex morphs
     void setMorphs(const std::vector<ModelMorph>& morphs);
     void setMorphs(const std::vector<ModelMorph>& morphs);

+ 223 - 62
Tools/AssetImporter/AssetImporter.cpp

@@ -91,6 +91,7 @@ struct ExportScene
 int main(int argc, char** argv);
 int main(int argc, char** argv);
 void run(const std::vector<std::string>& arguments);
 void run(const std::vector<std::string>& arguments);
 void dumpNodes(const aiScene* scene, aiNode* rootNode, unsigned level);
 void dumpNodes(const aiScene* scene, aiNode* rootNode, unsigned level);
+
 void exportModel(const aiScene* scene, aiNode* rootNode, const std::string& outName, bool noAnimations);
 void exportModel(const aiScene* scene, aiNode* rootNode, const std::string& outName, bool noAnimations);
 void collectMeshes(ExportModel& model, aiNode* node);
 void collectMeshes(ExportModel& model, aiNode* node);
 void collectBones(ExportModel& model);
 void collectBones(ExportModel& model);
@@ -99,13 +100,19 @@ void collectAnimations(ExportModel& model);
 void buildBoneCollisionInfo(ExportModel& model);
 void buildBoneCollisionInfo(ExportModel& model);
 void buildAndSaveModel(ExportModel& model);
 void buildAndSaveModel(ExportModel& model);
 void buildAndSaveAnimations(ExportModel& model);
 void buildAndSaveAnimations(ExportModel& model);
-void exportScene(const aiScene* scene, aiNode* rootNode, const std::string& outName, const std::string& resourcePath, bool localIDs,
-    bool noExtensions, bool saveBinary);
+
+void exportScene(const aiScene* scene, aiNode* rootNode, const std::string& outName, const std::string& resourcePath,
+    bool localIDs, bool noExtensions, bool saveBinary);
 void collectSceneModels(ExportScene& scene, aiNode* node);
 void collectSceneModels(ExportScene& scene, aiNode* node);
 void collectSceneNodes(ExportScene& scene, aiNode* node);
 void collectSceneNodes(ExportScene& scene, aiNode* node);
 void buildAndSaveScene(ExportScene& scene);
 void buildAndSaveScene(ExportScene& scene);
+
 void exportMaterials(const aiScene* scene, const std::string& outName, const std::string& resourcePath);
 void exportMaterials(const aiScene* scene, const std::string& outName, const std::string& resourcePath);
 void buildAndSaveMaterial(aiMaterial* material, const std::string& outName, const std::string& resourcePath);
 void buildAndSaveMaterial(aiMaterial* material, const std::string& outName, const std::string& resourcePath);
+
+void combineLods(const std::vector<float>& lodDistances, const std::vector<std::string>& modelNames, const std::string& outName,
+    unsigned collisionLodLevel, unsigned raycastLodLevel, unsigned occlusionLodLevel);
+
 std::set<unsigned> getMeshesUnderNodeSet(aiNode* node);
 std::set<unsigned> getMeshesUnderNodeSet(aiNode* node);
 std::vector<std::pair<aiNode*, aiMesh*> > getMeshesUnderNode(const aiScene* scene, aiNode* node);
 std::vector<std::pair<aiNode*, aiMesh*> > getMeshesUnderNode(const aiScene* scene, aiNode* node);
 unsigned getMeshIndex(const aiScene* scene, aiMesh* mesh);
 unsigned getMeshIndex(const aiScene* scene, aiMesh* mesh);
@@ -114,20 +121,23 @@ aiBone* getMeshBone(ExportModel& model, const std::string& boneName);
 Matrix4x3 getOffsetMatrix(ExportModel& model, const std::string& boneName, bool useMeshTransform);
 Matrix4x3 getOffsetMatrix(ExportModel& model, const std::string& boneName, bool useMeshTransform);
 void getBlendData(ExportModel& model, aiMesh* mesh, std::vector<unsigned>& boneMappings, std::vector<std::vector<unsigned char> >&
 void getBlendData(ExportModel& model, aiMesh* mesh, std::vector<unsigned>& boneMappings, std::vector<std::vector<unsigned char> >&
     blendIndices, std::vector<std::vector<float> >& blendWeights);
     blendIndices, std::vector<std::vector<float> >& blendWeights);
+
 void writeShortIndices(unsigned short*& dest, aiMesh* mesh, unsigned index, unsigned offset);
 void writeShortIndices(unsigned short*& dest, aiMesh* mesh, unsigned index, unsigned offset);
 void writeLargeIndices(unsigned*& dest, aiMesh* mesh, unsigned index, unsigned offset);
 void writeLargeIndices(unsigned*& dest, aiMesh* mesh, unsigned index, unsigned offset);
 void writeVertex(float*& dest, aiMesh* mesh, unsigned index, unsigned elementMask, BoundingBox& box,
 void writeVertex(float*& dest, aiMesh* mesh, unsigned index, unsigned elementMask, BoundingBox& box,
     const Matrix4x3& vertexTransform, const Matrix3& normalTransform, std::vector<std::vector<unsigned char> >& blendIndices,
     const Matrix4x3& vertexTransform, const Matrix3& normalTransform, std::vector<std::vector<unsigned char> >& blendIndices,
     std::vector<std::vector<float> >& blendWeights);
     std::vector<std::vector<float> >& blendWeights);
 unsigned getElementMask(aiMesh* mesh);
 unsigned getElementMask(aiMesh* mesh);
+
 aiNode* findNode(const std::string& name, aiNode* rootNode, bool caseSensitive = true);
 aiNode* findNode(const std::string& name, aiNode* rootNode, bool caseSensitive = true);
+aiMatrix4x4 getDerivedTransform(aiNode* node, aiNode* rootNode);
+aiMatrix4x4 getDerivedTransform(aiMatrix4x4 transform, aiNode* node, aiNode* rootNode);
+void getPosRotScale(const aiMatrix4x4& transform, Vector3& pos, Quaternion& rot, Vector3& scale);
+
 std::string toStdString(const aiString& str);
 std::string toStdString(const aiString& str);
 Vector3 toVector3(const aiVector3D& vec);
 Vector3 toVector3(const aiVector3D& vec);
 Vector2 toVector2(const aiVector2D& vec);
 Vector2 toVector2(const aiVector2D& vec);
 Quaternion toQuaternion(const aiQuaternion& quat);
 Quaternion toQuaternion(const aiQuaternion& quat);
-aiMatrix4x4 getDerivedTransform(aiNode* node, aiNode* rootNode);
-aiMatrix4x4 getDerivedTransform(aiMatrix4x4 transform, aiNode* node, aiNode* rootNode);
-void getPosRotScale(const aiMatrix4x4& transform, Vector3& pos, Quaternion& rot, Vector3& scale);
 void errorExit(const std::string& error);
 void errorExit(const std::string& error);
 
 
 int main(int argc, char** argv)
 int main(int argc, char** argv)
@@ -161,10 +171,15 @@ void run(const std::vector<std::string>& arguments)
             "model     Export a model and animations\n"
             "model     Export a model and animations\n"
             "scene     Export a scene and its models\n"
             "scene     Export a scene and its models\n"
             "dumpnodes Dump scene node structure. No output file is generated\n"
             "dumpnodes Dump scene node structure. No output file is generated\n"
+            "lod       Combine several Urho3D models as LOD levels of the output model\n"
+            "          Syntax: lod <dist0> <mdl0> <dist1 <mdl1> ... <output file>\n"
             "\n"
             "\n"
             "Options:\n"
             "Options:\n"
-            "-b        Save scene in binary format (default: XML)\n"
-            "-l        Use local ID's for scene entities\n"
+            "-b        Save scene in binary format, default format is XML\n"
+            "-i        Use local ID's for scene entities\n"
+            "-lcX      Use LOD level X for collision mesh, default is middle LOD\n"
+            "-lrX      Use LOD level X for raycast, default is same as visible\n"
+            "-loX      Use LOD level X for occlusion, default is same as visible\n"
             "-na       Do not export animations\n"
             "-na       Do not export animations\n"
             "-ne       Do not create Octree & PhysicsWorld extensions to the scene\n"
             "-ne       Do not create Octree & PhysicsWorld extensions to the scene\n"
             "-nm       Do not export materials\n"
             "-nm       Do not export materials\n"
@@ -175,12 +190,8 @@ void run(const std::vector<std::string>& arguments)
     }
     }
     
     
     std::string command = toLower(arguments[0]);
     std::string command = toLower(arguments[0]);
-    std::string inFile = arguments[1];
-    std::string outFile;
     std::string rootNodeName;
     std::string rootNodeName;
     std::string resourcePath;
     std::string resourcePath;
-    if (arguments.size() > 2)
-        outFile = arguments[2];
     
     
     bool noMaterials = false;
     bool noMaterials = false;
     bool noAnimations = false;
     bool noAnimations = false;
@@ -200,6 +211,10 @@ void run(const std::vector<std::string>& arguments)
         aiProcess_FindInstances |
         aiProcess_FindInstances |
         aiProcess_OptimizeMeshes;
         aiProcess_OptimizeMeshes;
     
     
+    unsigned collisionLodLevel = M_MAX_UNSIGNED;
+    unsigned raycastLodLevel = M_MAX_UNSIGNED;
+    unsigned occlusionLodLevel = M_MAX_UNSIGNED;
+    
     for (unsigned i = 3; i < arguments.size(); ++i)
     for (unsigned i = 3; i < arguments.size(); ++i)
     {
     {
         if ((arguments[i].length() >= 2) && (arguments[i][0] == '-'))
         if ((arguments[i].length() >= 2) && (arguments[i][0] == '-'))
@@ -213,9 +228,28 @@ void run(const std::vector<std::string>& arguments)
             case 'b':
             case 'b':
                 saveBinary = true;
                 saveBinary = true;
                 break;
                 break;
-            case 'l':
+            case 'i':
                 localIDs = true;
                 localIDs = true;
                 break;
                 break;
+            case 'l':
+                if (parameter.length() > 1)
+                {
+                    switch (tolower(parameter[0]))
+                    {
+                    case 'c':
+                        collisionLodLevel = toInt(parameter.substr(1));
+                        break;
+                        
+                    case 'r':
+                        raycastLodLevel = toInt(parameter.substr(1));
+                        break;
+                        
+                    case 'o':
+                        occlusionLodLevel = toInt(parameter.substr(1));
+                        break;
+                    }
+                }
+                break;
             case 'p':
             case 'p':
                 resourcePath = parameter;
                 resourcePath = parameter;
                 break;
                 break;
@@ -246,34 +280,87 @@ void run(const std::vector<std::string>& arguments)
         }
         }
     }
     }
     
     
-    if ((command != "model") && (command != "scene") && (command != "dumpnodes"))
-        errorExit("Unrecognized command " + command);
-    
-    Assimp::Importer importer;
-    std::cout << "Reading file " << inFile << std::endl;
-    const aiScene* scene = importer.ReadFile(inFile.c_str(), flags);
-    if (!scene)
-        errorExit("Could not open or parse input file " + inFile);
-    
-    aiNode* rootNode = scene->mRootNode;
-    if (!rootNodeName.empty())
+    if ((command == "model") || (command == "scene") || (command == "dumpnodes"))
+    {
+        std::string inFile = arguments[1];
+        std::string outFile;
+        if (arguments.size() > 2)
+            outFile = arguments[2];
+        
+        Assimp::Importer importer;
+        std::cout << "Reading file " << inFile << std::endl;
+        const aiScene* scene = importer.ReadFile(inFile.c_str(), flags);
+        if (!scene)
+            errorExit("Could not open or parse input file " + inFile);
+        
+        aiNode* rootNode = scene->mRootNode;
+        if (!rootNodeName.empty())
+        {
+            rootNode = findNode(rootNodeName, scene->mRootNode, false);
+            if (!rootNode)
+                errorExit("Could not find scene node " + rootNodeName);
+        }
+        
+        if (!resourcePath.empty())
+            resourcePath = fixPath(resourcePath);
+        
+        if (command == "model")
+            exportModel(scene, rootNode, outFile, noAnimations);
+        if (command == "scene")
+            exportScene(scene, rootNode, outFile, resourcePath, localIDs, noExtensions, saveBinary);
+        if ((!noMaterials) && ((command == "model") || (command == "scene")))
+            exportMaterials(scene, outFile, resourcePath);
+        if (command == "dumpnodes")
+            dumpNodes(scene, rootNode, 0);
+    }
+    else if (command == "lod")
     {
     {
-        rootNode = findNode(rootNodeName, scene->mRootNode, false);
-        if (!rootNode)
-            errorExit("Could not find scene node " + rootNodeName);
+        std::vector<float> lodDistances;
+        std::vector<std::string> modelNames;
+        std::string outFile;
+        
+        unsigned numLodArguments = 0;
+        for (unsigned i = 1; i < arguments.size(); ++i)
+        {
+            if (arguments[i][0] == '-')
+                break;
+            ++numLodArguments;
+        }
+        if (numLodArguments < 4)
+            errorExit("Must define at least 2 LOD levels");
+        if (!(numLodArguments & 1))
+            errorExit("No output file defined");
+        
+        for (unsigned i = 1; i < numLodArguments + 1; ++i)
+        {
+            if (i == numLodArguments)
+                outFile = arguments[i];
+            else
+            {
+                if (i & 1)
+                    lodDistances.push_back(max(toFloat(arguments[i]), 0.0f));
+                else
+                    modelNames.push_back(arguments[i]);
+            }
+        }
+        
+        if (collisionLodLevel >= lodDistances.size())
+            collisionLodLevel = M_MAX_UNSIGNED;
+        if (raycastLodLevel >= lodDistances.size())
+            raycastLodLevel = M_MAX_UNSIGNED;
+        if (occlusionLodLevel >= lodDistances.size())
+            occlusionLodLevel = M_MAX_UNSIGNED;
+        
+        if (lodDistances[0] != 0.0f)
+        {
+            std::cout << "Warning: first LOD distance forced to 0" << std::endl;
+            lodDistances[0] = 0.0f;
+        }
+        
+        combineLods(lodDistances, modelNames, outFile, collisionLodLevel, raycastLodLevel, occlusionLodLevel);
     }
     }
-    
-    if (!resourcePath.empty())
-        resourcePath = fixPath(resourcePath);
-    
-    if (command == "model")
-        exportModel(scene, rootNode, outFile, noAnimations);
-    if (command == "scene")
-        exportScene(scene, rootNode, outFile, resourcePath, localIDs, noExtensions, saveBinary);
-    if ((!noMaterials) && ((command == "model") || (command == "scene")))
-        exportMaterials(scene, outFile, resourcePath);
-    if (command == "dumpnodes")
-        dumpNodes(scene, rootNode, 0);
+    else
+        errorExit("Unrecognized command " + command);
 }
 }
 
 
 void dumpNodes(const aiScene* scene, aiNode* rootNode, unsigned level)
 void dumpNodes(const aiScene* scene, aiNode* rootNode, unsigned level)
@@ -443,6 +530,8 @@ void collectAnimations(ExportModel& model)
         if (modelBoneFound)
         if (modelBoneFound)
             model.mAnimations.push_back(anim);
             model.mAnimations.push_back(anim);
     }
     }
+    
+    //! \todo Vertex morphs are ignored for now
 }
 }
 
 
 void buildBoneCollisionInfo(ExportModel& model)
 void buildBoneCollisionInfo(ExportModel& model)
@@ -1122,6 +1211,78 @@ void buildAndSaveMaterial(aiMaterial* material, const std::string& outName, cons
     outMaterial.save(outFile);
     outMaterial.save(outFile);
 }
 }
 
 
+void combineLods(const std::vector<float>& lodDistances, const std::vector<std::string>& modelNames, const std::string& outName,
+    unsigned collisionLodLevel, unsigned raycastLodLevel, unsigned occlusionLodLevel)
+{
+    // Load models
+    std::vector<SharedPtr<Model> > srcModels;
+    for (unsigned i = 0; i < modelNames.size(); ++i)
+    {
+        std::cout << "Reading LOD level " << i << ": model " + modelNames[i] << " distance " << lodDistances[i] << std::endl;
+        File srcFile(modelNames[i]);
+        SharedPtr<Model> srcModel(new Model(0, modelNames[i]));
+        srcModel->load(srcFile, 0);
+        srcModels.push_back(srcModel);
+    }
+    
+    // Check that none of the models already has LOD levels
+    for (unsigned i = 0; i < srcModels.size(); ++i)
+    {
+        for (unsigned j = 0; j < srcModels[i]->getNumGeometries(); ++j)
+        {
+            if (srcModels[i]->getNumGeometryLodLevels(j) > 1)
+                errorExit(modelNames[i] + " already has multiple LOD levels defined");
+        }
+    }
+    
+    // Check for number of geometries (need to have same amount for now)
+    for (unsigned i = 1; i < srcModels.size(); ++i)
+    {
+        if (srcModels[i]->getNumGeometries() != srcModels[0]->getNumGeometries())
+            errorExit(modelNames[i] + " has different amount of geometries than " + modelNames[0]);
+    }
+    
+    // If there are bones, check for compatibility (need to have exact match for now)
+    for (unsigned i = 1; i < srcModels.size(); ++i)
+    {
+        if (srcModels[i]->getSkeleton().getNumBones() != srcModels[0]->getSkeleton().getNumBones())
+            errorExit(modelNames[i] + " has different amount of bones than " + modelNames[0]);
+        for (unsigned j = 0; j < srcModels[0]->getSkeleton().getNumBones(); ++j)
+        {
+            if (srcModels[i]->getSkeleton().getBone(j)->getName() != srcModels[0]->getSkeleton().getBone(j)->getName())
+                errorExit(modelNames[i] + " has different bones than " + modelNames[0]);
+        }
+        if (srcModels[i]->getGeometryBoneMappings() != srcModels[0]->getGeometryBoneMappings())
+            errorExit(modelNames[i] + " has different per-geometry bone mappings than " + modelNames[0]);
+    }
+    
+    // Create the final model
+    SharedPtr<Model> outModel(new Model(0, outName));
+    outModel->setNumGeometries(srcModels[0]->getNumGeometries());
+    for (unsigned i = 0; i < srcModels[0]->getNumGeometries(); ++i)
+    {
+        outModel->setNumGeometryLodLevels(i, srcModels.size());
+        for (unsigned j = 0; j < srcModels.size(); ++j)
+        {
+            Geometry* geom = srcModels[j]->getGeometry(i, 0);
+            geom->setLodDistance(lodDistances[j]);
+            outModel->setGeometry(i, j, geom);
+        }
+    }
+    outModel->setSkeleton(srcModels[0]->getSkeleton());
+    outModel->setGeometryBoneMappings(srcModels[0]->getGeometryBoneMappings());
+    outModel->setBoundingBox(srcModels[0]->getBoundingBox());
+    outModel->setCollisionLodLevel(collisionLodLevel);
+    outModel->setRaycastLodLevel(raycastLodLevel);
+    outModel->setOcclusionLodLevel(occlusionLodLevel);
+    //! \todo Vertex morphs are ignored for now
+    
+    // Save the final model
+    std::cout << "Writing output model" << std::endl;
+    File outFile(outName, FILE_WRITE);
+    outModel->save(outFile);
+}
+
 std::set<unsigned> getMeshesUnderNodeSet(aiNode* node)
 std::set<unsigned> getMeshesUnderNodeSet(aiNode* node)
 {
 {
     std::set<unsigned> ret;
     std::set<unsigned> ret;
@@ -1394,29 +1555,6 @@ aiNode* findNode(const std::string& name, aiNode* rootNode, bool caseSensitive)
     return 0;
     return 0;
 }
 }
 
 
-std::string toStdString(const aiString& str)
-{
-    if ((!str.data) || (!str.length))
-        return std::string();
-    else
-        return std::string(str.data);
-}
-
-Vector3 toVector3(const aiVector3D& vec)
-{
-    return Vector3(vec.x, vec.y, vec.z);
-}
-
-Vector2 toVector2(const aiVector2D& vec)
-{
-    return Vector2(vec.x, vec.y);
-}
-
-Quaternion toQuaternion(const aiQuaternion& quat)
-{
-    return Quaternion(quat.w, quat.x, quat.y, quat.z);
-}
-
 aiMatrix4x4 getDerivedTransform(aiNode* node, aiNode* rootNode)
 aiMatrix4x4 getDerivedTransform(aiNode* node, aiNode* rootNode)
 {
 {
     aiMatrix4x4 current = node->mTransformation;
     aiMatrix4x4 current = node->mTransformation;
@@ -1451,8 +1589,31 @@ void getPosRotScale(const aiMatrix4x4& transform, Vector3& pos, Quaternion& rot,
     scale = toVector3(aiScale);
     scale = toVector3(aiScale);
 }
 }
 
 
+
+std::string toStdString(const aiString& str)
+{
+    if ((!str.data) || (!str.length))
+        return std::string();
+    else
+        return std::string(str.data);
+}
+
+Vector3 toVector3(const aiVector3D& vec)
+{
+    return Vector3(vec.x, vec.y, vec.z);
+}
+
+Vector2 toVector2(const aiVector2D& vec)
+{
+    return Vector2(vec.x, vec.y);
+}
+
+Quaternion toQuaternion(const aiQuaternion& quat)
+{
+    return Quaternion(quat.w, quat.x, quat.y, quat.z);
+}
+
 void errorExit(const std::string& error)
 void errorExit(const std::string& error)
 {
 {
     throw Exception(error);
     throw Exception(error);
 }
 }
-

+ 12 - 10
Tools/ModelConverter/ModelConverter.cpp

@@ -85,16 +85,18 @@ void run(const std::vector<std::string>& arguments)
 {
 {
     if (arguments.size() < 2)
     if (arguments.size() < 2)
     {
     {
-        errorExit("Usage: ModelConverter <input file> <output file> [options]\n"
-                  "Options:\n"
-                  "-a   Export animations\n"
-                  "-lcX Use LOD level X for collision mesh, default middle LOD\n"
-                  "-lrX Use LOD level X for raycasts, default same as visible LOD\n"
-                  "-loX Use LOD level X for occlusion, default same as visible LOD\n"
-                  "-m   Export morphs\n"
-                  "-r   Export only rotations from animations\n"
-                  "-s   Split each submesh into own vertex buffer\n"
-                  "-t   Generate tangents\n");
+        errorExit(
+            "Usage: ModelConverter <input file> <output file> [options]\n\n"
+            "Options:\n"
+            "-a   Export animations\n"
+            "-lcX Use LOD level X for collision mesh, default is middle LOD\n"
+            "-lrX Use LOD level X for raycasts, default is same as visible LOD\n"
+            "-loX Use LOD level X for occlusion, default is same as visible LOD\n"
+            "-m   Export morphs\n"
+            "-r   Export only rotations from animations\n"
+            "-s   Split each submesh into own vertex buffer\n"
+            "-t   Generate tangents"
+        );
     }
     }
     
     
     bool generateTangents = false;
     bool generateTangents = false;

+ 6 - 1
Tools/NormalMapTool/NormalMapTool.cpp

@@ -61,7 +61,12 @@ int main(int argc, char** argv)
 void run(const std::vector<std::string>& arguments)
 void run(const std::vector<std::string>& arguments)
 {
 {
     if (arguments.size() < 1)
     if (arguments.size() < 1)
-        errorExit("Usage: NormalMapTool <input image file>");
+    {
+        errorExit(
+            "Usage: NormalMapTool <inputfile>\n"
+            "Output file will be saved as inputfile.dds"
+        );
+    }
     
     
     File source(arguments[0]);
     File source(arguments[0]);
     Image image;
     Image image;

+ 6 - 2
Tools/ShaderCompiler/ShaderCompiler.cpp

@@ -177,9 +177,13 @@ int main(int argc, char** argv)
 void run(const std::vector<std::string>& arguments)
 void run(const std::vector<std::string>& arguments)
 {
 {
     if (arguments.size() < 2)
     if (arguments.size() < 2)
-        errorExit("Usage: ShaderCompiler <definitionfile> <outputpath> [SM3] [define1] [define2] ...\n"
+    {
+        errorExit(
+            "Usage: ShaderCompiler <definitionfile> <outputpath> [SM3] [define1] [define2]\n"
             "Note: .hlsl files will be loaded from definition file directory, and binary\n"
             "Note: .hlsl files will be loaded from definition file directory, and binary\n"
-            "code will be output to same directory as the output file.");
+            "code will be output to same directory as the output file."
+        );
+    }
     
     
     size_t pos = arguments[0].rfind('\\');
     size_t pos = arguments[0].rfind('\\');
     if (pos != std::string::npos)
     if (pos != std::string::npos)