Browse Source

Merge remote-tracking branch 'ucupumar/particle-editor'

Lasse Öörni 10 years ago
parent
commit
925b69ef4c

+ 4 - 0
Source/Urho3D/AngelScript/GraphicsAPI.cpp

@@ -1319,12 +1319,14 @@ static void RegisterBillboardSet(asIScriptEngine* engine)
     engine->RegisterEnumValue("FaceCameraMode", "FC_ROTATE_Y", FC_ROTATE_Y);
     engine->RegisterEnumValue("FaceCameraMode", "FC_ROTATE_Y", FC_ROTATE_Y);
     engine->RegisterEnumValue("FaceCameraMode", "FC_LOOKAT_XYZ", FC_LOOKAT_XYZ);
     engine->RegisterEnumValue("FaceCameraMode", "FC_LOOKAT_XYZ", FC_LOOKAT_XYZ);
     engine->RegisterEnumValue("FaceCameraMode", "FC_LOOKAT_Y", FC_LOOKAT_Y);
     engine->RegisterEnumValue("FaceCameraMode", "FC_LOOKAT_Y", FC_LOOKAT_Y);
+    engine->RegisterEnumValue("FaceCameraMode", "FC_DIRECTION", FC_DIRECTION);
 
 
     engine->RegisterObjectType("Billboard", 0, asOBJ_REF);
     engine->RegisterObjectType("Billboard", 0, asOBJ_REF);
     engine->RegisterObjectBehaviour("Billboard", asBEHAVE_ADDREF, "void f()", asFUNCTION(FakeAddRef), asCALL_CDECL_OBJLAST);
     engine->RegisterObjectBehaviour("Billboard", asBEHAVE_ADDREF, "void f()", asFUNCTION(FakeAddRef), asCALL_CDECL_OBJLAST);
     engine->RegisterObjectBehaviour("Billboard", asBEHAVE_RELEASE, "void f()", asFUNCTION(FakeReleaseRef), asCALL_CDECL_OBJLAST);
     engine->RegisterObjectBehaviour("Billboard", asBEHAVE_RELEASE, "void f()", asFUNCTION(FakeReleaseRef), asCALL_CDECL_OBJLAST);
     engine->RegisterObjectProperty("Billboard", "Vector3 position", offsetof(Billboard, position_));
     engine->RegisterObjectProperty("Billboard", "Vector3 position", offsetof(Billboard, position_));
     engine->RegisterObjectProperty("Billboard", "Vector2 size", offsetof(Billboard, size_));
     engine->RegisterObjectProperty("Billboard", "Vector2 size", offsetof(Billboard, size_));
+    engine->RegisterObjectProperty("Billboard", "Vector3 direction", offsetof(Billboard, direction_));
     engine->RegisterObjectProperty("Billboard", "Rect uv", offsetof(Billboard, uv_));
     engine->RegisterObjectProperty("Billboard", "Rect uv", offsetof(Billboard, uv_));
     engine->RegisterObjectProperty("Billboard", "Color color", offsetof(Billboard, color_));
     engine->RegisterObjectProperty("Billboard", "Color color", offsetof(Billboard, color_));
     engine->RegisterObjectProperty("Billboard", "float rotation", offsetof(Billboard, rotation_));
     engine->RegisterObjectProperty("Billboard", "float rotation", offsetof(Billboard, rotation_));
@@ -1429,6 +1431,8 @@ static void RegisterParticleEffect(asIScriptEngine* engine)
     engine->RegisterObjectMethod("ParticleEffect", "float get_sizeAdd() const", asMETHOD(ParticleEffect, GetSizeAdd), asCALL_THISCALL);
     engine->RegisterObjectMethod("ParticleEffect", "float get_sizeAdd() const", asMETHOD(ParticleEffect, GetSizeAdd), asCALL_THISCALL);
     engine->RegisterObjectMethod("ParticleEffect", "void set_sizeMul(float)", asMETHOD(ParticleEffect, SetSizeMul), asCALL_THISCALL);
     engine->RegisterObjectMethod("ParticleEffect", "void set_sizeMul(float)", asMETHOD(ParticleEffect, SetSizeMul), asCALL_THISCALL);
     engine->RegisterObjectMethod("ParticleEffect", "float get_sizeMul() const", asMETHOD(ParticleEffect, GetSizeMul), asCALL_THISCALL);
     engine->RegisterObjectMethod("ParticleEffect", "float get_sizeMul() const", asMETHOD(ParticleEffect, GetSizeMul), asCALL_THISCALL);
+    engine->RegisterObjectMethod("ParticleEffect", "void set_faceCameraMode(FaceCameraMode)", asMETHOD(ParticleEffect, SetFaceCameraMode), asCALL_THISCALL);
+    engine->RegisterObjectMethod("ParticleEffect", "FaceCameraMode get_faceCameraMode() const", asMETHOD(ParticleEffect, GetFaceCameraMode), asCALL_THISCALL);
 
 
     engine->RegisterObjectMethod("ParticleEffect", "void AddColorTime(Color&, float)", asMETHOD(ParticleEffect, AddColorTime), asCALL_THISCALL);
     engine->RegisterObjectMethod("ParticleEffect", "void AddColorTime(Color&, float)", asMETHOD(ParticleEffect, AddColorTime), asCALL_THISCALL);
     engine->RegisterObjectMethod("ParticleEffect", "void AddColorFrame(ColorFrame@+)", asMETHOD(ParticleEffect, AddColorFrame), asCALL_THISCALL);
     engine->RegisterObjectMethod("ParticleEffect", "void AddColorFrame(ColorFrame@+)", asMETHOD(ParticleEffect, AddColorFrame), asCALL_THISCALL);

+ 203 - 70
Source/Urho3D/Graphics/BillboardSet.cpp

@@ -53,6 +53,7 @@ const char* faceCameraModeNames[] =
     "Rotate Y",
     "Rotate Y",
     "LookAt XYZ",
     "LookAt XYZ",
     "LookAt Y",
     "LookAt Y",
+    "Rotate Along Direction",
     0
     0
 };
 };
 
 
@@ -104,7 +105,7 @@ void BillboardSet::RegisterObject(Context* context)
     URHO3D_ACCESSOR_ATTRIBUTE("Sort By Distance", IsSorted, SetSorted, bool, false, AM_DEFAULT);
     URHO3D_ACCESSOR_ATTRIBUTE("Sort By Distance", IsSorted, SetSorted, bool, false, AM_DEFAULT);
     URHO3D_ACCESSOR_ATTRIBUTE("Can Be Occluded", IsOccludee, SetOccludee, bool, true, AM_DEFAULT);
     URHO3D_ACCESSOR_ATTRIBUTE("Can Be Occluded", IsOccludee, SetOccludee, bool, true, AM_DEFAULT);
     URHO3D_ATTRIBUTE("Cast Shadows", bool, castShadows_, false, AM_DEFAULT);
     URHO3D_ATTRIBUTE("Cast Shadows", bool, castShadows_, false, AM_DEFAULT);
-    URHO3D_ENUM_ATTRIBUTE("Face Camera Mode", faceCameraMode_, faceCameraModeNames, FC_ROTATE_XYZ, AM_DEFAULT);
+    URHO3D_ENUM_ACCESSOR_ATTRIBUTE("Face Camera Mode", GetFaceCameraMode, SetFaceCameraMode, FaceCameraMode, faceCameraModeNames, FC_ROTATE_XYZ, AM_DEFAULT);
     URHO3D_ACCESSOR_ATTRIBUTE("Draw Distance", GetDrawDistance, SetDrawDistance, float, 0.0f, AM_DEFAULT);
     URHO3D_ACCESSOR_ATTRIBUTE("Draw Distance", GetDrawDistance, SetDrawDistance, float, 0.0f, AM_DEFAULT);
     URHO3D_ACCESSOR_ATTRIBUTE("Shadow Distance", GetShadowDistance, SetShadowDistance, float, 0.0f, AM_DEFAULT);
     URHO3D_ACCESSOR_ATTRIBUTE("Shadow Distance", GetShadowDistance, SetShadowDistance, float, 0.0f, AM_DEFAULT);
     URHO3D_ACCESSOR_ATTRIBUTE("Animation LOD Bias", GetAnimationLodBias, SetAnimationLodBias, float, 1.0f, AM_DEFAULT);
     URHO3D_ACCESSOR_ATTRIBUTE("Animation LOD Bias", GetAnimationLodBias, SetAnimationLodBias, float, 1.0f, AM_DEFAULT);
@@ -170,8 +171,13 @@ void BillboardSet::UpdateBatches(const FrameInfo& frame)
     Vector3 worldPos = node_->GetWorldPosition();
     Vector3 worldPos = node_->GetWorldPosition();
     Vector3 offset = (worldPos - frame.camera_->GetNode()->GetWorldPosition());
     Vector3 offset = (worldPos - frame.camera_->GetNode()->GetWorldPosition());
     // Sort if position relative to camera has changed
     // Sort if position relative to camera has changed
