Browse Source

Added Temporary flag to Serializable, which means it will not be saved if enabled. Currently the sub-nodes created by Terrain are marked temporary.
Temporary objects are by default not shown in the editor.
Added trigonometric functions which take degrees argument into the C++ API, not just script.
Edited the Billboards sample.
Made the Urho3D logo in the samples slightly transparent.

Lasse Öörni 12 years ago
parent
commit
26a9bc763e

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

@@ -154,6 +154,7 @@ void LoadConfig()
     if (!hierarchyElem.isNull)
     {
         if (hierarchyElem.HasAttribute("showinternaluielement")) showInternalUIElement = hierarchyElem.GetBool("showinternaluielement");
+        if (hierarchyElem.HasAttribute("showtemporaryobject")) showTemporaryObject = hierarchyElem.GetBool("showtemporaryobject");
     }
 
     if (!inspectorElem.isNull)
@@ -218,6 +219,7 @@ void SaveConfig()
     uiElem.SetFloat("maxopacity", uiMaxOpacity);
 
     hierarchyElem.SetBool("showinternaluielement", showInternalUIElement);
+    hierarchyElem.SetBool("showtemporaryobject", showTemporaryObject);
 
     inspectorElem.SetColor("originalcolor", normalTextColor);
     inspectorElem.SetColor("modifiedcolor", modifiedTextColor);

+ 106 - 19
Bin/Data/Scripts/Editor/EditorHierarchyWindow.as

@@ -21,6 +21,7 @@ ListView@ hierarchyList;
 const uint UI_ELEMENT_BASE_ID = 1;
 uint uiElementNextID = UI_ELEMENT_BASE_ID;
 bool showInternalUIElement = false;
+bool showTemporaryObject = false;
 Array<uint> hierarchyUpdateSelections;
 
 Variant GetUIElementID(UIElement@ element)
@@ -75,6 +76,7 @@ void CreateHierarchyWindow()
     SubscribeToEvent(editorScene, "NodeNameChanged", "HandleNodeNameChanged");
     SubscribeToEvent(editorScene, "NodeEnabledChanged", "HandleNodeEnabledChanged");
     SubscribeToEvent(editorScene, "ComponentEnabledChanged", "HandleComponentEnabledChanged");
+    SubscribeToEvent("TemporaryChanged", "HandleTemporaryChanged");
 }
 
 bool ShowHierarchyWindow()
