BillboardSet.cpp 27 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811
  1. // Copyright (c) 2008-2023 the Urho3D project
  2. // License: MIT
  3. #include "../Precompiled.h"
  4. #include "../Core/Context.h"
  5. #include "../Core/Profiler.h"
  6. #include "../Graphics/Batch.h"
  7. #include "../Graphics/BillboardSet.h"
  8. #include "../Graphics/Camera.h"
  9. #include "../Graphics/Geometry.h"
  10. #include "../Graphics/Graphics.h"
  11. #include "../Graphics/OctreeQuery.h"
  12. #include "../GraphicsAPI/IndexBuffer.h"
  13. #include "../GraphicsAPI/VertexBuffer.h"
  14. #include "../IO/Log.h"
  15. #include "../IO/MemoryBuffer.h"
  16. #include "../Resource/ResourceCache.h"
  17. #include "../Scene/Node.h"
  18. #include "../DebugNew.h"
  19. namespace Urho3D
  20. {
  21. extern const char* GEOMETRY_CATEGORY;
  22. static const float INV_SQRT_TWO = 1.0f / sqrtf(2.0f);
  23. const char* faceCameraModeNames[] =
  24. {
  25. "None",
  26. "Rotate XYZ",
  27. "Rotate Y",
  28. "LookAt XYZ",
  29. "LookAt Y",
  30. "LookAt Mixed",
  31. "Direction",
  32. nullptr
  33. };
  34. static const StringVector billboardsStructureElementNames =
  35. {
  36. "Billboard Count",
  37. " Position",
  38. " Size",
  39. " UV Coordinates",
  40. " Color",
  41. " Rotation",
  42. " Direction",
  43. " Is Enabled"
  44. };
  45. inline bool CompareBillboards(Billboard* lhs, Billboard* rhs)
  46. {
  47. return lhs->sortDistance_ > rhs->sortDistance_;
  48. }
  49. BillboardSet::BillboardSet(Context* context) :
  50. Drawable(context, DrawableTypes::Geometry),
  51. animationLodBias_(1.0f),
  52. animationLodTimer_(0.0f),
  53. relative_(true),
  54. scaled_(true),
  55. sorted_(false),
  56. fixedScreenSize_(false),
  57. faceCameraMode_(FC_ROTATE_XYZ),
  58. minAngle_(0.0f),
  59. geometry_(new Geometry(context)),
  60. vertexBuffer_(new VertexBuffer(context_)),
  61. indexBuffer_(new IndexBuffer(context_)),
  62. bufferSizeDirty_(true),
  63. bufferDirty_(true),
  64. forceUpdate_(false),
  65. geometryTypeUpdate_(false),
  66. sortThisFrame_(false),
  67. hasOrthoCamera_(false),
  68. sortFrameNumber_(0),
  69. previousOffset_(Vector3::ZERO)
  70. {
  71. geometry_->SetVertexBuffer(0, vertexBuffer_);
  72. geometry_->SetIndexBuffer(indexBuffer_);
  73. batches_.Resize(1);
  74. batches_[0].geometry_ = geometry_;
  75. batches_[0].geometryType_ = GEOM_BILLBOARD;
  76. batches_[0].worldTransform_ = &transforms_[0];
  77. }
  78. BillboardSet::~BillboardSet() = default;
  79. void BillboardSet::RegisterObject(Context* context)
  80. {
  81. context->RegisterFactory<BillboardSet>(GEOMETRY_CATEGORY);
  82. URHO3D_ACCESSOR_ATTRIBUTE("Is Enabled", IsEnabled, SetEnabled, true, AM_DEFAULT);
  83. URHO3D_ACCESSOR_ATTRIBUTE("Material", GetMaterialAttr, SetMaterialAttr, ResourceRef(Material::GetTypeStatic()),
  84. AM_DEFAULT);
  85. URHO3D_ACCESSOR_ATTRIBUTE("Relative Position", IsRelative, SetRelative, true, AM_DEFAULT);
  86. URHO3D_ACCESSOR_ATTRIBUTE("Relative Scale", IsScaled, SetScaled, true, AM_DEFAULT);
  87. URHO3D_ACCESSOR_ATTRIBUTE("Sort By Distance", IsSorted, SetSorted, false, AM_DEFAULT);
  88. URHO3D_ACCESSOR_ATTRIBUTE("Fixed Screen Size", IsFixedScreenSize, SetFixedScreenSize, false, AM_DEFAULT);
  89. URHO3D_ACCESSOR_ATTRIBUTE("Can Be Occluded", IsOccludee, SetOccludee, true, AM_DEFAULT);
  90. URHO3D_ATTRIBUTE("Cast Shadows", castShadows_, false, AM_DEFAULT);
  91. URHO3D_ENUM_ACCESSOR_ATTRIBUTE("Face Camera Mode", GetFaceCameraMode, SetFaceCameraMode, faceCameraModeNames, FC_ROTATE_XYZ, AM_DEFAULT);
  92. URHO3D_ACCESSOR_ATTRIBUTE("Min Angle", GetMinAngle, SetMinAngle, 0.0f, AM_DEFAULT);
  93. URHO3D_ACCESSOR_ATTRIBUTE("Draw Distance", GetDrawDistance, SetDrawDistance, 0.0f, AM_DEFAULT);
  94. URHO3D_ACCESSOR_ATTRIBUTE("Shadow Distance", GetShadowDistance, SetShadowDistance, 0.0f, AM_DEFAULT);
  95. URHO3D_ACCESSOR_ATTRIBUTE("Animation LOD Bias", GetAnimationLodBias, SetAnimationLodBias, 1.0f, AM_DEFAULT);
  96. URHO3D_COPY_BASE_ATTRIBUTES(Drawable);
  97. URHO3D_ACCESSOR_ATTRIBUTE("Billboards", GetBillboardsAttr, SetBillboardsAttr, Variant::emptyVariantVector, AM_FILE)
  98. .SetMetadata(AttributeMetadata::P_VECTOR_STRUCT_ELEMENTS, billboardsStructureElementNames);
  99. URHO3D_ACCESSOR_ATTRIBUTE("Network Billboards", GetNetBillboardsAttr, SetNetBillboardsAttr,
  100. Variant::emptyBuffer, AM_NET | AM_NOEDIT);
  101. }
  102. void BillboardSet::ProcessRayQuery(const RayOctreeQuery& query, Vector<RayQueryResult>& results)
  103. {
  104. // If no billboard-level testing, use the Drawable test
  105. if (query.level_ < RAY_TRIANGLE)
  106. {
  107. Drawable::ProcessRayQuery(query, results);
  108. return;
  109. }
  110. // Check ray hit distance to AABB before proceeding with billboard-level tests
  111. if (query.ray_.HitDistance(GetWorldBoundingBox()) >= query.maxDistance_)
  112. return;
  113. const Matrix3x4& worldTransform = node_->GetWorldTransform();
  114. Matrix3x4 billboardTransform = relative_ ? worldTransform : Matrix3x4::IDENTITY;
  115. Vector3 billboardScale = scaled_ ? worldTransform.Scale() : Vector3::ONE;
  116. for (i32 i = 0; i < billboards_.Size(); ++i)
  117. {
  118. if (!billboards_[i].enabled_)
  119. continue;
  120. // Approximate the billboards as spheres for raycasting
  121. float size = INV_SQRT_TWO * (billboards_[i].size_.x_ * billboardScale.x_ + billboards_[i].size_.y_ * billboardScale.y_);
  122. if (fixedScreenSize_)
  123. size *= billboards_[i].screenScaleFactor_;
  124. Vector3 center = billboardTransform * billboards_[i].position_;
  125. Sphere billboardSphere(center, size);
  126. float distance = query.ray_.HitDistance(billboardSphere);
  127. if (distance < query.maxDistance_)
  128. {
  129. // If the code reaches here then we have a hit
  130. RayQueryResult result;
  131. result.position_ = query.ray_.origin_ + distance * query.ray_.direction_;
  132. result.normal_ = -query.ray_.direction_;
  133. result.distance_ = distance;
  134. result.drawable_ = this;
  135. result.node_ = node_;
  136. result.subObject_ = i;
  137. results.Push(result);
  138. }
  139. }
  140. }
  141. void BillboardSet::UpdateBatches(const FrameInfo& frame)
  142. {
  143. // If beginning a new frame, assume no sorting first
  144. if (frame.frameNumber_ != sortFrameNumber_)
  145. {
  146. sortThisFrame_ = false;
  147. sortFrameNumber_ = frame.frameNumber_;
  148. }
  149. Vector3 worldPos = node_->GetWorldPosition();
  150. Vector3 offset = (worldPos - frame.camera_->GetNode()->GetWorldPosition());
  151. // Sort if position relative to camera has changed
  152. if (offset != previousOffset_ || frame.camera_->IsOrthographic() != hasOrthoCamera_)
  153. {
  154. if (sorted_)
  155. sortThisFrame_ = true;
  156. if (faceCameraMode_ == FC_DIRECTION)
  157. bufferDirty_ = true;
  158. hasOrthoCamera_ = frame.camera_->IsOrthographic();
  159. }
  160. // Calculate fixed screen size scale factor for billboards. Will not dirty the buffer unless actually changed
  161. if (fixedScreenSize_)
  162. CalculateFixedScreenSize(frame);
  163. distance_ = frame.camera_->GetDistance(GetWorldBoundingBox().Center());
  164. // Calculate scaled distance for animation LOD
  165. float scale = GetWorldBoundingBox().Size().DotProduct(DOT_SCALE);
  166. // If there are no billboards, the size becomes zero, and LOD'ed updates no longer happen. Disable LOD in that case
  167. if (scale > M_EPSILON)
  168. lodDistance_ = frame.camera_->GetLodDistance(distance_, scale, lodBias_);
  169. else
  170. lodDistance_ = 0.0f;
  171. batches_[0].distance_ = distance_;
  172. batches_[0].numWorldTransforms_ = 2;
  173. // Billboard positioning
  174. transforms_[0] = relative_ ? node_->GetWorldTransform() : Matrix3x4::IDENTITY;
  175. // Billboard rotation
  176. transforms_[1] = Matrix3x4(Vector3::ZERO, faceCameraMode_ != FC_NONE ? frame.camera_->GetFaceCameraRotation(
  177. node_->GetWorldPosition(), node_->GetWorldRotation(), faceCameraMode_, minAngle_) : node_->GetWorldRotation(), Vector3::ONE);
  178. }
  179. void BillboardSet::UpdateGeometry(const FrameInfo& frame)
  180. {
  181. // If rendering from multiple views and fixed screen size is in use, re-update scale factors before each render
  182. if (fixedScreenSize_ && viewCameras_.Size() > 1)
  183. CalculateFixedScreenSize(frame);
  184. // If using camera facing, re-update the rotation for the current view now
  185. if (faceCameraMode_ != FC_NONE)
  186. {
  187. transforms_[1] = Matrix3x4(Vector3::ZERO, frame.camera_->GetFaceCameraRotation(node_->GetWorldPosition(),
  188. node_->GetWorldRotation(), faceCameraMode_, minAngle_), Vector3::ONE);
  189. }
  190. if (bufferSizeDirty_ || indexBuffer_->IsDataLost())
  191. UpdateBufferSize();
  192. if (bufferDirty_ || sortThisFrame_ || vertexBuffer_->IsDataLost())
  193. UpdateVertexBuffer(frame);
  194. }
  195. UpdateGeometryType BillboardSet::GetUpdateGeometryType()
  196. {
  197. // If using camera facing, always need some kind of geometry update, in case the billboard set is rendered from several views
  198. if (bufferDirty_ || bufferSizeDirty_ || vertexBuffer_->IsDataLost() || indexBuffer_->IsDataLost() || sortThisFrame_ ||
  199. faceCameraMode_ != FC_NONE || fixedScreenSize_)
  200. return UPDATE_MAIN_THREAD;
  201. else
  202. return UPDATE_NONE;
  203. }
  204. void BillboardSet::SetMaterial(Material* material)
  205. {
  206. batches_[0].material_ = material;
  207. MarkNetworkUpdate();
  208. }
  209. void BillboardSet::SetNumBillboards(i32 num)
  210. {
  211. // Prevent negative value being assigned from the editor
  212. if (num < 0)
  213. {
  214. URHO3D_LOGWARNING("BillboardSet::SetNumBillboards(i32): num < 0");
  215. num = 0;
  216. }
  217. i32 oldNum = billboards_.Size();
  218. if (num == oldNum)
  219. return;
  220. billboards_.Resize(num);
  221. // Set default values to new billboards
  222. for (i32 i = oldNum; i < num; ++i)
  223. {
  224. billboards_[i].position_ = Vector3::ZERO;
  225. billboards_[i].size_ = Vector2::ONE;
  226. billboards_[i].uv_ = Rect::POSITIVE;
  227. billboards_[i].color_ = Color(1.0f, 1.0f, 1.0f);
  228. billboards_[i].rotation_ = 0.0f;
  229. billboards_[i].direction_ = Vector3::UP;
  230. billboards_[i].enabled_ = false;
  231. billboards_[i].screenScaleFactor_ = 1.0f;
  232. }
  233. bufferSizeDirty_ = true;
  234. Commit();
  235. }
  236. void BillboardSet::SetRelative(bool enable)
  237. {
  238. relative_ = enable;
  239. Commit();
  240. }
  241. void BillboardSet::SetScaled(bool enable)
  242. {
  243. scaled_ = enable;
  244. Commit();
  245. }
  246. void BillboardSet::SetSorted(bool enable)
  247. {
  248. sorted_ = enable;
  249. Commit();
  250. }
  251. void BillboardSet::SetFixedScreenSize(bool enable)
  252. {
  253. fixedScreenSize_ = enable;
  254. Commit();
  255. }
  256. void BillboardSet::SetFaceCameraMode(FaceCameraMode mode)
  257. {
  258. if ((faceCameraMode_ != FC_DIRECTION && mode == FC_DIRECTION) || (faceCameraMode_ == FC_DIRECTION && mode != FC_DIRECTION))
  259. {
  260. faceCameraMode_ = mode;
  261. if (faceCameraMode_ == FC_DIRECTION)
  262. batches_[0].geometryType_ = GEOM_DIRBILLBOARD;
  263. else
  264. batches_[0].geometryType_ = GEOM_BILLBOARD;
  265. geometryTypeUpdate_ = true;
  266. bufferSizeDirty_ = true;
  267. Commit();
  268. }
  269. else
  270. {
  271. faceCameraMode_ = mode;
  272. MarkNetworkUpdate();
  273. }
  274. }
  275. void BillboardSet::SetMinAngle(float angle)
  276. {
  277. minAngle_ = angle;
  278. MarkNetworkUpdate();
  279. }
  280. void BillboardSet::SetAnimationLodBias(float bias)
  281. {
  282. animationLodBias_ = Max(bias, 0.0f);
  283. MarkNetworkUpdate();
  284. }
  285. void BillboardSet::Commit()
  286. {
  287. MarkPositionsDirty();
  288. MarkNetworkUpdate();
  289. }
  290. Material* BillboardSet::GetMaterial() const
  291. {
  292. return batches_[0].material_;
  293. }
  294. Billboard* BillboardSet::GetBillboard(i32 index)
  295. {
  296. if (index < 0 || index >= billboards_.Size())
  297. {
  298. URHO3D_LOGWARNING("BillboardSet::GetBillboard(i32): index out of range");
  299. return nullptr;
  300. }
  301. return &billboards_[index];
  302. }
  303. void BillboardSet::SetMaterialAttr(const ResourceRef& value)
  304. {
  305. auto* cache = GetSubsystem<ResourceCache>();
  306. SetMaterial(cache->GetResource<Material>(value.name_));
  307. }
  308. void BillboardSet::SetBillboardsAttr(const VariantVector& value)
  309. {
  310. i32 index = 0;
  311. i32 numBillboards = index < value.Size() ? value[index++].GetI32() : 0;
  312. assert(numBillboards >= 0);
  313. SetNumBillboards(numBillboards);
  314. // Dealing with old billboard format
  315. if (value.Size() == billboards_.Size() * 6 + 1)
  316. {
  317. for (Vector<Billboard>::Iterator i = billboards_.Begin(); i != billboards_.End() && index < value.Size(); ++i)
  318. {
  319. i->position_ = value[index++].GetVector3();
  320. i->size_ = value[index++].GetVector2();
  321. Vector4 uv = value[index++].GetVector4();
  322. i->uv_ = Rect(uv.x_, uv.y_, uv.z_, uv.w_);
  323. i->color_ = value[index++].GetColor();
  324. i->rotation_ = value[index++].GetFloat();
  325. i->enabled_ = value[index++].GetBool();
  326. }
  327. }
  328. // New billboard format
  329. else
  330. {
  331. for (Vector<Billboard>::Iterator i = billboards_.Begin(); i != billboards_.End() && index < value.Size(); ++i)
  332. {
  333. i->position_ = value[index++].GetVector3();
  334. i->size_ = value[index++].GetVector2();
  335. Vector4 uv = value[index++].GetVector4();
  336. i->uv_ = Rect(uv.x_, uv.y_, uv.z_, uv.w_);
  337. i->color_ = value[index++].GetColor();
  338. i->rotation_ = value[index++].GetFloat();
  339. i->direction_ = value[index++].GetVector3();
  340. i->enabled_ = value[index++].GetBool();
  341. }
  342. }
  343. Commit();
  344. }
  345. void BillboardSet::SetNetBillboardsAttr(const Vector<byte>& value)
  346. {
  347. MemoryBuffer buf(value);
  348. i32 numBillboards = buf.ReadVLE();
  349. SetNumBillboards(numBillboards);
  350. for (Vector<Billboard>::Iterator i = billboards_.Begin(); i != billboards_.End(); ++i)
  351. {
  352. i->position_ = buf.ReadVector3();
  353. i->size_ = buf.ReadVector2();
  354. i->uv_ = buf.ReadRect();
  355. i->color_ = buf.ReadColor();
  356. i->rotation_ = buf.ReadFloat();
  357. i->direction_ = buf.ReadVector3();
  358. i->enabled_ = buf.ReadBool();
  359. }
  360. Commit();
  361. }
  362. ResourceRef BillboardSet::GetMaterialAttr() const
  363. {
  364. return GetResourceRef(batches_[0].material_, Material::GetTypeStatic());
  365. }
  366. VariantVector BillboardSet::GetBillboardsAttr() const
  367. {
  368. VariantVector ret;
  369. ret.Reserve(billboards_.Size() * 7 + 1);
  370. ret.Push(billboards_.Size());
  371. for (Vector<Billboard>::ConstIterator i = billboards_.Begin(); i != billboards_.End(); ++i)
  372. {
  373. ret.Push(i->position_);
  374. ret.Push(i->size_);
  375. ret.Push(Vector4(i->uv_.min_.x_, i->uv_.min_.y_, i->uv_.max_.x_, i->uv_.max_.y_));
  376. ret.Push(i->color_);
  377. ret.Push(i->rotation_);
  378. ret.Push(i->direction_);
  379. ret.Push(i->enabled_);
  380. }
  381. return ret;
  382. }
  383. const Vector<byte>& BillboardSet::GetNetBillboardsAttr() const
  384. {
  385. attrBuffer_.Clear();
  386. attrBuffer_.WriteVLE(billboards_.Size());
  387. for (Vector<Billboard>::ConstIterator i = billboards_.Begin(); i != billboards_.End(); ++i)
  388. {
  389. attrBuffer_.WriteVector3(i->position_);
  390. attrBuffer_.WriteVector2(i->size_);
  391. attrBuffer_.WriteRect(i->uv_);
  392. attrBuffer_.WriteColor(i->color_);
  393. attrBuffer_.WriteFloat(i->rotation_);
  394. attrBuffer_.WriteVector3(i->direction_);
  395. attrBuffer_.WriteBool(i->enabled_);
  396. }
  397. return attrBuffer_.GetBuffer();
  398. }
  399. void BillboardSet::OnWorldBoundingBoxUpdate()
  400. {
  401. i32 enabledBillboards = 0; // TODO: not used
  402. const Matrix3x4& worldTransform = node_->GetWorldTransform();
  403. Matrix3x4 billboardTransform = relative_ ? worldTransform : Matrix3x4::IDENTITY;
  404. Vector3 billboardScale = scaled_ ? worldTransform.Scale() : Vector3::ONE;
  405. BoundingBox worldBox;
  406. for (const Billboard& billboard : billboards_)
  407. {
  408. if (!billboard.enabled_)
  409. continue;
  410. float size = INV_SQRT_TWO * (billboard.size_.x_ * billboardScale.x_ + billboard.size_.y_ * billboardScale.y_);
  411. if (fixedScreenSize_)
  412. size *= billboard.screenScaleFactor_;
  413. Vector3 center = billboardTransform * billboard.position_;
  414. Vector3 edge = Vector3::ONE * size;
  415. worldBox.Merge(BoundingBox(center - edge, center + edge));
  416. ++enabledBillboards;
  417. }
  418. // Always merge the node's own position to ensure particle emitter updates continue when the relative mode is switched
  419. worldBox.Merge(node_->GetWorldPosition());
  420. worldBoundingBox_ = worldBox;
  421. }
  422. void BillboardSet::UpdateBufferSize()
  423. {
  424. i32 numBillboards = billboards_.Size();
  425. if (vertexBuffer_->GetVertexCount() != numBillboards * 4 || geometryTypeUpdate_)
  426. {
  427. if (faceCameraMode_ == FC_DIRECTION)
  428. {
  429. vertexBuffer_->SetSize(numBillboards * 4, VertexElements::Position | VertexElements::Normal
  430. | VertexElements::Color | VertexElements::TexCoord1 | VertexElements::TexCoord2, true);
  431. geometry_->SetVertexBuffer(0, vertexBuffer_);
  432. }
  433. else
  434. {
  435. vertexBuffer_->SetSize(numBillboards * 4, VertexElements::Position | VertexElements::Color
  436. | VertexElements::TexCoord1 | VertexElements::TexCoord2, true);
  437. geometry_->SetVertexBuffer(0, vertexBuffer_);
  438. }
  439. geometryTypeUpdate_ = false;
  440. }
  441. bool largeIndices = (numBillboards * 4) >= 65536;
  442. if (indexBuffer_->GetIndexCount() != numBillboards * 6)
  443. indexBuffer_->SetSize(numBillboards * 6, largeIndices);
  444. bufferSizeDirty_ = false;
  445. bufferDirty_ = true;
  446. forceUpdate_ = true;
  447. if (!numBillboards)
  448. return;
  449. // Indices do not change for a given billboard capacity
  450. void* destPtr = indexBuffer_->Lock(0, numBillboards * 6, true);
  451. if (!destPtr)
  452. return;
  453. if (!largeIndices)
  454. {
  455. auto* dest = (unsigned short*)destPtr;
  456. unsigned short vertexIndex = 0;
  457. while (numBillboards--)
  458. {
  459. dest[0] = vertexIndex;
  460. dest[1] = vertexIndex + 1;
  461. dest[2] = vertexIndex + 2;
  462. dest[3] = vertexIndex + 2;
  463. dest[4] = vertexIndex + 3;
  464. dest[5] = vertexIndex;
  465. dest += 6;
  466. vertexIndex += 4;
  467. }
  468. }
  469. else
  470. {
  471. auto* dest = (unsigned*)destPtr;
  472. unsigned vertexIndex = 0;
  473. while (numBillboards--)
  474. {
  475. dest[0] = vertexIndex;
  476. dest[1] = vertexIndex + 1;
  477. dest[2] = vertexIndex + 2;
  478. dest[3] = vertexIndex + 2;
  479. dest[4] = vertexIndex + 3;
  480. dest[5] = vertexIndex;
  481. dest += 6;
  482. vertexIndex += 4;
  483. }
  484. }
  485. indexBuffer_->Unlock();
  486. indexBuffer_->ClearDataLost();
  487. }
  488. void BillboardSet::UpdateVertexBuffer(const FrameInfo& frame)
  489. {
  490. // If using animation LOD, accumulate time and see if it is time to update
  491. if (animationLodBias_ > 0.0f && lodDistance_ > 0.0f)
  492. {
  493. animationLodTimer_ += animationLodBias_ * frame.timeStep_ * ANIMATION_LOD_BASESCALE;
  494. if (animationLodTimer_ >= lodDistance_)
  495. animationLodTimer_ = fmodf(animationLodTimer_, lodDistance_);
  496. else
  497. {
  498. // No LOD if immediate update forced
  499. if (!forceUpdate_)
  500. return;
  501. }
  502. }
  503. i32 enabledBillboards = 0;
  504. const Matrix3x4& worldTransform = node_->GetWorldTransform();
  505. Matrix3x4 billboardTransform = relative_ ? worldTransform : Matrix3x4::IDENTITY;
  506. Vector3 billboardScale = scaled_ ? worldTransform.Scale() : Vector3::ONE;
  507. // First check number of enabled billboards
  508. for (const Billboard& billboard : billboards_)
  509. {
  510. if (billboard.enabled_)
  511. ++enabledBillboards;
  512. }
  513. sortedBillboards_.Resize(enabledBillboards);
  514. i32 index = 0;
  515. // Then set initial sort order and distances
  516. for (Billboard& billboard : billboards_)
  517. {
  518. if (billboard.enabled_)
  519. {
  520. sortedBillboards_[index++] = &billboard;
  521. if (sorted_)
  522. billboard.sortDistance_ = frame.camera_->GetDistanceSquared(billboardTransform * billboard.position_);
  523. }
  524. }
  525. batches_[0].geometry_->SetDrawRange(TRIANGLE_LIST, 0, enabledBillboards * 6, false);
  526. bufferDirty_ = false;
  527. forceUpdate_ = false;
  528. if (!enabledBillboards)
  529. return;
  530. if (sorted_)
  531. {
  532. Sort(sortedBillboards_.Begin(), sortedBillboards_.End(), CompareBillboards);
  533. Vector3 worldPos = node_->GetWorldPosition();
  534. // Store the "last sorted position" now
  535. previousOffset_ = (worldPos - frame.camera_->GetNode()->GetWorldPosition());
  536. }
  537. auto* dest = (float*)vertexBuffer_->Lock(0, enabledBillboards * 4, true);
  538. if (!dest)
  539. return;
  540. if (faceCameraMode_ != FC_DIRECTION)
  541. {
  542. for (i32 i = 0; i < enabledBillboards; ++i)
  543. {
  544. Billboard& billboard = *sortedBillboards_[i];
  545. Vector2 size(billboard.size_.x_ * billboardScale.x_, billboard.size_.y_ * billboardScale.y_);
  546. color32 color = billboard.color_.ToU32();
  547. if (fixedScreenSize_)
  548. size *= billboard.screenScaleFactor_;
  549. float rotationMatrix[2][2];
  550. SinCos(billboard.rotation_, rotationMatrix[0][1], rotationMatrix[0][0]);
  551. rotationMatrix[1][0] = -rotationMatrix[0][1];
  552. rotationMatrix[1][1] = rotationMatrix[0][0];
  553. dest[0] = billboard.position_.x_;
  554. dest[1] = billboard.position_.y_;
  555. dest[2] = billboard.position_.z_;
  556. ((color32&)dest[3]) = color;
  557. dest[4] = billboard.uv_.min_.x_;
  558. dest[5] = billboard.uv_.min_.y_;
  559. dest[6] = -size.x_ * rotationMatrix[0][0] + size.y_ * rotationMatrix[0][1];
  560. dest[7] = -size.x_ * rotationMatrix[1][0] + size.y_ * rotationMatrix[1][1];
  561. dest[8] = billboard.position_.x_;
  562. dest[9] = billboard.position_.y_;
  563. dest[10] = billboard.position_.z_;
  564. ((color32&)dest[11]) = color;
  565. dest[12] = billboard.uv_.max_.x_;
  566. dest[13] = billboard.uv_.min_.y_;
  567. dest[14] = size.x_ * rotationMatrix[0][0] + size.y_ * rotationMatrix[0][1];
  568. dest[15] = size.x_ * rotationMatrix[1][0] + size.y_ * rotationMatrix[1][1];
  569. dest[16] = billboard.position_.x_;
  570. dest[17] = billboard.position_.y_;
  571. dest[18] = billboard.position_.z_;
  572. ((color32&)dest[19]) = color;
  573. dest[20] = billboard.uv_.max_.x_;
  574. dest[21] = billboard.uv_.max_.y_;
  575. dest[22] = size.x_ * rotationMatrix[0][0] - size.y_ * rotationMatrix[0][1];
  576. dest[23] = size.x_ * rotationMatrix[1][0] - size.y_ * rotationMatrix[1][1];
  577. dest[24] = billboard.position_.x_;
  578. dest[25] = billboard.position_.y_;
  579. dest[26] = billboard.position_.z_;
  580. ((color32&)dest[27]) = color;
  581. dest[28] = billboard.uv_.min_.x_;
  582. dest[29] = billboard.uv_.max_.y_;
  583. dest[30] = -size.x_ * rotationMatrix[0][0] - size.y_ * rotationMatrix[0][1];
  584. dest[31] = -size.x_ * rotationMatrix[1][0] - size.y_ * rotationMatrix[1][1];
  585. dest += 32;
  586. }
  587. }
  588. else
  589. {
  590. for (i32 i = 0; i < enabledBillboards; ++i)
  591. {
  592. Billboard& billboard = *sortedBillboards_[i];
  593. Vector2 size(billboard.size_.x_ * billboardScale.x_, billboard.size_.y_ * billboardScale.y_);
  594. color32 color = billboard.color_.ToU32();
  595. if (fixedScreenSize_)
  596. size *= billboard.screenScaleFactor_;
  597. float rot2D[2][2];
  598. SinCos(billboard.rotation_, rot2D[0][1], rot2D[0][0]);
  599. rot2D[1][0] = -rot2D[0][1];
  600. rot2D[1][1] = rot2D[0][0];
  601. dest[0] = billboard.position_.x_;
  602. dest[1] = billboard.position_.y_;
  603. dest[2] = billboard.position_.z_;
  604. dest[3] = billboard.direction_.x_;
  605. dest[4] = billboard.direction_.y_;
  606. dest[5] = billboard.direction_.z_;
  607. ((color32&)dest[6]) = color;
  608. dest[7] = billboard.uv_.min_.x_;
  609. dest[8] = billboard.uv_.min_.y_;
  610. dest[9] = -size.x_ * rot2D[0][0] + size.y_ * rot2D[0][1];
  611. dest[10] = -size.x_ * rot2D[1][0] + size.y_ * rot2D[1][1];
  612. dest[11] = billboard.position_.x_;
  613. dest[12] = billboard.position_.y_;
  614. dest[13] = billboard.position_.z_;
  615. dest[14] = billboard.direction_.x_;
  616. dest[15] = billboard.direction_.y_;
  617. dest[16] = billboard.direction_.z_;
  618. ((color32&)dest[17]) = color;
  619. dest[18] = billboard.uv_.max_.x_;
  620. dest[19] = billboard.uv_.min_.y_;
  621. dest[20] = size.x_ * rot2D[0][0] + size.y_ * rot2D[0][1];
  622. dest[21] = size.x_ * rot2D[1][0] + size.y_ * rot2D[1][1];
  623. dest[22] = billboard.position_.x_;
  624. dest[23] = billboard.position_.y_;
  625. dest[24] = billboard.position_.z_;
  626. dest[25] = billboard.direction_.x_;
  627. dest[26] = billboard.direction_.y_;
  628. dest[27] = billboard.direction_.z_;
  629. ((color32&)dest[28]) = color;
  630. dest[29] = billboard.uv_.max_.x_;
  631. dest[30] = billboard.uv_.max_.y_;
  632. dest[31] = size.x_ * rot2D[0][0] - size.y_ * rot2D[0][1];
  633. dest[32] = size.x_ * rot2D[1][0] - size.y_ * rot2D[1][1];
  634. dest[33] = billboard.position_.x_;
  635. dest[34] = billboard.position_.y_;
  636. dest[35] = billboard.position_.z_;
  637. dest[36] = billboard.direction_.x_;
  638. dest[37] = billboard.direction_.y_;
  639. dest[38] = billboard.direction_.z_;
  640. ((color32&)dest[39]) = color;
  641. dest[40] = billboard.uv_.min_.x_;
  642. dest[41] = billboard.uv_.max_.y_;
  643. dest[42] = -size.x_ * rot2D[0][0] - size.y_ * rot2D[0][1];
  644. dest[43] = -size.x_ * rot2D[1][0] - size.y_ * rot2D[1][1];
  645. dest += 44;
  646. }
  647. }
  648. vertexBuffer_->Unlock();
  649. vertexBuffer_->ClearDataLost();
  650. }
  651. void BillboardSet::MarkPositionsDirty()
  652. {
  653. Drawable::OnMarkedDirty(node_);
  654. bufferDirty_ = true;
  655. }
  656. void BillboardSet::CalculateFixedScreenSize(const FrameInfo& frame)
  657. {
  658. float invViewHeight = 1.0f / frame.viewSize_.y_;
  659. float halfViewWorldSize = frame.camera_->GetHalfViewSize();
  660. bool scaleFactorChanged = false;
  661. if (!frame.camera_->IsOrthographic())
  662. {
  663. Matrix4 viewProj(frame.camera_->GetProjection() * frame.camera_->GetView());
  664. const Matrix3x4& worldTransform = node_->GetWorldTransform();
  665. Matrix3x4 billboardTransform = relative_ ? worldTransform : Matrix3x4::IDENTITY;
  666. for (Billboard& billboard : billboards_)
  667. {
  668. Vector4 projPos(viewProj * Vector4(billboardTransform * billboard.position_, 1.0f));
  669. float newScaleFactor = invViewHeight * halfViewWorldSize * projPos.w_;
  670. if (newScaleFactor != billboard.screenScaleFactor_)
  671. {
  672. billboard.screenScaleFactor_ = newScaleFactor;
  673. scaleFactorChanged = true;
  674. }
  675. }
  676. }
  677. else
  678. {
  679. for (Billboard& billboard : billboards_)
  680. {
  681. float newScaleFactor = invViewHeight * halfViewWorldSize;
  682. if (newScaleFactor != billboard.screenScaleFactor_)
  683. {
  684. billboard.screenScaleFactor_ = newScaleFactor;
  685. scaleFactorChanged = true;
  686. }
  687. }
  688. }
  689. if (scaleFactorChanged)
  690. {
  691. bufferDirty_ = true;
  692. forceUpdate_ = true;
  693. worldBoundingBoxDirty_ = true;
  694. }
  695. }
  696. }