-    if (offset != previousOffset_ & sorted_)
-        sortThisFrame_ = true;
+    if (offset != previousOffset_)
+    {
+        if(sorted_)
+            sortThisFrame_ = true;
+        if(faceCameraMode_ == FC_DIRECTION)
+            bufferDirty_ = true;
+    }
 
 
     distance_ = frame.camera_->GetDistance(GetWorldBoundingBox().Center());
     distance_ = frame.camera_->GetDistance(GetWorldBoundingBox().Center());
 
 
@@ -188,8 +194,11 @@ void BillboardSet::UpdateBatches(const FrameInfo& frame)
     // Billboard positioning
     // Billboard positioning
     transforms_[0] = relative_ ? node_->GetWorldTransform() : Matrix3x4::IDENTITY;
     transforms_[0] = relative_ ? node_->GetWorldTransform() : Matrix3x4::IDENTITY;
     // Billboard rotation
     // Billboard rotation
-    transforms_[1] = Matrix3x4(Vector3::ZERO, faceCameraMode_ != FC_NONE ? frame.camera_->GetFaceCameraRotation(
-        node_->GetWorldPosition(), node_->GetWorldRotation(), faceCameraMode_) : node_->GetWorldRotation(), Vector3::ONE);
+    if (faceCameraMode_ != FC_DIRECTION)
+    {
+        transforms_[1] = Matrix3x4(Vector3::ZERO, faceCameraMode_ != FC_NONE ? frame.camera_->GetFaceCameraRotation(
+            node_->GetWorldPosition(), node_->GetWorldRotation(), faceCameraMode_) : node_->GetWorldRotation(), Vector3::ONE);
+    }
 }
 }
 
 
 void BillboardSet::UpdateGeometry(const FrameInfo& frame)
 void BillboardSet::UpdateGeometry(const FrameInfo& frame)
@@ -201,7 +210,7 @@ void BillboardSet::UpdateGeometry(const FrameInfo& frame)
         UpdateVertexBuffer(frame);
         UpdateVertexBuffer(frame);
 
 
     // If using camera facing, re-update the rotation for the current view now
     // If using camera facing, re-update the rotation for the current view now
-    if (faceCameraMode_ != FC_NONE)
+    if (faceCameraMode_ != FC_NONE || faceCameraMode_ != FC_DIRECTION)
     {
     {
         transforms_[1] = Matrix3x4(Vector3::ZERO, frame.camera_->GetFaceCameraRotation(node_->GetWorldPosition(),
         transforms_[1] = Matrix3x4(Vector3::ZERO, frame.camera_->GetFaceCameraRotation(node_->GetWorldPosition(),
             node_->GetWorldRotation(), faceCameraMode_), Vector3::ONE);
             node_->GetWorldRotation(), faceCameraMode_), Vector3::ONE);
@@ -245,6 +254,7 @@ void BillboardSet::SetNumBillboards(unsigned num)
         billboards_[i].uv_ = Rect::POSITIVE;
         billboards_[i].uv_ = Rect::POSITIVE;
         billboards_[i].color_ = Color(1.0f, 1.0f, 1.0f);
         billboards_[i].color_ = Color(1.0f, 1.0f, 1.0f);
         billboards_[i].rotation_ = 0.0f;
         billboards_[i].rotation_ = 0.0f;
+        billboards_[i].direction_ = Vector3::UP;
         billboards_[i].enabled_ = false;
         billboards_[i].enabled_ = false;
     }
     }
 
 
@@ -272,8 +282,19 @@ void BillboardSet::SetSorted(bool enable)
 
 
 void BillboardSet::SetFaceCameraMode(FaceCameraMode mode)
 void BillboardSet::SetFaceCameraMode(FaceCameraMode mode)
 {
 {
-    faceCameraMode_ = mode;
-    MarkNetworkUpdate();
+    if((faceCameraMode_ != FC_DIRECTION && mode == FC_DIRECTION) || (faceCameraMode_ == FC_DIRECTION && mode != FC_DIRECTION))
+    {
+        faceCameraMode_ = mode;
+        if(faceCameraMode_ == FC_DIRECTION)
+            batches_[0].geometryType_ = GEOM_DIRBILLBOARD;
+        else batches_[0].geometryType_ = GEOM_BILLBOARD;
+        geometryTypeUpdate_ = true;
+        bufferSizeDirty_ = true;
+        Commit();
+    } else {
+        faceCameraMode_ = mode;
+        MarkNetworkUpdate();
+    }
 }
 }
 
 
 void BillboardSet::SetAnimationLodBias(float bias)
 void BillboardSet::SetAnimationLodBias(float bias)
@@ -310,16 +331,31 @@ void BillboardSet::SetBillboardsAttr(const VariantVector& value)
     unsigned numBillboards = index < value.Size() ? value[index++].GetUInt() : 0;
     unsigned numBillboards = index < value.Size() ? value[index++].GetUInt() : 0;
     SetNumBillboards(numBillboards);
     SetNumBillboards(numBillboards);
 
 
-    for (PODVector<Billboard>::Iterator i = billboards_.Begin(); i != billboards_.End() && index < value.Size(); ++i)
-    {
-        i->position_ = value[index++].GetVector3();
-        i->size_ = value[index++].GetVector2();
-        Vector4 uv = value[index++].GetVector4();
-        i->uv_ = Rect(uv.x_, uv.y_, uv.z_, uv.w_);
-        i->color_ = value[index++].GetColor();
-        i->rotation_ = value[index++].GetFloat();
-        i->enabled_ = value[index++].GetBool();
-    }
+    // Dealing with old billboard format
+    if(value.Size() == billboards_.Size() * 6 + 1)
+        for (PODVector<Billboard>::Iterator i = billboards_.Begin(); i != billboards_.End() && index < value.Size(); ++i)
+        {
+            i->position_ = value[index++].GetVector3();
+            i->size_ = value[index++].GetVector2();
+            Vector4 uv = value[index++].GetVector4();
+            i->uv_ = Rect(uv.x_, uv.y_, uv.z_, uv.w_);
+            i->color_ = value[index++].GetColor();
+            i->rotation_ = value[index++].GetFloat();
+            i->enabled_ = value[index++].GetBool();
+        }
+    // New billboard format
+    else
+        for (PODVector<Billboard>::Iterator i = billboards_.Begin(); i != billboards_.End() && index < value.Size(); ++i)
+        {
+            i->position_ = value[index++].GetVector3();
+            i->size_ = value[index++].GetVector2();
+            Vector4 uv = value[index++].GetVector4();
+            i->uv_ = Rect(uv.x_, uv.y_, uv.z_, uv.w_);
+            i->color_ = value[index++].GetColor();
+            i->rotation_ = value[index++].GetFloat();
+            i->direction_ = value[index++].GetVector3();
+            i->enabled_ = value[index++].GetBool();
+        }
 
 
     Commit();
     Commit();
 }
 }
@@ -337,6 +373,7 @@ void BillboardSet::SetNetBillboardsAttr(const PODVector<unsigned char>& value)
         i->uv_ = buf.ReadRect();
         i->uv_ = buf.ReadRect();
         i->color_ = buf.ReadColor();
         i->color_ = buf.ReadColor();
         i->rotation_ = buf.ReadFloat();
         i->rotation_ = buf.ReadFloat();
+        i->direction_ = buf.ReadVector3();
         i->enabled_ = buf.ReadBool();
         i->enabled_ = buf.ReadBool();
     }
     }
 
 
@@ -351,7 +388,7 @@ ResourceRef BillboardSet::GetMaterialAttr() const
 VariantVector BillboardSet::GetBillboardsAttr() const
 VariantVector BillboardSet::GetBillboardsAttr() const
 {
 {
     VariantVector ret;
     VariantVector ret;
-    ret.Reserve(billboards_.Size() * 6 + 1);
+    ret.Reserve(billboards_.Size() * 7 + 1);
     ret.Push(billboards_.Size());
     ret.Push(billboards_.Size());
 
 
     for (PODVector<Billboard>::ConstIterator i = billboards_.Begin(); i != billboards_.End(); ++i)
     for (PODVector<Billboard>::ConstIterator i = billboards_.Begin(); i != billboards_.End(); ++i)
@@ -361,6 +398,7 @@ VariantVector BillboardSet::GetBillboardsAttr() const
         ret.Push(Vector4(i->uv_.min_.x_, i->uv_.min_.y_, i->uv_.max_.x_, i->uv_.max_.y_));
         ret.Push(Vector4(i->uv_.min_.x_, i->uv_.min_.y_, i->uv_.max_.x_, i->uv_.max_.y_));
         ret.Push(i->color_);
         ret.Push(i->color_);
         ret.Push(i->rotation_);
         ret.Push(i->rotation_);
+        ret.Push(i->direction_);
         ret.Push(i->enabled_);
         ret.Push(i->enabled_);
     }
     }
 
 
@@ -379,6 +417,7 @@ const PODVector<unsigned char>& BillboardSet::GetNetBillboardsAttr() const
         attrBuffer_.WriteRect(i->uv_);
         attrBuffer_.WriteRect(i->uv_);
         attrBuffer_.WriteColor(i->color_);
         attrBuffer_.WriteColor(i->color_);
         attrBuffer_.WriteFloat(i->rotation_);
         attrBuffer_.WriteFloat(i->rotation_);
