Browse Source

3D Scene Editor

Josh Engebretson 10 years ago
parent
commit
4a8f1d1231

+ 35 - 36
Data/AtomicEditor/Resources/EditorData/AtomicEditor/typescript/editor/Editor.ts

@@ -2,72 +2,71 @@
 import MainFrame = require("../ui/MainFrame");
 import UIEvents = require("../ui/UIEvents");
 
-class Editor extends Atomic.ScriptObject
-{
+class Editor extends Atomic.ScriptObject {
 
-  project: ToolCore.Project;
-  view: Atomic.UIView;
-  mainframe: MainFrame;
+    project: ToolCore.Project;
+    view: Atomic.UIView;
+    mainframe: MainFrame;
 
-  static instance:Editor;
+    static instance: Editor;
 
-  loadProject(projectPath:string):boolean {
+    loadProject(projectPath: string): boolean {
 
-    var system = ToolCore.getToolSystem();
+        var system = ToolCore.getToolSystem();
 
-    if (system.project) {
+        if (system.project) {
 
-      this.sendEvent(UIEvents.MessageModalEvent,
-        { type:"error", title:"Project already loaded", message:"Project already loaded"} );
+            this.sendEvent(UIEvents.MessageModalEvent,
+                { type: "error", title: "Project already loaded", message: "Project already loaded" });
 
-        return false;
+            return false;
 
-    }
+        }
 
-    return system.loadProject(projectPath);
+        return system.loadProject(projectPath);
 
-  }
+    }
 
-  parseArguments() {
+    parseArguments() {
 
-    var args = Atomic.getArguments();
+        var args = Atomic.getArguments();
 
-    var idx = 0;
+        var idx = 0;
 
-    while (idx < args.length) {
+        while (idx < args.length) {
 
-      if (args[idx] == "--project") {
+            if (args[idx] == "--project") {
 
-        this.loadProject(args[idx + 1]);
+                this.loadProject(args[idx + 1]);
 
-      }
+            }
 
-      idx++;
+            idx++;
 
-    }
+        }
 
-  }
+    }
 
-  constructor() {
+    constructor() {
 
-    super();
+        super();
 
-    Editor.instance = this;
+        Editor.instance = this;
 
-    var graphics = Atomic.getGraphics();
+        var graphics = Atomic.getGraphics();
 
-    this.view = new Atomic.UIView();
+        this.view = new Atomic.UIView();
 
-    this.mainframe = new MainFrame();
+        this.mainframe = new MainFrame();
 
-    this.view.addChild(this.mainframe);
+        this.view.addChild(this.mainframe);
 
-    // set initial size
-    this.mainframe.setSize(graphics.width, graphics.height);
+        // set initial size
+        this.mainframe.setSize(graphics.width, graphics.height);
 
-    this.parseArguments();    
+        this.parseArguments();
 
-  }
+    }
 
 }
 

+ 6 - 0
Data/AtomicEditor/Resources/EditorData/AtomicEditor/typescript/ui/ResourceFrame.ts

@@ -34,7 +34,13 @@ class ResourceFrame extends ScriptWidget {
         var editor: Editor.ResourceEditor = null;
 
         if (ext == ".js") {
+
             editor = new Editor.JSResourceEditor(path, this.tabcontainer);
+            
+        } else if (ext == ".scene") {
+
+           editor = new Editor.SceneEditor3D(path, this.tabcontainer);
+
         }
 
         if (editor) {

+ 11 - 1
Source/Atomic/UI/UI.cpp

@@ -43,6 +43,7 @@ using namespace tb;
 #include "UIMessageWindow.h"
 #include "UISkinImage.h"
 #include "UITabContainer.h"
+#include "UISceneView.h"
 
 namespace tb
 {
@@ -535,12 +536,21 @@ UIWidget* UI::WrapWidget(tb::TBWidget* widget)
 
     if (widget->IsOfType<TBTabContainer>())
     {
-        UITabContainer* nwidget = new UITabContainer(context_);
+        UITabContainer* nwidget = new UITabContainer(context_, false);
         nwidget->SetWidget(widget);
         widgetWrap_[widget] = nwidget;
         return nwidget;
     }
 
+    if (widget->IsOfType<SceneViewWidget>())
+    {
+        UISceneView* nwidget = new UISceneView(context_, false);
+        nwidget->SetWidget(widget);
+        widgetWrap_[widget] = nwidget;
+        return nwidget;
+    }
+
+
     if (widget->IsOfType<TBLayout>())
     {
         UILayout* layout = new UILayout(context_, false);

+ 230 - 0
Source/Atomic/UI/UISceneView.cpp

@@ -0,0 +1,230 @@
+
+// Portions Copyright (c) 2008-2015 the Urho3D project.
+
+// Copyright (c) 2014-2015, THUNDERBEAST GAMES LLC All rights reserved
+// Please see LICENSE.md in repository root for license information
+// https://github.com/AtomicGameEngine/AtomicGameEngine
+
+#include "AtomicEditor.h"
+
+
+#include <Atomic/UI/UI.h>
+#include <Atomic/UI/UIBatch.h>
+#include <Atomic/IO/Log.h>
+#include <Atomic/Engine/Engine.h>
+#include <Atomic/Graphics/Graphics.h>
+#include <Atomic/Graphics/Camera.h>
+#include <Atomic/Graphics/RenderPath.h>
+#include <Atomic/Graphics/Renderer.h>
+#include <Atomic/Core/CoreEvents.h>
+
+#include "UISceneView.h"
+using namespace tb;
+
+namespace Atomic
+{
+
+UISceneView::UISceneView(Context* context, bool createWidget) : UIWidget(context, false),
+    rttFormat_(Graphics::GetRGBFormat()),
+    autoUpdate_(false),
+    size_(-1, -1),
+    resizeRequired_(false)
+{
+
+    if (createWidget)
+    {
+        renderTexture_ = new Texture2D(context_);
+        depthTexture_ = new Texture2D(context_);
+        viewport_ = new Viewport(context_);
+
+        widget_ = new SceneViewWidget();
+        widget_->SetDelegate(this);
+        widget_->SetGravity(WIDGET_GRAVITY_ALL);
+        ((SceneViewWidget*)widget_)->sceneView_ = this;
+
+        GetSubsystem<UI>()->WrapWidget(this, widget_);
+
+
+    }
+
+   SubscribeToEvent(E_ENDFRAME, HANDLER(UISceneView, HandleEndFrame));
+}
+
+UISceneView::~UISceneView()
+{
+    // FIXME: need to refactor Light2D viewport handling
+    if (viewport_.NotNull())
+    {
+        RenderPath* renderpath = viewport_->GetRenderPath();
+        if (renderpath)
+            renderpath->RemoveCommands("Light2D");
+    }
+
+}
+
+bool UISceneView::OnEvent(const TBWidgetEvent &ev)
+{
+    return false;
+}
+
+void UISceneView::HandleEndFrame(StringHash eventType, VariantMap& eventData)
+{
+    if (resizeRequired_)
+    {
+        TBRect rect = widget_->GetRect();
+        OnResize(IntVector2(rect.w, rect.h));
+        resizeRequired_ = false;
+    }
+
+}
+
+
+void UISceneView::OnResize(const IntVector2 &newSize)
+{
+    if (newSize.x_ == size_.x_ && newSize.y_ == size_.y_)
+        return;
+
+    int width = newSize.x_;
+    int height = newSize.y_;
+
+    if (width > 0 && height > 0)
+    {
+        viewport_->SetRect(IntRect(0, 0, width, height));
+        renderTexture_->SetSize(width, height, rttFormat_, TEXTURE_RENDERTARGET);
+        depthTexture_->SetSize(width, height, Graphics::GetDepthStencilFormat(), TEXTURE_DEPTHSTENCIL);
+
+        RenderSurface* surface = renderTexture_->GetRenderSurface();
+        surface->SetViewport(0, viewport_);
+        surface->SetUpdateMode(autoUpdate_ ? SURFACE_UPDATEALWAYS : SURFACE_MANUALUPDATE);
+        surface->SetLinkedDepthStencil(depthTexture_->GetRenderSurface());
+
+        size_ = newSize;
+
+    }
+}
+
+
+void UISceneView::SetView(Scene* scene, Camera* camera)
+{
+    scene_ = scene;
+    cameraNode_ = camera ? camera->GetNode() : 0;
+
+    viewport_->SetScene(scene_);
+    viewport_->SetCamera(camera);
+    QueueUpdate();
+}
+
+void UISceneView::SetFormat(unsigned format)
+{
+    if (format != rttFormat_)
+    {
+        rttFormat_ = format;
+    }
+}
+
+void UISceneView::SetAutoUpdate(bool enable)
+{
+    if (enable != autoUpdate_)
+    {
+        autoUpdate_ = enable;
+        RenderSurface* surface = renderTexture_->GetRenderSurface();
+        if (surface)
+            surface->SetUpdateMode(autoUpdate_ ? SURFACE_UPDATEALWAYS : SURFACE_MANUALUPDATE);
+    }
+}
+
+void UISceneView::QueueUpdate()
+{
+    if (!autoUpdate_)
+    {
+        RenderSurface* surface = renderTexture_->GetRenderSurface();
+        if (surface)
+            surface->QueueUpdate();
+    }
+}
+
+Scene* UISceneView::GetScene() const
+{
+    return scene_;
+}
+
+Node* UISceneView::GetCameraNode() const
+{
+    return cameraNode_;
+}
+
+Texture2D* UISceneView::GetRenderTexture() const
+{
+    return renderTexture_;
+}
+
+Texture2D* UISceneView::GetDepthTexture() const
+{
+    return depthTexture_;
+}
+
+Viewport* UISceneView::GetViewport() const
+{
+    return viewport_;
+}
+
+SceneViewWidget::SceneViewWidget()
+{
+    vertexData_.Resize(6 * UI_VERTEX_SIZE);
+    float color;
+    ((unsigned&)color) = 0xFFFFFFFF;
+
+    float* data = &vertexData_[0];
+
+    data[2] = 0; data[3] = color; data[4] = 0; data[5] = 0;
+    data[8] = 0; data[9] = color; data[10] = 1; data[11] = 0;
+    data[14] = 0; data[15] = color; data[16] = 1; data[17] = 1;
+    data[20] = 0; data[21] = color; data[22] = 0; data[23] = 0;
+    data[26] = 0; data[27] = color; data[28] = 1; data[29] = 1;
+    data[32] = 0; data[33] = color; data[34] = 0; data[35] = 1;
+}
+
+
+void SceneViewWidget::OnPaint(const PaintProps &paint_props)
+{
+    if (sceneView_.Null())
+        return;
+
+    TBRect rect = GetRect();
+    ConvertToRoot(rect.x, rect.y);
+
+    IntVector2 size = sceneView_->GetSize();
+
+    if (size.x_ != rect.w || size.y_ != rect.h)
+    {
+        size.x_ = rect.w;
+        size.y_ = rect.h;
+        sceneView_->SetResizeRequired();
+        return;
+    }
+
+    float* data = &vertexData_[0];
+
+    data[0] = rect.x;
+    data[1] = rect.y;
+
+    data[6] = rect.x + rect.w;
+    data[7] =  rect.y;
+
+    data[12] = rect.x + rect.w;
+    data[13] = rect.y + rect.h;
+
+    data[18] = rect.x;
+    data[19] = rect.y;
+
+    data[24] = rect.x + rect.w;
+    data[25] = rect.y + rect.h;
+
+    data[30] = rect.x;
+    data[31] = rect.y + rect.h;
+
+    sceneView_->GetSubsystem<UI>()->SubmitBatchVertexData(sceneView_->GetRenderTexture(), vertexData_);
+
+}
+
+}

+ 107 - 0
Source/Atomic/UI/UISceneView.h

@@ -0,0 +1,107 @@
+
+#pragma once
+
+#include "UIWidget.h"
+
+#include <Atomic/Graphics/Texture2D.h>
+#include <Atomic/Graphics/Viewport.h>
+#include <Atomic/Scene/Scene.h>
+
+#include <TurboBadger/tb_widgets.h>
+
+using namespace tb;
+
+namespace Atomic
+{
+
+class UISceneView;
+
+class SceneViewWidget : public tb::TBWidget
+{
+    friend class UISceneView;
+
+public:
+    // For safe typecasting
+    TBOBJECT_SUBCLASS(SceneViewWidget, tb::TBWidget);
+
+    SceneViewWidget();
+
+    virtual void OnPaint(const PaintProps &paint_props);
+
+private:
+
+    WeakPtr<UISceneView> sceneView_;
+    PODVector<float> vertexData_;
+
+};
+
+
+class UISceneView : public UIWidget
+{
+    OBJECT(UISceneView)
+
+public:
+
+    UISceneView(Context* context, bool createWidget = true);
+    virtual ~UISceneView();
+
+    /// React to resize.
+    void OnResize(const IntVector2& newSize);
+
+    /// Define the scene and camera to use in rendering. When ownScene is true the View3D will take ownership of them with shared pointers.
+    void SetView(Scene* scene, Camera* camera);
+    /// Set render texture pixel format. Default is RGB.
+    void SetFormat(unsigned format);
+    /// Set render target auto update mode. Default is true.
+    void SetAutoUpdate(bool enable);
+    /// Queue manual update on the render texture.
+    void QueueUpdate();
+
+    /// Return render texture pixel format.
+    unsigned GetFormat() const { return rttFormat_; }
+    /// Return whether render target updates automatically.
+    bool GetAutoUpdate() const { return autoUpdate_; }
+    /// Return scene.
+    Scene* GetScene() const;
+    /// Return camera scene node.
+    Node* GetCameraNode() const;
+    /// Return render texture.
+    Texture2D* GetRenderTexture() const;
+    /// Return depth stencil texture.
+    Texture2D* GetDepthTexture() const;
+    /// Return viewport.
+    Viewport* GetViewport() const;
+
+    void SetResizeRequired() {resizeRequired_ = true;}
+    const IntVector2& GetSize() const { return size_; }
+
+protected:
+
+    void HandleEndFrame(StringHash eventType, VariantMap& eventData);
+
+    /// Renderable texture.
+    SharedPtr<Texture2D> renderTexture_;
+    /// Depth stencil texture.
+    SharedPtr<Texture2D> depthTexture_;
+    /// Viewport.
+    SharedPtr<Viewport> viewport_;
+    /// Scene.
+    SharedPtr<Scene> scene_;
+    /// Camera scene node.
+    SharedPtr<Node> cameraNode_;
+    /// Render texture format.
+    unsigned rttFormat_;
+    /// Render texture auto update mode.
+    bool autoUpdate_;
+
+    bool resizeRequired_;
+
+    IntVector2 size_;
+
+    virtual bool OnEvent(const tb::TBWidgetEvent &ev);
+
+private:
+
+};
+
+}

+ 9 - 0
Source/Atomic/UI/UIWidget.cpp

@@ -33,6 +33,15 @@ UIWidget::~UIWidget()
 
 }
 
+void UIWidget::SetIsFocusable(bool value)
+{
+    if (!widget_)
+        return;
+
+    widget_->SetIsFocusable(value);
+
+}
+
 bool UIWidget::Load(const String& filename)
 {
     UI* ui = GetSubsystem<UI>();

+ 2 - 0
Source/Atomic/UI/UIWidget.h

@@ -74,6 +74,8 @@ public:
     UIWidget* GetFirstChild();
     UIWidget* GetNext();
 
+    void SetIsFocusable(bool value);
+
 
     // get this or child widget with id
     UIWidget* GetWidget(const String& id);

+ 437 - 0
Source/AtomicEditorWork/Editors/SceneEditor3D/Gizmo3D.cpp

@@ -0,0 +1,437 @@
+// Portions Copyright (c) 2008-2015 the Urho3D project.
+
+// Copyright (c) 2014-2015, THUNDERBEAST GAMES LLC All rights reserved
+// Please see LICENSE.md in repository root for license information
+// https://github.com/AtomicGameEngine/AtomicGameEngine
+
+#include "AtomicEditor.h"
+
+#include <Atomic/Atomic3D/Model.h>
+#include <Atomic/Graphics/Material.h>
+#include <Atomic/Graphics/Octree.h>
+#include <Atomic/Resource/ResourceCache.h>
+
+#include <Atomic/Input/Input.h>
+
+#include "Gizmo3D.h"
+
+namespace AtomicEditor
+{
+
+
+Gizmo3D::Gizmo3D(Context* context) : Object(context)
+{
+    ResourceCache* cache = GetSubsystem<ResourceCache>();
+
+    gizmoNode_ = new Node(context_);
+    gizmo_ = gizmoNode_->CreateComponent<StaticModel>();
+    gizmo_->SetModel(cache->GetResource<Model>("AtomicEditor/Models/Axes.mdl"));
+
+    gizmo_->SetEnabled(false);
+    gizmo_->SetViewMask(0x80000000); // Editor raycasts use viewmask 0x7fffffff
+    gizmo_->SetOccludee(false);
+
+    axisMode_ = AXIS_LOCAL;
+
+    gizmoAxisX_.lastSelected_ = false;
+    gizmoAxisY_.lastSelected_ = false;
+    gizmoAxisZ_.lastSelected_ = false;
+
+    editMode_ = EDIT_MOVE;
+    lastEditMode_ = EDIT_SELECT;
+}
+
+Gizmo3D::~Gizmo3D()
+{
+
+}
+
+void Gizmo3D::SetView(SceneView3D* view3D)
+{
+    view3D_ = view3D;
+    scene_ = view3D->GetScene();
+    camera_ = view3D->GetCameraNode()->GetComponent<Camera>();
+    assert(camera_.NotNull());
+}
+
+void Gizmo3D::Position()
+{
+    Vector3 center(0, 0, 0);
+    bool containsScene = false;
+
+    for (unsigned i = 0; i < editNodes_->Size(); ++i)
+    {
+        // Scene's transform should not be edited, so hide gizmo if it is included
+        if (editNodes_->At(i) == scene_)
+        {
+            containsScene = true;
+            break;
+        }
+        center += editNodes_->At(i)->GetWorldPosition();
+    }
+
+    if (editNodes_->Empty() || containsScene)
+    {
+        Hide();
+        return;
+    }
+
+    center /= editNodes_->Size();
+    gizmoNode_->SetPosition(center);
+
+    if (axisMode_ == AXIS_WORLD || editNodes_->Size() > 1)
+        gizmoNode_->SetRotation(Quaternion());
+    else
+        gizmoNode_->SetRotation(editNodes_->At(0)->GetWorldRotation());
+
+    ResourceCache* cache = GetSubsystem<ResourceCache>();
+
+    if (editMode_ != lastEditMode_)
+    {
+        switch (editMode_)
+        {
+        case EDIT_MOVE:
+            gizmo_->SetModel(cache->GetResource<Model>("AtomicEditor/Models/Axes.mdl"));
+            break;
+
+        case EDIT_ROTATE:
+            gizmo_->SetModel(cache->GetResource<Model>("AtomicEditor/Models/RotateAxes.mdl"));
+            break;
+
+        case EDIT_SCALE:
+            gizmo_->SetModel(cache->GetResource<Model>("AtomicEditor/Models/ScaleAxes.mdl"));
+            break;
+
+        default:
+            break;
+        }
+
+        lastEditMode_ = editMode_;
+    }
+
+    bool orbiting = false;
+    if ((editMode_ != EDIT_SELECT && !orbiting) && !gizmo_->IsEnabled())
+        Show();
+    else if ((editMode_ == EDIT_SELECT || orbiting) && gizmo_->IsEnabled())
+        Hide();
+
+    if (gizmo_->IsEnabled())
+    {
+        float scale = 0.1f / camera_->GetZoom();
+
+        if (camera_->IsOrthographic())
+            scale *= camera_->GetOrthoSize();
+        else
+            scale *= (camera_->GetView() * gizmoNode_->GetPosition()).z_;
+
+        gizmoNode_->SetScale(Vector3(scale, scale, scale));
+
+    }
+}
+
+void Gizmo3D::Update(Vector<Node *> &editNodes)
+{
+    editNodes_ = &editNodes;
+
+    Use();
+    Position();
+
+
+}
+
+void Gizmo3D::CalculateGizmoAxes()
+{
+    gizmoAxisX_.axisRay_ = Ray(gizmoNode_->GetPosition(), gizmoNode_->GetRotation() * Vector3(1, 0, 0));
+    gizmoAxisY_.axisRay_ = Ray(gizmoNode_->GetPosition(), gizmoNode_->GetRotation() * Vector3(0, 1, 0));
+    gizmoAxisZ_.axisRay_ = Ray(gizmoNode_->GetPosition(), gizmoNode_->GetRotation() * Vector3(0, 0, 1));
+}
+
+void Gizmo3D::Use()
+{
+    if (gizmo_.Null() || !gizmo_->IsEnabled() || editMode_ == EDIT_SELECT)
+    {
+        //StoreGizmoEditActions();
+        //previousGizmoDrag = false;
+        return;
+    }
+
+    ResourceCache* cache = GetSubsystem<ResourceCache>();
+    Input* input = GetSubsystem<Input>();
+
+    Ray cameraRay = view3D_->GetCameraRay();
+    float scale = gizmoNode_->GetScale().x_;
+
+    // Recalculate axes only when not left-dragging
+    bool drag = input->GetMouseButtonDown(MOUSEB_LEFT);// && (Abs(input->GetMouseMoveX()) > 3 || Abs(input->GetMouseMoveY()) > 3);
+    if (!drag)
+        CalculateGizmoAxes();
+
+    gizmoAxisX_.Update(cameraRay, scale, drag, camera_->GetNode());
+    gizmoAxisY_.Update(cameraRay, scale, drag, camera_->GetNode());
+    gizmoAxisZ_.Update(cameraRay, scale, drag, camera_->GetNode());
+
+    if (!editNodes_->Size() || editNodes_->At(0) == scene_)
+    {
+        gizmoAxisX_.selected_ = gizmoAxisY_.selected_ = gizmoAxisZ_.selected_ = false;
+        // this just forces an update
+        gizmoAxisX_.lastSelected_ = gizmoAxisY_.lastSelected_ = gizmoAxisZ_.lastSelected_ = true;
+    }
+
+
+    if (gizmoAxisX_.selected_ != gizmoAxisX_.lastSelected_)
+    {
+        gizmo_->SetMaterial(0, cache->GetResource<Material>(
+                                gizmoAxisX_.selected_ ?
+                                    "AtomicEditor/Materials/BrightRedUnlit.xml" : "AtomicEditor/Materials/RedUnlit.xml"));
+
+        gizmoAxisX_.lastSelected_ = gizmoAxisX_.selected_;
+    }
+
+    if (gizmoAxisY_.selected_ != gizmoAxisY_.lastSelected_)
+    {
+        gizmo_->SetMaterial(1, cache->GetResource<Material>(
+                                gizmoAxisY_.selected_ ?
+                                    "AtomicEditor/Materials/BrightGreenUnlit.xml" : "AtomicEditor/Materials/GreenUnlit.xml"));
+
+        gizmoAxisY_.lastSelected_ = gizmoAxisY_.selected_;
+    }
+    if (gizmoAxisZ_.selected_ != gizmoAxisZ_.lastSelected_)
+    {
+        gizmo_->SetMaterial(2, cache->GetResource<Material>(
+                                gizmoAxisZ_.selected_ ?
+                                    "AtomicEditor/Materials/BrightBlueUnlit.xml" : "AtomicEditor/Materials/BlueUnlit.xml"));
+
+        gizmoAxisZ_.lastSelected_ = gizmoAxisZ_.selected_;
+    }
+
+    if (drag)
+        Drag();
+
+}
+
+bool Gizmo3D::MoveEditNodes(Vector3 adjust)
+{
+    bool moved = false;
+
+    if (adjust.Length() > M_EPSILON)
+    {
+        for (unsigned i = 0; i < editNodes_->Size(); ++i)
+        {
+            /*
+            if (moveSnap)
+            {
+                float moveStepScaled = moveStep * snapScale;
+                adjust.x = Floor(adjust.x / moveStepScaled + 0.5) * moveStepScaled;
+                adjust.y = Floor(adjust.y / moveStepScaled + 0.5) * moveStepScaled;
+                adjust.z = Floor(adjust.z / moveStepScaled + 0.5) * moveStepScaled;
+            }
+            */
+
+            Node* node = editNodes_->At(i);
+            Vector3 nodeAdjust = adjust;
+            if (axisMode_ == AXIS_LOCAL && editNodes_->Size() == 1)
+                nodeAdjust = node->GetWorldRotation() * nodeAdjust;
+
+            Vector3 worldPos = node->GetWorldPosition();
+            Vector3 oldPos = node->GetPosition();
+
+            worldPos += nodeAdjust;
+
+            if (!node->GetParent())
+                node->SetPosition(worldPos);
+            else
+                node->SetPosition(node->GetParent()->WorldToLocal(worldPos));
+
+            if (node->GetPosition() != oldPos)
+                moved = true;
+        }
+    }
+
+    return moved;
+
+
+}
+
+bool Gizmo3D::RotateEditNodes(Vector3 adjust)
+{
+    bool moved = false;
+
+    /*
+    if (rotateSnap)
+    {
+        float rotateStepScaled = rotateStep * snapScale;
+        adjust.x = Floor(adjust.x / rotateStepScaled + 0.5) * rotateStepScaled;
+        adjust.y = Floor(adjust.y / rotateStepScaled + 0.5) * rotateStepScaled;
+        adjust.z = Floor(adjust.z / rotateStepScaled + 0.5) * rotateStepScaled;
+    }
+    */
+
+    if (adjust.Length() > M_EPSILON)
+    {
+        moved = true;
+
+        for (unsigned i = 0; i < editNodes_->Size(); ++i)
+        {
+            Node* node = editNodes_->At(i);
+            Quaternion rotQuat(adjust.x_, adjust.y_, adjust.z_);
+            if (axisMode_ == AXIS_LOCAL && editNodes_->Size() == 1)
+                node->SetRotation(node->GetRotation() * rotQuat);
+            else
+            {
+                Vector3 offset = node->GetWorldPosition() - gizmoAxisX_.axisRay_.origin_;
+                if (node->GetParent() && node->GetParent()->GetWorldRotation() != Quaternion(1, 0, 0, 0))
+                    rotQuat = node->GetParent()->GetWorldRotation().Inverse() * rotQuat * node->GetParent()->GetWorldRotation();
+                node->SetRotation(rotQuat * node->GetRotation());
+                Vector3 newPosition = gizmoAxisX_.axisRay_.origin_ + rotQuat * offset;
+                if (node->GetParent())
+                    newPosition = node->GetParent()->WorldToLocal(newPosition);
+                node->SetPosition(newPosition);
+            }
+        }
+    }
+
+    return moved;
+}
+
+bool Gizmo3D::ScaleEditNodes(Vector3 adjust)
+{
+    bool moved = false;
+
+    if (adjust.Length() > M_EPSILON)
+    {
+        for (unsigned i = 0; i < editNodes_->Size(); ++i)
+        {
+            Node* node = editNodes_->At(i);
+
+            Vector3 scale = node->GetScale();
+            Vector3 oldScale = scale;
+
+            if (true)//!scaleSnap)
+                scale += adjust;
+            else
+            {
+                /*
+                float scaleStepScaled = scaleStep * snapScale;
+                if (adjust.x != 0)
+                {
+                    scale.x += adjust.x * scaleStepScaled;
+                    scale.x = Floor(scale.x / scaleStepScaled + 0.5) * scaleStepScaled;
+                }
+                if (adjust.y != 0)
+                {
+                    scale.y += adjust.y * scaleStepScaled;
+                    scale.y = Floor(scale.y / scaleStepScaled + 0.5) * scaleStepScaled;
+                }
+                if (adjust.z != 0)
+                {
+                    scale.z += adjust.z * scaleStepScaled;
+                    scale.z = Floor(scale.z / scaleStepScaled + 0.5) * scaleStepScaled;
+                }
+                */
+            }
+
+            if (scale != oldScale)
+                moved = true;
+
+            node->SetScale(scale);
+        }
+    }
+
+    return moved;
+}
+
+void Gizmo3D::Moved()
+{
+    gizmoAxisX_.Moved();
+    gizmoAxisY_.Moved();
+    gizmoAxisZ_.Moved();
+}
+
+
+void Gizmo3D::Drag()
+{
+    bool moved = false;
+
+    float scale = gizmoNode_->GetScale().x_;
+
+    if (editMode_ == EDIT_MOVE)
+    {
+        Vector3 adjust(0, 0, 0);
+        if (gizmoAxisX_.selected_)
+            adjust += Vector3(1, 0, 0) * (gizmoAxisX_.t_ - gizmoAxisX_.lastT_);
+        if (gizmoAxisY_.selected_)
+            adjust += Vector3(0, 1, 0) * (gizmoAxisY_.t_ - gizmoAxisY_.lastT_);
+        if (gizmoAxisZ_.selected_)
+            adjust += Vector3(0, 0, 1) * (gizmoAxisZ_.t_ - gizmoAxisZ_.lastT_);
+
+        moved = MoveEditNodes(adjust);
+    }
+    else if (editMode_ == EDIT_ROTATE)
+    {
+        const float rotSensitivity = 50.0;
+
+        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;
+
+        moved = RotateEditNodes(adjust);
+    }
+    else if (editMode_ == EDIT_SCALE)
+    {
+        Vector3 adjust(0, 0, 0);
+        if (gizmoAxisX_.selected_)
+            adjust += Vector3(1, 0, 0) * (gizmoAxisX_.t_ - gizmoAxisX_.lastT_);
+        if (gizmoAxisY_.selected_)
+            adjust += Vector3(0, 1, 0) * (gizmoAxisY_.t_ - gizmoAxisY_.lastT_);
+        if (gizmoAxisZ_.selected_)
+            adjust += Vector3(0, 0, 1) * (gizmoAxisZ_.t_ - gizmoAxisZ_.lastT_);
+
+        // Special handling for uniform scale: use the unmodified X-axis movement only
+        if (editMode_ == EDIT_SCALE && gizmoAxisX_.selected_ && gizmoAxisY_.selected_ && gizmoAxisZ_.selected_)
+        {
+            float x = gizmoAxisX_.t_ - gizmoAxisX_.lastT_;
+            adjust = Vector3(x, x, x);
+        }
+
+        moved = ScaleEditNodes(adjust);
+    }
+
+    if (moved)
+    {
+        Moved();
+        //UpdateNodeAttributes();
+        //needGizmoUndo = true;
+    }
+
+}
+
+void Gizmo3D::SetEditMode(EditMode mode)
+{
+    editMode_ = mode;
+}
+
+void Gizmo3D::Hide()
+{
+    gizmo_->SetEnabled(false);
+}
+
+void Gizmo3D::Show()
+{
+    if (scene_.Null())
+        return;
+
+    gizmo_->SetEnabled(true);
+
+    Octree* octree = scene_->GetComponent<Octree>();
+    if (!octree)
+        return;
+
+    octree->AddManualDrawable(gizmo_);
+
+}
+
+}

+ 155 - 0
Source/AtomicEditorWork/Editors/SceneEditor3D/Gizmo3D.h

@@ -0,0 +1,155 @@
+
+// Portions Copyright (c) 2008-2015 the Urho3D project.
+
+// Copyright (c) 2014-2015, THUNDERBEAST GAMES LLC All rights reserved
+// Please see LICENSE.md in repository root for license information
+// https://github.com/AtomicGameEngine/AtomicGameEngine
+
+#pragma once
+
+#include <Atomic/Core/Object.h>
+#include <Atomic/Math/MathDefs.h>
+#include <Atomic/Math/Ray.h>
+#include <Atomic/Scene/Scene.h>
+
+#include <Atomic/Atomic3D/StaticModel.h>
+#include <Atomic/Graphics/Camera.h>
+
+
+#include "SceneView3D.h"
+
+using namespace Atomic;
+
+namespace Atomic
+{
+    class Node;
+}
+
+namespace AtomicEditor
+{
+
+class Gizmo3DAxis
+{
+public:
+
+    Ray axisRay_;
+    bool selected_;
+    bool lastSelected_;
+    float t_;
+    float d_;
+    float lastT_;
+    float lastD_;
+
+    Gizmo3DAxis()
+    {
+        selected_ = false;
+        lastSelected_ = false;
+        t_ = 0.0;
+        d_ = 0.0;
+        lastT_ = 0.0;
+        lastD_ = 0.0;
+    }
+
+    void Update(Ray cameraRay, float scale, bool drag, Node* cameraNode)
+    {
+        const float axisMaxD = 0.1f;
+        const float axisMaxT = 1.0f;
+
+        Vector3 closest = cameraRay.ClosestPoint(axisRay_);
+        Vector3 projected = axisRay_.Project(closest);
+        d_ = axisRay_.Distance(closest);
+        t_ = (projected - axisRay_.origin_).DotProduct(axisRay_.direction_);
+
+        // Determine the sign of d from a plane that goes through the camera position to the axis
+        Plane axisPlane(cameraNode->GetPosition(), axisRay_.origin_, axisRay_.origin_ + axisRay_.direction_);
+        if (axisPlane.Distance(closest) < 0.0)
+            d_ = -d_;
+
+        // Update selected status only when not dragging
+        if (!drag)
+        {
+            selected_ = Abs(d_) < axisMaxD * scale && t_ >= -axisMaxD * scale && t_ <= axisMaxT * scale;
+            lastT_ = t_;
+            lastD_ = d_;
+        }
+    }
+
+    void Moved()
+    {
+        lastT_ = t_;
+        lastD_ = d_;
+    }
+};
+
+class Gizmo3D: public Object
+{
+    OBJECT(Gizmo3D);
+
+public:
+
+    enum EditMode
+    {
+        EDIT_SELECT,
+        EDIT_MOVE,
+        EDIT_ROTATE,
+        EDIT_SCALE
+    };
+
+    enum AxisMode
+    {
+        AXIS_WORLD = 0,
+        AXIS_LOCAL
+    };
+
+    Gizmo3D(Context* context);
+    virtual ~Gizmo3D();
+
+    void SetView(SceneView3D* view3D);
+
+    void SetEditMode(EditMode);
+
+    bool Selected()
+    {
+        return gizmoAxisX_.selected_ || gizmoAxisY_.selected_ || gizmoAxisZ_.selected_;
+    }
+
+    void Show();
+    void Hide();
+    void Update(Vector<Node*>& editNodes);
+
+    Node* GetGizmoNode() { return gizmoNode_; }
+
+private:
+
+    void Position();
+    void Use();
+    void Drag();
+    void Moved();
+    void CalculateGizmoAxes();
+
+    bool MoveEditNodes(Vector3 adjust);
+    bool RotateEditNodes(Vector3 adjust);
+    bool ScaleEditNodes(Vector3 adjust);
+
+    SharedPtr<Node> gizmoNode_;
+
+    WeakPtr<SceneView3D> view3D_;
+    WeakPtr<Scene> scene_;
+    WeakPtr<Camera> camera_;
+    WeakPtr<StaticModel> gizmo_;
+
+    Gizmo3DAxis gizmoAxisX_;
+    Gizmo3DAxis gizmoAxisY_;
+    Gizmo3DAxis gizmoAxisZ_;
+
+    EditMode editMode_;
+    EditMode lastEditMode_;
+
+    AxisMode axisMode_;
+
+    Vector<Node *> *editNodes_;
+
+};
+
+}
+

+ 183 - 0
Source/AtomicEditorWork/Editors/SceneEditor3D/SceneEditor3D.cpp

@@ -0,0 +1,183 @@
+// Copyright (c) 2014-2015, THUNDERBEAST GAMES LLC All rights reserved
+// Please see LICENSE.md in repository root for license information
+// https://github.com/AtomicGameEngine/AtomicGameEngine
+
+#include "AtomicEditor.h"
+#include <Atomic/IO/Log.h>
+#include <Atomic/Core/CoreEvents.h>
+#include <Atomic/Scene/Scene.h>
+#include <Atomic/Graphics/Camera.h>
+
+#include <Atomic/Graphics/DebugRenderer.h>
+#include <Atomic/Graphics/Viewport.h>
+#include <Atomic/Graphics/Octree.h>
+
+#include <Atomic/IO/FileSystem.h>
+#include <Atomic/Resource/ResourceCache.h>
+
+#include <Atomic/Physics/PhysicsWorld.h>
+
+#include "AEEditor.h"
+#include "AEEvents.h"
+
+#include <Atomic/Input/Input.h>
+#include <Atomic/UI/UI.h>
+
+#include "SceneEditor3D.h"
+
+namespace AtomicEditor
+{
+
+SceneEditor3D ::SceneEditor3D(Context* context, const String &fullpath, UITabContainer *container) :
+    ResourceEditor(context, fullpath, container)
+{
+    ResourceCache* cache = GetSubsystem<ResourceCache>();
+
+    scene_ = new Scene(context_);
+    SharedPtr<File> xmlFile = cache->GetFile(fullpath);
+
+    if (GetExtension(fullpath) == ".scene")
+        scene_->LoadXML(*xmlFile);
+    else
+        scene_->Load(*xmlFile);
+
+    scene_->SetUpdateEnabled(false);
+
+    sceneView_ = new SceneView3D(context_, this);
+
+    // EARLY ACCESS
+    if (fullpath.Find(String("ToonTown")) != String::NPOS)
+    {
+          sceneView_->GetCameraNode()->SetWorldPosition(Vector3(-119.073, 76.1121, 16.47763));
+          Quaternion q(0.55, 0.14,  0.8, -0.2);
+          sceneView_->SetYaw(q.YawAngle());
+          sceneView_->SetPitch(q.PitchAngle());
+          sceneView_->GetCameraNode()->SetWorldRotation(q);
+    }
+    else
+    {
+        Node* playerSpawn = scene_->GetChild("PlayerInfoStart", true);
+        if (playerSpawn)
+        {
+            sceneView_->GetCameraNode()->SetPosition(playerSpawn->GetPosition());
+            sceneView_->SetYaw(playerSpawn->GetRotation().EulerAngles().y_);
+        }
+
+    }
+
+    sceneView_->SetGravity(WIDGET_GRAVITY_ALL);
+
+    rootContentWidget_->AddChild(sceneView_);
+
+    gizmo3D_ = new Gizmo3D(context_);
+    gizmo3D_->SetView(sceneView_);
+    gizmo3D_->Show();
+
+    SubscribeToEvent(E_UPDATE, HANDLER(SceneEditor3D, HandleUpdate));
+    SubscribeToEvent(E_EDITORACTIVENODECHANGE, HANDLER(SceneEditor3D, HandleEditorActiveNodeChange));
+
+    // FIXME: Set the size at the end of setup, so all children are updated accordingly
+    // future size changes will be handled automatically
+    IntRect rect = container_->GetContentRoot()->GetRect();
+    rootContentWidget_->SetSize(rect.Width(), rect.Height());
+
+    // TODO: generate this event properly
+    VariantMap eventData;
+    eventData[EditorActiveSceneChange::P_SCENE] = scene_;
+    SendEvent(E_EDITORACTIVESCENECHANGE, eventData);
+
+    SubscribeToEvent(E_EDITORPLAYSTARTED, HANDLER(SceneEditor3D, HandlePlayStarted));
+    SubscribeToEvent(E_EDITORPLAYSTOPPED, HANDLER(SceneEditor3D, HandlePlayStopped));
+
+}
+
+SceneEditor3D::~SceneEditor3D()
+{
+
+}
+
+bool SceneEditor3D::OnEvent(const TBWidgetEvent &ev)
+{
+
+    if (ev.type == EVENT_TYPE_SHORTCUT)
+    {
+        if (ev.ref_id == TBIDC("save"))
+        {
+            File file(context_);
+            if (file.Open(fullpath_, FILE_WRITE))
+            {
+                scene_->SaveXML(file);
+                file.Close();
+            }
+
+            return true;
+
+        }
+    }
+
+    if (ev.type == EVENT_TYPE_CLICK)
+    {
+        SetFocus();
+
+        if (ev.target)
+        {
+            if (ev.target->GetID() == TBIDC("3d_translate"))
+            {
+                gizmo3D_->SetEditMode(Gizmo3D::EDIT_MOVE);
+                return false;
+            }
+            else if (ev.target->GetID() == TBIDC("3d_rotate"))
+            {
+                gizmo3D_->SetEditMode(Gizmo3D::EDIT_ROTATE);
+                return false;
+            }
+            else if (ev.target->GetID() == TBIDC("3d_scale"))
+            {
+                gizmo3D_->SetEditMode(Gizmo3D::EDIT_SCALE);
+                return false;
+            }
+        }
+    }
+
+    return false;
+}
+
+void SceneEditor3D::SetFocus()
+{
+    sceneView_->SetFocus();
+}
+
+void SceneEditor3D::SelectNode(Node* node)
+{
+    selectedNode_ = node;
+}
+
+void SceneEditor3D::HandleUpdate(StringHash eventType, VariantMap& eventData)
+{    
+    Vector<Node*> editNodes;
+    if (selectedNode_.NotNull())
+        editNodes.Push(selectedNode_);
+    gizmo3D_->Update(editNodes);
+}
+
+void SceneEditor3D::HandleEditorActiveNodeChange(StringHash eventType, VariantMap& eventData)
+{
+    Node* node = (Node*) (eventData[EditorActiveNodeChange::P_NODE].GetPtr());
+    SelectNode(node);
+}
+
+void SceneEditor3D::HandlePlayStarted(StringHash eventType, VariantMap& eventData)
+{
+    sceneView_->Disable();
+
+}
+
+void SceneEditor3D::HandlePlayStopped(StringHash eventType, VariantMap& eventData)
+{
+    sceneView_->Enable();
+}
+
+
+
+
+}

+ 70 - 0
Source/AtomicEditorWork/Editors/SceneEditor3D/SceneEditor3D.h

@@ -0,0 +1,70 @@
+// Copyright (c) 2014-2015, THUNDERBEAST GAMES LLC All rights reserved
+// Please see LICENSE.md in repository root for license information
+// https://github.com/AtomicGameEngine/AtomicGameEngine
+
+#pragma once
+
+#include <TurboBadger/tb_widgets_common.h>
+
+#include "../ResourceEditor.h"
+#include "SceneView3D.h"
+#include "Gizmo3D.h"
+
+using namespace Atomic;
+using namespace tb;
+
+namespace Atomic
+{
+class Scene;
+class Node;
+class View3D;
+class Camera;
+class DebugRenderer;
+class Octree;
+
+}
+
+namespace AtomicEditor
+{
+
+class SceneEditor3D: public ResourceEditor
+{
+    OBJECT(SceneEditor3D);
+
+public:
+
+    SceneEditor3D(Context* context, const String& fullpath, UITabContainer* container);
+
+    virtual ~SceneEditor3D();
+
+    bool OnEvent(const TBWidgetEvent &ev);
+
+    void SelectNode(Node* node);
+
+    Scene* GetScene() { return scene_; }
+    Gizmo3D* GetGizmo() { return gizmo3D_; }
+
+    void SetFocus();
+
+    virtual bool RequiresInspector() { return true; }
+
+
+private:
+
+    void HandleUpdate(StringHash eventType, VariantMap& eventData);
+    void HandleEditorActiveNodeChange(StringHash eventType, VariantMap& eventData);
+    void HandlePlayStarted(StringHash eventType, VariantMap& eventData);
+    void HandlePlayStopped(StringHash eventType, VariantMap& eventData);
+
+    SharedPtr<Scene> scene_;
+
+    // TODO: multiple views
+    SharedPtr<SceneView3D> sceneView_;
+
+    SharedPtr<Gizmo3D> gizmo3D_;
+
+    WeakPtr<Node> selectedNode_;
+
+};
+
+}

+ 349 - 0
Source/AtomicEditorWork/Editors/SceneEditor3D/SceneView3D.cpp

@@ -0,0 +1,349 @@
+// Portions Copyright (c) 2008-2015 the Urho3D project.
+
+// Copyright (c) 2014-2015, THUNDERBEAST GAMES LLC All rights reserved
+// Please see LICENSE.md in repository root for license information
+// https://github.com/AtomicGameEngine/AtomicGameEngine
+
+#include "AtomicEditor.h"
+#include <Atomic/IO/Log.h>
+#include <Atomic/Core/CoreEvents.h>
+#include <Atomic/Scene/Scene.h>
+#include <Atomic/Graphics/Camera.h>
+
+#include <Atomic/Graphics/Graphics.h>
+#include <Atomic/Graphics/DebugRenderer.h>
+#include <Atomic/Graphics/Viewport.h>
+#include <Atomic/Graphics/Octree.h>
+#include <Atomic/Atomic3D/Terrain.h>
+
+#include <Atomic/Input/Input.h>
+
+#include <Atomic/IO/FileSystem.h>
+#include <Atomic/Resource/ResourceCache.h>
+
+#include <Atomic/Physics/PhysicsWorld.h>
+
+#include "AEEditor.h"
+#include "AEEvents.h"
+
+#include "SceneView3D.h"
+#include "SceneEditor3D.h"
+#include <Atomic/UI/UI.h>
+
+namespace AtomicEditor
+{
+
+SceneView3D ::SceneView3D(Context* context, SceneEditor3D *sceneEditor) :
+    UISceneView(context),
+    yaw_(0.0f),
+    pitch_(0.0f),
+    mouseLeftDown_(false),
+    mouseMoved_(false),
+    enabled_(true)
+{
+
+    sceneEditor_ = sceneEditor;
+
+    ResourceCache* cache = GetSubsystem<ResourceCache>();
+
+    scene_ = sceneEditor->GetScene();
+
+    debugRenderer_ = scene_->GetComponent<DebugRenderer>();
+
+    if (debugRenderer_.Null())
+    {
+        debugRenderer_ = scene_->CreateComponent<DebugRenderer>();
+    }
+
+    octree_ = scene_->GetComponent<Octree>();
+
+    if (octree_.Null())
+    {
+        LOGWARNING("Scene without an octree loaded");
+        octree_ = scene_->CreateComponent<Octree>();
+    }
+
+    cameraNode_ = scene_->CreateChild("Camera");
+    cameraNode_->SetTemporary(true);
+    camera_ = cameraNode_->CreateComponent<Camera>();
+
+    debugRenderer_ = scene_->GetComponent<DebugRenderer>();
+    assert(debugRenderer_.NotNull());
+    octree_ = scene_->GetComponent<Octree>();
+    assert(octree_.NotNull());
+
+    cameraNode_->SetPosition(Vector3(0, 0, -10));
+
+    SetView(scene_, camera_);
+    SetAutoUpdate(false);
+
+    SubscribeToEvent(E_UPDATE, HANDLER(SceneView3D, HandleUpdate));
+    SubscribeToEvent(E_EDITORACTIVENODECHANGE, HANDLER(SceneView3D, HandleEditorActiveNodeChange));
+    SubscribeToEvent(E_POSTRENDERUPDATE, HANDLER(SceneView3D, HandlePostRenderUpdate));
+
+    // TODO: generate this event properly
+    VariantMap eventData;
+    eventData[EditorActiveSceneChange::P_SCENE] = scene_;
+    SendEvent(E_EDITORACTIVESCENECHANGE, eventData);
+
+    SetIsFocusable(true);
+
+
+}
+
+SceneView3D::~SceneView3D()
+{
+
+}
+
+void SceneView3D::Enable()
+{
+    if (enabled_)
+        return;
+
+    enabled_ = true;
+
+    SetVisibility(WIDGET_VISIBILITY_VISIBLE);
+}
+
+void SceneView3D::Disable()
+{
+    if (!enabled_)
+        return;
+
+    enabled_ = false;
+
+    SetVisibility(WIDGET_VISIBILITY_INVISIBLE);
+
+}
+
+void SceneView3D::MoveCamera(float timeStep)
+{
+    if (!enabled_)
+        return;
+
+    Input* input = GetSubsystem<Input>();
+
+    // Movement speed as world units per second
+    float MOVE_SPEED = 20.0f;
+    // Mouse sensitivity as degrees per pixel
+    const float MOUSE_SENSITIVITY = 0.2f;
+
+    if (input->GetKeyDown(KEY_LSHIFT) || input->GetKeyDown(KEY_RSHIFT))
+        MOVE_SPEED *= 3.0f;
+
+    // Use this frame's mouse motion to adjust camera node yaw and pitch. Clamp the pitch between -90 and 90 degrees
+    if (input->GetMouseButtonDown(MOUSEB_RIGHT))
+    {
+        IntVector2 mouseMove = input->GetMouseMove();
+        yaw_ += MOUSE_SENSITIVITY * mouseMove.x_;
+        pitch_ += MOUSE_SENSITIVITY * mouseMove.y_;
+        pitch_ = Clamp(pitch_, -90.0f, 90.0f);
+        // Not working on OSX
+        //input->SetMouseMode(MM_RELATIVE);
+    }
+    else
+    {
+        // Not working on OSX
+        /*
+        if (input->GetMouseMode() != MM_ABSOLUTE)
+            input->SetMouseMode(MM_ABSOLUTE);
+        */
+    }
+
+
+    // Construct new orientation for the camera scene node from yaw and pitch. Roll is fixed to zero
+    cameraNode_->SetRotation(Quaternion(pitch_, yaw_, 0.0f));
+
+    //Vector3 pos = cameraNode_->GetWorldPosition();
+    //Quaternion q = cameraNode_->GetWorldRotation();
+    //LOGINFOF("%f %f %f : %f %f %f %f", pos.x_, pos.y_, pos.z_, q.x_, q.y_, q.z_, q.w_ );
+
+    // Read WASD keys and move the camera scene node to the corresponding direction if they are pressed
+    // Use the Translate() function (default local space) to move relative to the node's orientation.
+    if (input->GetKeyDown('W'))
+        cameraNode_->Translate(Vector3::FORWARD * MOVE_SPEED * timeStep);
+    if (input->GetKeyDown('S'))
+        cameraNode_->Translate(Vector3::BACK * MOVE_SPEED * timeStep);
+    if (input->GetKeyDown('A'))
+        cameraNode_->Translate(Vector3::LEFT * MOVE_SPEED * timeStep);
+    if (input->GetKeyDown('D'))
+        cameraNode_->Translate(Vector3::RIGHT * MOVE_SPEED * timeStep);
+}
+
+Ray SceneView3D::GetCameraRay()
+{
+    Ray camRay;
+
+    Input* input = GetSubsystem<Input>();
+    IntVector2 cpos = input->GetMousePosition();
+
+    IntRect rect = GetRect();
+
+    if (!rect.Width() || !rect.Height())
+        return camRay;
+
+    int x = rect.left_;
+    int y = rect.top_;
+    GetInternalWidget()->ConvertToRoot(x, y);
+
+    return  camera_->GetScreenRay(float(cpos.x_ - x) / rect.Width(),
+                                       float(cpos.y_ - y) / rect.Height());
+}
+
+void SceneView3D::DrawNodeDebug(Node* node, DebugRenderer* debug, bool drawNode)
+{
+    if (drawNode)
+        debug->AddNode(node, 1.0, false);
+
+    // Exception for the scene to avoid bringing the editor to its knees: drawing either the whole hierarchy or the subsystem-
+    // components can have a large performance hit. Also do not draw terrain child nodes due to their large amount
+    // (TerrainPatch component itself draws nothing as debug geometry)
+    if (node != scene_ && !node->GetComponent<Terrain>())
+    {
+        const Vector<SharedPtr<Component> >& components = node->GetComponents();
+
+        for (unsigned j = 0; j < components.Size(); ++j)
+            components[j]->DrawDebugGeometry(debug, false);
+
+        // To avoid cluttering the view, do not draw the node axes for child nodes
+        for (unsigned k = 0; k < node->GetNumChildren(); ++k)
+            DrawNodeDebug(node->GetChild(k), debug, false);
+    }
+}
+
+bool SceneView3D::MouseInView()
+{
+    Input* input = GetSubsystem<Input>();
+    IntVector2 pos = input->GetMousePosition();
+
+    IntRect rect = GetRect();
+    int x = rect.left_;
+    int y = rect.top_;
+
+    GetInternalWidget()->ConvertToRoot(x, y);
+
+    return rect.IsInside(IntVector2(x, y));
+
+}
+
+
+void SceneView3D::HandlePostRenderUpdate(StringHash eventType, VariantMap& eventData)
+{
+
+    // Visualize the currently selected nodes
+    if (selectedNode_.NotNull())
+    {
+        DrawNodeDebug(selectedNode_, debugRenderer_);
+
+    }
+
+    if (!MouseInView())
+        return;
+
+    Input* input = GetSubsystem<Input>();
+
+    mouseLeftDown_ = false;
+
+    if (input->GetMouseButtonPress(MOUSEB_LEFT))
+    {
+        if (!mouseMoved_ && !sceneEditor_->GetGizmo()->Selected())
+        {
+            Ray camRay  = GetCameraRay();
+            PODVector<RayQueryResult> result;
+
+            RayOctreeQuery query(result, camRay, RAY_TRIANGLE, camera_->GetFarClip(), DRAWABLE_GEOMETRY, 0x7fffffff);
+            octree_->RaycastSingle(query);
+
+            if (query.result_.Size())
+            {
+                const RayQueryResult& r = result[0];
+
+                if (r.drawable_)
+                {
+
+                    VariantMap neventData;
+                    neventData[EditorActiveNodeChange::P_NODE] = r.drawable_->GetNode();
+                    SendEvent(E_EDITORACTIVENODECHANGE, neventData);
+
+                }
+            }
+        }
+
+        mouseMoved_ = false;
+
+    }
+    else if (!input->GetMouseButtonDown(MOUSEB_LEFT))
+    {
+
+        Ray camRay  = GetCameraRay();
+        PODVector<RayQueryResult> result;
+
+        mouseMoved_ = false;
+
+        /*
+        Array<int> pickModeDrawableFlags = {
+            DRAWABLE_GEOMETRY,
+            DRAWABLE_LIGHT,
+            DRAWABLE_ZONE
+        };
+        */
+
+        RayOctreeQuery query(result, camRay, RAY_TRIANGLE, camera_->GetFarClip(), DRAWABLE_GEOMETRY, 0x7fffffff);
+        octree_->RaycastSingle(query);
+
+        if (query.result_.Size())
+        {
+            const RayQueryResult& r = result[0];
+
+            if (r.drawable_)
+            {
+                debugRenderer_->AddNode(r.drawable_->GetNode(), 1.0, false);
+                r.drawable_->DrawDebugGeometry(debugRenderer_, false);
+            }
+
+        }
+    }
+    else
+    {
+        mouseLeftDown_ = true;
+        if (Abs(input->GetMouseMoveX() > 3 || input->GetMouseMoveY() >  3))
+        {
+            mouseMoved_ = true;
+        }
+    }
+
+}
+
+void SceneView3D::SelectNode(Node* node)
+{
+    selectedNode_ = node;
+}
+
+bool SceneView3D::OnEvent(const TBWidgetEvent &ev)
+{
+    return sceneEditor_->OnEvent(ev);
+}
+
+
+void SceneView3D::HandleUpdate(StringHash eventType, VariantMap& eventData)
+{
+
+    // Timestep parameter is same no matter what event is being listened to
+    float timeStep = eventData[Update::P_TIMESTEP].GetFloat();
+
+    if (MouseInView())
+        MoveCamera(timeStep);
+
+    QueueUpdate();
+}
+
+void SceneView3D::HandleEditorActiveNodeChange(StringHash eventType, VariantMap& eventData)
+{
+    Node* node = (Node*) (eventData[EditorActiveNodeChange::P_NODE].GetPtr());
+    SelectNode(node);
+}
+
+
+
+}

+ 79 - 0
Source/AtomicEditorWork/Editors/SceneEditor3D/SceneView3D.h

@@ -0,0 +1,79 @@
+// Portions Copyright (c) 2008-2015 the Urho3D project.
+
+// Copyright (c) 2014-2015, THUNDERBEAST GAMES LLC All rights reserved
+// Please see LICENSE.md in repository root for license information
+// https://github.com/AtomicGameEngine/AtomicGameEngine
+
+#pragma once
+
+#include <Atomic/Core/Object.h>
+
+#include <Atomic/UI/UISceneView.h>
+
+using namespace Atomic;
+
+namespace Atomic
+{
+class Scene;
+class Node;
+class Camera;
+class DebugRenderer;
+class Octree;
+}
+
+namespace AtomicEditor
+{
+
+class SceneEditor3D;
+
+class SceneView3D: public UISceneView
+{
+    OBJECT(SceneView3D);
+
+public:
+
+    SceneView3D(Context* context, SceneEditor3D* sceneEditor);
+    virtual ~SceneView3D();
+    void SelectNode(Node* node);
+
+    Ray GetCameraRay();
+
+    bool OnEvent(const TBWidgetEvent &ev);
+
+    void SetPitch(float pitch) { pitch_ = pitch; }
+    void SetYaw(float yaw) { yaw_ = yaw; }
+
+    void Enable();
+    void Disable();
+    bool IsEnabled() { return enabled_; }
+
+private:
+
+    bool MouseInView();
+
+    void HandleUpdate(StringHash eventType, VariantMap& eventData);
+    void HandlePostRenderUpdate(StringHash eventType, VariantMap& eventData);
+    void HandleEditorActiveNodeChange(StringHash eventType, VariantMap& eventData);
+
+    void DrawNodeDebug(Node* node, DebugRenderer* debug, bool drawNode = true);
+
+    void MoveCamera(float timeStep);
+
+    WeakPtr<SceneEditor3D> sceneEditor_;
+
+    float yaw_;
+    float pitch_;
+
+    bool mouseLeftDown_;
+    bool mouseMoved_;
+
+    bool enabled_;
+
+    SharedPtr<Camera> camera_;
+    SharedPtr<DebugRenderer> debugRenderer_;
+    SharedPtr<Octree> octree_;
+    SharedPtr<Node> selectedNode_;
+
+};
+
+}

+ 1 - 0
Source/AtomicJS/Javascript/JSUI.cpp

@@ -51,6 +51,7 @@ JSUI::JSUI(Context* context) : Object(context),
     uiTypes_["UIMessageWindow"] = true;
     uiTypes_["UISkinImage"] = true;
     uiTypes_["UITabContainer"] = true;
+    uiTypes_["UISceneView"] = true;
 
 }
 

+ 1 - 1
Source/AtomicJS/Packages/Atomic/UI.json

@@ -7,7 +7,7 @@
 								"UISelectItem", "UISelectItemSource", "UIMenuWindow", "UIEditField",
 								"UIImageWidget", "UIClickLabel", "UICheckBox", "UIMenuItem", "UIMenuItemSource",
 								"UISelectList", "UIListView", "UIMessageWindow", "UILayoutParams", "UIFontDescription",
-								"UISkinImage", "UITabContainer"],
+								"UISkinImage", "UITabContainer", "UISceneView"],
 	"overloads" : {
 	}
 }

+ 2 - 2
Source/AtomicJS/Packages/Editor/Editor.json

@@ -1,5 +1,5 @@
 {
 	"name" : "Editor",
-	"sources" : ["Source/AtomicEditorWork/JSTest", "Source/AtomicEditorWork/Editors"],
-	"classes" : ["MyJSClass", "ResourceEditor", "JSResourceEditor"]
+	"sources" : ["Source/AtomicEditorWork/JSTest", "Source/AtomicEditorWork/Editors", "Source/AtomicEditorWork/Editors/SceneEditor3D"],
+	"classes" : ["MyJSClass", "ResourceEditor", "JSResourceEditor", "SceneEditor3D", "SceneView3D"]
 }