浏览代码

Initial gizmo-based node transform editing.
Fixed material assignments in the axes model.
Possibility to add unculled drawables to the octree.

Lasse Öörni 14 年之前
父节点
当前提交
b11b0f8de1

+ 4 - 0
Bin/CoreData/Materials/BlueUnlit.xml

@@ -0,0 +1,4 @@
+<material>
+    <technique name="Techniques/BasicNoDepth.xml" />
+    <parameter name="MatDiffColor" value="0 0 0.5 1" />
+</material>

+ 4 - 0
Bin/CoreData/Materials/BrightBlueUnlit.xml

@@ -0,0 +1,4 @@
+<material>
+    <technique name="Techniques/BasicNoDepth.xml" />
+    <parameter name="MatDiffColor" value="0 0 1 1" />
+</material>

+ 4 - 0
Bin/CoreData/Materials/BrightGreenUnlit.xml

@@ -0,0 +1,4 @@
+<material>
+    <technique name="Techniques/BasicNoDepth.xml" />
+    <parameter name="MatDiffColor" value="0 1 0 1" />
+</material>

+ 4 - 0
Bin/CoreData/Materials/BrightRedUnlit.xml

@@ -0,0 +1,4 @@
+<material>
+    <technique name="Techniques/BasicNoDepth.xml" />
+    <parameter name="MatDiffColor" value="1 0 0 1" />
+</material>

+ 4 - 0
Bin/CoreData/Materials/GreenUnlit.xml

@@ -0,0 +1,4 @@
+<material>
+    <technique name="Techniques/BasicNoDepth.xml" />
+    <parameter name="MatDiffColor" value="0 0.5 0 1" />
+</material>

+ 4 - 0
Bin/CoreData/Materials/RedUnlit.xml

@@ -0,0 +1,4 @@
+<material>
+    <technique name="Techniques/BasicNoDepth.xml" />
+    <parameter name="MatDiffColor" value="0.5 0 0 1" />
+</material>

二进制
Bin/CoreData/Models/Axes.mdl


+ 3 - 0
Bin/CoreData/Techniques/BasicNoDepth.xml

@@ -0,0 +1,3 @@
+<technique>
+    <pass name="extra" vs="Basic" ps="Basic" depthtest="always" depthwrite="false"/>
+</technique>

+ 2 - 0
Bin/Data/Scripts/Editor.as

@@ -2,6 +2,7 @@
 
 #include "Scripts/Editor/EditorView.as"
 #include "Scripts/Editor/EditorScene.as"
+#include "Scripts/Editor/EditorGizmo.as"
 #include "Scripts/Editor/EditorSettings.as"
 #include "Scripts/Editor/EditorUI.as"
 #include "Scripts/Editor/EditorImport.as"
@@ -53,6 +54,7 @@ void HandleUpdate(StringHash eventType, VariantMap& eventData)
     MoveCamera(timeStep);
     UpdateStats(timeStep);
     UpdateScene(timeStep);
+    UpdateGizmo();
 }
 
 void LoadConfig()

+ 194 - 0
Bin/Data/Scripts/Editor/EditorGizmo.as