+        attrBuffer_.WriteVector3(i->direction_);
         attrBuffer_.WriteBool(i->enabled_);
         attrBuffer_.WriteBool(i->enabled_);
     }
     }
 
 
@@ -416,8 +455,18 @@ void BillboardSet::UpdateBufferSize()
 {
 {
     unsigned numBillboards = billboards_.Size();
     unsigned numBillboards = billboards_.Size();
 
 
-    if (vertexBuffer_->GetVertexCount() != numBillboards * 4)
-        vertexBuffer_->SetSize(numBillboards * 4, MASK_POSITION | MASK_COLOR | MASK_TEXCOORD1 | MASK_TEXCOORD2, true);
+    if (vertexBuffer_->GetVertexCount() != numBillboards * 4 || geometryTypeUpdate_)
+    {
+        if (faceCameraMode_ == FC_DIRECTION)
+        {
+            geometry_->SetVertexBuffer(0, vertexBuffer_, MASK_POSITION | MASK_NORMAL | MASK_COLOR | MASK_TEXCOORD1 | MASK_TEXCOORD2 | MASK_TANGENT);
+            vertexBuffer_->SetSize(numBillboards * 4, MASK_POSITION | MASK_NORMAL | MASK_COLOR | MASK_TEXCOORD1 | MASK_TEXCOORD2 | MASK_TANGENT, true);
+        } else {
+            geometry_->SetVertexBuffer(0, vertexBuffer_, MASK_POSITION | MASK_COLOR | MASK_TEXCOORD1 | MASK_TEXCOORD2);
+            vertexBuffer_->SetSize(numBillboards * 4, MASK_POSITION | MASK_COLOR | MASK_TEXCOORD1 | MASK_TEXCOORD2, true);
+        }
+        geometryTypeUpdate_ = false;
+    }
     if (indexBuffer_->GetIndexCount() != numBillboards * 6)
     if (indexBuffer_->GetIndexCount() != numBillboards * 6)
         indexBuffer_->SetSize(numBillboards * 6, false);
         indexBuffer_->SetSize(numBillboards * 6, false);
 
 
@@ -514,56 +563,140 @@ void BillboardSet::UpdateVertexBuffer(const FrameInfo& frame)
     if (!dest)
     if (!dest)
         return;
         return;
 
 
-    for (unsigned i = 0; i < enabledBillboards; ++i)
+    if(faceCameraMode_ != FC_DIRECTION)
     {
     {
-        Billboard& billboard = *sortedBillboards_[i];
-
-        Vector2 size(billboard.size_.x_ * billboardScale.x_, billboard.size_.y_ * billboardScale.y_);
-        unsigned color = billboard.color_.ToUInt();
-
-        float rotationMatrix[2][2];
-        rotationMatrix[0][0] = Cos(billboard.rotation_);
-        rotationMatrix[0][1] = Sin(billboard.rotation_);
-        rotationMatrix[1][0] = -rotationMatrix[0][1];
-        rotationMatrix[1][1] = rotationMatrix[0][0];
-
-        dest[0] = billboard.position_.x_;
-        dest[1] = billboard.position_.y_;
-        dest[2] = billboard.position_.z_;
-        ((unsigned&)dest[3]) = color;
-        dest[4] = billboard.uv_.min_.x_;
-        dest[5] = billboard.uv_.min_.y_;
-        dest[6] = -size.x_ * rotationMatrix[0][0] + size.y_ * rotationMatrix[0][1];
-        dest[7] = -size.x_ * rotationMatrix[1][0] + size.y_ * rotationMatrix[1][1];
-
-        dest[8] = billboard.position_.x_;
-        dest[9] = billboard.position_.y_;
-        dest[10] = billboard.position_.z_;
-        ((unsigned&)dest[11]) = color;
-        dest[12] = billboard.uv_.max_.x_;
-        dest[13] = billboard.uv_.min_.y_;
-        dest[14] = size.x_ * rotationMatrix[0][0] + size.y_ * rotationMatrix[0][1];
-        dest[15] = size.x_ * rotationMatrix[1][0] + size.y_ * rotationMatrix[1][1];
-
-        dest[16] = billboard.position_.x_;
-        dest[17] = billboard.position_.y_;
-        dest[18] = billboard.position_.z_;
-        ((unsigned&)dest[19]) = color;
-        dest[20] = billboard.uv_.max_.x_;
-        dest[21] = billboard.uv_.max_.y_;
-        dest[22] = size.x_ * rotationMatrix[0][0] - size.y_ * rotationMatrix[0][1];
-        dest[23] = size.x_ * rotationMatrix[1][0] - size.y_ * rotationMatrix[1][1];
-
-        dest[24] = billboard.position_.x_;
-        dest[25] = billboard.position_.y_;
-        dest[26] = billboard.position_.z_;
-        ((unsigned&)dest[27]) = color;
-        dest[28] = billboard.uv_.min_.x_;
-        dest[29] = billboard.uv_.max_.y_;
-        dest[30] = -size.x_ * rotationMatrix[0][0] - size.y_ * rotationMatrix[0][1];
-        dest[31] = -size.x_ * rotationMatrix[1][0] - size.y_ * rotationMatrix[1][1];
-
-        dest += 32;
+        for (unsigned i = 0; i < enabledBillboards; ++i)
+        {
+            Billboard& billboard = *sortedBillboards_[i];
+
+            Vector2 size(billboard.size_.x_ * billboardScale.x_, billboard.size_.y_ * billboardScale.y_);
+            unsigned color = billboard.color_.ToUInt();
+
+            float rotationMatrix[2][2];
+            rotationMatrix[0][0] = Cos(billboard.rotation_);
+            rotationMatrix[0][1] = Sin(billboard.rotation_);
+            rotationMatrix[1][0] = -rotationMatrix[0][1];
+            rotationMatrix[1][1] = rotationMatrix[0][0];
+
+            dest[0] = billboard.position_.x_;
+            dest[1] = billboard.position_.y_;
+            dest[2] = billboard.position_.z_;
+            ((unsigned&)dest[3]) = color;
+            dest[4] = billboard.uv_.min_.x_;
+            dest[5] = billboard.uv_.min_.y_;
+            dest[6] = -size.x_ * rotationMatrix[0][0] + size.y_ * rotationMatrix[0][1];
+            dest[7] = -size.x_ * rotationMatrix[1][0] + size.y_ * rotationMatrix[1][1];
+
+            dest[8] = billboard.position_.x_;
+            dest[9] = billboard.position_.y_;
+            dest[10] = billboard.position_.z_;
+            ((unsigned&)dest[11]) = color;
+            dest[12] = billboard.uv_.max_.x_;
+            dest[13] = billboard.uv_.min_.y_;
+            dest[14] = size.x_ * rotationMatrix[0][0] + size.y_ * rotationMatrix[0][1];
+            dest[15] = size.x_ * rotationMatrix[1][0] + size.y_ * rotationMatrix[1][1];
+
+            dest[16] = billboard.position_.x_;
+            dest[17] = billboard.position_.y_;
+            dest[18] = billboard.position_.z_;
+            ((unsigned&)dest[19]) = color;
+            dest[20] = billboard.uv_.max_.x_;
+            dest[21] = billboard.uv_.max_.y_;
+            dest[22] = size.x_ * rotationMatrix[0][0] - size.y_ * rotationMatrix[0][1];
+            dest[23] = size.x_ * rotationMatrix[1][0] - size.y_ * rotationMatrix[1][1];
+
+            dest[24] = billboard.position_.x_;
+            dest[25] = billboard.position_.y_;
+            dest[26] = billboard.position_.z_;
+            ((unsigned&)dest[27]) = color;
+            dest[28] = billboard.uv_.min_.x_;
+            dest[29] = billboard.uv_.max_.y_;
+            dest[30] = -size.x_ * rotationMatrix[0][0] - size.y_ * rotationMatrix[0][1];
+            dest[31] = -size.x_ * rotationMatrix[1][0] - size.y_ * rotationMatrix[1][1];
+
+            dest += 32;
+        }
+    } else
+    {
+        for (unsigned i = 0; i < enabledBillboards; ++i)
+        {
+            Billboard& billboard = *sortedBillboards_[i];
+
+            Vector2 size(billboard.size_.x_ * billboardScale.x_, billboard.size_.y_ * billboardScale.y_);
+            unsigned color = billboard.color_.ToUInt();
+
+            float rot2D[2][2];
+            rot2D[0][0] = Cos(billboard.rotation_);
+            rot2D[0][1] = Sin(billboard.rotation_);
+            rot2D[1][0] = -rot2D[0][1];
+            rot2D[1][1] = rot2D[0][0];
+
+            dest[0] = billboard.position_.x_;
+            dest[1] = billboard.position_.y_;
+            dest[2] = billboard.position_.z_;
+            dest[3] = billboard.direction_.x_;
+            dest[4] = billboard.direction_.y_;
+            dest[5] = billboard.direction_.z_;
+            ((unsigned&)dest[6]) = color;
+            dest[7] = billboard.uv_.min_.x_;
+            dest[8] = billboard.uv_.min_.y_;
+            dest[9] = -size.x_ * rot2D[0][0] + size.y_ * rot2D[0][1];
+            dest[10] = -size.x_ * rot2D[1][0] + size.y_ * rot2D[1][1];
+            dest[11] = frame.camera_->GetNode()->GetWorldPosition().x_;
+            dest[12] = frame.camera_->GetNode()->GetWorldPosition().y_;
+            dest[13] = frame.camera_->GetNode()->GetWorldPosition().z_;
+            dest[14] = 1.0f;
+
+            dest[15] = billboard.position_.x_;
+            dest[16] = billboard.position_.y_;
+            dest[17] = billboard.position_.z_;
+            dest[18] = billboard.direction_.x_;
+            dest[19] = billboard.direction_.y_;
+            dest[20] = billboard.direction_.z_;
+            ((unsigned&)dest[21]) = color;
+            dest[22] = billboard.uv_.max_.x_;
+            dest[23] = billboard.uv_.min_.y_;
+            dest[24] = size.x_ * rot2D[0][0] + size.y_ * rot2D[0][1];
+            dest[25] = size.x_ * rot2D[1][0] + size.y_ * rot2D[1][1];
+            dest[26] = frame.camera_->GetNode()->GetWorldPosition().x_;
+            dest[27] = frame.camera_->GetNode()->GetWorldPosition().y_;
+            dest[28] = frame.camera_->GetNode()->GetWorldPosition().z_;
+            dest[29] = 1.0f;
+
+            dest[30] = billboard.position_.x_;
+            dest[31] = billboard.position_.y_;
+            dest[32] = billboard.position_.z_;
+            dest[33] = billboard.direction_.x_;
+            dest[34] = billboard.direction_.y_;
+            dest[35] = billboard.direction_.z_;
+            ((unsigned&)dest[36]) = color;
+            dest[37] = billboard.uv_.max_.x_;
+            dest[38] = billboard.uv_.max_.y_;
+            dest[39] = size.x_ * rot2D[0][0] - size.y_ * rot2D[0][1];
+            dest[40] = size.x_ * rot2D[1][0] - size.y_ * rot2D[1][1];
+            dest[41] = frame.camera_->GetNode()->GetWorldPosition().x_;
+            dest[42] = frame.camera_->GetNode()->GetWorldPosition().y_;
+            dest[43] = frame.camera_->GetNode()->GetWorldPosition().z_;
+            dest[44] = 1.0f;
+
+            dest[45] = billboard.position_.x_;
+            dest[46] = billboard.position_.y_;
+            dest[47] = billboard.position_.z_;
+            dest[48] = billboard.direction_.x_;
+            dest[49] = billboard.direction_.y_;
+            dest[50] = billboard.direction_.z_;
+            ((unsigned&)dest[51]) = color;
+            dest[52] = billboard.uv_.min_.x_;
+            dest[53] = billboard.uv_.max_.y_;
+            dest[54] = -size.x_ * rot2D[0][0] - size.y_ * rot2D[0][1];
+            dest[55] = -size.x_ * rot2D[1][0] - size.y_ * rot2D[1][1];
+            dest[56] = frame.camera_->GetNode()->GetWorldPosition().x_;
+            dest[57] = frame.camera_->GetNode()->GetWorldPosition().y_;
+            dest[58] = frame.camera_->GetNode()->GetWorldPosition().z_;
+            dest[59] = 1.0f;
+
+            dest += 60;
+        }
     }
     }
 
 
     vertexBuffer_->Unlock();
     vertexBuffer_->Unlock();

+ 4 - 0
Source/Urho3D/Graphics/BillboardSet.h

@@ -47,6 +47,8 @@ struct URHO3D_API Billboard
     Color color_;
     Color color_;
     /// Rotation.
     /// Rotation.
     float rotation_;
     float rotation_;
+    /// Direction (For direction based billboard only).
+    Vector3 direction_;
     /// Enabled flag.
     /// Enabled flag.
     bool enabled_;
     bool enabled_;
     /// Sort distance.
     /// Sort distance.
@@ -177,6 +179,8 @@ private:
     bool bufferDirty_;
     bool bufferDirty_;
     /// Force update flag (ignore animation LOD momentarily.)
     /// Force update flag (ignore animation LOD momentarily.)
     bool forceUpdate_;
     bool forceUpdate_;
+    /// Update billboard geometry type
+    bool geometryTypeUpdate_;
     /// Sorting flag. Triggers a vertex buffer rewrite for each view this billboard set is rendered from.
     /// Sorting flag. Triggers a vertex buffer rewrite for each view this billboard set is rendered from.
     bool sortThisFrame_;
     bool sortThisFrame_;
     /// Frame number on which was last sorted.
     /// Frame number on which was last sorted.

+ 5 - 3
Source/Urho3D/Graphics/GraphicsDefs.h

@@ -55,8 +55,9 @@ enum GeometryType
     GEOM_SKINNED = 1,
     GEOM_SKINNED = 1,
     GEOM_INSTANCED = 2,
     GEOM_INSTANCED = 2,
     GEOM_BILLBOARD = 3,
     GEOM_BILLBOARD = 3,
-    GEOM_STATIC_NOINSTANCING = 4,
-    MAX_GEOMETRYTYPES = 4,
+    GEOM_DIRBILLBOARD = 4,
+    GEOM_STATIC_NOINSTANCING = 5,
+    MAX_GEOMETRYTYPES = 5,
 };
 };
 
 
 /// Blending mode.
 /// Blending mode.