@@ -204,14 +206,16 @@ uint UpdateHierarchyItem(uint itemIndex, Serializable@ serializable, UIElement@
             for (uint i = 0; i < node.numComponents; ++i)
             {
                 Component@ component = node.components[i];
-                AddComponentItem(itemIndex++, component, text);
+                if (showTemporaryObject || !component.temporary)
+                    AddComponentItem(itemIndex++, component, text);
             }
 
             // Then update child nodes recursively
             for (uint i = 0; i < node.numChildren; ++i)
             {
                 Node@ childNode = node.children[i];
-                itemIndex = UpdateHierarchyItem(itemIndex, childNode, text);
+                if (showTemporaryObject || !childNode.temporary)
+                    itemIndex = UpdateHierarchyItem(itemIndex, childNode, text);
             }
 
             break;
@@ -232,11 +236,11 @@ uint UpdateHierarchyItem(uint itemIndex, Serializable@ serializable, UIElement@
             text.text = GetUIElementTitle(element);
             SetIconEnabledColor(text, element.visible);
 
-            // update child elements recursively
+            // Update child elements recursively
             for (uint i = 0; i < element.numChildren; ++i)
             {
                 UIElement@ childElement = element.children[i];
-                if (showInternalUIElement || !childElement.internal)
+                if ((showInternalUIElement || !childElement.internal) && (showTemporaryObject || !childElement.temporary))
                     itemIndex = UpdateHierarchyItem(itemIndex, childElement, text);
             }
 
@@ -418,32 +422,49 @@ uint GetComponentListIndex(Component@ component)
 
 String GetUIElementTitle(UIElement@ element)
 {
+    String ret;
+
     // Only top level UI-element has this variable
     String modifiedStr = element.GetVar(MODIFIED_VAR).GetBool() ? "*" : "";
-    return (element.name.empty ? element.typeName : element.name) + modifiedStr + " [" + GetUIElementID(element).ToString() + "]";
+    ret = (element.name.empty ? element.typeName : element.name) + modifiedStr + " [" + GetUIElementID(element).ToString() + "]";
+
+    if (element.temporary)
+        ret += " (Temp)";
+
+    return ret;
 }
 
 String GetNodeTitle(Node@ node)
 {
-    String idStr;
-    if (node.id >= FIRST_LOCAL_ID)
-        idStr = "Local " + String(node.id - FIRST_LOCAL_ID);
-    else
-        idStr = String(node.id);
+    String ret;
 
     if (node.name.empty)
-        return node.typeName + " (" + idStr + ")";
+        ret = node.typeName;
     else
-        return node.name + " (" + idStr + ")";
+        ret = node.name;
+
+    if (node.id >= FIRST_LOCAL_ID)
+        ret += " (Local " + String(node.id - FIRST_LOCAL_ID) + ")";
+    else
+        ret += " (" + String(node.id) + ")";
+
+    if (node.temporary)
+        ret += " (Temp)";
+
+    return ret;
 }
 
 String GetComponentTitle(Component@ component)
 {
-    String localStr;
+    String ret = component.typeName;
+
     if (component.id >= FIRST_LOCAL_ID)
-        localStr = " (Local)";
+        ret += " (Local)";
 
-    return component.typeName + localStr;
+    if (component.temporary)
+        ret += " (Temp)";
+
+    return ret;
 }
 
 void SelectNode(Node@ node, bool multiselect)
@@ -866,7 +887,8 @@ void HandleNodeAdded(StringHash eventType, VariantMap& eventData)
         return;
 
     Node@ node = eventData["Node"].GetNode();
-    UpdateHierarchyItem(node);
+    if (showTemporaryObject || !node.temporary)
+        UpdateHierarchyItem(node);
 }
 
 void HandleNodeRemoved(StringHash eventType, VariantMap& eventData)
@@ -887,8 +909,15 @@ void HandleComponentAdded(StringHash eventType, VariantMap& eventData)
     // Insert the newly added component at last component position but before the first child node position of the parent node
     Node@ node = eventData["Node"].GetNode();
     Component@ component = eventData["Component"].GetComponent();
-    uint index = node.numChildren > 0 ? GetListIndex(node.children[0]) : M_MAX_UNSIGNED;
-    UpdateHierarchyItem(index, component, hierarchyList.items[GetListIndex(node)]);
+    if (showTemporaryObject || !component.temporary)
+    {
+        uint nodeIndex = GetListIndex(node);
+        if (nodeIndex != NO_ITEM)
+        {
+            uint index = node.numChildren > 0 ? GetListIndex(node.children[0]) : M_MAX_UNSIGNED;
+            UpdateHierarchyItem(index, component, hierarchyList.items[nodeIndex]);
+        }
+    }
 }
 
 void HandleComponentRemoved(StringHash eventType, VariantMap& eventData)
@@ -937,7 +966,7 @@ void HandleUIElementAdded(StringHash eventType, VariantMap& eventData)
         return;
 
     UIElement@ element = eventData["Element"].GetUIElement();
-    if (showInternalUIElement || !element.internal)
+    if ((showInternalUIElement || !element.internal) && (showTemporaryObject || !element.temporary))
         UpdateHierarchyItem(element);
 }
 
@@ -982,6 +1011,64 @@ void HandleElementAttributeChanged(StringHash eventType, VariantMap& eventData)
     }
 }
 