@@ -0,0 +1,194 @@
+// Urho3D editor node transform gizmo handling
+
+Node@ gizmoNode;
+StaticModel@ gizmo;
+
+const float axisMaxD = 0.1;
+const float axisMaxT = 1.0;
+const float rotSensitivity = 50.0;
+
+class GizmoAxis
+{
+    Ray axisRay;
+    bool selected;
+    float t;
+    float d;
+    float lastT;
+    float lastD;
+
+    GizmoAxis()
+    {
+        selected = false;
+        t = 0.0;
+        d = 0.0;
+        lastT = 0.0;
+        lastD = 0.0;
+    }
+
+    void Update(Ray cameraRay, float scale, bool drag)
+    {
+        lastT = t;
+        lastD = d;
+
+        Vector3 closest = cameraRay.ClosestPoint(axisRay);
+        Vector3 projected = axisRay.Project(closest);
+        d = axisRay.Distance(closest);
+        t = (projected - axisRay.origin).DotProduct(axisRay.direction);
+
+        // Update selected status only when not dragging
+        if (!drag)
+            selected = d < axisMaxD * scale && t >= 0.0 && t <= axisMaxT * scale;
+    }
+}
+
+GizmoAxis gizmoAxisX;
+GizmoAxis gizmoAxisY;
+GizmoAxis gizmoAxisZ;
+
+void CreateGizmo()
+{
+    gizmoNode = Node();
+    gizmo = gizmoNode.CreateComponent("StaticModel");
+    gizmo.model = cache.GetResource("Model", "Models/Axes.mdl");
+    gizmo.materials[0] = cache.GetResource("Material", "Materials/RedUnlit.xml");
+    gizmo.materials[1] = cache.GetResource("Material", "Materials/GreenUnlit.xml");
+    gizmo.materials[2] = cache.GetResource("Material", "Materials/BlueUnlit.xml");
+    gizmo.visible = false;
+
+    if (editorScene.octree !is null)
+        editorScene.octree.AddUnculledDrawable(gizmo);
+}
+
+void UpdateGizmo()
+{
+    PositionGizmo();
+    ResizeGizmo();
+    UseGizmo();
+}
+
+void PositionGizmo()
+{
+    if (gizmo is null)
+        return;
+
+    if (editNodes.empty)
+    {
+        gizmo.visible = false;
+        return;
+    }
+    
+    Vector3 center(0, 0, 0);
+    
+    for (uint i = 0; i < editNodes.length; ++i)
+        center += editNodes[i].worldPosition;
+
+    center /= editNodes.length;
+    gizmoNode.position = center;
+
+    if (axisMode == AXIS_WORLD || editNodes.length > 1)
+        gizmoNode.rotation = Quaternion();
+    else
+        gizmoNode.rotation = editNodes[0].worldRotation;
+
+    gizmo.visible = true;
+}
+
+void ResizeGizmo()
+{
+    if (gizmo is null || !gizmo.visible)
+        return;
+
+    float c = 0.1;
+
+    if (camera.orthographic)
+        gizmoNode.scale = Vector3(c, c, c);
+    else
+    {
+        /// \todo if matrix classes were exposed to script could simply use the camera's inverse world transform
+        float z = (cameraNode.worldRotation.Inverse() * (gizmoNode.worldPosition - cameraNode.worldPosition)).z;
+        gizmoNode.scale = Vector3(c * z, c * z, c * z);
+    }
+}
+
+void CalculateGizmoAxes()
+{
+    gizmoAxisX.axisRay = Ray(gizmoNode.position, gizmoNode.rotation * Vector3(1, 0, 0));
+    gizmoAxisY.axisRay = Ray(gizmoNode.position, gizmoNode.rotation * Vector3(0, 1, 0));
+    gizmoAxisZ.axisRay = Ray(gizmoNode.position, gizmoNode.rotation * Vector3(0, 0, 1));
+}
+
+void UseGizmo()
+{
+    if (gizmo is null || !gizmo.visible)
+        return;
+
+    IntVector2 pos = ui.cursorPosition;
+    if (ui.GetElementAt(pos) !is null)
+        return;
+    Ray cameraRay = camera.GetScreenRay(float(pos.x) / graphics.width, float(pos.y) / graphics.height);
+    float scale = gizmoNode.scale.x;
+
+    // Recalculate axes only when not left-dragging
+    bool drag = input.mouseButtonDown[MOUSEB_LEFT];
+    if (!drag)
+        CalculateGizmoAxes();
+
+    gizmoAxisX.Update(cameraRay, scale, drag);
+    gizmoAxisY.Update(cameraRay, scale, drag);
+    gizmoAxisZ.Update(cameraRay, scale, drag);
+
+    gizmo.materials[0] = cache.GetResource("Material", gizmoAxisX.selected ? "Materials/BrightRedUnlit.xml" : "Materials/RedUnlit.xml");
+    gizmo.materials[1] = cache.GetResource("Material", gizmoAxisY.selected ? "Materials/BrightGreenUnlit.xml" : "Materials/GreenUnlit.xml");
+    gizmo.materials[2] = cache.GetResource("Material", gizmoAxisZ.selected ? "Materials/BrightBlueUnlit.xml" : "Materials/BlueUnlit.xml");
+
+    if (drag)
+    {
+        /// \todo Implement scaling
+        /// \todo Implement snapping
+        if (moveMode == OBJ_MOVE)
+        {
+            Vector3 adjust(0, 0, 0);
+            if (gizmoAxisX.selected)
+                adjust += gizmoAxisX.axisRay.direction * (gizmoAxisX.t - gizmoAxisX.lastT);
+            if (gizmoAxisY.selected)
+                adjust += gizmoAxisY.axisRay.direction * (gizmoAxisY.t - gizmoAxisY.lastT);
+            if (gizmoAxisZ.selected)
+                adjust += gizmoAxisZ.axisRay.direction * (gizmoAxisZ.t - gizmoAxisZ.lastT);
+
+            for (uint i = 0; i < editNodes.length; ++i)
+            {
+                Node@ node = editNodes[i];
+                Vector3 nodeAdjust = adjust;
+                if (node.parent !is null)
+                    nodeAdjust = node.parent.WorldToLocal(Vector4(nodeAdjust, 0.0));
+                node.position = node.position + nodeAdjust;
+            }
+        }
+        else if (moveMode == OBJ_ROTATE)
+        {
+            Vector3 adjust(0, 0, 0);
+            if (gizmoAxisX.selected)
+                adjust.x = (gizmoAxisX.d - gizmoAxisX.lastD) * rotSensitivity / scale;
+            if (gizmoAxisY.selected)
+                adjust.y = (gizmoAxisY.d - gizmoAxisY.lastD) * rotSensitivity / scale;
+            if (gizmoAxisZ.selected)
+                adjust.z = (gizmoAxisZ.d - gizmoAxisZ.lastD) * rotSensitivity / scale;
+
+            for (uint i = 0; i < editNodes.length; ++i)
+            {
+                /// \todo When multiple nodes selected, rotate them around the gizmo
+                Node@ node = editNodes[i];
+                Quaternion rotQuat(adjust);
+                if (axisMode == AXIS_WORLD)
+                    node.rotation = rotQuat * node.rotation;
+                else
+                    node.rotation = node.rotation * rotQuat;
+            }
+        }
+    }
+}
+
+bool IsGizmoSelected()
+{
+    return gizmoAxisX.selected || gizmoAxisY.selected || gizmoAxisZ.selected;
+}