@@ -273,7 +274,8 @@ enum FaceCameraMode
     FC_ROTATE_XYZ,
     FC_ROTATE_XYZ,
     FC_ROTATE_Y,
     FC_ROTATE_Y,
     FC_LOOKAT_XYZ,
     FC_LOOKAT_XYZ,
-    FC_LOOKAT_Y
+    FC_LOOKAT_Y,
+    FC_DIRECTION
 };
 };
 
 
 /// Shadow type
 /// Shadow type

+ 53 - 1
Source/Urho3D/Graphics/ParticleEffect.cpp

@@ -25,6 +25,7 @@
 #include "../Core/Context.h"
 #include "../Core/Context.h"
 #include "../Graphics/Material.h"
 #include "../Graphics/Material.h"
 #include "../Graphics/ParticleEffect.h"
 #include "../Graphics/ParticleEffect.h"
+#include "../Graphics/BillboardSet.h"
 #include "../IO/Log.h"
 #include "../IO/Log.h"
 #include "../Resource/ResourceCache.h"
 #include "../Resource/ResourceCache.h"
 #include "../Resource/XMLFile.h"
 #include "../Resource/XMLFile.h"
@@ -34,6 +35,8 @@
 namespace Urho3D
 namespace Urho3D
 {
 {
 
 
+extern const char* faceCameraModeNames[];
+
 static const char* emitterTypeNames[] =
 static const char* emitterTypeNames[] =
 {
 {
     "Sphere",
     "Sphere",
@@ -78,7 +81,8 @@ ParticleEffect::ParticleEffect(Context* context) :
     rotationSpeedMin_(0.0f),
     rotationSpeedMin_(0.0f),
     rotationSpeedMax_(0.0f),
     rotationSpeedMax_(0.0f),
     sizeAdd_(0.0f),
     sizeAdd_(0.0f),
-    sizeMul_(1.0f)
+    sizeMul_(1.0f),
+    faceCameraMode_(FC_ROTATE_XYZ)
 {
 {
 }
 }
 
 
@@ -141,6 +145,7 @@ bool ParticleEffect::BeginLoad(Deserializer& source)
     sizeMul_ = 1.0f;
     sizeMul_ = 1.0f;
     colorFrames_.Clear();
     colorFrames_.Clear();
     textureFrames_.Clear();
     textureFrames_.Clear();
+    faceCameraMode_ = FC_ROTATE_XYZ;
 
 
     if (rootElem.HasChild("material"))
     if (rootElem.HasChild("material"))
     {
     {
@@ -262,6 +267,25 @@ bool ParticleEffect::BeginLoad(Deserializer& source)
         SetColorFrames(fades);
         SetColorFrames(fades);
     }
     }
 
 
+    if (rootElem.HasChild("faceCameraMode"))
+    {
+        String type = rootElem.GetChild("faceCameraMode").GetAttributeLower("value");
+        if (type == "none")
+            faceCameraMode_ = FC_NONE;
+        else if (type == "rotate xyz")
+            faceCameraMode_ = FC_ROTATE_XYZ;
+        else if (type == "rotate y")
+            faceCameraMode_ = FC_ROTATE_Y;
+        else if (type == "lookat xyz")
+            faceCameraMode_ = FC_LOOKAT_XYZ;
+        else if (type == "lookat y")
+            faceCameraMode_ = FC_LOOKAT_Y;
+        else if (type == "rotate along direction")
+            faceCameraMode_ = FC_DIRECTION;
+        else
+            URHO3D_LOGERROR("Unknown face camera mode " + type);
+    }
+
     if (colorFrames_.Empty())
     if (colorFrames_.Empty())
         colorFrames_.Push(ColorFrame(Color::WHITE));
         colorFrames_.Push(ColorFrame(Color::WHITE));
 
 
@@ -330,6 +354,7 @@ bool ParticleEffect::Load(const XMLElement& source)
     sizeMul_ = 1.0f;
     sizeMul_ = 1.0f;
     colorFrames_.Clear();
     colorFrames_.Clear();
     textureFrames_.Clear();
     textureFrames_.Clear();
+    faceCameraMode_ = FC_ROTATE_XYZ;
 
 
     if (source.IsNull())
     if (source.IsNull())
     {
     {
@@ -429,6 +454,25 @@ bool ParticleEffect::Load(const XMLElement& source)
     if (source.HasChild("rotationspeed"))
     if (source.HasChild("rotationspeed"))
         GetFloatMinMax(source.GetChild("rotationspeed"), rotationSpeedMin_, rotationSpeedMax_);
         GetFloatMinMax(source.GetChild("rotationspeed"), rotationSpeedMin_, rotationSpeedMax_);
 
 
+    if (source.HasChild("faceCameraMode"))
+    {
+        String type = source.GetChild("faceCameraMode").GetAttributeLower("value");
+        if (type == "none")
+            faceCameraMode_ = FC_NONE;
+        else if (type == "rotate xyz")
+            faceCameraMode_ = FC_ROTATE_XYZ;
+        else if (type == "rotate y")
+            faceCameraMode_ = FC_ROTATE_Y;
+        else if (type == "lookat xyz")
+            faceCameraMode_ = FC_LOOKAT_XYZ;
+        else if (type == "lookat y")
+            faceCameraMode_ = FC_LOOKAT_Y;
+        else if (type == "rotate along direction")
+            faceCameraMode_ = FC_DIRECTION;
+        else
+            URHO3D_LOGERROR("Unknown face camera mode " + type);
+    }
+
     if (source.HasChild("sizedelta"))
     if (source.HasChild("sizedelta"))
     {
     {
         XMLElement deltaElem = source.GetChild("sizedelta");
         XMLElement deltaElem = source.GetChild("sizedelta");
@@ -562,6 +606,9 @@ bool ParticleEffect::Save(XMLElement& dest) const
     childElem.SetFloat("add", sizeAdd_);
     childElem.SetFloat("add", sizeAdd_);
     childElem.SetFloat("mul", sizeMul_);
     childElem.SetFloat("mul", sizeMul_);
 
 
+    childElem = dest.CreateChild("faceCameraMode");
+    childElem.SetAttribute("value", faceCameraModeNames[faceCameraMode_]);
+
     if (colorFrames_.Size() == 1)
     if (colorFrames_.Size() == 1)
     {
     {
         childElem = dest.CreateChild("color");
         childElem = dest.CreateChild("color");
@@ -796,6 +843,11 @@ void ParticleEffect::SetNumColorFrames(unsigned number)
         colorFrames_.Resize(number);
         colorFrames_.Resize(number);
 }
 }
 
 
+void ParticleEffect::SetFaceCameraMode(FaceCameraMode mode)
+{
+    faceCameraMode_ = mode;
+}
+
 void ParticleEffect::SortColorFrames()
 void ParticleEffect::SortColorFrames()
 {
 {
     Vector<ColorFrame> cf = colorFrames_;
     Vector<ColorFrame> cf = colorFrames_;

+ 8 - 0
Source/Urho3D/Graphics/ParticleEffect.h

@@ -22,6 +22,7 @@
 
 
 #pragma once
 #pragma once
 
 
+#include "../Graphics/GraphicsDefs.h"
 #include "../Resource/Resource.h"
 #include "../Resource/Resource.h"
 
 
 namespace Urho3D
 namespace Urho3D
@@ -180,6 +181,8 @@ public:
     void SetSizeAdd(float sizeAdd);
     void SetSizeAdd(float sizeAdd);
     /// Set particle size multiplicative modifier.
     /// Set particle size multiplicative modifier.
     void SetSizeMul(float sizeMul);
     void SetSizeMul(float sizeMul);
+    /// Set how the particles should rotate in relation to the camera. Default is to follow camera rotation on all axes (FC_ROTATE_XYZ.)
+    void SetFaceCameraMode(FaceCameraMode mode);
 
 
     /// Add a color frame sorted in the correct position based on time.
     /// Add a color frame sorted in the correct position based on time.
     void AddColorTime(const Color& color, const float time);
     void AddColorTime(const Color& color, const float time);
@@ -316,6 +319,9 @@ public:
     /// Return a texture animation frame, or null if outside range.
     /// Return a texture animation frame, or null if outside range.
     const TextureFrame* GetTextureFrame(unsigned index) const;
     const TextureFrame* GetTextureFrame(unsigned index) const;
 
 
+    /// Return how the particles rotate in relation to the camera.
+    FaceCameraMode GetFaceCameraMode() const { return faceCameraMode_; }
+
     /// Return random direction.
     /// Return random direction.
     Vector3 GetRandomDirection() const;
     Vector3 GetRandomDirection() const;
     /// Return random size.
     /// Return random size.
@@ -401,6 +407,8 @@ private:
     Vector<TextureFrame> textureFrames_;
     Vector<TextureFrame> textureFrames_;
     /// Material name acquired during BeginLoad().
     /// Material name acquired during BeginLoad().
     String loadMaterialName_;
     String loadMaterialName_;
+    /// Particle rotation mode in relation to the camera.
+    FaceCameraMode faceCameraMode_;
 };
 };
 
 
 }
 }

+ 21 - 12
Source/Urho3D/Graphics/ParticleEmitter.cpp

@@ -66,7 +66,7 @@ void ParticleEmitter::RegisterObject(Context* context)
     URHO3D_ACCESSOR_ATTRIBUTE("Is Enabled", IsEnabled, SetEnabled, bool, true, AM_DEFAULT);
     URHO3D_ACCESSOR_ATTRIBUTE("Is Enabled", IsEnabled, SetEnabled, bool, true, AM_DEFAULT);
     URHO3D_MIXED_ACCESSOR_ATTRIBUTE("Effect", GetEffectAttr, SetEffectAttr, ResourceRef, ResourceRef(ParticleEffect::GetTypeStatic()),
     URHO3D_MIXED_ACCESSOR_ATTRIBUTE("Effect", GetEffectAttr, SetEffectAttr, ResourceRef, ResourceRef(ParticleEffect::GetTypeStatic()),
         AM_DEFAULT);
         AM_DEFAULT);
-    URHO3D_ENUM_ATTRIBUTE("Face Camera Mode", faceCameraMode_, faceCameraModeNames, FC_ROTATE_XYZ, AM_DEFAULT);
+    //URHO3D_ENUM_ATTRIBUTE("Face Camera Mode", faceCameraMode_, faceCameraModeNames, FC_ROTATE_XYZ, AM_DEFAULT);
     URHO3D_ACCESSOR_ATTRIBUTE("Can Be Occluded", IsOccludee, SetOccludee, bool, true, AM_DEFAULT);
     URHO3D_ACCESSOR_ATTRIBUTE("Can Be Occluded", IsOccludee, SetOccludee, bool, true, AM_DEFAULT);
     URHO3D_ATTRIBUTE("Cast Shadows", bool, castShadows_, false, AM_DEFAULT);
     URHO3D_ATTRIBUTE("Cast Shadows", bool, castShadows_, false, AM_DEFAULT);
     URHO3D_ACCESSOR_ATTRIBUTE("Draw Distance", GetDrawDistance, SetDrawDistance, float, 0.0f, AM_DEFAULT);
     URHO3D_ACCESSOR_ATTRIBUTE("Draw Distance", GetDrawDistance, SetDrawDistance, float, 0.0f, AM_DEFAULT);
@@ -205,6 +205,7 @@ void ParticleEmitter::Update(const FrameInfo& frame)
                 particle.velocity_ += lastTimeStep_ * force;
                 particle.velocity_ += lastTimeStep_ * force;
             }
             }
             billboard.position_ += lastTimeStep_ * particle.velocity_ * scaleVector;
             billboard.position_ += lastTimeStep_ * particle.velocity_ * scaleVector;
+            billboard.direction_ = particle.velocity_.Normalized();
 
 
             // Rotation
             // Rotation
             billboard.rotation_ += lastTimeStep_ * particle.rotationSpeed_;
             billboard.rotation_ += lastTimeStep_ * particle.rotationSpeed_;
@@ -338,6 +339,7 @@ void ParticleEmitter::ApplyEffect()
     SetScaled(effect_->IsScaled());
     SetScaled(effect_->IsScaled());
     SetSorted(effect_->IsSorted());
     SetSorted(effect_->IsSorted());
     SetAnimationLodBias(effect_->GetAnimationLodBias());
     SetAnimationLodBias(effect_->GetAnimationLodBias());
+    SetFaceCameraMode(effect_->GetFaceCameraMode());
 }
 }
 
 
 ParticleEffect* ParticleEmitter::GetEffect() const
 ParticleEffect* ParticleEmitter::GetEffect() const
@@ -408,7 +410,7 @@ VariantVector ParticleEmitter::GetParticleBillboardsAttr() const
         return ret;
         return ret;
     }
     }
 
 
