mirror of
https://github.com/lubosz/overte.git
synced 2025-04-23 09:25:31 +02:00
merge upstream/master into andrew/isentropic
Conflicts: libraries/networking/src/PacketHeaders.cpp
This commit is contained in:
commit
51d05cc43e
26 changed files with 1245 additions and 21 deletions
16
examples/hmdDefaults.js
Normal file
16
examples/hmdDefaults.js
Normal file
|
@ -0,0 +1,16 @@
|
|||
//
|
||||
// hmdDefaults.js
|
||||
// examples
|
||||
//
|
||||
// Created by David Rowe on 6 Mar 2015.
|
||||
// Copyright 2015 High Fidelity, Inc.
|
||||
//
|
||||
// Distributed under the Apache License, Version 2.0.
|
||||
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
|
||||
//
|
||||
|
||||
Script.load("progress.js");
|
||||
Script.load("lobby.js");
|
||||
Script.load("notifications.js");
|
||||
Script.load("controllers/oculus/goTo.js");
|
||||
//Script.load("scripts.js"); // Not created yet
|
|
@ -141,7 +141,7 @@ using namespace std;
|
|||
static unsigned STARFIELD_NUM_STARS = 50000;
|
||||
static unsigned STARFIELD_SEED = 1;
|
||||
|
||||
const qint64 MAXIMUM_CACHE_SIZE = 10737418240; // 10GB
|
||||
const qint64 MAXIMUM_CACHE_SIZE = 10 * BYTES_PER_GIGABYTES; // 10GB
|
||||
|
||||
static QTimer* locationUpdateTimer = NULL;
|
||||
static QTimer* balanceUpdateTimer = NULL;
|
||||
|
@ -445,7 +445,7 @@ Application::Application(int& argc, char** argv, QElapsedTimer &startup_time) :
|
|||
cache->setMaximumCacheSize(MAXIMUM_CACHE_SIZE);
|
||||
cache->setCacheDirectory(!cachePath.isEmpty() ? cachePath : "interfaceCache");
|
||||
networkAccessManager.setCache(cache);
|
||||
|
||||
|
||||
ResourceCache::setRequestLimit(3);
|
||||
|
||||
_window->setCentralWidget(_glWidget);
|
||||
|
@ -490,6 +490,9 @@ Application::Application(int& argc, char** argv, QElapsedTimer &startup_time) :
|
|||
connect(nodeList.data(), SIGNAL(dataReceived(const quint8, const int)),
|
||||
bandwidthRecorder.data(), SLOT(updateInboundData(const quint8, const int)));
|
||||
|
||||
connect(&_myAvatar->getSkeletonModel(), &SkeletonModel::skeletonLoaded,
|
||||
this, &Application::checkSkeleton, Qt::QueuedConnection);
|
||||
|
||||
// check first run...
|
||||
if (_firstRun.get()) {
|
||||
qDebug() << "This is a first run...";
|
||||
|
@ -4041,3 +4044,19 @@ void Application::notifyPacketVersionMismatch() {
|
|||
msgBox.exec();
|
||||
}
|
||||
}
|
||||
|
||||
void Application::checkSkeleton() {
|
||||
if (_myAvatar->getSkeletonModel().isActive() && !_myAvatar->getSkeletonModel().hasSkeleton()) {
|
||||
qDebug() << "MyAvatar model has no skeleton";
|
||||
|
||||
QString message = "Your selected avatar body has no skeleton.\n\nThe default body will be loaded...";
|
||||
QMessageBox msgBox;
|
||||
msgBox.setText(message);
|
||||
msgBox.setStandardButtons(QMessageBox::Ok);
|
||||
msgBox.setIcon(QMessageBox::Warning);
|
||||
msgBox.exec();
|
||||
|
||||
_myAvatar->setSkeletonModelURL(DEFAULT_BODY_MODEL_URL);
|
||||
_myAvatar->sendIdentityPacket();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -585,6 +585,8 @@ private:
|
|||
QTimer _settingsTimer;
|
||||
|
||||
GLCanvas* _glWidget = new GLCanvas(); // our GLCanvas has a couple extra features
|
||||
|
||||
void checkSkeleton();
|
||||
};
|
||||
|
||||
#endif // hifi_Application_h
|
||||
|
|
|
@ -436,6 +436,8 @@ Menu::Menu() {
|
|||
SLOT(disable(bool)));
|
||||
addActionToQMenuAndActionHash(networkMenu, MenuOption::CachesSize, 0,
|
||||
dialogsManager.data(), SLOT(cachesSizeDialog()));
|
||||
addActionToQMenuAndActionHash(networkMenu, MenuOption::DiskCacheEditor, 0,
|
||||
dialogsManager.data(), SLOT(toggleDiskCacheEditor()));
|
||||
|
||||
QMenu* timingMenu = developerMenu->addMenu("Timing and Stats");
|
||||
QMenu* perfTimerMenu = timingMenu->addMenu("Performance Timer");
|
||||
|
|
|
@ -125,7 +125,7 @@ namespace MenuOption {
|
|||
const QString BookmarkLocation = "Bookmark Location";
|
||||
const QString Bookmarks = "Bookmarks";
|
||||
const QString CascadedShadows = "Cascaded";
|
||||
const QString CachesSize = "Caches Size";
|
||||
const QString CachesSize = "RAM Caches Size";
|
||||
const QString Chat = "Chat...";
|
||||
const QString ChatCircling = "Chat Circling";
|
||||
const QString CollideAsRagdoll = "Collide With Self (Ragdoll)";
|
||||
|
@ -143,6 +143,7 @@ namespace MenuOption {
|
|||
const QString DisableAutoAdjustLOD = "Disable Automatically Adjusting LOD";
|
||||
const QString DisableLightEntities = "Disable Light Entities";
|
||||
const QString DisableNackPackets = "Disable NACK Packets";
|
||||
const QString DiskCacheEditor = "Disk Cache Editor";
|
||||
const QString DisplayHands = "Show Hand Info";
|
||||
const QString DisplayHandTargets = "Show Hand Targets";
|
||||
const QString DisplayModelBounds = "Display Model Bounds";
|
||||
|
|
|
@ -177,6 +177,22 @@ bool ModelUploader::zip() {
|
|||
}
|
||||
QByteArray fbxContents = fbx.readAll();
|
||||
FBXGeometry geometry = readFBX(fbxContents, QVariantHash());
|
||||
|
||||
// Make sure that a skeleton model has a skeleton
|
||||
if (_modelType == SKELETON_MODEL) {
|
||||
if (geometry.rootJointIndex == -1) {
|
||||
|
||||
QString message = "Your selected skeleton model has no skeleton.\n\nThe upload will be canceled.";
|
||||
QMessageBox msgBox;
|
||||
msgBox.setWindowTitle("Model Upload");
|
||||
msgBox.setText(message);
|
||||
msgBox.setStandardButtons(QMessageBox::Ok);
|
||||
msgBox.setIcon(QMessageBox::Warning);
|
||||
msgBox.exec();
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// make sure we have some basic mappings
|
||||
populateBasicMapping(mapping, filename, geometry);
|
||||
|
@ -314,14 +330,16 @@ void ModelUploader::populateBasicMapping(QVariantHash& mapping, QString filename
|
|||
// mixamo blendshapes - in the event that a mixamo file was edited by some other tool, it's likely the applicationName will
|
||||
// be rewritten, so we detect the existence of several different blendshapes which indicate we're likely a mixamo file
|
||||
bool likelyMixamoFile = geometry.applicationName == "mixamo.com" ||
|
||||
(geometry.blendshapeChannelNames.contains("BrowsDown_Left") &&
|
||||
(geometry.blendshapeChannelNames.contains("Facial_Blends") &&
|
||||
geometry.blendshapeChannelNames.contains("BrowsDown_Right") &&
|
||||
geometry.blendshapeChannelNames.contains("MouthOpen") &&
|
||||
geometry.blendshapeChannelNames.contains("TongueUp") &&
|
||||
geometry.blendshapeChannelNames.contains("MouthWhistle_NarrowAdjust_Left") &&
|
||||
geometry.blendshapeChannelNames.contains("NoseScrunch_Left") &&
|
||||
geometry.blendshapeChannelNames.contains("Blink_Left") &&
|
||||
geometry.blendshapeChannelNames.contains("Blink_Right") &&
|
||||
geometry.blendshapeChannelNames.contains("Squint_Right"));
|
||||
|
||||
|
||||
qDebug() << "likelyMixamoFile:" << likelyMixamoFile;
|
||||
qDebug() << "geometry.blendshapeChannelNames:" << geometry.blendshapeChannelNames;
|
||||
|
||||
if (!mapping.contains(BLENDSHAPE_FIELD) && likelyMixamoFile) {
|
||||
QVariantHash blendshapes;
|
||||
blendshapes.insertMulti("BrowsD_L", QVariantList() << "BrowsDown_Left" << 1.0);
|
||||
|
|
|
@ -12,7 +12,6 @@
|
|||
#include <algorithm>
|
||||
#include <vector>
|
||||
|
||||
#include <QMessageBox>
|
||||
#include <QBuffer>
|
||||
|
||||
#include <glm/gtx/norm.hpp>
|
||||
|
@ -195,6 +194,12 @@ void MyAvatar::simulate(float deltaTime) {
|
|||
PerformanceTimer perfTimer("skeleton");
|
||||
_skeletonModel.simulate(deltaTime);
|
||||
}
|
||||
|
||||
if (!_skeletonModel.hasSkeleton()) {
|
||||
// All the simulation that can be done has been done
|
||||
return;
|
||||
}
|
||||
|
||||
{
|
||||
PerformanceTimer perfTimer("attachments");
|
||||
simulateAttachments(deltaTime);
|
||||
|
|
|
@ -81,6 +81,8 @@ void SkeletonModel::setJointStates(QVector<JointState> states) {
|
|||
if (_enableShapes) {
|
||||
buildShapes();
|
||||
}
|
||||
|
||||
emit skeletonLoaded();
|
||||
}
|
||||
|
||||
const float PALM_PRIORITY = DEFAULT_PRIORITY;
|
||||
|
@ -721,7 +723,8 @@ void SkeletonModel::buildShapes() {
|
|||
}
|
||||
|
||||
const FBXGeometry& geometry = _geometry->getFBXGeometry();
|
||||
if (geometry.joints.isEmpty()) {
|
||||
if (geometry.joints.isEmpty() || geometry.rootJointIndex == -1) {
|
||||
// rootJointIndex == -1 if the avatar model has no skeleton
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -1006,3 +1009,6 @@ void SkeletonModel::renderJointCollisionShapes(float alpha) {
|
|||
glPopMatrix();
|
||||
}
|
||||
|
||||
bool SkeletonModel::hasSkeleton() {
|
||||
return isActive() ? _geometry->getFBXGeometry().rootJointIndex != -1 : false;
|
||||
}
|
||||
|
|
|
@ -123,7 +123,13 @@ public:
|
|||
void resetShapePositionsToDefaultPose(); // DEBUG method
|
||||
|
||||
void renderRagdoll();
|
||||
|
||||
|
||||
bool hasSkeleton();
|
||||
|
||||
signals:
|
||||
|
||||
void skeletonLoaded();
|
||||
|
||||
protected:
|
||||
|
||||
void buildShapes();
|
||||
|
|
|
@ -20,6 +20,7 @@
|
|||
#include "AttachmentsDialog.h"
|
||||
#include "BandwidthDialog.h"
|
||||
#include "CachesSizeDialog.h"
|
||||
#include "DiskCacheEditor.h"
|
||||
#include "HMDToolsDialog.h"
|
||||
#include "LodToolsDialog.h"
|
||||
#include "LoginDialog.h"
|
||||
|
@ -37,6 +38,11 @@ void DialogsManager::toggleAddressBar() {
|
|||
}
|
||||
}
|
||||
|
||||
void DialogsManager::toggleDiskCacheEditor() {
|
||||
maybeCreateDialog(_diskCacheEditor);
|
||||
_diskCacheEditor->toggle();
|
||||
}
|
||||
|
||||
void DialogsManager::toggleLoginDialog() {
|
||||
maybeCreateDialog(_loginDialog);
|
||||
_loginDialog->toggleQAction();
|
||||
|
@ -61,7 +67,6 @@ void DialogsManager::octreeStatsDetails() {
|
|||
}
|
||||
|
||||
void DialogsManager::cachesSizeDialog() {
|
||||
qDebug() << "Caches size:" << _cachesSizeDialog.isNull();
|
||||
if (!_cachesSizeDialog) {
|
||||
maybeCreateDialog(_cachesSizeDialog);
|
||||
|
||||
|
|
|
@ -24,8 +24,9 @@ class QAction;
|
|||
class AddressBarDialog;
|
||||
class AnimationsDialog;
|
||||
class AttachmentsDialog;
|
||||
class CachesSizeDialog;
|
||||
class BandwidthDialog;
|
||||
class CachesSizeDialog;
|
||||
class DiskCacheEditor;
|
||||
class LodToolsDialog;
|
||||
class LoginDialog;
|
||||
class OctreeStatsDialog;
|
||||
|
@ -45,6 +46,7 @@ public:
|
|||
|
||||
public slots:
|
||||
void toggleAddressBar();
|
||||
void toggleDiskCacheEditor();
|
||||
void toggleLoginDialog();
|
||||
void showLoginDialog();
|
||||
void octreeStatsDetails();
|
||||
|
@ -73,7 +75,7 @@ private:
|
|||
member = new T(parent);
|
||||
Q_CHECK_PTR(member);
|
||||
|
||||
if (_hmdToolsDialog) {
|
||||
if (_hmdToolsDialog && member->windowHandle()) {
|
||||
_hmdToolsDialog->watchWindow(member->windowHandle());
|
||||
}
|
||||
}
|
||||
|
@ -84,6 +86,7 @@ private:
|
|||
QPointer<AttachmentsDialog> _attachmentsDialog;
|
||||
QPointer<BandwidthDialog> _bandwidthDialog;
|
||||
QPointer<CachesSizeDialog> _cachesSizeDialog;
|
||||
QPointer<DiskCacheEditor> _diskCacheEditor;
|
||||
QPointer<QMessageBox> _ircInfoBox;
|
||||
QPointer<HMDToolsDialog> _hmdToolsDialog;
|
||||
QPointer<LodToolsDialog> _lodToolsDialog;
|
||||
|
|
150
interface/src/ui/DiskCacheEditor.cpp
Normal file
150
interface/src/ui/DiskCacheEditor.cpp
Normal file
|
@ -0,0 +1,150 @@
|
|||
//
|
||||
// DiskCacheEditor.cpp
|
||||
//
|
||||
//
|
||||
// Created by Clement on 3/4/15.
|
||||
// Copyright 2015 High Fidelity, Inc.
|
||||
//
|
||||
// Distributed under the Apache License, Version 2.0.
|
||||
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
|
||||
//
|
||||
|
||||
#include <functional>
|
||||
|
||||
#include <QDebug>
|
||||
#include <QDialog>
|
||||
#include <QGridLayout>
|
||||
#include <QPushButton>
|
||||
#include <QLabel>
|
||||
#include <QNetworkDiskCache>
|
||||
#include <QMessageBox>
|
||||
|
||||
#include <NetworkAccessManager.h>
|
||||
|
||||
#include "DiskCacheEditor.h"
|
||||
|
||||
DiskCacheEditor::DiskCacheEditor(QWidget* parent) : QObject(parent) {
|
||||
|
||||
}
|
||||
|
||||
QWindow* DiskCacheEditor::windowHandle() {
|
||||
return (_dialog) ? _dialog->windowHandle() : nullptr;
|
||||
}
|
||||
|
||||
void DiskCacheEditor::toggle() {
|
||||
qDebug() << "DiskCacheEditor::toggle()";
|
||||
if (!_dialog) {
|
||||
makeDialog();
|
||||
}
|
||||
|
||||
if (!_dialog->isActiveWindow()) {
|
||||
_dialog->show();
|
||||
_dialog->raise();
|
||||
_dialog->activateWindow();
|
||||
} else {
|
||||
_dialog->close();
|
||||
}
|
||||
}
|
||||
|
||||
void DiskCacheEditor::makeDialog() {
|
||||
_dialog = new QDialog(static_cast<QWidget*>(parent()));
|
||||
Q_CHECK_PTR(_dialog);
|
||||
_dialog->setAttribute(Qt::WA_DeleteOnClose);
|
||||
_dialog->setWindowTitle("Disk Cache Editor");
|
||||
|
||||
QGridLayout* layout = new QGridLayout(_dialog);
|
||||
Q_CHECK_PTR(layout);
|
||||
_dialog->setLayout(layout);
|
||||
|
||||
|
||||
QLabel* path = new QLabel("Path : ", _dialog);
|
||||
Q_CHECK_PTR(path);
|
||||
path->setAlignment(Qt::AlignRight);
|
||||
layout->addWidget(path, 0, 0);
|
||||
|
||||
QLabel* size = new QLabel("Max Size : ", _dialog);
|
||||
Q_CHECK_PTR(size);
|
||||
size->setAlignment(Qt::AlignRight);
|
||||
layout->addWidget(size, 1, 0);
|
||||
|
||||
QLabel* maxSize = new QLabel("Current Size : ", _dialog);
|
||||
Q_CHECK_PTR(maxSize);
|
||||
maxSize->setAlignment(Qt::AlignRight);
|
||||
layout->addWidget(maxSize, 2, 0);
|
||||
|
||||
|
||||
_path = new QLabel(_dialog);
|
||||
Q_CHECK_PTR(_path);
|
||||
_path->setAlignment(Qt::AlignLeft);
|
||||
layout->addWidget(_path, 0, 1, 1, 3);
|
||||
|
||||
_size = new QLabel(_dialog);
|
||||
Q_CHECK_PTR(_size);
|
||||
_size->setAlignment(Qt::AlignLeft);
|
||||
layout->addWidget(_size, 1, 1, 1, 3);
|
||||
|
||||
_maxSize = new QLabel(_dialog);
|
||||
Q_CHECK_PTR(_maxSize);
|
||||
_maxSize->setAlignment(Qt::AlignLeft);
|
||||
layout->addWidget(_maxSize, 2, 1, 1, 3);
|
||||
|
||||
refresh();
|
||||
|
||||
|
||||
QPushButton* refreshCacheButton = new QPushButton(_dialog);
|
||||
Q_CHECK_PTR(refreshCacheButton);
|
||||
refreshCacheButton->setText("Refresh");
|
||||
refreshCacheButton->setToolTip("Reload the cache stats.");
|
||||
connect(refreshCacheButton, SIGNAL(clicked()), SLOT(refresh()));
|
||||
layout->addWidget(refreshCacheButton, 3, 2);
|
||||
|
||||
QPushButton* clearCacheButton = new QPushButton(_dialog);
|
||||
Q_CHECK_PTR(clearCacheButton);
|
||||
clearCacheButton->setText("Clear");
|
||||
clearCacheButton->setToolTip("Erases the entire content of the disk cache.");
|
||||
connect(clearCacheButton, SIGNAL(clicked()), SLOT(clear()));
|
||||
layout->addWidget(clearCacheButton, 3, 3);
|
||||
}
|
||||
|
||||
void DiskCacheEditor::refresh() {
|
||||
static const std::function<QString(qint64)> stringify = [](qint64 number) {
|
||||
static const QStringList UNITS = QStringList() << "B" << "KB" << "MB" << "GB";
|
||||
static const qint64 CHUNK = 1024;
|
||||
QString unit;
|
||||
int i = 0;
|
||||
for (i = 0; i < 4; ++i) {
|
||||
if (number / CHUNK > 0) {
|
||||
number /= CHUNK;
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
return QString("%0 %1").arg(number).arg(UNITS[i]);
|
||||
};
|
||||
QNetworkDiskCache* cache = qobject_cast<QNetworkDiskCache*>(NetworkAccessManager::getInstance().cache());
|
||||
|
||||
if (_path) {
|
||||
_path->setText(cache->cacheDirectory());
|
||||
}
|
||||
if (_size) {
|
||||
_size->setText(stringify(cache->cacheSize()));
|
||||
}
|
||||
if (_maxSize) {
|
||||
_maxSize->setText(stringify(cache->maximumCacheSize()));
|
||||
}
|
||||
}
|
||||
|
||||
void DiskCacheEditor::clear() {
|
||||
QMessageBox::StandardButton buttonClicked =
|
||||
QMessageBox::question(_dialog, "Clearing disk cache",
|
||||
"You are about to erase all the content of the disk cache,"
|
||||
"are you sure you want to do that?");
|
||||
if (buttonClicked == QMessageBox::Yes) {
|
||||
QNetworkDiskCache* cache = qobject_cast<QNetworkDiskCache*>(NetworkAccessManager::getInstance().cache());
|
||||
if (cache) {
|
||||
qDebug() << "DiskCacheEditor::clear(): Clearing disk cache.";
|
||||
cache->clear();
|
||||
}
|
||||
}
|
||||
refresh();
|
||||
}
|
46
interface/src/ui/DiskCacheEditor.h
Normal file
46
interface/src/ui/DiskCacheEditor.h
Normal file
|
@ -0,0 +1,46 @@
|
|||
//
|
||||
// DiskCacheEditor.h
|
||||
//
|
||||
//
|
||||
// Created by Clement on 3/4/15.
|
||||
// Copyright 2015 High Fidelity, Inc.
|
||||
//
|
||||
// Distributed under the Apache License, Version 2.0.
|
||||
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
|
||||
//
|
||||
|
||||
#ifndef hifi_DiskCacheEditor_h
|
||||
#define hifi_DiskCacheEditor_h
|
||||
|
||||
#include <QObject>
|
||||
#include <QPointer>
|
||||
|
||||
class QDialog;
|
||||
class QLabel;
|
||||
class QWindow;
|
||||
|
||||
class DiskCacheEditor : public QObject {
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
DiskCacheEditor(QWidget* parent = nullptr);
|
||||
|
||||
QWindow* windowHandle();
|
||||
|
||||
public slots:
|
||||
void toggle();
|
||||
|
||||
private slots:
|
||||
void refresh();
|
||||
void clear();
|
||||
|
||||
private:
|
||||
void makeDialog();
|
||||
|
||||
QPointer<QDialog> _dialog;
|
||||
QPointer<QLabel> _path;
|
||||
QPointer<QLabel> _size;
|
||||
QPointer<QLabel> _maxSize;
|
||||
};
|
||||
|
||||
#endif // hifi_DiskCacheEditor_h
|
|
@ -32,6 +32,7 @@
|
|||
#include "RenderableModelEntityItem.h"
|
||||
#include "RenderableSphereEntityItem.h"
|
||||
#include "RenderableTextEntityItem.h"
|
||||
#include "RenderableParticleEffectEntityItem.h"
|
||||
|
||||
|
||||
EntityTreeRenderer::EntityTreeRenderer(bool wantScripts, AbstractViewStateInterface* viewState,
|
||||
|
@ -53,6 +54,7 @@ EntityTreeRenderer::EntityTreeRenderer(bool wantScripts, AbstractViewStateInterf
|
|||
REGISTER_ENTITY_TYPE_WITH_FACTORY(Sphere, RenderableSphereEntityItem::factory)
|
||||
REGISTER_ENTITY_TYPE_WITH_FACTORY(Light, RenderableLightEntityItem::factory)
|
||||
REGISTER_ENTITY_TYPE_WITH_FACTORY(Text, RenderableTextEntityItem::factory)
|
||||
REGISTER_ENTITY_TYPE_WITH_FACTORY(ParticleEffect, RenderableParticleEffectEntityItem::factory)
|
||||
|
||||
_currentHoverOverEntityID = EntityItemID::createInvalidEntityID(); // makes it the unknown ID
|
||||
_currentClickingOnEntityID = EntityItemID::createInvalidEntityID(); // makes it the unknown ID
|
||||
|
|
|
@ -0,0 +1,92 @@
|
|||
//
|
||||
// RenderableParticleEffectEntityItem.cpp
|
||||
// interface/src
|
||||
//
|
||||
// Created by Jason Rickwald on 3/2/15.
|
||||
//
|
||||
// Distributed under the Apache License, Version 2.0.
|
||||
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
|
||||
//
|
||||
|
||||
#include <glm/gtx/quaternion.hpp>
|
||||
|
||||
#include <gpu/GPUConfig.h>
|
||||
|
||||
#include <DependencyManager.h>
|
||||
#include <DeferredLightingEffect.h>
|
||||
#include <PerfStat.h>
|
||||
#include <GeometryCache.h>
|
||||
|
||||
#include "RenderableParticleEffectEntityItem.h"
|
||||
|
||||
EntityItem* RenderableParticleEffectEntityItem::factory(const EntityItemID& entityID, const EntityItemProperties& properties) {
|
||||
return new RenderableParticleEffectEntityItem(entityID, properties);
|
||||
}
|
||||
|
||||
RenderableParticleEffectEntityItem::RenderableParticleEffectEntityItem(const EntityItemID& entityItemID, const EntityItemProperties& properties) :
|
||||
ParticleEffectEntityItem(entityItemID, properties) {
|
||||
_cacheID = DependencyManager::get<GeometryCache>()->allocateID();
|
||||
}
|
||||
|
||||
void RenderableParticleEffectEntityItem::render(RenderArgs* args) {
|
||||
PerformanceTimer perfTimer("RenderableParticleEffectEntityItem::render");
|
||||
assert(getType() == EntityTypes::ParticleEffect);
|
||||
glm::vec3 position = getPositionInMeters();
|
||||
glm::vec3 center = getCenterInMeters();
|
||||
glm::quat rotation = getRotation();
|
||||
float pa_rad = getParticleRadius();
|
||||
|
||||
const float MAX_COLOR = 255.0f;
|
||||
glm::vec4 paColor(getColor()[RED_INDEX] / MAX_COLOR, getColor()[GREEN_INDEX] / MAX_COLOR,
|
||||
getColor()[BLUE_INDEX] / MAX_COLOR, getLocalRenderAlpha());
|
||||
|
||||
// Right now we're just iterating over particles and rendering as a cross of four quads.
|
||||
// This is pretty dumb, it was quick enough to code up. Really, there should be many
|
||||
// rendering modes, including the all-important textured billboards.
|
||||
|
||||
QVector<glm::vec3>* pointVec = new QVector<glm::vec3>(_paCount * VERTS_PER_PARTICLE);
|
||||
quint32 paIter = _paHead;
|
||||
while (_paLife[paIter] > 0.0f) {
|
||||
int j = paIter * XYZ_STRIDE;
|
||||
|
||||
pointVec->append(glm::vec3(_paPosition[j] - pa_rad, _paPosition[j + 1] + pa_rad, _paPosition[j + 2]));
|
||||
pointVec->append(glm::vec3(_paPosition[j] + pa_rad, _paPosition[j + 1] + pa_rad, _paPosition[j + 2]));
|
||||
pointVec->append(glm::vec3(_paPosition[j] + pa_rad, _paPosition[j + 1] - pa_rad, _paPosition[j + 2]));
|
||||
pointVec->append(glm::vec3(_paPosition[j] - pa_rad, _paPosition[j + 1] - pa_rad, _paPosition[j + 2]));
|
||||
|
||||
pointVec->append(glm::vec3(_paPosition[j] + pa_rad, _paPosition[j + 1] + pa_rad, _paPosition[j + 2]));
|
||||
pointVec->append(glm::vec3(_paPosition[j] - pa_rad, _paPosition[j + 1] + pa_rad, _paPosition[j + 2]));
|
||||
pointVec->append(glm::vec3(_paPosition[j] - pa_rad, _paPosition[j + 1] - pa_rad, _paPosition[j + 2]));
|
||||
pointVec->append(glm::vec3(_paPosition[j] + pa_rad, _paPosition[j + 1] - pa_rad, _paPosition[j + 2]));
|
||||
|
||||
pointVec->append(glm::vec3(_paPosition[j], _paPosition[j + 1] + pa_rad, _paPosition[j + 2] - pa_rad));
|
||||
pointVec->append(glm::vec3(_paPosition[j], _paPosition[j + 1] + pa_rad, _paPosition[j + 2] + pa_rad));
|
||||
pointVec->append(glm::vec3(_paPosition[j], _paPosition[j + 1] - pa_rad, _paPosition[j + 2] + pa_rad));
|
||||
pointVec->append(glm::vec3(_paPosition[j], _paPosition[j + 1] - pa_rad, _paPosition[j + 2] - pa_rad));
|
||||
|
||||
pointVec->append(glm::vec3(_paPosition[j], _paPosition[j + 1] + pa_rad, _paPosition[j + 2] + pa_rad));
|
||||
pointVec->append(glm::vec3(_paPosition[j], _paPosition[j + 1] + pa_rad, _paPosition[j + 2] - pa_rad));
|
||||
pointVec->append(glm::vec3(_paPosition[j], _paPosition[j + 1] - pa_rad, _paPosition[j + 2] - pa_rad));
|
||||
pointVec->append(glm::vec3(_paPosition[j], _paPosition[j + 1] - pa_rad, _paPosition[j + 2] + pa_rad));
|
||||
|
||||
paIter = (paIter + 1) % _maxParticles;
|
||||
}
|
||||
|
||||
DependencyManager::get<GeometryCache>()->updateVertices(_cacheID, *pointVec, paColor);
|
||||
|
||||
glPushMatrix();
|
||||
glTranslatef(position.x, position.y, position.z);
|
||||
glm::vec3 axis = glm::axis(rotation);
|
||||
glRotatef(glm::degrees(glm::angle(rotation)), axis.x, axis.y, axis.z);
|
||||
|
||||
|
||||
glPushMatrix();
|
||||
glm::vec3 positionToCenter = center - position;
|
||||
glTranslatef(positionToCenter.x, positionToCenter.y, positionToCenter.z);
|
||||
|
||||
DependencyManager::get<GeometryCache>()->renderVertices(gpu::QUADS, _cacheID);
|
||||
glPopMatrix();
|
||||
glPopMatrix();
|
||||
|
||||
delete pointVec;
|
||||
};
|
|
@ -0,0 +1,28 @@
|
|||
//
|
||||
// RenderableParticleEffectEntityItem.h
|
||||
// interface/src/entities
|
||||
//
|
||||
// Created by Jason Rickwald on 3/2/15.
|
||||
//
|
||||
// Distributed under the Apache License, Version 2.0.
|
||||
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
|
||||
//
|
||||
|
||||
#ifndef hifi_RenderableParticleEffectEntityItem_h
|
||||
#define hifi_RenderableParticleEffectEntityItem_h
|
||||
|
||||
#include <ParticleEffectEntityItem.h>
|
||||
|
||||
class RenderableParticleEffectEntityItem : public ParticleEffectEntityItem {
|
||||
public:
|
||||
static EntityItem* factory(const EntityItemID& entityID, const EntityItemProperties& properties);
|
||||
RenderableParticleEffectEntityItem(const EntityItemID& entityItemID, const EntityItemProperties& properties);
|
||||
virtual void render(RenderArgs* args);
|
||||
|
||||
protected:
|
||||
int _cacheID;
|
||||
const int VERTS_PER_PARTICLE = 16;
|
||||
};
|
||||
|
||||
|
||||
#endif // hifi_RenderableParticleEffectEntityItem_h
|
|
@ -23,6 +23,7 @@
|
|||
#include "EntityItemPropertiesDefaults.h"
|
||||
#include "ModelEntityItem.h"
|
||||
#include "TextEntityItem.h"
|
||||
#include "ParticleEffectEntityItem.h"
|
||||
|
||||
|
||||
EntityItemProperties::EntityItemProperties() :
|
||||
|
@ -61,6 +62,13 @@ EntityItemProperties::EntityItemProperties() :
|
|||
CONSTRUCT_PROPERTY(textColor, TextEntityItem::DEFAULT_TEXT_COLOR),
|
||||
CONSTRUCT_PROPERTY(backgroundColor, TextEntityItem::DEFAULT_BACKGROUND_COLOR),
|
||||
CONSTRUCT_PROPERTY(shapeType, SHAPE_TYPE_NONE),
|
||||
CONSTRUCT_PROPERTY(maxParticles, ParticleEffectEntityItem::DEFAULT_MAX_PARTICLES),
|
||||
CONSTRUCT_PROPERTY(lifespan, ParticleEffectEntityItem::DEFAULT_LIFESPAN),
|
||||
CONSTRUCT_PROPERTY(emitRate, ParticleEffectEntityItem::DEFAULT_EMIT_RATE),
|
||||
CONSTRUCT_PROPERTY(emitDirection, ParticleEffectEntityItem::DEFAULT_EMIT_DIRECTION),
|
||||
CONSTRUCT_PROPERTY(emitStrength, ParticleEffectEntityItem::DEFAULT_EMIT_STRENGTH),
|
||||
CONSTRUCT_PROPERTY(localGravity, ParticleEffectEntityItem::DEFAULT_LOCAL_GRAVITY),
|
||||
CONSTRUCT_PROPERTY(particleRadius, ParticleEffectEntityItem::DEFAULT_PARTICLE_RADIUS),
|
||||
|
||||
_id(UNKNOWN_ENTITY_ID),
|
||||
_idSet(false),
|
||||
|
@ -228,6 +236,13 @@ EntityPropertyFlags EntityItemProperties::getChangedProperties() const {
|
|||
CHECK_PROPERTY_CHANGE(PROP_TEXT_COLOR, textColor);
|
||||
CHECK_PROPERTY_CHANGE(PROP_BACKGROUND_COLOR, backgroundColor);
|
||||
CHECK_PROPERTY_CHANGE(PROP_SHAPE_TYPE, shapeType);
|
||||
CHECK_PROPERTY_CHANGE(PROP_MAX_PARTICLES, maxParticles);
|
||||
CHECK_PROPERTY_CHANGE(PROP_LIFESPAN, lifespan);
|
||||
CHECK_PROPERTY_CHANGE(PROP_EMIT_RATE, emitRate);
|
||||
CHECK_PROPERTY_CHANGE(PROP_EMIT_DIRECTION, emitDirection);
|
||||
CHECK_PROPERTY_CHANGE(PROP_EMIT_STRENGTH, emitStrength);
|
||||
CHECK_PROPERTY_CHANGE(PROP_LOCAL_GRAVITY, localGravity);
|
||||
CHECK_PROPERTY_CHANGE(PROP_PARTICLE_RADIUS, particleRadius);
|
||||
|
||||
return changedProperties;
|
||||
}
|
||||
|
@ -282,6 +297,13 @@ QScriptValue EntityItemProperties::copyToScriptValue(QScriptEngine* engine) cons
|
|||
COPY_PROPERTY_TO_QSCRIPTVALUE_COLOR_GETTER(textColor, getTextColor());
|
||||
COPY_PROPERTY_TO_QSCRIPTVALUE_COLOR_GETTER(backgroundColor, getBackgroundColor());
|
||||
COPY_PROPERTY_TO_QSCRIPTVALUE_GETTER(shapeType, getShapeTypeAsString());
|
||||
COPY_PROPERTY_TO_QSCRIPTVALUE(maxParticles);
|
||||
COPY_PROPERTY_TO_QSCRIPTVALUE(lifespan);
|
||||
COPY_PROPERTY_TO_QSCRIPTVALUE(emitRate);
|
||||
COPY_PROPERTY_TO_QSCRIPTVALUE_VEC3(emitDirection);
|
||||
COPY_PROPERTY_TO_QSCRIPTVALUE(emitStrength);
|
||||
COPY_PROPERTY_TO_QSCRIPTVALUE(localGravity);
|
||||
COPY_PROPERTY_TO_QSCRIPTVALUE(particleRadius);
|
||||
|
||||
// Sitting properties support
|
||||
QScriptValue sittingPoints = engine->newObject();
|
||||
|
@ -355,6 +377,13 @@ void EntityItemProperties::copyFromScriptValue(const QScriptValue& object) {
|
|||
COPY_PROPERTY_FROM_QSCRIPTVALUE_COLOR(textColor, setTextColor);
|
||||
COPY_PROPERTY_FROM_QSCRIPTVALUE_COLOR(backgroundColor, setBackgroundColor);
|
||||
COPY_PROPERTY_FROM_QSCRITPTVALUE_ENUM(shapeType, ShapeType);
|
||||
COPY_PROPERTY_FROM_QSCRIPTVALUE_FLOAT(maxParticles, setMaxParticles);
|
||||
COPY_PROPERTY_FROM_QSCRIPTVALUE_FLOAT(lifespan, setLifespan);
|
||||
COPY_PROPERTY_FROM_QSCRIPTVALUE_FLOAT(emitRate, setEmitRate);
|
||||
COPY_PROPERTY_FROM_QSCRIPTVALUE_VEC3(emitDirection, setEmitDirection);
|
||||
COPY_PROPERTY_FROM_QSCRIPTVALUE_FLOAT(emitStrength, setEmitStrength);
|
||||
COPY_PROPERTY_FROM_QSCRIPTVALUE_FLOAT(localGravity, setLocalGravity);
|
||||
COPY_PROPERTY_FROM_QSCRIPTVALUE_FLOAT(particleRadius, setParticleRadius);
|
||||
|
||||
_lastEdited = usecTimestampNow();
|
||||
}
|
||||
|
@ -528,6 +557,16 @@ bool EntityItemProperties::encodeEntityEditPacket(PacketType command, EntityItem
|
|||
APPEND_ENTITY_PROPERTY(PROP_EXPONENT, appendValue, properties.getExponent());
|
||||
APPEND_ENTITY_PROPERTY(PROP_CUTOFF, appendValue, properties.getCutoff());
|
||||
}
|
||||
|
||||
if (properties.getType() == EntityTypes::ParticleEffect) {
|
||||
APPEND_ENTITY_PROPERTY(PROP_MAX_PARTICLES, appendValue, properties.getMaxParticles());
|
||||
APPEND_ENTITY_PROPERTY(PROP_LIFESPAN, appendValue, properties.getLifespan());
|
||||
APPEND_ENTITY_PROPERTY(PROP_EMIT_RATE, appendValue, properties.getEmitRate());
|
||||
APPEND_ENTITY_PROPERTY(PROP_EMIT_DIRECTION, appendValue, properties.getEmitDirection());
|
||||
APPEND_ENTITY_PROPERTY(PROP_EMIT_STRENGTH, appendValue, properties.getEmitStrength());
|
||||
APPEND_ENTITY_PROPERTY(PROP_LOCAL_GRAVITY, appendValue, properties.getLocalGravity());
|
||||
APPEND_ENTITY_PROPERTY(PROP_PARTICLE_RADIUS, appendValue, properties.getParticleRadius());
|
||||
}
|
||||
}
|
||||
if (propertyCount > 0) {
|
||||
int endOfEntityItemData = packetData->getUncompressedByteOffset();
|
||||
|
@ -746,6 +785,16 @@ bool EntityItemProperties::decodeEntityEditPacket(const unsigned char* data, int
|
|||
READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_EXPONENT, float, setExponent);
|
||||
READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_CUTOFF, float, setCutoff);
|
||||
}
|
||||
|
||||
if (properties.getType() == EntityTypes::ParticleEffect) {
|
||||
READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_MAX_PARTICLES, float, setMaxParticles);
|
||||
READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_LIFESPAN, float, setLifespan);
|
||||
READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_EMIT_RATE, float, setEmitRate);
|
||||
READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_EMIT_DIRECTION, glm::vec3, setEmitDirection);
|
||||
READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_EMIT_STRENGTH, float, setEmitStrength);
|
||||
READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_LOCAL_GRAVITY, float, setLocalGravity);
|
||||
READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_PARTICLE_RADIUS, float, setParticleRadius);
|
||||
}
|
||||
|
||||
return valid;
|
||||
}
|
||||
|
@ -818,6 +867,14 @@ void EntityItemProperties::markAllChanged() {
|
|||
_textColorChanged = true;
|
||||
_backgroundColorChanged = true;
|
||||
_shapeTypeChanged = true;
|
||||
|
||||
_maxParticlesChanged = true;
|
||||
_lifespanChanged = true;
|
||||
_emitRateChanged = true;
|
||||
_emitDirectionChanged = true;
|
||||
_emitStrengthChanged = true;
|
||||
_localGravityChanged = true;
|
||||
_particleRadiusChanged = true;
|
||||
}
|
||||
|
||||
/// The maximum bounding cube for the entity, independent of it's rotation.
|
||||
|
|
|
@ -83,9 +83,18 @@ enum EntityPropertyList {
|
|||
PROP_ANIMATION_SETTINGS,
|
||||
PROP_USER_DATA,
|
||||
PROP_SHAPE_TYPE,
|
||||
|
||||
// used by ParticleEffect entities
|
||||
PROP_MAX_PARTICLES,
|
||||
PROP_LIFESPAN,
|
||||
PROP_EMIT_RATE,
|
||||
PROP_EMIT_DIRECTION,
|
||||
PROP_EMIT_STRENGTH,
|
||||
PROP_LOCAL_GRAVITY,
|
||||
PROP_PARTICLE_RADIUS,
|
||||
|
||||
// NOTE: add new properties ABOVE this line and then modify PROP_LAST_ITEM below
|
||||
PROP_LAST_ITEM = PROP_SHAPE_TYPE,
|
||||
PROP_LAST_ITEM = PROP_PARTICLE_RADIUS,
|
||||
|
||||
// These properties of TextEntity piggy back off of properties of ModelEntities, the type doesn't matter
|
||||
// since the derived class knows how to interpret it's own properties and knows the types it expects
|
||||
|
@ -110,6 +119,7 @@ class EntityItemProperties {
|
|||
friend class SphereEntityItem; // TODO: consider removing this friend relationship and use public methods
|
||||
friend class LightEntityItem; // TODO: consider removing this friend relationship and use public methods
|
||||
friend class TextEntityItem; // TODO: consider removing this friend relationship and use public methods
|
||||
friend class ParticleEffectEntityItem; // TODO: consider removing this friend relationship and use public methods
|
||||
public:
|
||||
EntityItemProperties();
|
||||
virtual ~EntityItemProperties();
|
||||
|
@ -176,6 +186,13 @@ public:
|
|||
DEFINE_PROPERTY_REF(PROP_TEXT_COLOR, TextColor, textColor, xColor);
|
||||
DEFINE_PROPERTY_REF(PROP_BACKGROUND_COLOR, BackgroundColor, backgroundColor, xColor);
|
||||
DEFINE_PROPERTY_REF_ENUM(PROP_SHAPE_TYPE, ShapeType, shapeType, ShapeType);
|
||||
DEFINE_PROPERTY(PROP_MAX_PARTICLES, MaxParticles, maxParticles, quint32);
|
||||
DEFINE_PROPERTY(PROP_LIFESPAN, Lifespan, lifespan, float);
|
||||
DEFINE_PROPERTY(PROP_EMIT_RATE, EmitRate, emitRate, float);
|
||||
DEFINE_PROPERTY_REF(PROP_EMIT_DIRECTION, EmitDirection, emitDirection, glm::vec3);
|
||||
DEFINE_PROPERTY(PROP_EMIT_STRENGTH, EmitStrength, emitStrength, float);
|
||||
DEFINE_PROPERTY(PROP_LOCAL_GRAVITY, LocalGravity, localGravity, float);
|
||||
DEFINE_PROPERTY(PROP_PARTICLE_RADIUS, ParticleRadius, particleRadius, float);
|
||||
|
||||
public:
|
||||
float getMaxDimension() const { return glm::max(_dimensions.x, _dimensions.y, _dimensions.z); }
|
||||
|
@ -295,6 +312,13 @@ inline QDebug operator<<(QDebug debug, const EntityItemProperties& properties) {
|
|||
DEBUG_PROPERTY_IF_CHANGED(debug, properties, TextColor, textColor, "");
|
||||
DEBUG_PROPERTY_IF_CHANGED(debug, properties, BackgroundColor, backgroundColor, "");
|
||||
DEBUG_PROPERTY_IF_CHANGED(debug, properties, ShapeType, shapeType, "");
|
||||
DEBUG_PROPERTY_IF_CHANGED(debug, properties, MaxParticles, maxParticles, "");
|
||||
DEBUG_PROPERTY_IF_CHANGED(debug, properties, Lifespan, lifespan, "");
|
||||
DEBUG_PROPERTY_IF_CHANGED(debug, properties, EmitRate, emitRate, "");
|
||||
DEBUG_PROPERTY_IF_CHANGED(debug, properties, EmitDirection, emitDirection, "");
|
||||
DEBUG_PROPERTY_IF_CHANGED(debug, properties, EmitStrength, emitStrength, "");
|
||||
DEBUG_PROPERTY_IF_CHANGED(debug, properties, LocalGravity, localGravity, "");
|
||||
DEBUG_PROPERTY_IF_CHANGED(debug, properties, ParticleRadius, particleRadius, "");
|
||||
|
||||
debug << " last edited:" << properties.getLastEdited() << "\n";
|
||||
debug << " edited ago:" << properties.getEditedAgo() << "\n";
|
||||
|
|
|
@ -23,6 +23,7 @@
|
|||
#include "ModelEntityItem.h"
|
||||
#include "SphereEntityItem.h"
|
||||
#include "TextEntityItem.h"
|
||||
#include "ParticleEffectEntityItem.h"
|
||||
|
||||
QMap<EntityTypes::EntityType, QString> EntityTypes::_typeToNameMap;
|
||||
QMap<QString, EntityTypes::EntityType> EntityTypes::_nameToTypeMap;
|
||||
|
@ -37,6 +38,7 @@ REGISTER_ENTITY_TYPE(Box)
|
|||
REGISTER_ENTITY_TYPE(Sphere)
|
||||
REGISTER_ENTITY_TYPE(Light)
|
||||
REGISTER_ENTITY_TYPE(Text)
|
||||
REGISTER_ENTITY_TYPE(ParticleEffect)
|
||||
|
||||
|
||||
const QString& EntityTypes::getEntityTypeName(EntityType entityType) {
|
||||
|
|
|
@ -35,7 +35,8 @@ public:
|
|||
Sphere,
|
||||
Light,
|
||||
Text,
|
||||
LAST = Text
|
||||
ParticleEffect,
|
||||
LAST = ParticleEffect
|
||||
} EntityType;
|
||||
|
||||
static const QString& getEntityTypeName(EntityType entityType);
|
||||
|
|
512
libraries/entities/src/ParticleEffectEntityItem.cpp
Normal file
512
libraries/entities/src/ParticleEffectEntityItem.cpp
Normal file
|
@ -0,0 +1,512 @@
|
|||
//
|
||||
// ParticleEffectEntityItem.cpp
|
||||
// libraries/entities/src
|
||||
//
|
||||
// Some starter code for a particle simulation entity, which could ideally be used for a variety of effects.
|
||||
// This is some really early and rough stuff here. It was enough for now to just get it up and running in the interface.
|
||||
//
|
||||
// Todo's and other notes:
|
||||
// - The simulation should restart when the AnimationLoop's max frame is reached (or passed), but there doesn't seem
|
||||
// to be a good way to set that max frame to something reasonable right now.
|
||||
// - There seems to be a bug whereby entities on the edge of screen will just pop off or on. This is probably due
|
||||
// to my lack of understanding of how entities in the octree are picked for rendering. I am updating the entity
|
||||
// dimensions based on the bounds of the sim, but maybe I need to update a dirty flag or something.
|
||||
// - This should support some kind of pre-roll of the simulation.
|
||||
// - Just to get this out the door, I just did forward Euler integration. There are better ways.
|
||||
// - Gravity always points along the Y axis. Support an actual gravity vector.
|
||||
// - Add the ability to add arbitrary forces to the simulation.
|
||||
// - Add controls for spread (which is currently hard-coded) and varying emission strength (not currently implemented).
|
||||
// - Add drag.
|
||||
// - Add some kind of support for collisions.
|
||||
// - For simplicity, I'm currently just rendering each particle as a cross of four axis-aligned quads. Really, we'd
|
||||
// want multiple render modes, including (the most important) textured billboards (always facing camera). Also, these
|
||||
// should support animated textures.
|
||||
// - There's no synchronization of the simulation across clients at all. In fact, it's using rand() under the hood, so
|
||||
// there's no gaurantee that different clients will see simulations that look anything like the other.
|
||||
// - MORE?
|
||||
//
|
||||
// Created by Jason Rickwald on 3/2/15.
|
||||
//
|
||||
// Distributed under the Apache License, Version 2.0.
|
||||
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
|
||||
//
|
||||
|
||||
|
||||
#include <glm/gtx/transform.hpp>
|
||||
#include <QtCore/QJsonDocument>
|
||||
|
||||
#include <QDebug>
|
||||
|
||||
#include <ByteCountCoding.h>
|
||||
#include <GeometryUtil.h>
|
||||
|
||||
#include "EntityTree.h"
|
||||
#include "EntityTreeElement.h"
|
||||
#include "ParticleEffectEntityItem.h"
|
||||
|
||||
const float ParticleEffectEntityItem::DEFAULT_ANIMATION_FRAME_INDEX = 0.0f;
|
||||
const bool ParticleEffectEntityItem::DEFAULT_ANIMATION_IS_PLAYING = false;
|
||||
const float ParticleEffectEntityItem::DEFAULT_ANIMATION_FPS = 30.0f;
|
||||
const quint32 ParticleEffectEntityItem::DEFAULT_MAX_PARTICLES = 1000;
|
||||
const float ParticleEffectEntityItem::DEFAULT_LIFESPAN = 3.0f;
|
||||
const float ParticleEffectEntityItem::DEFAULT_EMIT_RATE = 15.0f;
|
||||
const glm::vec3 ParticleEffectEntityItem::DEFAULT_EMIT_DIRECTION(0.0f, 1.0f, 0.0f);
|
||||
const float ParticleEffectEntityItem::DEFAULT_EMIT_STRENGTH = 25.0f;
|
||||
const float ParticleEffectEntityItem::DEFAULT_LOCAL_GRAVITY = -9.8f;
|
||||
const float ParticleEffectEntityItem::DEFAULT_PARTICLE_RADIUS = 0.025f;
|
||||
|
||||
|
||||
EntityItem* ParticleEffectEntityItem::factory(const EntityItemID& entityID, const EntityItemProperties& properties) {
|
||||
return new ParticleEffectEntityItem(entityID, properties);
|
||||
}
|
||||
|
||||
// our non-pure virtual subclass for now...
|
||||
ParticleEffectEntityItem::ParticleEffectEntityItem(const EntityItemID& entityItemID, const EntityItemProperties& properties) :
|
||||
EntityItem(entityItemID, properties) {
|
||||
_type = EntityTypes::ParticleEffect;
|
||||
_maxParticles = DEFAULT_MAX_PARTICLES;
|
||||
_lifespan = DEFAULT_LIFESPAN;
|
||||
_emitRate = DEFAULT_EMIT_RATE;
|
||||
_emitDirection = DEFAULT_EMIT_DIRECTION;
|
||||
_emitStrength = DEFAULT_EMIT_STRENGTH;
|
||||
_localGravity = DEFAULT_LOCAL_GRAVITY;
|
||||
_particleRadius = DEFAULT_PARTICLE_RADIUS;
|
||||
setProperties(properties);
|
||||
// this is a pretty dumb thing to do, and it should probably be changed to use a more dynamic
|
||||
// data structure in the future. I'm just trying to get some code out the door for now (and it's
|
||||
// at least time efficient (though not space efficient).
|
||||
// Also, this being a real-time application, it's doubtful we'll ever have millions of particles
|
||||
// to keep track of, so this really isn't all that bad.
|
||||
_paLife = new float[_maxParticles];
|
||||
_paPosition = new float[_maxParticles * XYZ_STRIDE]; // x,y,z
|
||||
_paVelocity = new float[_maxParticles * XYZ_STRIDE]; // x,y,z
|
||||
_paXmax = _paYmax = _paZmax = 1.0f;
|
||||
_paXmin = _paYmin = _paZmin = -1.0f;
|
||||
_randSeed = (unsigned int) glm::abs(_lifespan + _emitRate + _localGravity + getPosition().x + getPosition().y + getPosition().z);
|
||||
resetSimulation();
|
||||
_lastAnimated = usecTimestampNow();
|
||||
}
|
||||
|
||||
ParticleEffectEntityItem::~ParticleEffectEntityItem() {
|
||||
delete [] _paLife;
|
||||
delete [] _paPosition;
|
||||
delete [] _paVelocity;
|
||||
}
|
||||
|
||||
EntityItemProperties ParticleEffectEntityItem::getProperties() const {
|
||||
EntityItemProperties properties = EntityItem::getProperties(); // get the properties from our base class
|
||||
|
||||
COPY_ENTITY_PROPERTY_TO_PROPERTIES(color, getXColor);
|
||||
COPY_ENTITY_PROPERTY_TO_PROPERTIES(animationIsPlaying, getAnimationIsPlaying);
|
||||
COPY_ENTITY_PROPERTY_TO_PROPERTIES(animationFrameIndex, getAnimationFrameIndex);
|
||||
COPY_ENTITY_PROPERTY_TO_PROPERTIES(animationFPS, getAnimationFPS);
|
||||
COPY_ENTITY_PROPERTY_TO_PROPERTIES(glowLevel, getGlowLevel);
|
||||
COPY_ENTITY_PROPERTY_TO_PROPERTIES(animationSettings, getAnimationSettings);
|
||||
COPY_ENTITY_PROPERTY_TO_PROPERTIES(shapeType, getShapeType);
|
||||
COPY_ENTITY_PROPERTY_TO_PROPERTIES(maxParticles, getMaxParticles);
|
||||
COPY_ENTITY_PROPERTY_TO_PROPERTIES(lifespan, getLifespan);
|
||||
COPY_ENTITY_PROPERTY_TO_PROPERTIES(emitRate, getEmitRate);
|
||||
COPY_ENTITY_PROPERTY_TO_PROPERTIES(emitDirection, getEmitDirection);
|
||||
COPY_ENTITY_PROPERTY_TO_PROPERTIES(emitStrength, getEmitStrength);
|
||||
COPY_ENTITY_PROPERTY_TO_PROPERTIES(localGravity, getLocalGravity);
|
||||
COPY_ENTITY_PROPERTY_TO_PROPERTIES(particleRadius, getParticleRadius);
|
||||
|
||||
return properties;
|
||||
}
|
||||
|
||||
bool ParticleEffectEntityItem::setProperties(const EntityItemProperties& properties) {
|
||||
bool somethingChanged = EntityItem::setProperties(properties); // set the properties in our base class
|
||||
|
||||
SET_ENTITY_PROPERTY_FROM_PROPERTIES(color, setColor);
|
||||
SET_ENTITY_PROPERTY_FROM_PROPERTIES(animationIsPlaying, setAnimationIsPlaying);
|
||||
SET_ENTITY_PROPERTY_FROM_PROPERTIES(animationFrameIndex, setAnimationFrameIndex);
|
||||
SET_ENTITY_PROPERTY_FROM_PROPERTIES(animationFPS, setAnimationFPS);
|
||||
SET_ENTITY_PROPERTY_FROM_PROPERTIES(glowLevel, setGlowLevel);
|
||||
SET_ENTITY_PROPERTY_FROM_PROPERTIES(animationSettings, setAnimationSettings);
|
||||
SET_ENTITY_PROPERTY_FROM_PROPERTIES(shapeType, updateShapeType);
|
||||
SET_ENTITY_PROPERTY_FROM_PROPERTIES(maxParticles, setMaxParticles);
|
||||
SET_ENTITY_PROPERTY_FROM_PROPERTIES(lifespan, setLifespan);
|
||||
SET_ENTITY_PROPERTY_FROM_PROPERTIES(emitRate, setEmitRate);
|
||||
SET_ENTITY_PROPERTY_FROM_PROPERTIES(emitDirection, setEmitDirection);
|
||||
SET_ENTITY_PROPERTY_FROM_PROPERTIES(emitStrength, setEmitStrength);
|
||||
SET_ENTITY_PROPERTY_FROM_PROPERTIES(localGravity, setLocalGravity);
|
||||
SET_ENTITY_PROPERTY_FROM_PROPERTIES(particleRadius, setParticleRadius);
|
||||
|
||||
if (somethingChanged) {
|
||||
bool wantDebug = false;
|
||||
if (wantDebug) {
|
||||
uint64_t now = usecTimestampNow();
|
||||
int elapsed = now - getLastEdited();
|
||||
qDebug() << "ParticleEffectEntityItem::setProperties() AFTER update... edited AGO=" << elapsed <<
|
||||
"now=" << now << " getLastEdited()=" << getLastEdited();
|
||||
}
|
||||
setLastEdited(properties.getLastEdited());
|
||||
}
|
||||
return somethingChanged;
|
||||
}
|
||||
|
||||
int ParticleEffectEntityItem::readEntitySubclassDataFromBuffer(const unsigned char* data, int bytesLeftToRead,
|
||||
ReadBitstreamToTreeParams& args,
|
||||
EntityPropertyFlags& propertyFlags, bool overwriteLocalData) {
|
||||
|
||||
int bytesRead = 0;
|
||||
const unsigned char* dataAt = data;
|
||||
|
||||
READ_ENTITY_PROPERTY_COLOR(PROP_COLOR, _color);
|
||||
|
||||
// Because we're using AnimationLoop which will reset the frame index if you change it's running state
|
||||
// we want to read these values in the order they appear in the buffer, but call our setters in an
|
||||
// order that allows AnimationLoop to preserve the correct frame rate.
|
||||
float animationFPS = getAnimationFPS();
|
||||
float animationFrameIndex = getAnimationFrameIndex();
|
||||
bool animationIsPlaying = getAnimationIsPlaying();
|
||||
READ_ENTITY_PROPERTY(PROP_ANIMATION_FPS, float, animationFPS);
|
||||
READ_ENTITY_PROPERTY(PROP_ANIMATION_FRAME_INDEX, float, animationFrameIndex);
|
||||
READ_ENTITY_PROPERTY(PROP_ANIMATION_PLAYING, bool, animationIsPlaying);
|
||||
|
||||
if (propertyFlags.getHasProperty(PROP_ANIMATION_PLAYING)) {
|
||||
if (animationIsPlaying != getAnimationIsPlaying()) {
|
||||
setAnimationIsPlaying(animationIsPlaying);
|
||||
}
|
||||
}
|
||||
if (propertyFlags.getHasProperty(PROP_ANIMATION_FPS)) {
|
||||
setAnimationFPS(animationFPS);
|
||||
}
|
||||
if (propertyFlags.getHasProperty(PROP_ANIMATION_FRAME_INDEX)) {
|
||||
setAnimationFrameIndex(animationFrameIndex);
|
||||
}
|
||||
|
||||
READ_ENTITY_PROPERTY_STRING(PROP_ANIMATION_SETTINGS, setAnimationSettings);
|
||||
READ_ENTITY_PROPERTY_SETTER(PROP_SHAPE_TYPE, ShapeType, updateShapeType);
|
||||
READ_ENTITY_PROPERTY(PROP_MAX_PARTICLES, quint32, _maxParticles);
|
||||
READ_ENTITY_PROPERTY(PROP_LIFESPAN, float, _lifespan);
|
||||
READ_ENTITY_PROPERTY(PROP_EMIT_RATE, float, _emitRate);
|
||||
READ_ENTITY_PROPERTY_SETTER(PROP_EMIT_DIRECTION, glm::vec3, setEmitDirection);
|
||||
READ_ENTITY_PROPERTY(PROP_EMIT_STRENGTH, float, _emitStrength);
|
||||
READ_ENTITY_PROPERTY(PROP_LOCAL_GRAVITY, float, _localGravity);
|
||||
READ_ENTITY_PROPERTY(PROP_PARTICLE_RADIUS, float, _particleRadius);
|
||||
|
||||
return bytesRead;
|
||||
}
|
||||
|
||||
|
||||
// TODO: eventually only include properties changed since the params.lastViewFrustumSent time
|
||||
EntityPropertyFlags ParticleEffectEntityItem::getEntityProperties(EncodeBitstreamParams& params) const {
|
||||
EntityPropertyFlags requestedProperties = EntityItem::getEntityProperties(params);
|
||||
|
||||
requestedProperties += PROP_COLOR;
|
||||
requestedProperties += PROP_ANIMATION_FPS;
|
||||
requestedProperties += PROP_ANIMATION_FRAME_INDEX;
|
||||
requestedProperties += PROP_ANIMATION_PLAYING;
|
||||
requestedProperties += PROP_ANIMATION_SETTINGS;
|
||||
requestedProperties += PROP_SHAPE_TYPE;
|
||||
requestedProperties += PROP_MAX_PARTICLES;
|
||||
requestedProperties += PROP_LIFESPAN;
|
||||
requestedProperties += PROP_EMIT_RATE;
|
||||
requestedProperties += PROP_EMIT_DIRECTION;
|
||||
requestedProperties += PROP_EMIT_STRENGTH;
|
||||
requestedProperties += PROP_LOCAL_GRAVITY;
|
||||
requestedProperties += PROP_PARTICLE_RADIUS;
|
||||
|
||||
return requestedProperties;
|
||||
}
|
||||
|
||||
void ParticleEffectEntityItem::appendSubclassData(OctreePacketData* packetData, EncodeBitstreamParams& params,
|
||||
EntityTreeElementExtraEncodeData* modelTreeElementExtraEncodeData,
|
||||
EntityPropertyFlags& requestedProperties,
|
||||
EntityPropertyFlags& propertyFlags,
|
||||
EntityPropertyFlags& propertiesDidntFit,
|
||||
int& propertyCount,
|
||||
OctreeElement::AppendState& appendState) const {
|
||||
|
||||
bool successPropertyFits = true;
|
||||
APPEND_ENTITY_PROPERTY(PROP_COLOR, appendColor, getColor());
|
||||
APPEND_ENTITY_PROPERTY(PROP_ANIMATION_FPS, appendValue, getAnimationFPS());
|
||||
APPEND_ENTITY_PROPERTY(PROP_ANIMATION_FRAME_INDEX, appendValue, getAnimationFrameIndex());
|
||||
APPEND_ENTITY_PROPERTY(PROP_ANIMATION_PLAYING, appendValue, getAnimationIsPlaying());
|
||||
APPEND_ENTITY_PROPERTY(PROP_ANIMATION_SETTINGS, appendValue, getAnimationSettings());
|
||||
APPEND_ENTITY_PROPERTY(PROP_SHAPE_TYPE, appendValue, (uint32_t)getShapeType());
|
||||
APPEND_ENTITY_PROPERTY(PROP_MAX_PARTICLES, appendValue, getMaxParticles());
|
||||
APPEND_ENTITY_PROPERTY(PROP_LIFESPAN, appendValue, getLifespan());
|
||||
APPEND_ENTITY_PROPERTY(PROP_EMIT_RATE, appendValue, getEmitRate());
|
||||
APPEND_ENTITY_PROPERTY(PROP_EMIT_DIRECTION, appendValue, getEmitDirection());
|
||||
APPEND_ENTITY_PROPERTY(PROP_EMIT_STRENGTH, appendValue, getEmitStrength());
|
||||
APPEND_ENTITY_PROPERTY(PROP_LOCAL_GRAVITY, appendValue, getLocalGravity());
|
||||
APPEND_ENTITY_PROPERTY(PROP_PARTICLE_RADIUS, appendValue, getParticleRadius());
|
||||
}
|
||||
|
||||
bool ParticleEffectEntityItem::isAnimatingSomething() const {
|
||||
return getAnimationIsPlaying() &&
|
||||
getAnimationFPS() != 0.0f;
|
||||
}
|
||||
|
||||
bool ParticleEffectEntityItem::needsToCallUpdate() const {
|
||||
return isAnimatingSomething() ? true : EntityItem::needsToCallUpdate();
|
||||
}
|
||||
|
||||
void ParticleEffectEntityItem::update(const quint64& now) {
|
||||
// only advance the frame index if we're playing
|
||||
if (getAnimationIsPlaying()) {
|
||||
float deltaTime = (float)(now - _lastAnimated) / (float)USECS_PER_SECOND;
|
||||
_lastAnimated = now;
|
||||
float lastFrame = _animationLoop.getFrameIndex();
|
||||
_animationLoop.simulate(deltaTime);
|
||||
float curFrame = _animationLoop.getFrameIndex();
|
||||
if (curFrame > lastFrame) {
|
||||
stepSimulation(deltaTime);
|
||||
}
|
||||
else if (curFrame < lastFrame) {
|
||||
// we looped around, so restart the sim and only sim up to the point
|
||||
// since the beginning of the frame range.
|
||||
resetSimulation();
|
||||
stepSimulation((curFrame - _animationLoop.getFirstFrame()) / _animationLoop.getFPS());
|
||||
}
|
||||
}
|
||||
else {
|
||||
_lastAnimated = now;
|
||||
}
|
||||
|
||||
// update the dimensions
|
||||
glm::vec3 dims;
|
||||
dims.x = glm::max(glm::abs(_paXmin), glm::abs(_paXmax)) * 2.0;
|
||||
dims.y = glm::max(glm::abs(_paYmin), glm::abs(_paYmax)) * 2.0;
|
||||
dims.z = glm::max(glm::abs(_paZmin), glm::abs(_paZmax)) * 2.0;
|
||||
setDimensionsInMeters(dims);
|
||||
|
||||
EntityItem::update(now); // let our base class handle it's updates...
|
||||
}
|
||||
|
||||
void ParticleEffectEntityItem::debugDump() const {
|
||||
quint64 now = usecTimestampNow();
|
||||
qDebug() << "PA EFFECT EntityItem id:" << getEntityItemID() << "---------------------------------------------";
|
||||
qDebug() << " color:" << _color[0] << "," << _color[1] << "," << _color[2];
|
||||
qDebug() << " position:" << debugTreeVector(_position);
|
||||
qDebug() << " dimensions:" << debugTreeVector(_dimensions);
|
||||
qDebug() << " getLastEdited:" << debugTime(getLastEdited(), now);
|
||||
}
|
||||
|
||||
void ParticleEffectEntityItem::updateShapeType(ShapeType type) {
|
||||
if (type != _shapeType) {
|
||||
_shapeType = type;
|
||||
_dirtyFlags |= EntityItem::DIRTY_SHAPE | EntityItem::DIRTY_MASS;
|
||||
}
|
||||
}
|
||||
|
||||
void ParticleEffectEntityItem::setAnimationFrameIndex(float value) {
|
||||
#ifdef WANT_DEBUG
|
||||
if (isAnimatingSomething()) {
|
||||
qDebug() << "ParticleEffectEntityItem::setAnimationFrameIndex()";
|
||||
qDebug() << " value:" << value;
|
||||
qDebug() << " was:" << _animationLoop.getFrameIndex();
|
||||
}
|
||||
#endif
|
||||
_animationLoop.setFrameIndex(value);
|
||||
}
|
||||
|
||||
void ParticleEffectEntityItem::setAnimationSettings(const QString& value) {
|
||||
// the animations setting is a JSON string that may contain various animation settings.
|
||||
// if it includes fps, frameIndex, or running, those values will be parsed out and
|
||||
// will over ride the regular animation settings
|
||||
|
||||
QJsonDocument settingsAsJson = QJsonDocument::fromJson(value.toUtf8());
|
||||
QJsonObject settingsAsJsonObject = settingsAsJson.object();
|
||||
QVariantMap settingsMap = settingsAsJsonObject.toVariantMap();
|
||||
if (settingsMap.contains("fps")) {
|
||||
float fps = settingsMap["fps"].toFloat();
|
||||
setAnimationFPS(fps);
|
||||
}
|
||||
|
||||
if (settingsMap.contains("frameIndex")) {
|
||||
float frameIndex = settingsMap["frameIndex"].toFloat();
|
||||
#ifdef WANT_DEBUG
|
||||
if (isAnimatingSomething()) {
|
||||
qDebug() << "ParticleEffectEntityItem::setAnimationSettings() calling setAnimationFrameIndex()...";
|
||||
qDebug() << " settings:" << value;
|
||||
qDebug() << " settingsMap[frameIndex]:" << settingsMap["frameIndex"];
|
||||
qDebug(" frameIndex: %20.5f", frameIndex);
|
||||
}
|
||||
#endif
|
||||
|
||||
setAnimationFrameIndex(frameIndex);
|
||||
}
|
||||
|
||||
if (settingsMap.contains("running")) {
|
||||
bool running = settingsMap["running"].toBool();
|
||||
if (running != getAnimationIsPlaying()) {
|
||||
setAnimationIsPlaying(running);
|
||||
}
|
||||
}
|
||||
|
||||
if (settingsMap.contains("firstFrame")) {
|
||||
float firstFrame = settingsMap["firstFrame"].toFloat();
|
||||
setAnimationFirstFrame(firstFrame);
|
||||
}
|
||||
|
||||
if (settingsMap.contains("lastFrame")) {
|
||||
float lastFrame = settingsMap["lastFrame"].toFloat();
|
||||
setAnimationLastFrame(lastFrame);
|
||||
}
|
||||
|
||||
if (settingsMap.contains("loop")) {
|
||||
bool loop = settingsMap["loop"].toBool();
|
||||
setAnimationLoop(loop);
|
||||
}
|
||||
|
||||
if (settingsMap.contains("hold")) {
|
||||
bool hold = settingsMap["hold"].toBool();
|
||||
setAnimationHold(hold);
|
||||
}
|
||||
|
||||
if (settingsMap.contains("startAutomatically")) {
|
||||
bool startAutomatically = settingsMap["startAutomatically"].toBool();
|
||||
setAnimationStartAutomatically(startAutomatically);
|
||||
}
|
||||
|
||||
_animationSettings = value;
|
||||
_dirtyFlags |= EntityItem::DIRTY_UPDATEABLE;
|
||||
}
|
||||
|
||||
void ParticleEffectEntityItem::setAnimationIsPlaying(bool value) {
|
||||
_dirtyFlags |= EntityItem::DIRTY_UPDATEABLE;
|
||||
_animationLoop.setRunning(value);
|
||||
}
|
||||
|
||||
void ParticleEffectEntityItem::setAnimationFPS(float value) {
|
||||
_dirtyFlags |= EntityItem::DIRTY_UPDATEABLE;
|
||||
_animationLoop.setFPS(value);
|
||||
}
|
||||
|
||||
QString ParticleEffectEntityItem::getAnimationSettings() const {
|
||||
// the animations setting is a JSON string that may contain various animation settings.
|
||||
// if it includes fps, frameIndex, or running, those values will be parsed out and
|
||||
// will over ride the regular animation settings
|
||||
QString value = _animationSettings;
|
||||
|
||||
QJsonDocument settingsAsJson = QJsonDocument::fromJson(value.toUtf8());
|
||||
QJsonObject settingsAsJsonObject = settingsAsJson.object();
|
||||
QVariantMap settingsMap = settingsAsJsonObject.toVariantMap();
|
||||
|
||||
QVariant fpsValue(getAnimationFPS());
|
||||
settingsMap["fps"] = fpsValue;
|
||||
|
||||
QVariant frameIndexValue(getAnimationFrameIndex());
|
||||
settingsMap["frameIndex"] = frameIndexValue;
|
||||
|
||||
QVariant runningValue(getAnimationIsPlaying());
|
||||
settingsMap["running"] = runningValue;
|
||||
|
||||
QVariant firstFrameValue(getAnimationFirstFrame());
|
||||
settingsMap["firstFrame"] = firstFrameValue;
|
||||
|
||||
QVariant lastFrameValue(getAnimationLastFrame());
|
||||
settingsMap["lastFrame"] = lastFrameValue;
|
||||
|
||||
QVariant loopValue(getAnimationLoop());
|
||||
settingsMap["loop"] = loopValue;
|
||||
|
||||
QVariant holdValue(getAnimationHold());
|
||||
settingsMap["hold"] = holdValue;
|
||||
|
||||
QVariant startAutomaticallyValue(getAnimationStartAutomatically());
|
||||
settingsMap["startAutomatically"] = startAutomaticallyValue;
|
||||
|
||||
settingsAsJsonObject = QJsonObject::fromVariantMap(settingsMap);
|
||||
QJsonDocument newDocument(settingsAsJsonObject);
|
||||
QByteArray jsonByteArray = newDocument.toJson(QJsonDocument::Compact);
|
||||
QString jsonByteString(jsonByteArray);
|
||||
return jsonByteString;
|
||||
}
|
||||
|
||||
void ParticleEffectEntityItem::stepSimulation(float deltaTime) {
|
||||
_paXmin = _paYmin = _paZmin = -1.0;
|
||||
_paXmax = _paYmax = _paZmax = 1.0;
|
||||
|
||||
// update particles
|
||||
quint32 updateIter = _paHead;
|
||||
while (_paLife[updateIter] > 0.0f) {
|
||||
_paLife[updateIter] -= deltaTime;
|
||||
if (_paLife[updateIter] <= 0.0f) {
|
||||
_paLife[updateIter] = -1.0f;
|
||||
_paHead = (_paHead + 1) % _maxParticles;
|
||||
_paCount--;
|
||||
}
|
||||
else {
|
||||
// DUMB FORWARD EULER just to get it done
|
||||
int j = updateIter * XYZ_STRIDE;
|
||||
_paPosition[j] += _paVelocity[j] * deltaTime;
|
||||
_paPosition[j+1] += _paVelocity[j+1] * deltaTime;
|
||||
_paPosition[j+2] += _paVelocity[j+2] * deltaTime;
|
||||
|
||||
_paXmin = glm::min(_paXmin, _paPosition[j]);
|
||||
_paYmin = glm::min(_paYmin, _paPosition[j+1]);
|
||||
_paZmin = glm::min(_paZmin, _paPosition[j+2]);
|
||||
_paXmax = glm::max(_paXmax, _paPosition[j]);
|
||||
_paYmax = glm::max(_paYmax, _paPosition[j + 1]);
|
||||
_paZmax = glm::max(_paZmax, _paPosition[j + 2]);
|
||||
|
||||
// massless particles
|
||||
_paVelocity[j + 1] += deltaTime * _localGravity;
|
||||
}
|
||||
updateIter = (updateIter + 1) % _maxParticles;
|
||||
}
|
||||
|
||||
// emit new particles
|
||||
quint32 emitIdx = updateIter;
|
||||
_partialEmit += ((float)_emitRate) * deltaTime;
|
||||
quint32 birthed = (quint32)_partialEmit;
|
||||
_partialEmit -= (float)birthed;
|
||||
glm::vec3 randOffset;
|
||||
|
||||
for (quint32 i = 0; i < birthed; i++) {
|
||||
if (_paLife[emitIdx] < 0.0f) {
|
||||
int j = emitIdx * XYZ_STRIDE;
|
||||
_paLife[emitIdx] = _lifespan;
|
||||
randOffset.x = (((double)rand() / (double)RAND_MAX) - 0.5) * 0.25 * _emitStrength;
|
||||
randOffset.y = (((double)rand() / (double)RAND_MAX) - 0.5) * 0.25 * _emitStrength;
|
||||
randOffset.z = (((double)rand() / (double)RAND_MAX) - 0.5) * 0.25 * _emitStrength;
|
||||
_paVelocity[j] = (_emitDirection.x * _emitStrength) + randOffset.x;
|
||||
_paVelocity[j + 1] = (_emitDirection.y * _emitStrength) + randOffset.y;
|
||||
_paVelocity[j + 2] = (_emitDirection.z * _emitStrength) + randOffset.z;
|
||||
|
||||
// DUMB FORWARD EULER just to get it done
|
||||
_paPosition[j] += _paVelocity[j] * deltaTime;
|
||||
_paPosition[j + 1] += _paVelocity[j + 1] * deltaTime;
|
||||
_paPosition[j + 2] += _paVelocity[j + 2] * deltaTime;
|
||||
|
||||
_paXmin = glm::min(_paXmin, _paPosition[j]);
|
||||
_paYmin = glm::min(_paYmin, _paPosition[j + 1]);
|
||||
_paZmin = glm::min(_paZmin, _paPosition[j + 2]);
|
||||
_paXmax = glm::max(_paXmax, _paPosition[j]);
|
||||
_paYmax = glm::max(_paYmax, _paPosition[j + 1]);
|
||||
_paZmax = glm::max(_paZmax, _paPosition[j + 2]);
|
||||
|
||||
// massless particles
|
||||
// and simple gravity down
|
||||
_paVelocity[j + 1] += deltaTime * _localGravity;
|
||||
|
||||
emitIdx = (emitIdx + 1) % _maxParticles;
|
||||
_paCount++;
|
||||
}
|
||||
else
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void ParticleEffectEntityItem::resetSimulation() {
|
||||
for (int i = 0; i < _maxParticles; i++) {
|
||||
int j = i * XYZ_STRIDE;
|
||||
_paLife[i] = -1.0f;
|
||||
_paPosition[j] = 0.0f;
|
||||
_paPosition[j+1] = 0.0f;
|
||||
_paPosition[j+2] = 0.0f;
|
||||
_paVelocity[j] = 0.0f;
|
||||
_paVelocity[j+1] = 0.0f;
|
||||
_paVelocity[j+2] = 0.0f;
|
||||
}
|
||||
_paCount = 0;
|
||||
_paHead = 0;
|
||||
_partialEmit = 0.0f;
|
||||
|
||||
srand(_randSeed);
|
||||
}
|
||||
|
183
libraries/entities/src/ParticleEffectEntityItem.h
Normal file
183
libraries/entities/src/ParticleEffectEntityItem.h
Normal file
|
@ -0,0 +1,183 @@
|
|||
//
|
||||
// ParticleEffectEntityItem.h
|
||||
// libraries/entities/src
|
||||
//
|
||||
// Some starter code for a particle simulation entity, which could ideally be used for a variety of effects.
|
||||
// This is some really early and rough stuff here. It was enough for now to just get it up and running in the interface.
|
||||
//
|
||||
// Todo's and other notes:
|
||||
// - The simulation should restart when the AnimationLoop's max frame is reached (or passed), but there doesn't seem
|
||||
// to be a good way to set that max frame to something reasonable right now.
|
||||
// - There seems to be a bug whereby entities on the edge of screen will just pop off or on. This is probably due
|
||||
// to my lack of understanding of how entities in the octree are picked for rendering. I am updating the entity
|
||||
// dimensions based on the bounds of the sim, but maybe I need to update a dirty flag or something.
|
||||
// - This should support some kind of pre-roll of the simulation.
|
||||
// - Just to get this out the door, I just did forward Euler integration. There are better ways.
|
||||
// - Gravity always points along the Y axis. Support an actual gravity vector.
|
||||
// - Add the ability to add arbitrary forces to the simulation.
|
||||
// - Add controls for spread (which is currently hard-coded) and varying emission strength (not currently implemented).
|
||||
// - Add drag.
|
||||
// - Add some kind of support for collisions.
|
||||
// - For simplicity, I'm currently just rendering each particle as a cross of four axis-aligned quads. Really, we'd
|
||||
// want multiple render modes, including (the most important) textured billboards (always facing camera). Also, these
|
||||
// should support animated textures.
|
||||
// - There's no synchronization of the simulation across clients at all. In fact, it's using rand() under the hood, so
|
||||
// there's no gaurantee that different clients will see simulations that look anything like the other.
|
||||
// - MORE?
|
||||
//
|
||||
// Created by Jason Rickwald on 3/2/15.
|
||||
//
|
||||
// Distributed under the Apache License, Version 2.0.
|
||||
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
|
||||
//
|
||||
|
||||
#ifndef hifi_ParticleEffectEntityItem_h
|
||||
#define hifi_ParticleEffectEntityItem_h
|
||||
|
||||
#include <AnimationLoop.h>
|
||||
#include "EntityItem.h"
|
||||
|
||||
class ParticleEffectEntityItem : public EntityItem {
|
||||
public:
|
||||
|
||||
static EntityItem* factory(const EntityItemID& entityID, const EntityItemProperties& properties);
|
||||
|
||||
ParticleEffectEntityItem(const EntityItemID& entityItemID, const EntityItemProperties& properties);
|
||||
virtual ~ParticleEffectEntityItem();
|
||||
|
||||
ALLOW_INSTANTIATION // This class can be instantiated
|
||||
|
||||
// methods for getting/setting all properties of this entity
|
||||
virtual EntityItemProperties getProperties() const;
|
||||
virtual bool setProperties(const EntityItemProperties& properties);
|
||||
|
||||
virtual EntityPropertyFlags getEntityProperties(EncodeBitstreamParams& params) const;
|
||||
|
||||
virtual void appendSubclassData(OctreePacketData* packetData, EncodeBitstreamParams& params,
|
||||
EntityTreeElementExtraEncodeData* modelTreeElementExtraEncodeData,
|
||||
EntityPropertyFlags& requestedProperties,
|
||||
EntityPropertyFlags& propertyFlags,
|
||||
EntityPropertyFlags& propertiesDidntFit,
|
||||
int& propertyCount,
|
||||
OctreeElement::AppendState& appendState) const;
|
||||
|
||||
virtual int readEntitySubclassDataFromBuffer(const unsigned char* data, int bytesLeftToRead,
|
||||
ReadBitstreamToTreeParams& args,
|
||||
EntityPropertyFlags& propertyFlags, bool overwriteLocalData);
|
||||
|
||||
virtual void update(const quint64& now);
|
||||
virtual bool needsToCallUpdate() const;
|
||||
|
||||
const rgbColor& getColor() const { return _color; }
|
||||
xColor getXColor() const { xColor color = { _color[RED_INDEX], _color[GREEN_INDEX], _color[BLUE_INDEX] }; return color; }
|
||||
|
||||
void setColor(const rgbColor& value) { memcpy(_color, value, sizeof(_color)); }
|
||||
void setColor(const xColor& value) {
|
||||
_color[RED_INDEX] = value.red;
|
||||
_color[GREEN_INDEX] = value.green;
|
||||
_color[BLUE_INDEX] = value.blue;
|
||||
}
|
||||
|
||||
void updateShapeType(ShapeType type);
|
||||
virtual ShapeType getShapeType() const { return _shapeType; }
|
||||
|
||||
virtual void debugDump() const;
|
||||
|
||||
static const float DEFAULT_ANIMATION_FRAME_INDEX;
|
||||
void setAnimationFrameIndex(float value);
|
||||
void setAnimationSettings(const QString& value);
|
||||
|
||||
static const bool DEFAULT_ANIMATION_IS_PLAYING;
|
||||
void setAnimationIsPlaying(bool value);
|
||||
|
||||
static const float DEFAULT_ANIMATION_FPS;
|
||||
void setAnimationFPS(float value);
|
||||
|
||||
void setAnimationLoop(bool loop) { _animationLoop.setLoop(loop); }
|
||||
bool getAnimationLoop() const { return _animationLoop.getLoop(); }
|
||||
|
||||
void setAnimationHold(bool hold) { _animationLoop.setHold(hold); }
|
||||
bool getAnimationHold() const { return _animationLoop.getHold(); }
|
||||
|
||||
void setAnimationStartAutomatically(bool startAutomatically) { _animationLoop.setStartAutomatically(startAutomatically); }
|
||||
bool getAnimationStartAutomatically() const { return _animationLoop.getStartAutomatically(); }
|
||||
|
||||
void setAnimationFirstFrame(float firstFrame) { _animationLoop.setFirstFrame(firstFrame); }
|
||||
float getAnimationFirstFrame() const { return _animationLoop.getFirstFrame(); }
|
||||
|
||||
void setAnimationLastFrame(float lastFrame) { _animationLoop.setLastFrame(lastFrame); }
|
||||
float getAnimationLastFrame() const { return _animationLoop.getLastFrame(); }
|
||||
|
||||
static const quint32 DEFAULT_MAX_PARTICLES;
|
||||
void setMaxParticles(quint32 maxParticles) { _maxParticles = maxParticles; }
|
||||
quint32 getMaxParticles() const { return _maxParticles; }
|
||||
|
||||
static const float DEFAULT_LIFESPAN;
|
||||
void setLifespan(float lifespan) { _lifespan = lifespan; }
|
||||
float getLifespan() const { return _lifespan; }
|
||||
|
||||
static const float DEFAULT_EMIT_RATE;
|
||||
void setEmitRate(float emitRate) { _emitRate = emitRate; }
|
||||
float getEmitRate() const { return _emitRate; }
|
||||
|
||||
static const glm::vec3 DEFAULT_EMIT_DIRECTION;
|
||||
void setEmitDirection(glm::vec3 emitDirection) { _emitDirection = emitDirection; }
|
||||
const glm::vec3& getEmitDirection() const { return _emitDirection; }
|
||||
|
||||
static const float DEFAULT_EMIT_STRENGTH;
|
||||
void setEmitStrength(float emitStrength) { _emitStrength = emitStrength; }
|
||||
float getEmitStrength() const { return _emitStrength; }
|
||||
|
||||
static const float DEFAULT_LOCAL_GRAVITY;
|
||||
void setLocalGravity(float localGravity) { _localGravity = localGravity; }
|
||||
float getLocalGravity() const { return _localGravity; }
|
||||
|
||||
static const float DEFAULT_PARTICLE_RADIUS;
|
||||
void setParticleRadius(float particleRadius) { _particleRadius = particleRadius; }
|
||||
float getParticleRadius() const { return _particleRadius; }
|
||||
|
||||
bool getAnimationIsPlaying() const { return _animationLoop.isRunning(); }
|
||||
float getAnimationFrameIndex() const { return _animationLoop.getFrameIndex(); }
|
||||
float getAnimationFPS() const { return _animationLoop.getFPS(); }
|
||||
QString getAnimationSettings() const;
|
||||
|
||||
protected:
|
||||
|
||||
bool isAnimatingSomething() const;
|
||||
void stepSimulation(float deltaTime);
|
||||
void resetSimulation();
|
||||
|
||||
// the properties of this entity
|
||||
rgbColor _color;
|
||||
quint32 _maxParticles;
|
||||
float _lifespan;
|
||||
float _emitRate;
|
||||
glm::vec3 _emitDirection;
|
||||
float _emitStrength;
|
||||
float _localGravity;
|
||||
float _particleRadius;
|
||||
quint64 _lastAnimated;
|
||||
AnimationLoop _animationLoop;
|
||||
QString _animationSettings;
|
||||
ShapeType _shapeType = SHAPE_TYPE_NONE;
|
||||
|
||||
// all the internals of running the particle sim
|
||||
const quint32 XYZ_STRIDE = 3;
|
||||
float* _paLife;
|
||||
float* _paPosition;
|
||||
float* _paVelocity;
|
||||
float _partialEmit;
|
||||
quint32 _paCount;
|
||||
quint32 _paHead;
|
||||
float _paXmin;
|
||||
float _paXmax;
|
||||
float _paYmin;
|
||||
float _paYmax;
|
||||
float _paZmin;
|
||||
float _paZmax;
|
||||
unsigned int _randSeed;
|
||||
|
||||
};
|
||||
|
||||
#endif // hifi_ParticleEffectEntityItem_h
|
||||
|
|
@ -114,7 +114,7 @@ bool SphereEntityItem::findDetailedRayIntersection(const glm::vec3& origin, cons
|
|||
distance = glm::distance(origin,hitAt);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
|
|
|
@ -129,7 +129,8 @@ const PacketVersion VERSION_ENTITIES_HAVE_USER_DATA = 6;
|
|||
const PacketVersion VERSION_ENTITIES_HAS_LAST_SIMULATED_TIME = 7;
|
||||
const PacketVersion VERSION_MODEL_ENTITIES_SUPPORT_SHAPE_TYPE = 8;
|
||||
const PacketVersion VERSION_ENTITIES_LIGHT_HAS_INTENSITY_AND_COLOR_PROPERTIES = 9;
|
||||
const PacketVersion VERSION_ENTITIES_USE_METERS_AND_RADIANS = 10;
|
||||
const PacketVersion VERSION_ENTITIES_HAS_PARTICLES = 10;
|
||||
const PacketVersion VERSION_ENTITIES_USE_METERS_AND_RADIANS = 11;
|
||||
const PacketVersion VERSION_OCTREE_HAS_FILE_BREAKS = 1;
|
||||
|
||||
#endif // hifi_PacketHeaders_h
|
||||
|
|
|
@ -12,9 +12,10 @@
|
|||
#include <cfloat>
|
||||
#include <cmath>
|
||||
|
||||
#include <QDebug>
|
||||
#include <QNetworkDiskCache>
|
||||
#include <QThread>
|
||||
#include <QTimer>
|
||||
#include <QtDebug>
|
||||
|
||||
#include <SharedUtil.h>
|
||||
|
||||
|
@ -314,13 +315,51 @@ void Resource::handleReplyTimeout() {
|
|||
"received" << _bytesReceived << "total" << _bytesTotal);
|
||||
}
|
||||
|
||||
void Resource::maybeRefresh() {
|
||||
if (Q_LIKELY(NetworkAccessManager::getInstance().cache())) {
|
||||
QNetworkReply* reply = qobject_cast<QNetworkReply*>(sender());
|
||||
QVariant variant = reply->header(QNetworkRequest::LastModifiedHeader);
|
||||
QNetworkCacheMetaData metaData = NetworkAccessManager::getInstance().cache()->metaData(_url);
|
||||
if (variant.isValid() && variant.canConvert<QDateTime>() && metaData.isValid()) {
|
||||
QDateTime lastModified = variant.value<QDateTime>();
|
||||
QDateTime lastModifiedOld = metaData.lastModified();
|
||||
if (lastModified.isValid() && lastModifiedOld.isValid() &&
|
||||
lastModifiedOld == lastModified) {
|
||||
// We don't need to update, return
|
||||
return;
|
||||
}
|
||||
}
|
||||
qDebug() << "Loaded" << _url.fileName() << "from the disk cache but the network version is newer, refreshing.";
|
||||
refresh();
|
||||
}
|
||||
}
|
||||
|
||||
void Resource::makeRequest() {
|
||||
_reply = NetworkAccessManager::getInstance().get(_request);
|
||||
|
||||
connect(_reply, SIGNAL(downloadProgress(qint64,qint64)), SLOT(handleDownloadProgress(qint64,qint64)));
|
||||
connect(_reply, SIGNAL(error(QNetworkReply::NetworkError)), SLOT(handleReplyError()));
|
||||
connect(_reply, SIGNAL(finished()), SLOT(handleReplyFinished()));
|
||||
|
||||
|
||||
if (_reply->attribute(QNetworkRequest::SourceIsFromCacheAttribute).toBool()) {
|
||||
// If the file as been updated since it was cached, refresh it
|
||||
QNetworkRequest request(_request);
|
||||
request.setAttribute(QNetworkRequest::CacheLoadControlAttribute, QNetworkRequest::AlwaysNetwork);
|
||||
request.setAttribute(QNetworkRequest::CacheSaveControlAttribute, false);
|
||||
QNetworkReply* reply = NetworkAccessManager::getInstance().head(request);
|
||||
connect(reply, &QNetworkReply::finished, this, &Resource::maybeRefresh);
|
||||
} else {
|
||||
if (Q_LIKELY(NetworkAccessManager::getInstance().cache())) {
|
||||
QNetworkCacheMetaData metaData = NetworkAccessManager::getInstance().cache()->metaData(_url);
|
||||
if (metaData.expirationDate().isNull() || metaData.expirationDate() <= QDateTime::currentDateTime()) {
|
||||
// If the expiration date is NULL or in the past,
|
||||
// put one far enough away that it won't be an issue.
|
||||
metaData.setExpirationDate(QDateTime::currentDateTime().addYears(100));
|
||||
NetworkAccessManager::getInstance().cache()->updateMetaData(metaData);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
_replyTimer = new QTimer(this);
|
||||
connect(_replyTimer, SIGNAL(timeout()), SLOT(handleReplyTimeout()));
|
||||
_replyTimer->setSingleShot(true);
|
||||
|
|
|
@ -149,7 +149,7 @@ public:
|
|||
|
||||
/// For loading resources, returns the load progress.
|
||||
float getProgress() const { return (_bytesTotal <= 0) ? 0.0f : (float)_bytesReceived / _bytesTotal; }
|
||||
|
||||
|
||||
/// Refreshes the resource.
|
||||
void refresh();
|
||||
|
||||
|
@ -169,6 +169,10 @@ signals:
|
|||
protected slots:
|
||||
|
||||
void attemptRequest();
|
||||
|
||||
/// Refreshes the resource if the last modified date on the network
|
||||
/// is greater than the last modified date in the cache.
|
||||
void maybeRefresh();
|
||||
|
||||
protected:
|
||||
|
||||
|
|
Loading…
Reference in a new issue