+ 2 - 0
Bin/Data/Scripts/Editor/EditorScene.as

@@ -59,6 +59,7 @@ void CreateScene()
     sceneFileName = "";
     UpdateWindowTitle();
     CreateCamera();
+    CreateGizmo();
 
     script.defaultScene = editorScene;
 }
@@ -204,6 +205,7 @@ void LoadScene(const String&in fileName)
     UpdateSceneWindow();
     UpdateNodeWindow();
     ResetCamera();
+    CreateGizmo();
 }
 
 void SaveScene(const String&in fileName)

+ 1 - 0
Bin/Data/Scripts/Editor/EditorSceneWindow.as

@@ -564,6 +564,7 @@ void HandleNodeListSelectionChange()
     else
         editNodes = selectedNodes;
 
+    PositionGizmo();
     UpdateNodeWindow();
 }
 

+ 3 - 2
Bin/Data/Scripts/Editor/EditorView.as

@@ -451,7 +451,8 @@ void ViewRaycast(bool mouseClick)
     IntVector2 pos = ui.cursorPosition;
     Component@ selected;
 
-    if (ui.GetElementAt(pos) !is null)
+    // Do not raycast / change selection if hovering over an UI element or the gizmo
+    if (ui.GetElementAt(pos) !is null || IsGizmoSelected())
         return;
 
     Ray cameraRay = camera.GetScreenRay(float(pos.x) / graphics.width, float(pos.y) / graphics.height);