-    ret.Reserve(billboards_.Size() * 6 + 1);
+    ret.Reserve(billboards_.Size() * 7 + 1);
     ret.Push(billboards_.Size());
     ret.Push(billboards_.Size());
 
 
     for (PODVector<Billboard>::ConstIterator i = billboards_.Begin(); i != billboards_.End(); ++i)
     for (PODVector<Billboard>::ConstIterator i = billboards_.Begin(); i != billboards_.End(); ++i)
@@ -418,6 +420,7 @@ VariantVector ParticleEmitter::GetParticleBillboardsAttr() const
         ret.Push(Vector4(i->uv_.min_.x_, i->uv_.min_.y_, i->uv_.max_.x_, i->uv_.max_.y_));
         ret.Push(Vector4(i->uv_.min_.x_, i->uv_.min_.y_, i->uv_.max_.x_, i->uv_.max_.y_));
         ret.Push(i->color_);
         ret.Push(i->color_);
         ret.Push(i->rotation_);
         ret.Push(i->rotation_);
+        ret.Push(i->direction_);
         ret.Push(i->enabled_);
         ret.Push(i->enabled_);
     }
     }
 
 
@@ -443,9 +446,11 @@ bool ParticleEmitter::EmitNewParticle()
     Particle& particle = particles_[index];
     Particle& particle = particles_[index];
     Billboard& billboard = billboards_[index];
     Billboard& billboard = billboards_[index];
 
 