+void HandleTemporaryChanged(StringHash eventType, VariantMap& eventData)
+{
+    if (suppressSceneChanges || suppressUIElementChanges)
+        return;
+
+    Serializable@ serializable = cast<Serializable>(GetEventSender());
+
+    Node@ node = cast<Node>(serializable);
+    if (node !is null && node.scene is editorScene)
+    {
+        if (showTemporaryObject)
+            UpdateHierarchyItemText(GetListIndex(node), node.enabled);
+        else if (!node.temporary && GetListIndex(node) == NO_ITEM)
+            UpdateHierarchyItem(node);
+        else if (node.temporary)
+            UpdateHierarchyItem(GetListIndex(node), null, null);
+
+        return;
+    }
+
+    Component@ component = cast<Component>(serializable);
+    if (component !is null && component.node !is null && component.node.scene is editorScene)
+    {
+        if (showTemporaryObject)
+            UpdateHierarchyItemText(GetComponentListIndex(component), node.enabled);
+        else if (!component.temporary && GetComponentListIndex(component) == NO_ITEM)
+        {
+            uint nodeIndex = GetListIndex(node);
+            if (nodeIndex != NO_ITEM)
+            {
+                uint index = node.numChildren > 0 ? GetListIndex(node.children[0]) : M_MAX_UNSIGNED;
+                UpdateHierarchyItem(index, component, hierarchyList.items[nodeIndex]);
+            }
+        }
+        else if (component.temporary)
+        {
+            uint index = GetComponentListIndex(component);
+            if (index != NO_ITEM)
+                hierarchyList.RemoveItem(index);
+        }
+
+        return;
+    }
+
+    UIElement@ element = cast<UIElement>(serializable);
+    if (element !is null)
+    {
+        if (showTemporaryObject)
+            UpdateHierarchyItemText(GetListIndex(element), element.visible);
+        else if (!element.temporary && GetListIndex(element) == NO_ITEM)
+            UpdateHierarchyItem(element);
+        else if (element.temporary)
+            UpdateHierarchyItem(GetListIndex(element), null, null);
+
+        return;
+    }
+}
+
 // Hierarchy window edit functions
 bool Undo()
 {

+ 11 - 0
Bin/Data/Scripts/Editor/EditorPreferences.as

@@ -51,6 +51,9 @@ void UpdateEditorPreferencesDialog()
     CheckBox@ showInternalUIElementToggle = preferencesDialog.GetChild("ShowInternalUIElement", true);
     showInternalUIElementToggle.checked = showInternalUIElement;
 
+    CheckBox@ showTemporaryObjectToggle = preferencesDialog.GetChild("ShowTemporaryObject", true);
+    showTemporaryObjectToggle.checked = showTemporaryObject;
+
     CheckBox@ showNonEditableAttributeToggle = preferencesDialog.GetChild("ShowNonEditableAttribute", true);
     showNonEditableAttributeToggle.checked = showNonEditableAttribute;
 
@@ -71,6 +74,7 @@ void UpdateEditorPreferencesDialog()
         SubscribeToEvent(uiMinOpacityEdit, "TextFinished", "EditUIMinOpacity");
         SubscribeToEvent(uiMaxOpacityEdit, "TextFinished", "EditUIMaxOpacity");
         SubscribeToEvent(showInternalUIElementToggle, "Toggled", "ToggleShowInternalUIElement");
+        SubscribeToEvent(showTemporaryObjectToggle, "Toggled", "ToggleShowTemporaryObject");
         SubscribeToEvent(showNonEditableAttributeToggle, "Toggled", "ToggleShowNonEditableAttribute");
         SubscribeToEvent(originalAttributeTextColorEditR, "TextFinished", "EditOriginalAttributeTextColor");
         SubscribeToEvent(originalAttributeTextColorEditG, "TextFinished", "EditOriginalAttributeTextColor");
@@ -123,6 +127,13 @@ void ToggleShowInternalUIElement(StringHash eventType, VariantMap& eventData)
     UpdateHierarchyItem(editorUIElement, true);
 }
 