@@ -462,7 +463,7 @@ void ViewRaycast(bool mouseClick)
             return;
 
         Array<RayQueryResult> result = editorScene.octree.Raycast(cameraRay, RAY_TRIANGLE, camera.farClip,
-            pickModeDrawableFlags[pickMode]);
+            pickModeDrawableFlags[pickMode], 0x7fffffff);
         if (!result.empty)
         {
             for (uint i = 0; i < result.length; ++i)

+ 5 - 0
Docs/ScriptAPI.dox

@@ -460,6 +460,9 @@ Ray
 
 Methods:<br>
 - void Define(const Vector3&, const Vector3&)
+- Vector3 Project(const Vector3&) const
+- float Distance(const Vector3&) const
+- Vector3 ClosestPoint(const Ray&) const
 - float HitDistance(const Sphere&) const
 - float HitDistance(const BoundingBox&) const
 - float HitDistance(const Vector3&, const Vector3&, const Vector3&) const
@@ -1991,6 +1994,8 @@ Methods:<br>
 - void Resize(const BoundingBox&, uint)
 - void DrawDebugGeometry(bool) const
 - void AddDrawable(Drawable@)
+- void AddUnculledDrawable(Drawable@)
+- void RemoveUnculledDrawable(Drawable@)
 - RayQueryResult[]@ Raycast(const Ray&, RayQueryLevel arg1 = RAY_TRIANGLE, float arg2 = M_INFINITY, uint8 arg3 = DRAWABLE_ANY, uint arg4 = DEFAULT_VIEWMASK)
 - Node@[]@ GetDrawables(const Vector3&, uint8 arg1 = DRAWABLE_ANY, uint arg2 = DEFAULT_VIEWMASK)
 - Node@[]@ GetDrawables(const BoundingBox&, uint8 arg1 = DRAWABLE_ANY, uint arg2 = DEFAULT_VIEWMASK)

+ 2 - 0
Engine/Engine/GraphicsAPI.cpp

@@ -919,6 +919,8 @@ static void RegisterOctree(asIScriptEngine* engine)
     engine->RegisterObjectMethod("Octree", "void Resize(const BoundingBox&in, uint)", asMETHOD(Octree, Resize), asCALL_THISCALL);
     engine->RegisterObjectMethod("Octree", "void DrawDebugGeometry(bool) const", asMETHOD(Octree, DrawDebugGeometry), asCALL_THISCALL);
     engine->RegisterObjectMethod("Octree", "void AddDrawable(Drawable@+)", asFUNCTION(OctreeAddDrawable), asCALL_CDECL_OBJLAST);
+    engine->RegisterObjectMethod("Octree", "void AddUnculledDrawable(Drawable@+)", asMETHOD(Octree, AddUnculledDrawable), asCALL_THISCALL);
+    engine->RegisterObjectMethod("Octree", "void RemoveUnculledDrawable(Drawable@+)", asMETHOD(Octree, RemoveUnculledDrawable), asCALL_THISCALL);
     engine->RegisterObjectMethod("Octree", "Array<RayQueryResult>@ Raycast(const Ray&in, RayQueryLevel level = RAY_TRIANGLE, float maxDistance = M_INFINITY, uint8 drawableFlags = DRAWABLE_ANY, uint viewMask = DEFAULT_VIEWMASK)", asFUNCTION(OctreeRaycast), asCALL_CDECL_OBJLAST);
     engine->RegisterObjectMethod("Octree", "Array<Node@>@ GetDrawables(const Vector3&in, uint8 drawableFlags = DRAWABLE_ANY, uint viewMask = DEFAULT_VIEWMASK)", asFUNCTION(OctreeGetDrawablesPoint), asCALL_CDECL_OBJLAST);
     engine->RegisterObjectMethod("Octree", "Array<Node@>@ GetDrawables(const BoundingBox&in, uint8 drawableFlags = DRAWABLE_ANY, uint viewMask = DEFAULT_VIEWMASK)", asFUNCTION(OctreeGetDrawablesBox), asCALL_CDECL_OBJLAST);

+ 3 - 0
Engine/Engine/MathAPI.cpp

@@ -416,6 +416,9 @@ static void RegisterRay(asIScriptEngine* engine)
     engine->RegisterObjectMethod("Ray", "Ray& opAssign(const Ray&in)", asMETHOD(Ray, operator =), asCALL_THISCALL);
     engine->RegisterObjectMethod("Ray", "bool opEquals(const Ray&in) const", asMETHOD(Ray, operator ==), asCALL_THISCALL);
     engine->RegisterObjectMethod("Ray", "void Define(const Vector3&in, const Vector3&in)", asMETHOD(Ray, Define), asCALL_THISCALL);
+    engine->RegisterObjectMethod("Ray", "Vector3 Project(const Vector3&in) const", asMETHOD(Ray, Project), asCALL_THISCALL);
+    engine->RegisterObjectMethod("Ray", "float Distance(const Vector3&in) const", asMETHOD(Ray, Distance), asCALL_THISCALL);
+    engine->RegisterObjectMethod("Ray", "Vector3 ClosestPoint(const Ray&in) const", asMETHOD(Ray, ClosestPoint), asCALL_THISCALL);
     engine->RegisterObjectMethod("Ray", "float HitDistance(const Sphere&in) const", asMETHODPR(Ray, HitDistance, (const Sphere&) const, float), asCALL_THISCALL);
     engine->RegisterObjectMethod("Ray", "float HitDistance(const BoundingBox&in) const", asMETHODPR(Ray, HitDistance, (const BoundingBox&) const, float), asCALL_THISCALL);
     engine->RegisterObjectMethod("Ray", "float HitDistance(const Vector3&in, const Vector3&in, const Vector3&in) const", asMETHODPR(Ray, HitDistance, (const Vector3&, const Vector3&, const Vector3&) const, float), asCALL_THISCALL);

+ 47 - 0
Engine/Graphics/Octree.cpp

@@ -316,6 +316,15 @@ void Octree::Update(const FrameInfo& frame)
         // Let drawables update themselves before reinsertion
         for (HashSet<Drawable*>::Iterator i = drawableUpdates_.Begin(); i != drawableUpdates_.End(); ++i)
             (*i)->Update(frame);
+        
+        for (unsigned i = unculledDrawables_.Size() - 1; i < unculledDrawables_.Size(); --i)
+        {
+            // Remove expired unculled drawables at this point
+            if (!unculledDrawables_[i])
+                unculledDrawables_.Erase(i, 1);
+            else
+                unculledDrawables_[i]->Update(frame);
+        }
     }
     
     {
@@ -357,6 +366,35 @@ void Octree::Update(const FrameInfo& frame)
     drawableReinsertions_.Clear();
 }
 