-    Vector3 startPos;
     Vector3 startDir;
     Vector3 startDir;
+    Vector3 startPos;
 
 
+    startDir = effect_->GetRandomDirection();
+    startDir.Normalize();
 
 
     switch (effect_->GetEmitterType())
     switch (effect_->GetEmitterType())
     {
     {
@@ -473,8 +478,18 @@ bool ParticleEmitter::EmitNewParticle()
         break;
         break;
     }
     }
 
 
-    startDir = effect_->GetRandomDirection();
-    startDir.Normalize();
+    particle.size_ = effect_->GetRandomSize();
+    particle.timer_ = 0.0f;
+    particle.timeToLive_ = effect_->GetRandomTimeToLive();
+    particle.scale_ = 1.0f;
+    particle.rotationSpeed_ = effect_->GetRandomRotationSpeed();
+    particle.colorIndex_ = 0;
+    particle.texIndex_ = 0;
+
+    if(faceCameraMode_ == FC_DIRECTION)
+    {
+        startPos += startDir * particle.size_.y_;
+    }
 
 
     if (!relative_)
     if (!relative_)
     {
     {
@@ -483,13 +498,6 @@ bool ParticleEmitter::EmitNewParticle()
     };
     };
 
 
     particle.velocity_ = effect_->GetRandomVelocity() * startDir;
     particle.velocity_ = effect_->GetRandomVelocity() * startDir;
-    particle.size_ = effect_->GetRandomSize();
-    particle.timer_ = 0.0f;
-    particle.timeToLive_ = effect_->GetRandomTimeToLive();
-    particle.scale_ = 1.0f;
-    particle.rotationSpeed_ = effect_->GetRandomRotationSpeed();
-    particle.colorIndex_ = 0;
-    particle.texIndex_ = 0;
 
 
     billboard.position_ = startPos;
     billboard.position_ = startPos;
     billboard.size_ = particles_[index].size_;
     billboard.size_ = particles_[index].size_;
@@ -499,6 +507,7 @@ bool ParticleEmitter::EmitNewParticle()
     const Vector<ColorFrame>& colorFrames_ = effect_->GetColorFrames();
     const Vector<ColorFrame>& colorFrames_ = effect_->GetColorFrames();
     billboard.color_ = colorFrames_.Size() ? colorFrames_[0].color_ : Color();
     billboard.color_ = colorFrames_.Size() ? colorFrames_[0].color_ : Color();
     billboard.enabled_ = true;
     billboard.enabled_ = true;
+    billboard.direction_ = startDir;
 
 
     return true;
     return true;
 }
 }

+ 2 - 1
Source/Urho3D/Graphics/Renderer.cpp

@@ -180,7 +180,8 @@ static const char* geometryVSVariations[] =
     "",
     "",
     "SKINNED ",
     "SKINNED ",
     "INSTANCED ",
     "INSTANCED ",
-    "BILLBOARD "
+    "BILLBOARD ",
+    "DIRBILLBOARD "
 };
 };
 
 
 static const char* lightVSVariations[] =
 static const char* lightVSVariations[] =

+ 32 - 1
bin/CoreData/Shaders/GLSL/Transform.glsl

@@ -87,6 +87,33 @@ vec3 GetBillboardNormal()
 }
 }
 #endif
 #endif
 
 
+#ifdef DIRBILLBOARD
+mat3 GetFaceCameraRotation(vec3 cameraPos, vec3 position, vec3 direction)
+{
+    vec3 cameraDir = normalize(position - cameraPos);
+    vec3 front = normalize(direction);
+    vec3 right = normalize(cross(front, cameraDir));
+    vec3 up = normalize(cross(front, right));
+
+    return mat3(
+        right.x, up.x, front.x,
+        right.y, up.y, front.y,
+        right.z, up.z, front.z
+    );
+}
+
+vec3 GetBillboardPos(vec4 iPos, vec3 iDirection, vec3 iCameraPos, mat4 modelMatrix)
+{
+    return (iPos * modelMatrix).xyz + 
+        vec3(iTexCoord2.x, 0.0, iTexCoord2.y) * GetFaceCameraRotation(iCameraPos, iPos.xyz, iDirection);
+}
+
+vec3 GetBillboardNormal(vec4 iPos, vec3 iDirection, vec3 iCameraPos)
+{
+    return vec3(0.0, 1.0, 0.0) * GetFaceCameraRotation(iCameraPos, iPos.xyz, iDirection);
+}
+#endif
+
 #if defined(SKINNED)
 #if defined(SKINNED)
     #define iModelMatrix GetSkinMatrix(iBlendWeights, iBlendIndices)
     #define iModelMatrix GetSkinMatrix(iBlendWeights, iBlendIndices)
 #elif defined(INSTANCED)
 #elif defined(INSTANCED)