+void ToggleShowTemporaryObject(StringHash eventType, VariantMap& eventData)
+{
+    showTemporaryObject = cast<CheckBox>(eventData["Element"].GetUIElement()).checked;
+    UpdateHierarchyItem(editorScene, true);
+    UpdateHierarchyItem(editorUIElement, true);
+}
+
 void ToggleShowNonEditableAttribute(StringHash eventType, VariantMap& eventData)
 {
     showNonEditableAttribute = cast<CheckBox>(eventData["Element"].GetUIElement()).checked;

+ 12 - 0
Bin/Data/UI/EditorPreferencesDialog.xml

@@ -73,6 +73,18 @@
             <attribute name="Name" value="ShowInternalUIElement" />
         </element>
     </element>
+    <element>
+        <attribute name="Min Size" value="0 17" />
+        <attribute name="Max Size" value="2147483647 17" />
+        <attribute name="Layout Mode" value="Horizontal" />
+        <attribute name="Layout Border" value="20 0 0 0" />
+        <element type="Text">
+            <attribute name="Text" value="Show temporary objects" />
+        </element>
+        <element type="CheckBox">
+            <attribute name="Name" value="ShowTemporaryObject" />
+        </element>
+    </element>
     <element type="BorderImage" style="EditorDivider" />
     <element>
         <attribute name="Min Size" value="0 17" />

+ 1 - 1
Source/Engine/Graphics/Batch.cpp

@@ -355,7 +355,7 @@ void Batch::Prepare(View* view, bool setModelTransform) const
                     invRange = 1.0f / Max(vertexLight->GetRange(), M_EPSILON);
                 if (type == LIGHT_SPOT)
                 {
-                    cutoff = cosf(vertexLight->GetFov() * 0.5f * M_DEGTORAD);
+                    cutoff = Cos(vertexLight->GetFov() * 0.5f);
                     invCutoff = 1.0f / (1.0f - cutoff);
                 }
                 else

+ 2 - 3
Source/Engine/Graphics/BillboardSet.cpp

@@ -433,11 +433,10 @@ void BillboardSet::UpdateVertexBuffer(const FrameInfo& frame)
         
         Vector2 size(billboard.size_.x_ * billboardScale.x_, billboard.size_.y_ * billboardScale.y_);
         unsigned color = billboard.color_.ToUInt();
-        float angleRad = billboard.rotation_ * M_DEGTORAD;
         
         float rotationMatrix[2][2];
-        rotationMatrix[0][0] = cosf(angleRad);
-        rotationMatrix[0][1] = sinf(angleRad);
+        rotationMatrix[0][0] = Cos(billboard.rotation_);
+        rotationMatrix[0][1] = Sin(billboard.rotation_);
         rotationMatrix[1][0] = -rotationMatrix[0][1];
         rotationMatrix[1][1] = rotationMatrix[0][0];
         

+ 4 - 4
Source/Engine/Graphics/DebugRenderer.cpp

@@ -204,10 +204,10 @@ void DebugRenderer::AddSphere(const Sphere& sphere, const Color& color, bool dep
     for (unsigned i = 0; i < 360; i += 45)
     {
         unsigned j = i + 45;
-        float a = radius * sinf(i * M_DEGTORAD);
-        float b = radius * cosf(i * M_DEGTORAD);
-        float c = radius * sinf(j * M_DEGTORAD);
-        float d = radius * cosf(j * M_DEGTORAD);
+        float a = radius * Sin(i);
+        float b = radius * Cos(i);
+        float c = radius * Sin(j);
+        float d = radius * Cos(j);
         Vector3 start, end;
 
         start = center + Vector3(a, b, 0.0f);

+ 2 - 3
Source/Engine/Graphics/Material.cpp

@@ -360,9 +360,8 @@ void Material::SetUVTransform(const Vector2& offset, float rotation, const Vecto
     transform.m13_ = -0.5f * transform.m11_ + 0.5f;
     
     Matrix3x4 rotationMatrix(Matrix3x4::IDENTITY);
-    float angleRad = rotation * M_DEGTORAD;
-    rotationMatrix.m00_ = cosf(angleRad);
-    rotationMatrix.m01_ = sinf(angleRad);
+    rotationMatrix.m00_ = Cos(rotation);
+    rotationMatrix.m01_ = Sin(rotation);
     rotationMatrix.m10_ = -rotationMatrix.m01_;
     rotationMatrix.m11_ = rotationMatrix.m00_;
     rotationMatrix.m03_ = 0.5f - 0.5f * (rotationMatrix.m00_ + rotationMatrix.m01_);

+ 7 - 1
Source/Engine/Graphics/Terrain.cpp

@@ -665,9 +665,15 @@ void Terrain::CreateGeometry()
             {
                 String nodeName = "Patch_" + String(x) + "_" + String(z);
                 Node* patchNode = node_->GetChild(nodeName);
+                
                 if (!patchNode)
+                {
+                    // Create the patch scene node as local and temporary so that it is not unnecessarily serialized to either
+                    // file or replicated over the network
                     patchNode = node_->CreateChild(nodeName, LOCAL);
-
+                    patchNode->SetTemporary(true);
+                }
+                
                 patchNode->SetPosition(Vector3(patchWorldOrigin_.x_ + (float)x * patchWorldSize_.x_, 0.0f, patchWorldOrigin_.y_ +
                     (float)z * patchWorldSize_.y_));
 

+ 17 - 2
Source/Engine/Math/MathDefs.h

@@ -56,6 +56,8 @@ enum Intersection
     INSIDE
 };
 
+/// Check whether two floating point values are equal within accuracy.
+inline bool Equals(float lhs, float rhs) { return lhs + M_EPSILON >= rhs && lhs - M_EPSILON <= rhs; }
 /// Linear interpolation between two float values.
 inline float Lerp(float lhs, float rhs, float t) { return lhs * (1.0f - t) + rhs * t; }
 /// Return the smaller of two floats.
@@ -76,8 +78,21 @@ inline float Clamp(float value, float min, float max)
         return value;
 }
 
-/// Check whether two floating point values are equal within accuracy.
-inline bool Equals(float lhs, float rhs) { return lhs + M_EPSILON >= rhs && lhs - M_EPSILON <= rhs; }
+/// Return sine of an angle in degrees.
+inline float Sin(float angle) { return sinf(angle * M_DEGTORAD); }
+/// Return cosine of an angle in degrees.
+inline float Cos(float angle) { return cosf(angle * M_DEGTORAD); }
+/// Return tangent of an angle in degrees.
+inline float Tan(float angle) { return tanf(angle * M_DEGTORAD); }
+/// Return arc sine in degrees.
+inline float Asin(float x) { return M_RADTODEG * asinf(Clamp(x, -1.0f, 1.0f)); }
+/// Return arc cosine in degrees.
+inline float Acos(float x) { return M_RADTODEG * acosf(Clamp(x, -1.0f, 1.0f)); }
+/// Return arc tangent in degrees.
+inline float Atan(float x) { return M_RADTODEG * atanf(x); }
+/// Return arc tangent of y/x in degrees.
+inline float Atan2(float y, float x) { return M_RADTODEG * atan2f(y, x); }
+
 /// Return the smaller of two integers.
 inline int Min(int lhs, int rhs) { return lhs < rhs ? lhs : rhs; }
 /// Return the larger of two integers.

+ 40 - 2
Source/Engine/Scene/Node.cpp

@@ -115,10 +115,13 @@ bool Node::Save(Serializer& dest) const
         return false;
 
     // Write components
-    dest.WriteVLE(components_.Size());
+    dest.WriteVLE(GetNumPersistentComponents());
     for (unsigned i = 0; i < components_.Size(); ++i)
     {
         Component* component = components_[i];
+        if (component->IsTemporary())
+            continue;
+        
         // Create a separate buffer to be able to skip unknown components during deserialization
         VectorBuffer compBuffer;
         if (!component->Save(compBuffer))
@@ -128,10 +131,13 @@ bool Node::Save(Serializer& dest) const
     }
 
     // Write child nodes
-    dest.WriteVLE(children_.Size());
+    dest.WriteVLE(GetNumPersistentChildren());
     for (unsigned i = 0; i < children_.Size(); ++i)
     {
         Node* node = children_[i];
+        if (node->IsTemporary())
+            continue;
+        
         if (!node->Save(dest))
             return false;
     }
@@ -172,6 +178,9 @@ bool Node::SaveXML(XMLElement& dest) const
     for (unsigned i = 0; i < components_.Size(); ++i)
     {
         Component* component = components_[i];
+        if (component->IsTemporary())
+            continue;
+        
         XMLElement compElem = dest.CreateChild("component");
         if (!component->SaveXML(compElem))
             return false;
@@ -181,6 +190,9 @@ bool Node::SaveXML(XMLElement& dest) const
     for (unsigned i = 0; i < children_.Size(); ++i)
     {
         Node* node = children_[i];
+        if (node->IsTemporary())
+            continue;
+        
         XMLElement childElem = dest.CreateChild("node");
         if (!node->SaveXML(childElem))
             return false;
@@ -1252,6 +1264,32 @@ void Node::AddComponent(Component* component, unsigned id, CreateMode mode)
     }
 }
 
+unsigned Node::GetNumPersistentChildren() const
+{
+    unsigned ret = 0;
+    
+    for (Vector<SharedPtr<Node> >::ConstIterator i = children_.Begin(); i != children_.End(); ++i)
+    {
+        if (!(*i)->IsTemporary())
+            ++ret;
+    }
+    
+    return ret;
+}
+
+unsigned Node::GetNumPersistentComponents() const
+{
+    unsigned ret = 0;
+    
+    for (Vector<SharedPtr<Component> >::ConstIterator i = components_.Begin(); i != components_.End(); ++i)
+    {
+        if (!(*i)->IsTemporary())
+            ++ret;
+    }
+    
+    return ret;
+}
+
 void Node::UpdateWorldTransform() const
 {
     Matrix3x4 transform = GetTransform();

+ 5 - 1
Source/Engine/Scene/Node.h

@@ -335,7 +335,11 @@ public:
     Node* CreateChild(unsigned id, CreateMode mode);
     /// Add a pre-created component.
     void AddComponent(Component* component, unsigned id, CreateMode mode);
-
+    /// Calculate number of non-temporary child nodes.
+    unsigned GetNumPersistentChildren() const;
+    /// Calculate number of non-temporary components.
+    unsigned GetNumPersistentComponents() const;
+    
 protected:
     /// User variables.
     VariantMap vars_;

+ 6 - 0
Source/Engine/Scene/SceneEvents.h

@@ -141,4 +141,10 @@ EVENT(E_COMPONENTENABLEDCHANGED, ComponentEnabledChanged)
     PARAM(P_COMPONENT, Component);          // Component pointer
 }
 
+/// A serializable's temporary state has changed.
+EVENT(E_TEMPORARYCHANGED, TemporaryChanged)
+{
+    PARAM(P_SERIALIZABLE, Serializable);    // Serializable pointer
+}
+
 }

+ 18 - 1
Source/Engine/Scene/Serializable.cpp

@@ -25,6 +25,7 @@
 #include "Deserializer.h"
 #include "Log.h"
 #include "ReplicationState.h"
+#include "SceneEvents.h"
 #include "Serializable.h"
 #include "Serializer.h"
 #include "StringUtils.h"
@@ -38,7 +39,8 @@ namespace Urho3D
 Serializable::Serializable(Context* context) :
     Object(context),
     networkState_(0),
-    instanceDefaultValues_(0)
+    instanceDefaultValues_(0),
+    temporary_(false)
 {
 }
 
@@ -500,6 +502,21 @@ void Serializable::RemoveInstanceDefault()
     instanceDefaultValues_ = 0;
 }
 
+void Serializable::SetTemporary(bool enable)
+{
+    if (enable != temporary_)
+    {
+        temporary_ = enable;
+        
+        using namespace TemporaryChanged;
+        
+        VariantMap eventData;
+        eventData[P_SERIALIZABLE] = (void*)this;
+        
+        SendEvent(E_TEMPORARYCHANGED, eventData);
+    }
+}
+
 void Serializable::AllocateNetworkState()
 {
     if (!networkState_)

+ 7 - 0
Source/Engine/Scene/Serializable.h

@@ -79,6 +79,8 @@ public:
     void ResetToDefault();
     /// Remove instance's default values if they are set previously.
     void RemoveInstanceDefault();
+    /// Set temporary flag. Temporary objects will not be saved.
+    void SetTemporary(bool enable);
     /// Allocate network attribute state.
     void AllocateNetworkState();
     /// Write initial delta network update.
@@ -104,6 +106,8 @@ public:
     unsigned GetNumAttributes() const;
     /// Return number of network replication attributes.
     unsigned GetNumNetworkAttributes() const;
+    /// Return whether is temporary.
+    bool IsTemporary() const { return temporary_; }
 
 protected:
     /// Network attribute state.
@@ -114,8 +118,11 @@ private:
     void SetInstanceDefault(const String& name, const Variant& defaultValue);
     /// Get instance-level default value.
     Variant GetInstanceDefault(const String& name) const;
+    
     /// Attribute default value at each instance level.
     VariantMap* instanceDefaultValues_;
+    /// Temporary flag.
+    bool temporary_;
 };
 
 /// Template implementation of the attribute accessor invoke helper class.

+ 2 - 0
Source/Engine/Script/APITemplates.h

@@ -383,6 +383,8 @@ template <class T> void RegisterSerializable(asIScriptEngine* engine, const char
     engine->RegisterObjectMethod(className, "Variant get_attributes(uint) const", asMETHODPR(T, GetAttribute, (unsigned) const, Variant), asCALL_THISCALL);
     engine->RegisterObjectMethod(className, "Variant get_attributeDefaults(uint) const", asMETHODPR(T, GetAttributeDefault, (unsigned) const, Variant), asCALL_THISCALL);
     engine->RegisterObjectMethod(className, "const AttributeInfo& get_attributeInfos(uint) const", asFUNCTION(SerializableGetAttributeInfo), asCALL_CDECL_OBJLAST);
+    engine->RegisterObjectMethod(className, "void set_temporary(bool)", asMETHODPR(T, SetTemporary, (bool), void), asCALL_THISCALL);
+    engine->RegisterObjectMethod(className, "bool get_temporary() const", asMETHODPR(T, IsTemporary, () const, bool), asCALL_THISCALL);
     RegisterSubclass<Object, T>(engine, "Serializable", className);
 }
 

+ 0 - 35
Source/Engine/Script/MathAPI.cpp

@@ -32,41 +32,6 @@
 namespace Urho3D
 {
 
-static float Sin(float angle)
-{
-    return sinf(angle * M_DEGTORAD);
-}
-
-static float Cos(float angle)
-{
-    return cosf(angle * M_DEGTORAD);
-}
-
-static float Tan(float angle)
-{
-    return tanf(angle * M_DEGTORAD);
-}
-
-static float Asin(float x)
-{
-    return M_RADTODEG * asinf(Clamp(x, -1.0f, 1.0f));
-}
-
-static float Acos(float x)
-{
-    return M_RADTODEG * acosf(Clamp(x, -1.0f, 1.0f));
-}
-
-static float Atan(float x)
-{
-    return M_RADTODEG * atanf(x);
-}
-
-static float Atan2(float y, float x)
-{
-    return M_RADTODEG * atan2f(y, x);
-}
-
 static void RegisterMathFunctions(asIScriptEngine* engine)
 {
     engine->RegisterGlobalProperty("const float M_INFINITY", (void*)&M_INFINITY);

+ 2 - 1
Source/Engine/Script/SceneAPI.cpp

@@ -72,7 +72,8 @@ static void RegisterNode(asIScriptEngine* engine)
     // Now GetNode can be registered
     engine->RegisterObjectMethod("Component", "Node@+ get_node() const", asMETHOD(Component, GetNode), asCALL_THISCALL);
 
-    // Register Variant GetPtr() for Node & Component
+    // Register Variant GetPtr() for Serializable, Node & Component
+    engine->RegisterObjectMethod("Variant", "Serializable@+ GetSerializable() const", asFUNCTION(GetVariantPtr<Serializable>), asCALL_CDECL_OBJLAST);
     engine->RegisterObjectMethod("Variant", "Node@+ GetNode() const", asFUNCTION(GetVariantPtr<Node>), asCALL_CDECL_OBJLAST);
     engine->RegisterObjectMethod("Variant", "Component@+ GetComponent() const", asFUNCTION(GetVariantPtr<Component>), asCALL_CDECL_OBJLAST);
 }

+ 3 - 0
Source/Engine/UI/UIElement.cpp

@@ -356,6 +356,9 @@ bool UIElement::SaveXML(XMLElement& dest) const
     for (unsigned i = 0; i < children_.Size(); ++i)
     {
         UIElement* element = children_[i];
+        if (element->IsTemporary())
+            continue;
+        
         XMLElement childElem = dest.CreateChild("element");
         if (!element->SaveXML(childElem))
             return false;

+ 1 - 1
Source/Engine/UI/UIElement.h

@@ -129,7 +129,7 @@ public:
     virtual bool LoadXML(const XMLElement& source, bool setInstanceDefault = false);
     /// Load from XML data with style. Return true if successful.
     virtual bool LoadXML(const XMLElement& source, XMLFile* styleFile, bool setInstanceDefault = false);
-    /// Create a child by Loading from XML data with style. Return true if successful.
+    /// Create a child by loading from XML data with style. Return true if successful.
     virtual bool LoadChildXML(const XMLElement& childElem, XMLFile* styleFile = 0, bool setInstanceDefault = false);
     /// Save as XML data. Return true if successful.
     virtual bool SaveXML(XMLElement& dest) const;

+ 4 - 0
Source/Extras/LuaScript/pkgs/Scene/Serializable.pkg

@@ -2,4 +2,8 @@ $#include "Serializable.h"
 
 class Serializable : public Object
 {
+    void SetTemporary(bool enable);
+    bool IsTemporary() const;
+    
+    tolua_property__is_set bool temporary;
 };

+ 21 - 36
Source/Samples/07_Billboards/Billboards.cpp

@@ -43,9 +43,6 @@
 
 #include "DebugNew.h"
 
-// Custom variable identifier for storing light angle into the light scene nodes
-static const ShortStringHash VAR_ANGLE("Angle");
-
 // Expands to this example's entry-point
 DEFINE_APPLICATION_MAIN(Billboards)
 
@@ -117,22 +114,22 @@ void Billboards::CreateScene()
         }
     }
     
-    // Create groups of mushrooms, which act as shadows casters
-    const unsigned NUM_MUSHROOMGROUPS = 20;
-    const unsigned NUM_MUSHROOMS = 50;
+    // Create groups of mushrooms, which act as shadow casters
+    const unsigned NUM_MUSHROOMGROUPS = 25;
+    const unsigned NUM_MUSHROOMS = 25;
     
     for (unsigned i = 0; i < NUM_MUSHROOMGROUPS; ++i)
     {
         // First create a scene node for the group. The individual mushrooms nodes will be created as children
         Node* groupNode = scene_->CreateChild("MushroomGroup");
-        groupNode->SetPosition(Vector3(Random(180.0f) - 90.0f, 0.0f, Random(180.0f) - 90.0f));
+        groupNode->SetPosition(Vector3(Random(190.0f) - 95.0f, 0.0f, Random(190.0f) - 95.0f));
         
         for (unsigned j = 0; j < NUM_MUSHROOMS; ++j)
         {
             Node* mushroomNode = groupNode->CreateChild("Mushroom");
-            mushroomNode->SetPosition(Vector3(Random(20.0f) - 10.0f, 0.0f, Random(20.0f) - 10.0f));
+            mushroomNode->SetPosition(Vector3(Random(25.0f) - 12.5f, 0.0f, Random(25.0f) - 12.5f));
             mushroomNode->SetRotation(Quaternion(0.0f, Random() * 360.0f, 0.0f));
-            mushroomNode->SetScale(1.0f + Random() * 2.0f);
+            mushroomNode->SetScale(1.0f + Random() * 4.0f);
             StaticModel* mushroomObject = mushroomNode->CreateComponent<StaticModel>();
             mushroomObject->SetModel(cache->GetResource<Model>("Models/Mushroom.mdl"));
             mushroomObject->SetMaterial(cache->GetResource<Material>("Materials/Mushroom.xml"));
@@ -141,13 +138,13 @@ void Billboards::CreateScene()
     }
     
     // Create billboard sets (floating smoke)
-    const unsigned NUM_BILLBOARDNODES = 30;
+    const unsigned NUM_BILLBOARDNODES = 40;
     const unsigned NUM_BILLBOARDS = 15;
 
     for (unsigned i = 0; i < NUM_BILLBOARDNODES; ++i)
     {
         Node* smokeNode = scene_->CreateChild("Smoke");
-        smokeNode->SetPosition(Vector3(Random(200.0f) - 100.0f, Random(15.0f) + 10.0f, Random(200.0f) - 100.0f));
+        smokeNode->SetPosition(Vector3(Random(200.0f) - 100.0f, Random(20.0f) + 10.0f, Random(200.0f) - 100.0f));
         
         BillboardSet* billboardObject = smokeNode->CreateComponent<BillboardSet>();
         billboardObject->SetNumBillboards(NUM_BILLBOARDS);
@@ -168,30 +165,25 @@ void Billboards::CreateScene()
     }
     
     // Create shadow casting spotlights
-    const unsigned NUM_LIGHTS = 20;
+    const unsigned NUM_LIGHTS = 9;
     
     for (unsigned i = 0; i < NUM_LIGHTS; ++i)
     {
         Node* lightNode = scene_->CreateChild("Light");
         Light* light = lightNode->CreateComponent<Light>();
-
-        Vector3 position(Random(150.0f) - 75.0f, Random(30.f) + 30.0f, Random(150.0f) - 75.0f);
-        Color color((Rand() & 1) * 0.5f + 0.5f, (Rand() & 1) * 0.5f + 0.5f, (Rand() & 1) * 0.5f + 0.5f);
-        // If color is dark grey, use white instead
-        if (color.r_ == 0.5f && color.g_ == 0.5f && color.b_ == 0.5f)
-            color = Color(1.0f, 1.0f, 1.0f);
-
-        float angle = Random() * 360.0f;
-
+        
+        float angle = 0.0f;
+        
+        Vector3 position((i % 3) * 60.0f - 60.0f, 45.0f, (i / 3) * 60.0f - 60.0f);
+        Color color(((i + 1) & 1) * 0.5f + 0.5f, (((i + 1) >> 1) & 1) * 0.5f + 0.5f, (((i + 1) >> 2) & 1) * 0.5f + 0.5f);
+        
         lightNode->SetPosition(position);
-        lightNode->SetDirection(Vector3(sinf(angle * M_DEGTORAD), -1.0f, cosf(angle * M_DEGTORAD)));
-        // Store the original angle as a custom variable of the node
-        lightNode->SetVar(VAR_ANGLE, angle);
+        lightNode->SetDirection(Vector3(Sin(angle), -1.5f, Cos(angle)));
         
         light->SetLightType(LIGHT_SPOT);
-        light->SetRange(75.0f);
+        light->SetRange(90.0f);
         light->SetRampTexture(cache->GetResource<Texture2D>("Textures/RampExtreme.png"));
-        light->SetFov(15.0f);
+        light->SetFov(45.0f);
         light->SetColor(color);
         light->SetSpecularIntensity(1.0f);
         light->SetCastShadows(true);
@@ -284,19 +276,12 @@ void Billboards::AnimateScene(float timeStep)
     scene_->GetChildrenWithComponent<Light>(lightNodes);
     scene_->GetChildrenWithComponent<BillboardSet>(billboardNodes);
     
-    const float LIGHT_ROTATION_SPEED = 10.0f;
+    const float LIGHT_ROTATION_SPEED = 20.0f;
     const float BILLBOARD_ROTATION_SPEED = 50.0f;
     
-    // Rotate the lights
+    // Rotate the lights around the world Y-axis
     for (unsigned i = 0; i < lightNodes.Size(); ++i)
-    {
-        Node* lightNode = lightNodes[i];
-        
-        float angle = lightNode->GetVar(VAR_ANGLE).GetFloat();
-        angle += LIGHT_ROTATION_SPEED * timeStep;
-        lightNode->SetDirection(Vector3(sinf(angle * M_DEGTORAD), -1.0f, cosf(angle * M_DEGTORAD)));
-        lightNode->SetVar(VAR_ANGLE, angle);
-    }
+        lightNodes[i]->Rotate(Quaternion(0.0f, LIGHT_ROTATION_SPEED * timeStep, 0.0f), true);
     
     // Rotate the individual billboards within the billboard sets, then recommit to make the changes visible
     for (unsigned i = 0; i < billboardNodes.Size(); ++i)

+ 3 - 0
Source/Samples/Sample.inl

@@ -91,6 +91,9 @@ void Sample::CreateLogo()
     // Set logo sprite alignment
     logoSprite_->SetAlignment(HA_LEFT, VA_BOTTOM);
     
+    // Make logo not fully opaque to show the scene underneath
+    logoSprite_->SetOpacity(0.75f);
+    
     // Set a low priority for the logo so that other UI elements can be drawn on top
     logoSprite_->SetPriority(-100);
 }