+void Octree::AddUnculledDrawable(Drawable* drawable)
+{
+    if (!drawable)
+        return;
+    
+    for (Vector<WeakPtr<Drawable> >::ConstIterator i = unculledDrawables_.Begin(); i != unculledDrawables_.End(); ++i)
+    {
+        if ((*i) == drawable)
+            return;
+    }
+    
+    unculledDrawables_.Push(WeakPtr<Drawable>(drawable));
+}
+
+void Octree::RemoveUnculledDrawable(Drawable* drawable)
+{
+    if (!drawable)
+        return;
+    
+    for (Vector<WeakPtr<Drawable> >::Iterator i = unculledDrawables_.Begin(); i != unculledDrawables_.End(); ++i)
+    {
+        if ((*i) == drawable)
+        {
+            unculledDrawables_.Erase(i);
+            return;
+        }
+    }
+}
+
 void Octree::GetDrawables(OctreeQuery& query) const
 {
     PROFILE(OctreeQuery);
@@ -374,6 +412,15 @@ void Octree::GetDrawables(RayOctreeQuery& query) const
     Sort(query.result_.Begin(), query.result_.End(), CompareRayQueryResults);
 }
 
+void Octree::GetUnculledDrawables(PODVector<Drawable*>& dest, unsigned char drawableFlags) const
+{
+    for (Vector<WeakPtr<Drawable> >::ConstIterator i = unculledDrawables_.Begin(); i != unculledDrawables_.End(); ++i)
+    {
+        if (*i && (*i)->IsVisible() && (*i)->GetDrawableFlags() & drawableFlags)
+            dest.Push(*i);
+    }
+}
+
 void Octree::QueueUpdate(Drawable* drawable)
 {
     drawableUpdates_.Insert(drawable);

+ 8 - 0
Engine/Graphics/Octree.h

@@ -162,11 +162,17 @@ public:
     void Resize(const BoundingBox& box, unsigned numLevels);
     /// Update and reinsert drawable objects.
     void Update(const FrameInfo& frame);
+    /// Add a drawable that should not be subject to culling.
+    void AddUnculledDrawable(Drawable* drawable);
+    /// Remove an unculled drawable.
+    void RemoveUnculledDrawable(Drawable* drawable);
     
     /// Return drawable objects by a query.
     void GetDrawables(OctreeQuery& query) const;
     /// Return drawable objects by a ray query.
     void GetDrawables(RayOctreeQuery& query) const;
+    /// Return unculled drawables by drawable type. The destination vector will not be cleared.
+    void GetUnculledDrawables(PODVector<Drawable*>& dest, unsigned char drawableFlags) const;
     /// Return subdivision levels.
     unsigned GetNumLevels() const { return numLevels_; }
     
@@ -186,6 +192,8 @@ private:
     HashSet<Drawable*> drawableUpdates_;
     /// %Set of drawable objects that require reinsertion.
     HashSet<Drawable*> drawableReinsertions_;
+    /// Unculled drawables.
+    Vector<WeakPtr<Drawable> > unculledDrawables_;
     /// Subdivision level.
     unsigned numLevels_;
 };