@@ -99,6 +126,8 @@ vec3 GetWorldPos(mat4 modelMatrix)
 {
 {
     #if defined(BILLBOARD)
     #if defined(BILLBOARD)
         return GetBillboardPos(iPos, iTexCoord2, modelMatrix);
         return GetBillboardPos(iPos, iTexCoord2, modelMatrix);
+    #elif defined(DIRBILLBOARD)
+        return GetBillboardPos(iPos, iNormal, iTangent.xyz, modelMatrix);
     #else
     #else
         return (iPos * modelMatrix).xyz;
         return (iPos * modelMatrix).xyz;
     #endif
     #endif
@@ -108,6 +137,8 @@ vec3 GetWorldNormal(mat4 modelMatrix)
 {
 {
     #if defined(BILLBOARD)
     #if defined(BILLBOARD)
         return GetBillboardNormal();
         return GetBillboardNormal();
+    #elif defined(DIRBILLBOARD)
+        return GetBillboardNormal(iPos, iNormal, iTangent.xyz);
     #else
     #else
         return normalize(iNormal * GetNormalMatrix(modelMatrix));
         return normalize(iNormal * GetNormalMatrix(modelMatrix));
     #endif
     #endif
@@ -137,4 +168,4 @@ out vec4 fragData[1];
 #define gl_FragData fragData
 #define gl_FragData fragData
 #endif
 #endif
 
 
-#endif
+#endif

+ 5 - 1
bin/CoreData/Shaders/HLSL/Basic.hlsl

@@ -16,9 +16,13 @@ void VS(float4 iPos : POSITION,
     #ifdef INSTANCED
     #ifdef INSTANCED
         float4x3 iModelInstance : TEXCOORD2,
         float4x3 iModelInstance : TEXCOORD2,
     #endif
     #endif
-    #ifdef BILLBOARD
+    #if defined(BILLBOARD) || defined(DIRBILLBOARD)
         float2 iSize : TEXCOORD1,
         float2 iSize : TEXCOORD1,
     #endif
     #endif
+    #ifdef DIRBILLBOARD
+        float3 iNormal : NORMAL,
+        float4 iTangent : TANGENT,
+    #endif
     #ifdef DIFFMAP
     #ifdef DIFFMAP
         out float2 oTexCoord : TEXCOORD0,
         out float2 oTexCoord : TEXCOORD0,
     #endif
     #endif

+ 4 - 1
bin/CoreData/Shaders/HLSL/LitParticle.hlsl

@@ -21,9 +21,12 @@ void VS(float4 iPos : POSITION,
     #ifdef INSTANCED
     #ifdef INSTANCED
         float4x3 iModelInstance : TEXCOORD2,
         float4x3 iModelInstance : TEXCOORD2,
     #endif
     #endif
-    #ifdef BILLBOARD
+    #if defined(BILLBOARD) || defined(DIRBILLBOARD)
         float2 iSize : TEXCOORD1,
         float2 iSize : TEXCOORD1,
     #endif
     #endif
+    #ifdef DIRBILLBOARD
+        float4 iTangent : TANGENT,
+    #endif
     out float2 oTexCoord : TEXCOORD0,
     out float2 oTexCoord : TEXCOORD0,
     out float4 oWorldPos : TEXCOORD3,
     out float4 oWorldPos : TEXCOORD3,
     #if PERPIXEL
     #if PERPIXEL

+ 2 - 2
bin/CoreData/Shaders/HLSL/LitSolid.hlsl

@@ -18,7 +18,7 @@ void VS(float4 iPos : POSITION,
     #if defined(LIGHTMAP) || defined(AO)
     #if defined(LIGHTMAP) || defined(AO)
         float2 iTexCoord2 : TEXCOORD1,
         float2 iTexCoord2 : TEXCOORD1,
     #endif
     #endif
-    #ifdef NORMALMAP
+    #if defined(NORMALMAP) || defined(DIRBILLBOARD)
         float4 iTangent : TANGENT,
         float4 iTangent : TANGENT,
     #endif
     #endif
     #ifdef SKINNED
     #ifdef SKINNED
@@ -28,7 +28,7 @@ void VS(float4 iPos : POSITION,
     #ifdef INSTANCED
     #ifdef INSTANCED
         float4x3 iModelInstance : TEXCOORD2,
         float4x3 iModelInstance : TEXCOORD2,
     #endif
     #endif
-    #ifdef BILLBOARD
+    #if defined(BILLBOARD) || defined(DIRBILLBOARD)
         float2 iSize : TEXCOORD1,
         float2 iSize : TEXCOORD1,
     #endif
     #endif
     #ifndef NORMALMAP
     #ifndef NORMALMAP

+ 4 - 1
bin/CoreData/Shaders/HLSL/TerrainBlend.hlsl

@@ -48,9 +48,12 @@ void VS(float4 iPos : POSITION,
     #ifdef INSTANCED
     #ifdef INSTANCED
         float4x3 iModelInstance : TEXCOORD2,
         float4x3 iModelInstance : TEXCOORD2,
     #endif
     #endif
-    #ifdef BILLBOARD
+    #if defined(BILLBOARD) || defined(DIRBILLBOARD)
         float2 iSize : TEXCOORD1,
         float2 iSize : TEXCOORD1,
     #endif
     #endif
+    #ifdef DIRBILLBOARD
+        float4 iTangent : TANGENT,
+    #endif
     out float2 oTexCoord : TEXCOORD0,
     out float2 oTexCoord : TEXCOORD0,
     out float3 oNormal : TEXCOORD1,
     out float3 oNormal : TEXCOORD1,
     out float4 oWorldPos : TEXCOORD2,
     out float4 oWorldPos : TEXCOORD2,

+ 33 - 2
bin/CoreData/Shaders/HLSL/Transform.hlsl

@@ -48,6 +48,33 @@ float3 GetBillboardNormal()
 }
 }
 #endif
 #endif
 
 
+#ifdef DIRBILLBOARD
+float3x3 GetFaceCameraRotation(float3 cameraPos, float3 position, float3 direction)
+{
+    float3 cameraDir = normalize(position - cameraPos);
+    float3 front = normalize(direction);
+    float3 right = normalize(cross(front, cameraDir));
+    float3 up = normalize(cross(front, right));
+
+    return float3x3(
+        right.x, right.y, right.z,
+        up.x, up.y, up.z,
+        front.x, front.y, front.z
+    );
+}
+
+float3 GetBillboardPos(float4 iPos, float2 iSize, float3 iDirection, float3 iCameraPos, float4x3 modelMatrix)
+{
+    return mul(iPos, modelMatrix) + 
+        mul(float3(iSize.x, 0.0, iSize.y), GetFaceCameraRotation(iCameraPos, iPos.xyz, iDirection));
+}
+
+float3 GetBillboardNormal(float4 iPos, float3 iDirection, float3 iCameraPos)
+{
+    return mul(float3(0.0, 1.0, 0.0), GetFaceCameraRotation(iCameraPos, iPos.xyz, iDirection));
+}
+#endif
+
 #if defined(SKINNED)
 #if defined(SKINNED)
     #define iModelMatrix GetSkinMatrix(iBlendWeights, iBlendIndices);
     #define iModelMatrix GetSkinMatrix(iBlendWeights, iBlendIndices);
 #elif defined(INSTANCED)
 #elif defined(INSTANCED)
@@ -56,14 +83,18 @@ float3 GetBillboardNormal()
     #define iModelMatrix cModel
     #define iModelMatrix cModel
 #endif
 #endif
 
 
-#ifdef BILLBOARD
+#if defined(BILLBOARD)
     #define GetWorldPos(modelMatrix) GetBillboardPos(iPos, iSize, modelMatrix)
     #define GetWorldPos(modelMatrix) GetBillboardPos(iPos, iSize, modelMatrix)
+#elif defined(DIRBILLBOARD)
+    #define GetWorldPos(modelMatrix) GetBillboardPos(iPos, iSize, iNormal, iTangent.xyz, modelMatrix)
 #else
 #else
     #define GetWorldPos(modelMatrix) mul(iPos, modelMatrix)
     #define GetWorldPos(modelMatrix) mul(iPos, modelMatrix)
 #endif
 #endif
 
 
-#ifdef BILLBOARD
+#if defined(BILLBOARD)
     #define GetWorldNormal(modelMatrix) GetBillboardNormal()
     #define GetWorldNormal(modelMatrix) GetBillboardNormal()
+#elif defined(DIRBILLBOARD)
+    #define GetWorldNormal(modelMatrix) GetBillboardNormal(iPos, iNormal, iTangent.xyz)
 #else
 #else
     #define GetWorldNormal(modelMatrix) normalize(mul(iNormal, (float3x3)modelMatrix))
     #define GetWorldNormal(modelMatrix) normalize(mul(iNormal, (float3x3)modelMatrix))
 #endif
 #endif

+ 6 - 2
bin/CoreData/Shaders/HLSL/Unlit.hlsl

@@ -17,9 +17,13 @@ void VS(float4 iPos : POSITION,
     #ifdef INSTANCED
     #ifdef INSTANCED
         float4x3 iModelInstance : TEXCOORD2,
         float4x3 iModelInstance : TEXCOORD2,
     #endif
     #endif
-    #ifdef BILLBOARD
+    #if defined(BILLBOARD) || defined(DIRBILLBOARD)
         float2 iSize : TEXCOORD1,
         float2 iSize : TEXCOORD1,
     #endif
     #endif
+    #ifdef DIRBILLBOARD
+        float3 iNormal : NORMAL,
+        float4 iTangent : TANGENT,
+    #endif
     out float2 oTexCoord : TEXCOORD0,
     out float2 oTexCoord : TEXCOORD0,
     out float4 oWorldPos : TEXCOORD2,
     out float4 oWorldPos : TEXCOORD2,
     #ifdef VERTEXCOLOR
     #ifdef VERTEXCOLOR
@@ -103,4 +107,4 @@ void PS(float2 iTexCoord : TEXCOORD0,
     #else
     #else
         oColor = float4(GetFog(diffColor.rgb, fogFactor), diffColor.a);
         oColor = float4(GetFog(diffColor.rgb, fogFactor), diffColor.a);
     #endif
     #endif
-}
+}

+ 2 - 2
bin/CoreData/Shaders/HLSL/Vegetation.hlsl

@@ -39,7 +39,7 @@ void VS(float4 iPos : POSITION,
     #if defined(LIGHTMAP) || defined(AO)
     #if defined(LIGHTMAP) || defined(AO)
         float2 iTexCoord2 : TEXCOORD1,
         float2 iTexCoord2 : TEXCOORD1,
     #endif
     #endif
-    #ifdef NORMALMAP
+    #if defined(NORMALMAP) || defined(DIRBILLBOARD)
         float4 iTangent : TANGENT,
         float4 iTangent : TANGENT,
     #endif
     #endif
     #ifdef SKINNED
     #ifdef SKINNED
@@ -49,7 +49,7 @@ void VS(float4 iPos : POSITION,
     #ifdef INSTANCED
     #ifdef INSTANCED
         float4x3 iModelInstance : TEXCOORD2,
         float4x3 iModelInstance : TEXCOORD2,
     #endif
     #endif
-    #ifdef BILLBOARD
+    #if defined(BILLBOARD) || defined(DIRBILLBOARD)
         float2 iSize : TEXCOORD1,
         float2 iSize : TEXCOORD1,
     #endif
     #endif
     #ifndef NORMALMAP
     #ifndef NORMALMAP

+ 28 - 0
bin/Data/Particle/Burst.xml

@@ -0,0 +1,28 @@
+<?xml version="1.0"?>
+<particleeffect>
+	<material name="Materials/Particle.xml" />
+	<numparticles value="200" />
+	<updateinvisible enable="false" />
+	<relative enable="true" />
+	<scaled enable="true" />
+	<sorted enable="false" />
+	<animlodbias value="0" />
+	<emittertype value="Sphere" />
+	<emittersize value="0 0 0" />
+	<direction min="-1 -1 -1" max="1 1 1" />
+	<constantforce value="0 -20 0" />
+	<dampingforce value="0" />
+	<activetime value="0" />
+	<inactivetime value="0" />
+	<emissionrate min="2000" max="2000" />
+	<particlesize min="0.03 0.3" max="0.06 1" />
+	<timetolive min="1" max="1" />
+	<velocity min="8" max="10" />
+	<rotation min="0" max="0" />
+	<rotationspeed min="0" max="0" />
+	<sizedelta add="0" mul="0.01" />
+	<faceCameraMode value="Rotate Along Direction" />
+	<colorfade color="1 1 1 1" time="0" />
+	<colorfade color="0.5 0.75 1 1" time="0.5" />
+	<colorfade color="0 0 0 0" time="1" />
+</particleeffect>

+ 1 - 0
bin/Data/Scripts/Editor/AttributeEditor.as

@@ -1512,6 +1512,7 @@ void InitVectorStructs()
         "   UV Coordinates",
         "   UV Coordinates",
         "   Color",
         "   Color",
         "   Rotation",
         "   Rotation",
+        "   Direction",
         "   Is Enabled"
         "   Is Enabled"
     };
     };
     vectorStructs.Push(VectorStruct("BillboardSet", "Billboards", billboardVariables, 1));
     vectorStructs.Push(VectorStruct("BillboardSet", "Billboards", billboardVariables, 1));

+ 67 - 0
bin/Data/Scripts/Editor/EditorParticleEffect.as

@@ -128,6 +128,7 @@ void CreateParticleEffectEditor()
     SubscribeToEvent(particleEffectWindow.GetChild("Scaled", true), "Toggled", "EditParticleEffectScaled");
     SubscribeToEvent(particleEffectWindow.GetChild("Scaled", true), "Toggled", "EditParticleEffectScaled");
     SubscribeToEvent(particleEffectWindow.GetChild("Sorted", true), "Toggled", "EditParticleEffectSorted");
     SubscribeToEvent(particleEffectWindow.GetChild("Sorted", true), "Toggled", "EditParticleEffectSorted");
     SubscribeToEvent(particleEffectWindow.GetChild("Relative", true), "Toggled", "EditParticleEffectRelative");
     SubscribeToEvent(particleEffectWindow.GetChild("Relative", true), "Toggled", "EditParticleEffectRelative");
+    SubscribeToEvent(particleEffectWindow.GetChild("FaceCameraMode", true), "ItemSelected", "EditParticleEffectFaceCameraMode");
     
     
     SubscribeToEvent(particleEffectWindow.GetChild("ResetViewport", true), "Released", "ParticleEffectResetViewport");
     SubscribeToEvent(particleEffectWindow.GetChild("ResetViewport", true), "Released", "ParticleEffectResetViewport");
     SubscribeToEvent(particleEffectWindow.GetChild("ShowGrid", true), "Toggled", "ParticleEffectShowGrid");
     SubscribeToEvent(particleEffectWindow.GetChild("ShowGrid", true), "Toggled", "ParticleEffectShowGrid");
@@ -696,6 +697,50 @@ void EditParticleEffectEmitterShape(StringHash eventType, VariantMap& eventData)
     EndParticleEffectEdit();
     EndParticleEffectEdit();
 }
 }
 
 
+void EditParticleEffectFaceCameraMode(StringHash eventType, VariantMap& eventData)
+{
+    if (inParticleEffectRefresh)
+        return;
+
+    if (editParticleEffect is null)
+        return;
+
+    BeginParticleEffectEdit();
+
+    DropDownList@ element = eventData["Element"].GetPtr();
+
+    switch (element.selection)
+    {
+        case 0:
+            editParticleEffect.faceCameraMode = FC_NONE;
+            break;
+
+        case 1:
+            editParticleEffect.faceCameraMode = FC_ROTATE_XYZ;
+            break;
+
+        case 2:
+            editParticleEffect.faceCameraMode = FC_ROTATE_Y;
+            break;
+
+        case 3:
+            editParticleEffect.faceCameraMode = FC_LOOKAT_XYZ;
+            break;
+
+        case 4:
+            editParticleEffect.faceCameraMode = FC_LOOKAT_Y;
+            break;
+
+        case 5:
+            editParticleEffect.faceCameraMode = FC_DIRECTION;
+            break;
+    }
+
+    particleEffectEmitter.ApplyEffect();
+
+    EndParticleEffectEdit();
+}
+
 void EditParticleEffectMaterial(StringHash eventType, VariantMap& eventData)
 void EditParticleEffectMaterial(StringHash eventType, VariantMap& eventData)
 {
 {
     if (inParticleEffectRefresh)
     if (inParticleEffectRefresh)
@@ -1410,6 +1455,28 @@ void RefreshParticleEffectBasicAttributes()
             break;
             break;
     }
     }
 
 
+    switch (editParticleEffect.faceCameraMode)
+    {
+        case FC_NONE:
+            cast<DropDownList>(particleEffectWindow.GetChild("FaceCameraMode", true)).selection = 0;
+            break;
+        case FC_ROTATE_XYZ:
+            cast<DropDownList>(particleEffectWindow.GetChild("FaceCameraMode", true)).selection = 1;
+            break;
+        case FC_ROTATE_Y:
+            cast<DropDownList>(particleEffectWindow.GetChild("FaceCameraMode", true)).selection = 2;
+            break;
+        case FC_LOOKAT_XYZ:
+            cast<DropDownList>(particleEffectWindow.GetChild("FaceCameraMode", true)).selection = 3;
+            break;
+        case FC_LOOKAT_Y:
+            cast<DropDownList>(particleEffectWindow.GetChild("FaceCameraMode", true)).selection = 4;
+            break;
+        case FC_DIRECTION:
+            cast<DropDownList>(particleEffectWindow.GetChild("FaceCameraMode", true)).selection = 5;
+            break;
+    }
+
     cast<CheckBox>(particleEffectWindow.GetChild("Scaled", true)).checked = editParticleEffect.scaled;
     cast<CheckBox>(particleEffectWindow.GetChild("Scaled", true)).checked = editParticleEffect.scaled;
     cast<CheckBox>(particleEffectWindow.GetChild("Sorted", true)).checked = editParticleEffect.sorted;
     cast<CheckBox>(particleEffectWindow.GetChild("Sorted", true)).checked = editParticleEffect.sorted;
     cast<CheckBox>(particleEffectWindow.GetChild("Relative", true)).checked = editParticleEffect.relative;
     cast<CheckBox>(particleEffectWindow.GetChild("Relative", true)).checked = editParticleEffect.relative;

+ 41 - 0
bin/Data/UI/EditorParticleEffectWindow.xml

@@ -713,6 +713,47 @@
                                     </element>
                                     </element>
                                 </element>
                                 </element>
                             </element>
                             </element>
+                            <element>
+                                <!-- Face Camera Mode -->
+                                <attribute name="Min Size" value="0 16" />
+                                <attribute name="Max Size" value="2147483647 16" />
+                                <attribute name="Layout Mode" value="Horizontal" />
+                                <attribute name="Layout Spacing" value="10" />
+                                <element type="Text" style="EditorAttributeText">
+                                    <attribute name="Text" value="Face Camera Mode" />
+                                </element>
+                                <element type="DropDownList">
+                                    <attribute name="Name" value="FaceCameraMode" />
+                                    <attribute name="Resize Popup" value="true" />
+                                    <!-- Skip style processing as the purpose of below tags is to populate the content element -->
+                                    <element type="Window" internal="true" popup="true" style="none">
+                                        <element type="ListView" internal="true" style="none">
+                                            <element type="BorderImage" internal="true" style="none">
+                                                <element internal="true" style="none">
+                                                    <element type="Text" style="FileSelectorFilterText">
+                                                        <attribute name="Text" value="None" />
+                                                    </element>
+                                                    <element type="Text" style="FileSelectorFilterText">
+                                                        <attribute name="Text" value="Rotate XYZ" />
+                                                    </element>
+                                                    <element type="Text" style="FileSelectorFilterText">
+                                                        <attribute name="Text" value="Rotate Y" />
+                                                    </element>
+                                                    <element type="Text" style="FileSelectorFilterText">
+                                                        <attribute name="Text" value="LookAt XYZ" />
+                                                    </element>
+                                                    <element type="Text" style="FileSelectorFilterText">
+                                                        <attribute name="Text" value="LookAt Y" />
+                                                    </element>
+                                                    <element type="Text" style="FileSelectorFilterText">
+                                                        <attribute name="Text" value="Rotate Along Direction" />
+                                                    </element>
+                                                </element>
+                                            </element>
+                                        </element>
+                                    </element>
+                                </element>
+                            </element>
 
 
                             <!-- End of Renderer Attributes -->
                             <!-- End of Renderer Attributes -->
                         </element>
                         </element>