+ 3 - 0
Engine/Graphics/View.cpp

@@ -285,6 +285,9 @@ void View::GetDrawables()
         octree_->GetDrawables(query);
     }
     
+    // Add unculled geometries & lights
+    octree_->GetUnculledDrawables(tempDrawables_, DRAWABLE_GEOMETRY | DRAWABLE_LIGHT);
+    
     // Sort into geometries & lights, and build visible scene bounding boxes in world and view space
     sceneBox_.min_ = sceneBox_.max_ = Vector3::ZERO;
     sceneBox_.defined_ = false;

+ 28 - 0
Engine/Math/Ray.cpp

@@ -34,6 +34,34 @@ Vector3 Ray::Project(const Vector3& point) const
     return origin_ + offset.DotProduct(direction_) * direction_;
 }
 
+float Ray::Distance(const Vector3& point) const
+{
+    Vector3 projected = Project(point);
+    return (point - projected).Length();
+}
+
+Vector3 Ray::ClosestPoint(const Ray& ray) const
+{
+    // Algorithm based on http://paulbourke.net/geometry/lineline3d/
+    Vector3 p13 = origin_ - ray.origin_;
+    Vector3 p43 = ray.direction_;
+    Vector3 p21 = direction_;
+    
+    float d1343 = p13.DotProduct(p43);
+    float d4321 = p43.DotProduct(p21);
+    float d1321 = p13.DotProduct(p21);
+    float d4343 = p43.DotProduct(p43);
+    float d2121 = p21.DotProduct(p21);
+    
+    float d = d2121 * d4343 - d4321 * d4321;
+    if (fabsf(d) < M_EPSILON)
+        return origin_;
+    float n = d1343 * d4321 - d1321 * d4343;
+    float a = n / d;
+    
+    return origin_ + a * direction_;
+}
+
 float Ray::HitDistance(const Plane& plane) const
 {
     float d = plane.normal_.DotProduct(direction_);

+ 4 - 0
Engine/Math/Ray.h

@@ -75,6 +75,10 @@ public:
     
     /// Project a point on the ray.
     Vector3 Project(const Vector3& point) const;
+    /// Return distance of a point from the ray
+    float Distance(const Vector3& point) const;
+    /// Return closest point to another ray.
+    Vector3 ClosestPoint(const Ray& ray) const;
     /// Return hit distance to a plane, or infinity if no hit.
     float HitDistance(const Plane& plane) const;
     /// Return hit distance to a bounding box, or infinity if no hit.

二进制
SourceAssets/Models/Axes.blend