mirror of
https://github.com/overte-org/overte.git
synced 2025-08-09 13:28:09 +02:00
Merge pull request #15237 from SamGondelman/particleShapes
Case 21859: Particle Entity emitter shapes, including model
This commit is contained in:
commit
1cc2cd3194
16 changed files with 427 additions and 69 deletions
|
@ -307,10 +307,6 @@ void RenderableModelEntityItem::setShapeType(ShapeType type) {
|
||||||
}
|
}
|
||||||
|
|
||||||
void RenderableModelEntityItem::setCompoundShapeURL(const QString& url) {
|
void RenderableModelEntityItem::setCompoundShapeURL(const QString& url) {
|
||||||
// because the caching system only allows one Geometry per url, and because this url might also be used
|
|
||||||
// as a visual model, we need to change this url in some way. We add a "collision-hull" query-arg so it
|
|
||||||
// will end up in a different hash-key in ResourceCache. TODO: It would be better to use the same URL and
|
|
||||||
// parse it twice.
|
|
||||||
auto currentCompoundShapeURL = getCompoundShapeURL();
|
auto currentCompoundShapeURL = getCompoundShapeURL();
|
||||||
ModelEntityItem::setCompoundShapeURL(url);
|
ModelEntityItem::setCompoundShapeURL(url);
|
||||||
if (getCompoundShapeURL() != currentCompoundShapeURL || !getModel()) {
|
if (getCompoundShapeURL() != currentCompoundShapeURL || !getModel()) {
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
//
|
//
|
||||||
// RenderableParticleEffectEntityItem.cpp
|
// RenderableParticleEffectEntityItem.cpp
|
||||||
// interface/src
|
// interface/src
|
||||||
//
|
//
|
||||||
|
@ -9,12 +9,12 @@
|
||||||
//
|
//
|
||||||
|
|
||||||
#include "RenderableParticleEffectEntityItem.h"
|
#include "RenderableParticleEffectEntityItem.h"
|
||||||
|
|
||||||
#include <StencilMaskPass.h>
|
#include <StencilMaskPass.h>
|
||||||
|
|
||||||
#include <GeometryCache.h>
|
#include <GeometryCache.h>
|
||||||
#include <shaders/Shaders.h>
|
#include <shaders/Shaders.h>
|
||||||
|
|
||||||
|
#include <glm/gtx/transform.hpp>
|
||||||
|
|
||||||
using namespace render;
|
using namespace render;
|
||||||
using namespace render::entities;
|
using namespace render::entities;
|
||||||
|
@ -79,6 +79,14 @@ bool ParticleEffectEntityRenderer::needsRenderUpdateFromTypedEntity(const TypedE
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (_shapeType != entity->getShapeType()) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (_compoundShapeURL != entity->getCompoundShapeURL()) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -87,10 +95,10 @@ void ParticleEffectEntityRenderer::doRenderUpdateSynchronousTyped(const ScenePoi
|
||||||
if (!newParticleProperties.valid()) {
|
if (!newParticleProperties.valid()) {
|
||||||
qCWarning(entitiesrenderer) << "Bad particle properties";
|
qCWarning(entitiesrenderer) << "Bad particle properties";
|
||||||
}
|
}
|
||||||
|
|
||||||
if (resultWithReadLock<bool>([&]{ return _particleProperties != newParticleProperties; })) {
|
if (resultWithReadLock<bool>([&] { return _particleProperties != newParticleProperties; })) {
|
||||||
_timeUntilNextEmit = 0;
|
_timeUntilNextEmit = 0;
|
||||||
withWriteLock([&]{
|
withWriteLock([&] {
|
||||||
_particleProperties = newParticleProperties;
|
_particleProperties = newParticleProperties;
|
||||||
if (!_prevEmitterShouldTrailInitialized) {
|
if (!_prevEmitterShouldTrailInitialized) {
|
||||||
_prevEmitterShouldTrailInitialized = true;
|
_prevEmitterShouldTrailInitialized = true;
|
||||||
|
@ -101,13 +109,20 @@ void ParticleEffectEntityRenderer::doRenderUpdateSynchronousTyped(const ScenePoi
|
||||||
|
|
||||||
withWriteLock([&] {
|
withWriteLock([&] {
|
||||||
_pulseProperties = entity->getPulseProperties();
|
_pulseProperties = entity->getPulseProperties();
|
||||||
|
_shapeType = entity->getShapeType();
|
||||||
|
QString compoundShapeURL = entity->getCompoundShapeURL();
|
||||||
|
if (_compoundShapeURL != compoundShapeURL) {
|
||||||
|
_compoundShapeURL = compoundShapeURL;
|
||||||
|
_hasComputedTriangles = false;
|
||||||
|
fetchGeometryResource();
|
||||||
|
}
|
||||||
});
|
});
|
||||||
_emitting = entity->getIsEmitting();
|
_emitting = entity->getIsEmitting();
|
||||||
|
|
||||||
bool textureEmpty = resultWithReadLock<bool>([&]{ return _particleProperties.textures.isEmpty(); });
|
bool textureEmpty = resultWithReadLock<bool>([&] { return _particleProperties.textures.isEmpty(); });
|
||||||
if (textureEmpty) {
|
if (textureEmpty) {
|
||||||
if (_networkTexture) {
|
if (_networkTexture) {
|
||||||
withWriteLock([&] {
|
withWriteLock([&] {
|
||||||
_networkTexture.reset();
|
_networkTexture.reset();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -116,11 +131,11 @@ void ParticleEffectEntityRenderer::doRenderUpdateSynchronousTyped(const ScenePoi
|
||||||
entity->setVisuallyReady(true);
|
entity->setVisuallyReady(true);
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
bool textureNeedsUpdate = resultWithReadLock<bool>([&]{
|
bool textureNeedsUpdate = resultWithReadLock<bool>([&] {
|
||||||
return !_networkTexture || _networkTexture->getURL() != QUrl(_particleProperties.textures);
|
return !_networkTexture || _networkTexture->getURL() != QUrl(_particleProperties.textures);
|
||||||
});
|
});
|
||||||
if (textureNeedsUpdate) {
|
if (textureNeedsUpdate) {
|
||||||
withWriteLock([&] {
|
withWriteLock([&] {
|
||||||
_networkTexture = DependencyManager::get<TextureCache>()->getTexture(_particleProperties.textures);
|
_networkTexture = DependencyManager::get<TextureCache>()->getTexture(_particleProperties.textures);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -144,7 +159,7 @@ void ParticleEffectEntityRenderer::doRenderUpdateSynchronousTyped(const ScenePoi
|
||||||
void ParticleEffectEntityRenderer::doRenderUpdateAsynchronousTyped(const TypedEntityPointer& entity) {
|
void ParticleEffectEntityRenderer::doRenderUpdateAsynchronousTyped(const TypedEntityPointer& entity) {
|
||||||
// Fill in Uniforms structure
|
// Fill in Uniforms structure
|
||||||
ParticleUniforms particleUniforms;
|
ParticleUniforms particleUniforms;
|
||||||
withReadLock([&]{
|
withReadLock([&] {
|
||||||
particleUniforms.radius.start = _particleProperties.radius.range.start;
|
particleUniforms.radius.start = _particleProperties.radius.range.start;
|
||||||
particleUniforms.radius.middle = _particleProperties.radius.gradient.target;
|
particleUniforms.radius.middle = _particleProperties.radius.gradient.target;
|
||||||
particleUniforms.radius.finish = _particleProperties.radius.range.finish;
|
particleUniforms.radius.finish = _particleProperties.radius.range.finish;
|
||||||
|
@ -181,9 +196,32 @@ Item::Bound ParticleEffectEntityRenderer::getBound() {
|
||||||
return _bound;
|
return _bound;
|
||||||
}
|
}
|
||||||
|
|
||||||
static const size_t VERTEX_PER_PARTICLE = 4;
|
// FIXME: these methods assume uniform emitDimensions, need to importance sample based on dimensions
|
||||||
|
float importanceSample2DDimension(float startDim) {
|
||||||
|
float dimension = 1.0f;
|
||||||
|
if (startDim < 1.0f) {
|
||||||
|
float innerDimensionSquared = startDim * startDim;
|
||||||
|
float outerDimensionSquared = 1.0f; // pow(particle::MAXIMUM_EMIT_RADIUS_START, 2);
|
||||||
|
float randDimensionSquared = randFloatInRange(innerDimensionSquared, outerDimensionSquared);
|
||||||
|
dimension = std::sqrt(randDimensionSquared);
|
||||||
|
}
|
||||||
|
return dimension;
|
||||||
|
}
|
||||||
|
|
||||||
ParticleEffectEntityRenderer::CpuParticle ParticleEffectEntityRenderer::createParticle(uint64_t now, const Transform& baseTransform, const particle::Properties& particleProperties) {
|
float importanceSample3DDimension(float startDim) {
|
||||||
|
float dimension = 1.0f;
|
||||||
|
if (startDim < 1.0f) {
|
||||||
|
float innerDimensionCubed = startDim * startDim * startDim;
|
||||||
|
float outerDimensionCubed = 1.0f; // pow(particle::MAXIMUM_EMIT_RADIUS_START, 3);
|
||||||
|
float randDimensionCubed = randFloatInRange(innerDimensionCubed, outerDimensionCubed);
|
||||||
|
dimension = std::cbrt(randDimensionCubed);
|
||||||
|
}
|
||||||
|
return dimension;
|
||||||
|
}
|
||||||
|
|
||||||
|
ParticleEffectEntityRenderer::CpuParticle ParticleEffectEntityRenderer::createParticle(uint64_t now, const Transform& baseTransform, const particle::Properties& particleProperties,
|
||||||
|
const ShapeType& shapeType, const GeometryResource::Pointer& geometryResource,
|
||||||
|
const TriangleInfo& triangleInfo) {
|
||||||
CpuParticle particle;
|
CpuParticle particle;
|
||||||
|
|
||||||
const auto& accelerationSpread = particleProperties.emission.acceleration.spread;
|
const auto& accelerationSpread = particleProperties.emission.acceleration.spread;
|
||||||
|
@ -221,33 +259,130 @@ ParticleEffectEntityRenderer::CpuParticle ParticleEffectEntityRenderer::createPa
|
||||||
|
|
||||||
float azimuth;
|
float azimuth;
|
||||||
if (azimuthFinish >= azimuthStart) {
|
if (azimuthFinish >= azimuthStart) {
|
||||||
azimuth = azimuthStart + (azimuthFinish - azimuthStart) * randFloat();
|
azimuth = azimuthStart + (azimuthFinish - azimuthStart) * randFloat();
|
||||||
} else {
|
} else {
|
||||||
azimuth = azimuthStart + (TWO_PI + azimuthFinish - azimuthStart) * randFloat();
|
azimuth = azimuthStart + (TWO_PI + azimuthFinish - azimuthStart) * randFloat();
|
||||||
}
|
}
|
||||||
|
// TODO: azimuth and elevation are only used for ellipsoids/circles, but could be used for other shapes too
|
||||||
|
|
||||||
if (emitDimensions == Vectors::ZERO) {
|
if (emitDimensions == Vectors::ZERO) {
|
||||||
// Point
|
// Point
|
||||||
emitDirection = glm::quat(glm::vec3(PI_OVER_TWO - elevation, 0.0f, azimuth)) * Vectors::UNIT_Z;
|
emitDirection = glm::quat(glm::vec3(PI_OVER_TWO - elevation, 0.0f, azimuth)) * Vectors::UNIT_Z;
|
||||||
} else {
|
} else {
|
||||||
// Ellipsoid
|
glm::vec3 emitPosition;
|
||||||
float radiusScale = 1.0f;
|
switch (shapeType) {
|
||||||
if (emitRadiusStart < 1.0f) {
|
case SHAPE_TYPE_BOX: {
|
||||||
float randRadius =
|
glm::vec3 dim = importanceSample3DDimension(emitRadiusStart) * 0.5f * emitDimensions;
|
||||||
emitRadiusStart + randFloatInRange(0.0f, particle::MAXIMUM_EMIT_RADIUS_START - emitRadiusStart);
|
|
||||||
radiusScale = 1.0f - std::pow(1.0f - randRadius, 3.0f);
|
int side = randIntInRange(0, 5);
|
||||||
|
int axis = side % 3;
|
||||||
|
float direction = side > 2 ? 1.0f : -1.0f;
|
||||||
|
|
||||||
|
emitDirection[axis] = direction;
|
||||||
|
emitPosition[axis] = direction * dim[axis];
|
||||||
|
axis = (axis + 1) % 3;
|
||||||
|
emitPosition[axis] = dim[axis] * randFloatInRange(-1.0f, 1.0f);
|
||||||
|
axis = (axis + 1) % 3;
|
||||||
|
emitPosition[axis] = dim[axis] * randFloatInRange(-1.0f, 1.0f);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
case SHAPE_TYPE_CYLINDER_X:
|
||||||
|
case SHAPE_TYPE_CYLINDER_Y:
|
||||||
|
case SHAPE_TYPE_CYLINDER_Z: {
|
||||||
|
glm::vec3 radii = importanceSample2DDimension(emitRadiusStart) * 0.5f * emitDimensions;
|
||||||
|
int axis = shapeType - SHAPE_TYPE_CYLINDER_X;
|
||||||
|
|
||||||
|
emitPosition[axis] = emitDimensions[axis] * randFloatInRange(-0.5f, 0.5f);
|
||||||
|
emitDirection[axis] = 0.0f;
|
||||||
|
axis = (axis + 1) % 3;
|
||||||
|
emitPosition[axis] = radii[axis] * glm::cos(azimuth);
|
||||||
|
emitDirection[axis] = radii[axis] > 0.0f ? emitPosition[axis] / (radii[axis] * radii[axis]) : 0.0f;
|
||||||
|
axis = (axis + 1) % 3;
|
||||||
|
emitPosition[axis] = radii[axis] * glm::sin(azimuth);
|
||||||
|
emitDirection[axis] = radii[axis] > 0.0f ? emitPosition[axis] / (radii[axis] * radii[axis]) : 0.0f;
|
||||||
|
emitDirection = glm::normalize(emitDirection);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
case SHAPE_TYPE_CIRCLE: {
|
||||||
|
glm::vec2 radii = importanceSample2DDimension(emitRadiusStart) * 0.5f * glm::vec2(emitDimensions.x, emitDimensions.z);
|
||||||
|
float x = radii.x * glm::cos(azimuth);
|
||||||
|
float z = radii.y * glm::sin(azimuth);
|
||||||
|
emitPosition = glm::vec3(x, 0.0f, z);
|
||||||
|
emitDirection = Vectors::UP;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case SHAPE_TYPE_PLANE: {
|
||||||
|
glm::vec2 dim = importanceSample2DDimension(emitRadiusStart) * 0.5f * glm::vec2(emitDimensions.x, emitDimensions.z);
|
||||||
|
|
||||||
|
int side = randIntInRange(0, 3);
|
||||||
|
int axis = side % 2;
|
||||||
|
float direction = side > 1 ? 1.0f : -1.0f;
|
||||||
|
|
||||||
|
glm::vec2 pos;
|
||||||
|
pos[axis] = direction * dim[axis];
|
||||||
|
axis = (axis + 1) % 2;
|
||||||
|
pos[axis] = dim[axis] * randFloatInRange(-1.0f, 1.0f);
|
||||||
|
|
||||||
|
emitPosition = glm::vec3(pos.x, 0.0f, pos.y);
|
||||||
|
emitDirection = Vectors::UP;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
case SHAPE_TYPE_COMPOUND: {
|
||||||
|
// if we get here we know that geometryResource is loaded
|
||||||
|
|
||||||
|
size_t index = randFloat() * triangleInfo.totalSamples;
|
||||||
|
Triangle triangle;
|
||||||
|
for (size_t i = 0; i < triangleInfo.samplesPerTriangle.size(); i++) {
|
||||||
|
size_t numSamples = triangleInfo.samplesPerTriangle[i];
|
||||||
|
if (index < numSamples) {
|
||||||
|
triangle = triangleInfo.triangles[i];
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
index -= numSamples;
|
||||||
|
}
|
||||||
|
|
||||||
|
float edgeLength1 = glm::length(triangle.v1 - triangle.v0);
|
||||||
|
float edgeLength2 = glm::length(triangle.v2 - triangle.v1);
|
||||||
|
float edgeLength3 = glm::length(triangle.v0 - triangle.v2);
|
||||||
|
|
||||||
|
float perimeter = edgeLength1 + edgeLength2 + edgeLength3;
|
||||||
|
float fraction1 = randFloatInRange(0.0f, 1.0f);
|
||||||
|
float fractionEdge1 = glm::min(fraction1 * perimeter / edgeLength1, 1.0f);
|
||||||
|
float fraction2 = fraction1 - edgeLength1 / perimeter;
|
||||||
|
float fractionEdge2 = glm::clamp(fraction2 * perimeter / edgeLength2, 0.0f, 1.0f);
|
||||||
|
float fraction3 = fraction2 - edgeLength2 / perimeter;
|
||||||
|
float fractionEdge3 = glm::clamp(fraction3 * perimeter / edgeLength3, 0.0f, 1.0f);
|
||||||
|
|
||||||
|
float dim = importanceSample2DDimension(emitRadiusStart);
|
||||||
|
triangle = triangle * (glm::scale(emitDimensions) * triangleInfo.transform);
|
||||||
|
glm::vec3 center = (triangle.v0 + triangle.v1 + triangle.v2) / 3.0f;
|
||||||
|
glm::vec3 v0 = (dim * (triangle.v0 - center)) + center;
|
||||||
|
glm::vec3 v1 = (dim * (triangle.v1 - center)) + center;
|
||||||
|
glm::vec3 v2 = (dim * (triangle.v2 - center)) + center;
|
||||||
|
|
||||||
|
emitPosition = glm::mix(v0, glm::mix(v1, glm::mix(v2, v0, fractionEdge3), fractionEdge2), fractionEdge1);
|
||||||
|
emitDirection = triangle.getNormal();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
case SHAPE_TYPE_SPHERE:
|
||||||
|
case SHAPE_TYPE_ELLIPSOID:
|
||||||
|
default: {
|
||||||
|
glm::vec3 radii = importanceSample3DDimension(emitRadiusStart) * 0.5f * emitDimensions;
|
||||||
|
float x = radii.x * glm::cos(elevation) * glm::cos(azimuth);
|
||||||
|
float y = radii.y * glm::cos(elevation) * glm::sin(azimuth);
|
||||||
|
float z = radii.z * glm::sin(elevation);
|
||||||
|
emitPosition = glm::vec3(x, y, z);
|
||||||
|
emitDirection = glm::normalize(glm::vec3(radii.x > 0.0f ? x / (radii.x * radii.x) : 0.0f,
|
||||||
|
radii.y > 0.0f ? y / (radii.y * radii.y) : 0.0f,
|
||||||
|
radii.z > 0.0f ? z / (radii.z * radii.z) : 0.0f));
|
||||||
|
break;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
glm::vec3 radii = radiusScale * 0.5f * emitDimensions;
|
|
||||||
float x = radii.x * glm::cos(elevation) * glm::cos(azimuth);
|
|
||||||
float y = radii.y * glm::cos(elevation) * glm::sin(azimuth);
|
|
||||||
float z = radii.z * glm::sin(elevation);
|
|
||||||
glm::vec3 emitPosition = glm::vec3(x, y, z);
|
|
||||||
emitDirection = glm::normalize(glm::vec3(
|
|
||||||
radii.x > 0.0f ? x / (radii.x * radii.x) : 0.0f,
|
|
||||||
radii.y > 0.0f ? y / (radii.y * radii.y) : 0.0f,
|
|
||||||
radii.z > 0.0f ? z / (radii.z * radii.z) : 0.0f
|
|
||||||
));
|
|
||||||
particle.relativePosition += emitOrientation * emitPosition;
|
particle.relativePosition += emitOrientation * emitPosition;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -267,20 +402,28 @@ void ParticleEffectEntityRenderer::stepSimulation() {
|
||||||
const auto now = usecTimestampNow();
|
const auto now = usecTimestampNow();
|
||||||
const auto interval = std::min<uint64_t>(USECS_PER_SECOND / 60, now - _lastSimulated);
|
const auto interval = std::min<uint64_t>(USECS_PER_SECOND / 60, now - _lastSimulated);
|
||||||
_lastSimulated = now;
|
_lastSimulated = now;
|
||||||
|
|
||||||
particle::Properties particleProperties;
|
particle::Properties particleProperties;
|
||||||
withReadLock([&]{
|
ShapeType shapeType;
|
||||||
|
GeometryResource::Pointer geometryResource;
|
||||||
|
withReadLock([&] {
|
||||||
particleProperties = _particleProperties;
|
particleProperties = _particleProperties;
|
||||||
|
shapeType = _shapeType;
|
||||||
|
geometryResource = _geometryResource;
|
||||||
});
|
});
|
||||||
|
|
||||||
const auto& modelTransform = getModelTransform();
|
const auto& modelTransform = getModelTransform();
|
||||||
if (_emitting && particleProperties.emitting()) {
|
if (_emitting && particleProperties.emitting() &&
|
||||||
|
(shapeType != SHAPE_TYPE_COMPOUND || (geometryResource && geometryResource->isLoaded()))) {
|
||||||
uint64_t emitInterval = particleProperties.emitIntervalUsecs();
|
uint64_t emitInterval = particleProperties.emitIntervalUsecs();
|
||||||
if (emitInterval > 0 && interval >= _timeUntilNextEmit) {
|
if (emitInterval > 0 && interval >= _timeUntilNextEmit) {
|
||||||
auto timeRemaining = interval;
|
auto timeRemaining = interval;
|
||||||
while (timeRemaining > _timeUntilNextEmit) {
|
while (timeRemaining > _timeUntilNextEmit) {
|
||||||
|
if (_shapeType == SHAPE_TYPE_COMPOUND && !_hasComputedTriangles) {
|
||||||
|
computeTriangles(geometryResource->getHFMModel());
|
||||||
|
}
|
||||||
// emit particle
|
// emit particle
|
||||||
_cpuParticles.push_back(createParticle(now, modelTransform, particleProperties));
|
_cpuParticles.push_back(createParticle(now, modelTransform, particleProperties, shapeType, geometryResource, _triangleInfo));
|
||||||
_timeUntilNextEmit = emitInterval;
|
_timeUntilNextEmit = emitInterval;
|
||||||
if (emitInterval < timeRemaining) {
|
if (emitInterval < timeRemaining) {
|
||||||
timeRemaining -= emitInterval;
|
timeRemaining -= emitInterval;
|
||||||
|
@ -297,7 +440,7 @@ void ParticleEffectEntityRenderer::stepSimulation() {
|
||||||
}
|
}
|
||||||
|
|
||||||
const float deltaTime = (float)interval / (float)USECS_PER_SECOND;
|
const float deltaTime = (float)interval / (float)USECS_PER_SECOND;
|
||||||
// update the particles
|
// update the particles
|
||||||
for (auto& particle : _cpuParticles) {
|
for (auto& particle : _cpuParticles) {
|
||||||
if (_prevEmitterShouldTrail != particleProperties.emission.shouldTrail) {
|
if (_prevEmitterShouldTrail != particleProperties.emission.shouldTrail) {
|
||||||
if (_prevEmitterShouldTrail) {
|
if (_prevEmitterShouldTrail) {
|
||||||
|
@ -313,7 +456,7 @@ void ParticleEffectEntityRenderer::stepSimulation() {
|
||||||
static GpuParticles gpuParticles;
|
static GpuParticles gpuParticles;
|
||||||
gpuParticles.clear();
|
gpuParticles.clear();
|
||||||
gpuParticles.reserve(_cpuParticles.size()); // Reserve space
|
gpuParticles.reserve(_cpuParticles.size()); // Reserve space
|
||||||
std::transform(_cpuParticles.begin(), _cpuParticles.end(), std::back_inserter(gpuParticles), [&particleProperties, &modelTransform](const CpuParticle& particle) {
|
std::transform(_cpuParticles.begin(), _cpuParticles.end(), std::back_inserter(gpuParticles), [&particleProperties, &modelTransform] (const CpuParticle& particle) {
|
||||||
glm::vec3 position = particle.relativePosition + (particleProperties.emission.shouldTrail ? particle.basePosition : modelTransform.getTranslation());
|
glm::vec3 position = particle.relativePosition + (particleProperties.emission.shouldTrail ? particle.basePosition : modelTransform.getTranslation());
|
||||||
return GpuParticle(position, glm::vec2(particle.lifetime, particle.seed));
|
return GpuParticle(position, glm::vec2(particle.lifetime, particle.seed));
|
||||||
});
|
});
|
||||||
|
@ -356,5 +499,131 @@ void ParticleEffectEntityRenderer::doRender(RenderArgs* args) {
|
||||||
batch.setInputBuffer(0, _particleBuffer, 0, sizeof(GpuParticle));
|
batch.setInputBuffer(0, _particleBuffer, 0, sizeof(GpuParticle));
|
||||||
|
|
||||||
auto numParticles = _particleBuffer->getSize() / sizeof(GpuParticle);
|
auto numParticles = _particleBuffer->getSize() / sizeof(GpuParticle);
|
||||||
|
static const size_t VERTEX_PER_PARTICLE = 4;
|
||||||
batch.drawInstanced((gpu::uint32)numParticles, gpu::TRIANGLE_STRIP, (gpu::uint32)VERTEX_PER_PARTICLE);
|
batch.drawInstanced((gpu::uint32)numParticles, gpu::TRIANGLE_STRIP, (gpu::uint32)VERTEX_PER_PARTICLE);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void ParticleEffectEntityRenderer::fetchGeometryResource() {
|
||||||
|
QUrl hullURL(_compoundShapeURL);
|
||||||
|
if (hullURL.isEmpty()) {
|
||||||
|
_geometryResource.reset();
|
||||||
|
} else {
|
||||||
|
_geometryResource = DependencyManager::get<ModelCache>()->getCollisionGeometryResource(hullURL);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// FIXME: this is very similar to Model::calculateTriangleSets
|
||||||
|
void ParticleEffectEntityRenderer::computeTriangles(const hfm::Model& hfmModel) {
|
||||||
|
PROFILE_RANGE(render, __FUNCTION__);
|
||||||
|
|
||||||
|
int numberOfMeshes = hfmModel.meshes.size();
|
||||||
|
|
||||||
|
_hasComputedTriangles = true;
|
||||||
|
_triangleInfo.triangles.clear();
|
||||||
|
_triangleInfo.samplesPerTriangle.clear();
|
||||||
|
|
||||||
|
std::vector<float> areas;
|
||||||
|
float minArea = FLT_MAX;
|
||||||
|
AABox bounds;
|
||||||
|
|
||||||
|
for (int i = 0; i < numberOfMeshes; i++) {
|
||||||
|
const HFMMesh& mesh = hfmModel.meshes.at(i);
|
||||||
|
|
||||||
|
const int numberOfParts = mesh.parts.size();
|
||||||
|
for (int j = 0; j < numberOfParts; j++) {
|
||||||
|
const HFMMeshPart& part = mesh.parts.at(j);
|
||||||
|
|
||||||
|
const int INDICES_PER_TRIANGLE = 3;
|
||||||
|
const int INDICES_PER_QUAD = 4;
|
||||||
|
const int TRIANGLES_PER_QUAD = 2;
|
||||||
|
|
||||||
|
// tell our triangleSet how many triangles to expect.
|
||||||
|
int numberOfQuads = part.quadIndices.size() / INDICES_PER_QUAD;
|
||||||
|
int numberOfTris = part.triangleIndices.size() / INDICES_PER_TRIANGLE;
|
||||||
|
int totalTriangles = (numberOfQuads * TRIANGLES_PER_QUAD) + numberOfTris;
|
||||||
|
_triangleInfo.triangles.reserve(_triangleInfo.triangles.size() + totalTriangles);
|
||||||
|
areas.reserve(areas.size() + totalTriangles);
|
||||||
|
|
||||||
|
auto meshTransform = hfmModel.offset * mesh.modelTransform;
|
||||||
|
|
||||||
|
if (part.quadIndices.size() > 0) {
|
||||||
|
int vIndex = 0;
|
||||||
|
for (int q = 0; q < numberOfQuads; q++) {
|
||||||
|
int i0 = part.quadIndices[vIndex++];
|
||||||
|
int i1 = part.quadIndices[vIndex++];
|
||||||
|
int i2 = part.quadIndices[vIndex++];
|
||||||
|
int i3 = part.quadIndices[vIndex++];
|
||||||
|
|
||||||
|
// track the model space version... these points will be transformed by the FST's offset,
|
||||||
|
// which includes the scaling, rotation, and translation specified by the FST/FBX,
|
||||||
|
// this can't change at runtime, so we can safely store these in our TriangleSet
|
||||||
|
glm::vec3 v0 = glm::vec3(meshTransform * glm::vec4(mesh.vertices[i0], 1.0f));
|
||||||
|
glm::vec3 v1 = glm::vec3(meshTransform * glm::vec4(mesh.vertices[i1], 1.0f));
|
||||||
|
glm::vec3 v2 = glm::vec3(meshTransform * glm::vec4(mesh.vertices[i2], 1.0f));
|
||||||
|
glm::vec3 v3 = glm::vec3(meshTransform * glm::vec4(mesh.vertices[i3], 1.0f));
|
||||||
|
|
||||||
|
Triangle tri1 = { v0, v1, v3 };
|
||||||
|
Triangle tri2 = { v1, v2, v3 };
|
||||||
|
_triangleInfo.triangles.push_back(tri1);
|
||||||
|
_triangleInfo.triangles.push_back(tri2);
|
||||||
|
|
||||||
|
float area1 = tri1.getArea();
|
||||||
|
areas.push_back(area1);
|
||||||
|
if (area1 > EPSILON) {
|
||||||
|
minArea = std::min(minArea, area1);
|
||||||
|
}
|
||||||
|
|
||||||
|
float area2 = tri2.getArea();
|
||||||
|
areas.push_back(area2);
|
||||||
|
if (area2 > EPSILON) {
|
||||||
|
minArea = std::min(minArea, area2);
|
||||||
|
}
|
||||||
|
|
||||||
|
bounds += v0;
|
||||||
|
bounds += v1;
|
||||||
|
bounds += v2;
|
||||||
|
bounds += v3;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (part.triangleIndices.size() > 0) {
|
||||||
|
int vIndex = 0;
|
||||||
|
for (int t = 0; t < numberOfTris; t++) {
|
||||||
|
int i0 = part.triangleIndices[vIndex++];
|
||||||
|
int i1 = part.triangleIndices[vIndex++];
|
||||||
|
int i2 = part.triangleIndices[vIndex++];
|
||||||
|
|
||||||
|
// track the model space version... these points will be transformed by the FST's offset,
|
||||||
|
// which includes the scaling, rotation, and translation specified by the FST/FBX,
|
||||||
|
// this can't change at runtime, so we can safely store these in our TriangleSet
|
||||||
|
glm::vec3 v0 = glm::vec3(meshTransform * glm::vec4(mesh.vertices[i0], 1.0f));
|
||||||
|
glm::vec3 v1 = glm::vec3(meshTransform * glm::vec4(mesh.vertices[i1], 1.0f));
|
||||||
|
glm::vec3 v2 = glm::vec3(meshTransform * glm::vec4(mesh.vertices[i2], 1.0f));
|
||||||
|
|
||||||
|
Triangle tri = { v0, v1, v2 };
|
||||||
|
_triangleInfo.triangles.push_back(tri);
|
||||||
|
|
||||||
|
float area = tri.getArea();
|
||||||
|
areas.push_back(area);
|
||||||
|
if (area > EPSILON) {
|
||||||
|
minArea = std::min(minArea, area);
|
||||||
|
}
|
||||||
|
|
||||||
|
bounds += v0;
|
||||||
|
bounds += v1;
|
||||||
|
bounds += v2;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
_triangleInfo.totalSamples = 0;
|
||||||
|
for (auto& area : areas) {
|
||||||
|
size_t numSamples = area / minArea;
|
||||||
|
_triangleInfo.samplesPerTriangle.push_back(numSamples);
|
||||||
|
_triangleInfo.totalSamples += numSamples;
|
||||||
|
}
|
||||||
|
|
||||||
|
glm::vec3 scale = bounds.getScale();
|
||||||
|
_triangleInfo.transform = glm::scale(1.0f / scale) * glm::translate(-bounds.calcCenter());
|
||||||
|
}
|
|
@ -81,7 +81,18 @@ private:
|
||||||
glm::vec2 spare;
|
glm::vec2 spare;
|
||||||
};
|
};
|
||||||
|
|
||||||
static CpuParticle createParticle(uint64_t now, const Transform& baseTransform, const particle::Properties& particleProperties);
|
void computeTriangles(const hfm::Model& hfmModel);
|
||||||
|
bool _hasComputedTriangles{ false };
|
||||||
|
struct TriangleInfo {
|
||||||
|
std::vector<Triangle> triangles;
|
||||||
|
std::vector<size_t> samplesPerTriangle;
|
||||||
|
size_t totalSamples;
|
||||||
|
glm::mat4 transform;
|
||||||
|
} _triangleInfo;
|
||||||
|
|
||||||
|
static CpuParticle createParticle(uint64_t now, const Transform& baseTransform, const particle::Properties& particleProperties,
|
||||||
|
const ShapeType& shapeType, const GeometryResource::Pointer& geometryResource,
|
||||||
|
const TriangleInfo& triangleInfo);
|
||||||
void stepSimulation();
|
void stepSimulation();
|
||||||
|
|
||||||
particle::Properties _particleProperties;
|
particle::Properties _particleProperties;
|
||||||
|
@ -90,11 +101,16 @@ private:
|
||||||
CpuParticles _cpuParticles;
|
CpuParticles _cpuParticles;
|
||||||
bool _emitting { false };
|
bool _emitting { false };
|
||||||
uint64_t _timeUntilNextEmit { 0 };
|
uint64_t _timeUntilNextEmit { 0 };
|
||||||
BufferPointer _particleBuffer{ std::make_shared<Buffer>() };
|
BufferPointer _particleBuffer { std::make_shared<Buffer>() };
|
||||||
BufferView _uniformBuffer;
|
BufferView _uniformBuffer;
|
||||||
quint64 _lastSimulated { 0 };
|
quint64 _lastSimulated { 0 };
|
||||||
|
|
||||||
PulsePropertyGroup _pulseProperties;
|
PulsePropertyGroup _pulseProperties;
|
||||||
|
ShapeType _shapeType;
|
||||||
|
QString _compoundShapeURL;
|
||||||
|
|
||||||
|
void fetchGeometryResource();
|
||||||
|
GeometryResource::Pointer _geometryResource;
|
||||||
|
|
||||||
NetworkTexturePointer _networkTexture;
|
NetworkTexturePointer _networkTexture;
|
||||||
ScenePointer _scene;
|
ScenePointer _scene;
|
||||||
|
|
|
@ -127,6 +127,7 @@ void buildStringToShapeTypeLookup() {
|
||||||
addShapeType(SHAPE_TYPE_SIMPLE_COMPOUND);
|
addShapeType(SHAPE_TYPE_SIMPLE_COMPOUND);
|
||||||
addShapeType(SHAPE_TYPE_STATIC_MESH);
|
addShapeType(SHAPE_TYPE_STATIC_MESH);
|
||||||
addShapeType(SHAPE_TYPE_ELLIPSOID);
|
addShapeType(SHAPE_TYPE_ELLIPSOID);
|
||||||
|
addShapeType(SHAPE_TYPE_CIRCLE);
|
||||||
}
|
}
|
||||||
|
|
||||||
QHash<QString, MaterialMappingMode> stringToMaterialMappingModeLookup;
|
QHash<QString, MaterialMappingMode> stringToMaterialMappingModeLookup;
|
||||||
|
@ -1114,23 +1115,28 @@ EntityPropertyFlags EntityItemProperties::getChangedProperties() const {
|
||||||
* default, particles emit along the entity's local z-axis, and <code>azimuthStart</code> and <code>azimuthFinish</code>
|
* default, particles emit along the entity's local z-axis, and <code>azimuthStart</code> and <code>azimuthFinish</code>
|
||||||
* are relative to the entity's local x-axis. The default value is a rotation of -90 degrees about the local x-axis, i.e.,
|
* are relative to the entity's local x-axis. The default value is a rotation of -90 degrees about the local x-axis, i.e.,
|
||||||
* the particles emit vertically.
|
* the particles emit vertically.
|
||||||
* @property {Vec3} emitDimensions=0,0,0 - The dimensions of the ellipsoid from which particles are emitted.
|
* @property {Vec3} emitDimensions=0,0,0 - The dimensions of the shape from which particles are emitted. The shape is specified with
|
||||||
* @property {number} emitRadiusStart=1 - The starting radius within the ellipsoid at which particles start being emitted;
|
* <code>shapeType</code>.
|
||||||
* range <code>0.0</code> – <code>1.0</code> for the ellipsoid center to the ellipsoid surface, respectively.
|
* @property {number} emitRadiusStart=1 - The starting radius within the shape at which particles start being emitted;
|
||||||
* Particles are emitted from the portion of the ellipsoid that lies between <code>emitRadiusStart</code> and the
|
* range <code>0.0</code> – <code>1.0</code> for the center to the surface, respectively.
|
||||||
* ellipsoid's surface.
|
* Particles are emitted from the portion of the shape that lies between <code>emitRadiusStart</code> and the
|
||||||
|
* shape's surface.
|
||||||
* @property {number} polarStart=0 - The angle in radians from the entity's local z-axis at which particles start being emitted
|
* @property {number} polarStart=0 - The angle in radians from the entity's local z-axis at which particles start being emitted
|
||||||
* within the ellipsoid; range <code>0</code> – <code>Math.PI</code>. Particles are emitted from the portion of the
|
* within the shape; range <code>0</code> – <code>Math.PI</code>. Particles are emitted from the portion of the
|
||||||
* ellipsoid that lies between <code>polarStart<code> and <code>polarFinish</code>.
|
* shape that lies between <code>polarStart<code> and <code>polarFinish</code>. Only used if <code>shapeType</code> is
|
||||||
|
* <code>ellipsoid</code> or <code>sphere</code>.
|
||||||
* @property {number} polarFinish=0 - The angle in radians from the entity's local z-axis at which particles stop being emitted
|
* @property {number} polarFinish=0 - The angle in radians from the entity's local z-axis at which particles stop being emitted
|
||||||
* within the ellipsoid; range <code>0</code> – <code>Math.PI</code>. Particles are emitted from the portion of the
|
* within the shape; range <code>0</code> – <code>Math.PI</code>. Particles are emitted from the portion of the
|
||||||
* ellipsoid that lies between <code>polarStart<code> and <code>polarFinish</code>.
|
* shape that lies between <code>polarStart<code> and <code>polarFinish</code>. Only used if <code>shapeType</code> is
|
||||||
|
* <code>ellipsoid</code> or <code>sphere</code>.
|
||||||
* @property {number} azimuthStart=-Math.PI - The angle in radians from the entity's local x-axis about the entity's local
|
* @property {number} azimuthStart=-Math.PI - The angle in radians from the entity's local x-axis about the entity's local
|
||||||
* z-axis at which particles start being emitted; range <code>-Math.PI</code> – <code>Math.PI</code>. Particles are
|
* z-axis at which particles start being emitted; range <code>-Math.PI</code> – <code>Math.PI</code>. Particles are
|
||||||
* emitted from the portion of the ellipsoid that lies between <code>azimuthStart<code> and <code>azimuthFinish</code>.
|
* emitted from the portion of the shape that lies between <code>azimuthStart<code> and <code>azimuthFinish</code>.
|
||||||
|
* Only used if <code>shapeType</code> is <code>ellipsoid</code>, <code>sphere</code>, or <code>circle</code>.
|
||||||
* @property {number} azimuthFinish=Math.PI - The angle in radians from the entity's local x-axis about the entity's local
|
* @property {number} azimuthFinish=Math.PI - The angle in radians from the entity's local x-axis about the entity's local
|
||||||
* z-axis at which particles stop being emitted; range <code>-Math.PI</code> – <code>Math.PI</code>. Particles are
|
* z-axis at which particles stop being emitted; range <code>-Math.PI</code> – <code>Math.PI</code>. Particles are
|
||||||
* emitted from the portion of the ellipsoid that lies between <code>azimuthStart<code> and <code>azimuthFinish</code>.
|
* emitted from the portion of the shape that lies between <code>azimuthStart<code> and <code>azimuthFinish</code>.
|
||||||
|
* Only used if <code>shapeType</code> is <code>ellipsoid</code>, <code>sphere</code>, or <code>circle</code>..
|
||||||
*
|
*
|
||||||
* @property {string} textures="" - The URL of a JPG or PNG image file to display for each particle. If you want transparency,
|
* @property {string} textures="" - The URL of a JPG or PNG image file to display for each particle. If you want transparency,
|
||||||
* use PNG format.
|
* use PNG format.
|
||||||
|
@ -1170,7 +1176,9 @@ EntityPropertyFlags EntityItemProperties::getChangedProperties() const {
|
||||||
* up in the world. If true, they will point towards the entity's up vector, based on its orientation.
|
* up in the world. If true, they will point towards the entity's up vector, based on its orientation.
|
||||||
* @property {Entities.Pulse} pulse - The pulse-related properties. Deprecated.
|
* @property {Entities.Pulse} pulse - The pulse-related properties. Deprecated.
|
||||||
*
|
*
|
||||||
* @property {ShapeType} shapeType="none" - <em>Currently not used.</em> <em>Read-only.</em>
|
* @property {ShapeType} shapeType="ellipsoid" - The shape of the collision hull used if collisions are enabled.
|
||||||
|
* @property {string} compoundShapeURL="" - The model file to use for the compound shape if <code>shapeType</code> is
|
||||||
|
* <code>"compound"</code>.
|
||||||
*
|
*
|
||||||
* @example <caption>Create a ball of green smoke.</caption>
|
* @example <caption>Create a ball of green smoke.</caption>
|
||||||
* particles = Entities.addEntity({
|
* particles = Entities.addEntity({
|
||||||
|
@ -1658,6 +1666,7 @@ QScriptValue EntityItemProperties::copyToScriptValue(QScriptEngine* engine, bool
|
||||||
// Particles only
|
// Particles only
|
||||||
if (_type == EntityTypes::ParticleEffect) {
|
if (_type == EntityTypes::ParticleEffect) {
|
||||||
COPY_PROPERTY_TO_QSCRIPTVALUE_GETTER(PROP_SHAPE_TYPE, shapeType, getShapeTypeAsString());
|
COPY_PROPERTY_TO_QSCRIPTVALUE_GETTER(PROP_SHAPE_TYPE, shapeType, getShapeTypeAsString());
|
||||||
|
COPY_PROPERTY_TO_QSCRIPTVALUE(PROP_COMPOUND_SHAPE_URL, compoundShapeURL);
|
||||||
COPY_PROPERTY_TO_QSCRIPTVALUE_TYPED(PROP_COLOR, color, u8vec3Color);
|
COPY_PROPERTY_TO_QSCRIPTVALUE_TYPED(PROP_COLOR, color, u8vec3Color);
|
||||||
COPY_PROPERTY_TO_QSCRIPTVALUE(PROP_ALPHA, alpha);
|
COPY_PROPERTY_TO_QSCRIPTVALUE(PROP_ALPHA, alpha);
|
||||||
_pulse.copyToScriptValue(_desiredProperties, properties, engine, skipDefaults, defaultEntityProperties);
|
_pulse.copyToScriptValue(_desiredProperties, properties, engine, skipDefaults, defaultEntityProperties);
|
||||||
|
@ -3104,6 +3113,7 @@ OctreeElement::AppendState EntityItemProperties::encodeEntityEditPacket(PacketTy
|
||||||
|
|
||||||
if (properties.getType() == EntityTypes::ParticleEffect) {
|
if (properties.getType() == EntityTypes::ParticleEffect) {
|
||||||
APPEND_ENTITY_PROPERTY(PROP_SHAPE_TYPE, (uint32_t)(properties.getShapeType()));
|
APPEND_ENTITY_PROPERTY(PROP_SHAPE_TYPE, (uint32_t)(properties.getShapeType()));
|
||||||
|
APPEND_ENTITY_PROPERTY(PROP_COMPOUND_SHAPE_URL, properties.getCompoundShapeURL());
|
||||||
APPEND_ENTITY_PROPERTY(PROP_COLOR, properties.getColor());
|
APPEND_ENTITY_PROPERTY(PROP_COLOR, properties.getColor());
|
||||||
APPEND_ENTITY_PROPERTY(PROP_ALPHA, properties.getAlpha());
|
APPEND_ENTITY_PROPERTY(PROP_ALPHA, properties.getAlpha());
|
||||||
_staticPulse.setProperties(properties);
|
_staticPulse.setProperties(properties);
|
||||||
|
@ -3584,6 +3594,7 @@ bool EntityItemProperties::decodeEntityEditPacket(const unsigned char* data, int
|
||||||
|
|
||||||
if (properties.getType() == EntityTypes::ParticleEffect) {
|
if (properties.getType() == EntityTypes::ParticleEffect) {
|
||||||
READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_SHAPE_TYPE, ShapeType, setShapeType);
|
READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_SHAPE_TYPE, ShapeType, setShapeType);
|
||||||
|
READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_COMPOUND_SHAPE_URL, QString, setCompoundShapeURL);
|
||||||
READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_COLOR, u8vec3Color, setColor);
|
READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_COLOR, u8vec3Color, setColor);
|
||||||
READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_ALPHA, float, setAlpha);
|
READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_ALPHA, float, setAlpha);
|
||||||
properties.getPulse().decodeFromEditPacket(propertyFlags, dataAt, processedBytes);
|
properties.getPulse().decodeFromEditPacket(propertyFlags, dataAt, processedBytes);
|
||||||
|
|
|
@ -49,8 +49,6 @@ class LineEntityItem : public EntityItem {
|
||||||
|
|
||||||
QVector<glm::vec3> getLinePoints() const;
|
QVector<glm::vec3> getLinePoints() const;
|
||||||
|
|
||||||
virtual ShapeType getShapeType() const override { return SHAPE_TYPE_NONE; }
|
|
||||||
|
|
||||||
// never have a ray intersection pick a LineEntityItem.
|
// never have a ray intersection pick a LineEntityItem.
|
||||||
virtual bool supportsDetailedIntersection() const override { return true; }
|
virtual bool supportsDetailedIntersection() const override { return true; }
|
||||||
virtual bool findDetailedRayIntersection(const glm::vec3& origin, const glm::vec3& direction,
|
virtual bool findDetailedRayIntersection(const glm::vec3& origin, const glm::vec3& direction,
|
||||||
|
|
|
@ -175,7 +175,7 @@ protected:
|
||||||
|
|
||||||
QString _textures;
|
QString _textures;
|
||||||
|
|
||||||
ShapeType _shapeType = SHAPE_TYPE_NONE;
|
ShapeType _shapeType { SHAPE_TYPE_NONE };
|
||||||
|
|
||||||
private:
|
private:
|
||||||
uint64_t _lastAnimated{ 0 };
|
uint64_t _lastAnimated{ 0 };
|
||||||
|
|
|
@ -410,6 +410,7 @@ EntityItemProperties ParticleEffectEntityItem::getProperties(const EntityPropert
|
||||||
EntityItemProperties properties = EntityItem::getProperties(desiredProperties, allowEmptyDesiredProperties); // get the properties from our base class
|
EntityItemProperties properties = EntityItem::getProperties(desiredProperties, allowEmptyDesiredProperties); // get the properties from our base class
|
||||||
|
|
||||||
COPY_ENTITY_PROPERTY_TO_PROPERTIES(shapeType, getShapeType);
|
COPY_ENTITY_PROPERTY_TO_PROPERTIES(shapeType, getShapeType);
|
||||||
|
COPY_ENTITY_PROPERTY_TO_PROPERTIES(compoundShapeURL, getCompoundShapeURL);
|
||||||
COPY_ENTITY_PROPERTY_TO_PROPERTIES(color, getColor);
|
COPY_ENTITY_PROPERTY_TO_PROPERTIES(color, getColor);
|
||||||
COPY_ENTITY_PROPERTY_TO_PROPERTIES(alpha, getAlpha);
|
COPY_ENTITY_PROPERTY_TO_PROPERTIES(alpha, getAlpha);
|
||||||
withReadLock([&] {
|
withReadLock([&] {
|
||||||
|
@ -464,6 +465,7 @@ bool ParticleEffectEntityItem::setProperties(const EntityItemProperties& propert
|
||||||
bool somethingChanged = EntityItem::setProperties(properties); // set the properties in our base class
|
bool somethingChanged = EntityItem::setProperties(properties); // set the properties in our base class
|
||||||
|
|
||||||
SET_ENTITY_PROPERTY_FROM_PROPERTIES(shapeType, setShapeType);
|
SET_ENTITY_PROPERTY_FROM_PROPERTIES(shapeType, setShapeType);
|
||||||
|
SET_ENTITY_PROPERTY_FROM_PROPERTIES(compoundShapeURL, setCompoundShapeURL);
|
||||||
SET_ENTITY_PROPERTY_FROM_PROPERTIES(color, setColor);
|
SET_ENTITY_PROPERTY_FROM_PROPERTIES(color, setColor);
|
||||||
SET_ENTITY_PROPERTY_FROM_PROPERTIES(alpha, setAlpha);
|
SET_ENTITY_PROPERTY_FROM_PROPERTIES(alpha, setAlpha);
|
||||||
withWriteLock([&] {
|
withWriteLock([&] {
|
||||||
|
@ -540,6 +542,7 @@ int ParticleEffectEntityItem::readEntitySubclassDataFromBuffer(const unsigned ch
|
||||||
const unsigned char* dataAt = data;
|
const unsigned char* dataAt = data;
|
||||||
|
|
||||||
READ_ENTITY_PROPERTY(PROP_SHAPE_TYPE, ShapeType, setShapeType);
|
READ_ENTITY_PROPERTY(PROP_SHAPE_TYPE, ShapeType, setShapeType);
|
||||||
|
READ_ENTITY_PROPERTY(PROP_COMPOUND_SHAPE_URL, QString, setCompoundShapeURL);
|
||||||
READ_ENTITY_PROPERTY(PROP_COLOR, u8vec3Color, setColor);
|
READ_ENTITY_PROPERTY(PROP_COLOR, u8vec3Color, setColor);
|
||||||
READ_ENTITY_PROPERTY(PROP_ALPHA, float, setAlpha);
|
READ_ENTITY_PROPERTY(PROP_ALPHA, float, setAlpha);
|
||||||
withWriteLock([&] {
|
withWriteLock([&] {
|
||||||
|
@ -598,6 +601,7 @@ EntityPropertyFlags ParticleEffectEntityItem::getEntityProperties(EncodeBitstrea
|
||||||
EntityPropertyFlags requestedProperties = EntityItem::getEntityProperties(params);
|
EntityPropertyFlags requestedProperties = EntityItem::getEntityProperties(params);
|
||||||
|
|
||||||
requestedProperties += PROP_SHAPE_TYPE;
|
requestedProperties += PROP_SHAPE_TYPE;
|
||||||
|
requestedProperties += PROP_COMPOUND_SHAPE_URL;
|
||||||
requestedProperties += PROP_COLOR;
|
requestedProperties += PROP_COLOR;
|
||||||
requestedProperties += PROP_ALPHA;
|
requestedProperties += PROP_ALPHA;
|
||||||
requestedProperties += _pulseProperties.getEntityProperties(params);
|
requestedProperties += _pulseProperties.getEntityProperties(params);
|
||||||
|
@ -656,6 +660,7 @@ void ParticleEffectEntityItem::appendSubclassData(OctreePacketData* packetData,
|
||||||
|
|
||||||
bool successPropertyFits = true;
|
bool successPropertyFits = true;
|
||||||
APPEND_ENTITY_PROPERTY(PROP_SHAPE_TYPE, (uint32_t)getShapeType());
|
APPEND_ENTITY_PROPERTY(PROP_SHAPE_TYPE, (uint32_t)getShapeType());
|
||||||
|
APPEND_ENTITY_PROPERTY(PROP_COMPOUND_SHAPE_URL, getCompoundShapeURL());
|
||||||
APPEND_ENTITY_PROPERTY(PROP_COLOR, getColor());
|
APPEND_ENTITY_PROPERTY(PROP_COLOR, getColor());
|
||||||
APPEND_ENTITY_PROPERTY(PROP_ALPHA, getAlpha());
|
APPEND_ENTITY_PROPERTY(PROP_ALPHA, getAlpha());
|
||||||
withReadLock([&] {
|
withReadLock([&] {
|
||||||
|
@ -718,11 +723,42 @@ void ParticleEffectEntityItem::debugDump() const {
|
||||||
}
|
}
|
||||||
|
|
||||||
void ParticleEffectEntityItem::setShapeType(ShapeType type) {
|
void ParticleEffectEntityItem::setShapeType(ShapeType type) {
|
||||||
|
switch (type) {
|
||||||
|
case SHAPE_TYPE_NONE:
|
||||||
|
case SHAPE_TYPE_CAPSULE_X:
|
||||||
|
case SHAPE_TYPE_CAPSULE_Y:
|
||||||
|
case SHAPE_TYPE_CAPSULE_Z:
|
||||||
|
case SHAPE_TYPE_HULL:
|
||||||
|
case SHAPE_TYPE_SIMPLE_HULL:
|
||||||
|
case SHAPE_TYPE_SIMPLE_COMPOUND:
|
||||||
|
case SHAPE_TYPE_STATIC_MESH:
|
||||||
|
// these types are unsupported for ParticleEffectEntity
|
||||||
|
type = particle::DEFAULT_SHAPE_TYPE;
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
withWriteLock([&] {
|
withWriteLock([&] {
|
||||||
if (type != _shapeType) {
|
_shapeType = type;
|
||||||
_shapeType = type;
|
});
|
||||||
_flags |= Simulation::DIRTY_SHAPE | Simulation::DIRTY_MASS;
|
}
|
||||||
}
|
|
||||||
|
ShapeType ParticleEffectEntityItem::getShapeType() const {
|
||||||
|
return resultWithReadLock<ShapeType>([&] {
|
||||||
|
return _shapeType;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
void ParticleEffectEntityItem::setCompoundShapeURL(const QString& compoundShapeURL) {
|
||||||
|
withWriteLock([&] {
|
||||||
|
_compoundShapeURL = compoundShapeURL;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
QString ParticleEffectEntityItem::getCompoundShapeURL() const {
|
||||||
|
return resultWithReadLock<QString>([&] {
|
||||||
|
return _compoundShapeURL;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -79,6 +79,7 @@ namespace particle {
|
||||||
static const QString DEFAULT_TEXTURES = "";
|
static const QString DEFAULT_TEXTURES = "";
|
||||||
static const bool DEFAULT_EMITTER_SHOULD_TRAIL = false;
|
static const bool DEFAULT_EMITTER_SHOULD_TRAIL = false;
|
||||||
static const bool DEFAULT_ROTATE_WITH_ENTITY = false;
|
static const bool DEFAULT_ROTATE_WITH_ENTITY = false;
|
||||||
|
static const ShapeType DEFAULT_SHAPE_TYPE = ShapeType::SHAPE_TYPE_ELLIPSOID;
|
||||||
|
|
||||||
template <typename T>
|
template <typename T>
|
||||||
struct Range {
|
struct Range {
|
||||||
|
@ -255,7 +256,10 @@ public:
|
||||||
float getAlphaSpread() const { return _particleProperties.alpha.gradient.spread; }
|
float getAlphaSpread() const { return _particleProperties.alpha.gradient.spread; }
|
||||||
|
|
||||||
void setShapeType(ShapeType type) override;
|
void setShapeType(ShapeType type) override;
|
||||||
virtual ShapeType getShapeType() const override { return _shapeType; }
|
virtual ShapeType getShapeType() const override;
|
||||||
|
|
||||||
|
QString getCompoundShapeURL() const;
|
||||||
|
virtual void setCompoundShapeURL(const QString& url);
|
||||||
|
|
||||||
virtual void debugDump() const override;
|
virtual void debugDump() const override;
|
||||||
|
|
||||||
|
@ -349,7 +353,8 @@ protected:
|
||||||
PulsePropertyGroup _pulseProperties;
|
PulsePropertyGroup _pulseProperties;
|
||||||
bool _isEmitting { true };
|
bool _isEmitting { true };
|
||||||
|
|
||||||
ShapeType _shapeType { SHAPE_TYPE_NONE };
|
ShapeType _shapeType{ particle::DEFAULT_SHAPE_TYPE };
|
||||||
|
QString _compoundShapeURL { "" };
|
||||||
};
|
};
|
||||||
|
|
||||||
#endif // hifi_ParticleEffectEntityItem_h
|
#endif // hifi_ParticleEffectEntityItem_h
|
||||||
|
|
|
@ -112,7 +112,7 @@ protected:
|
||||||
//! This is SHAPE_TYPE_ELLIPSOID rather than SHAPE_TYPE_NONE to maintain
|
//! This is SHAPE_TYPE_ELLIPSOID rather than SHAPE_TYPE_NONE to maintain
|
||||||
//! prior functionality where new or unsupported shapes are treated as
|
//! prior functionality where new or unsupported shapes are treated as
|
||||||
//! ellipsoids.
|
//! ellipsoids.
|
||||||
ShapeType _collisionShapeType{ ShapeType::SHAPE_TYPE_ELLIPSOID };
|
ShapeType _collisionShapeType { ShapeType::SHAPE_TYPE_ELLIPSOID };
|
||||||
};
|
};
|
||||||
|
|
||||||
#endif // hifi_ShapeEntityItem_h
|
#endif // hifi_ShapeEntityItem_h
|
||||||
|
|
|
@ -13,7 +13,6 @@
|
||||||
|
|
||||||
#include <glm/gtx/transform.hpp>
|
#include <glm/gtx/transform.hpp>
|
||||||
#include <QDebug>
|
#include <QDebug>
|
||||||
#include <QUrlQuery>
|
|
||||||
|
|
||||||
#include <ByteCountCoding.h>
|
#include <ByteCountCoding.h>
|
||||||
|
|
||||||
|
@ -353,7 +352,7 @@ bool ZoneEntityItem::contains(const glm::vec3& point) const {
|
||||||
|
|
||||||
Extents meshExtents = hfmModel.getMeshExtents();
|
Extents meshExtents = hfmModel.getMeshExtents();
|
||||||
glm::vec3 meshExtentsDiagonal = meshExtents.maximum - meshExtents.minimum;
|
glm::vec3 meshExtentsDiagonal = meshExtents.maximum - meshExtents.minimum;
|
||||||
glm::vec3 offset = -meshExtents.minimum- (meshExtentsDiagonal * getRegistrationPoint());
|
glm::vec3 offset = -meshExtents.minimum - (meshExtentsDiagonal * getRegistrationPoint());
|
||||||
glm::vec3 scale(getScaledDimensions() / meshExtentsDiagonal);
|
glm::vec3 scale(getScaledDimensions() / meshExtentsDiagonal);
|
||||||
|
|
||||||
glm::mat4 hfmToEntityMatrix = glm::scale(scale) * glm::translate(offset);
|
glm::mat4 hfmToEntityMatrix = glm::scale(scale) * glm::translate(offset);
|
||||||
|
@ -463,9 +462,6 @@ void ZoneEntityItem::fetchCollisionGeometryResource() {
|
||||||
if (hullURL.isEmpty()) {
|
if (hullURL.isEmpty()) {
|
||||||
_shapeResource.reset();
|
_shapeResource.reset();
|
||||||
} else {
|
} else {
|
||||||
QUrlQuery queryArgs(hullURL);
|
|
||||||
queryArgs.addQueryItem("collision-hull", "");
|
|
||||||
hullURL.setQuery(queryArgs);
|
|
||||||
_shapeResource = DependencyManager::get<ModelCache>()->getCollisionGeometryResource(hullURL);
|
_shapeResource = DependencyManager::get<ModelCache>()->getCollisionGeometryResource(hullURL);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -133,7 +133,7 @@ protected:
|
||||||
KeyLightPropertyGroup _keyLightProperties;
|
KeyLightPropertyGroup _keyLightProperties;
|
||||||
AmbientLightPropertyGroup _ambientLightProperties;
|
AmbientLightPropertyGroup _ambientLightProperties;
|
||||||
|
|
||||||
ShapeType _shapeType = DEFAULT_SHAPE_TYPE;
|
ShapeType _shapeType { DEFAULT_SHAPE_TYPE };
|
||||||
QString _compoundShapeURL;
|
QString _compoundShapeURL;
|
||||||
|
|
||||||
// The following 3 values are the defaults for zone creation
|
// The following 3 values are the defaults for zone creation
|
||||||
|
|
|
@ -267,6 +267,7 @@ enum class EntityVersion : PacketVersion {
|
||||||
ReOrderParentIDProperties,
|
ReOrderParentIDProperties,
|
||||||
CertificateTypeProperty,
|
CertificateTypeProperty,
|
||||||
DisableWebMedia,
|
DisableWebMedia,
|
||||||
|
ParticleShapeType,
|
||||||
|
|
||||||
// Add new versions above here
|
// Add new versions above here
|
||||||
NUM_PACKET_TYPE,
|
NUM_PACKET_TYPE,
|
||||||
|
|
|
@ -358,6 +358,12 @@ glm::vec3 Triangle::getNormal() const {
|
||||||
return glm::normalize(glm::cross(edge1, edge2));
|
return glm::normalize(glm::cross(edge1, edge2));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
float Triangle::getArea() const {
|
||||||
|
glm::vec3 edge1 = v1 - v0;
|
||||||
|
glm::vec3 edge2 = v2 - v0;
|
||||||
|
return 0.5f * glm::length(glm::cross(edge1, edge2));
|
||||||
|
}
|
||||||
|
|
||||||
Triangle Triangle::operator*(const glm::mat4& transform) const {
|
Triangle Triangle::operator*(const glm::mat4& transform) const {
|
||||||
return {
|
return {
|
||||||
glm::vec3(transform * glm::vec4(v0, 1.0f)),
|
glm::vec3(transform * glm::vec4(v0, 1.0f)),
|
||||||
|
|
|
@ -125,6 +125,7 @@ public:
|
||||||
glm::vec3 v1;
|
glm::vec3 v1;
|
||||||
glm::vec3 v2;
|
glm::vec3 v2;
|
||||||
glm::vec3 getNormal() const;
|
glm::vec3 getNormal() const;
|
||||||
|
float getArea() const;
|
||||||
Triangle operator*(const glm::mat4& transform) const;
|
Triangle operator*(const glm::mat4& transform) const;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -227,6 +227,14 @@
|
||||||
"speedSpread": {
|
"speedSpread": {
|
||||||
"tooltip": "The spread in speeds at which particles are emitted at, resulting in a variety of speeds."
|
"tooltip": "The spread in speeds at which particles are emitted at, resulting in a variety of speeds."
|
||||||
},
|
},
|
||||||
|
"particleShapeType": {
|
||||||
|
"tooltip": "The shape of the surface from which to emit particles.",
|
||||||
|
"jsPropertyName": "shapeType"
|
||||||
|
},
|
||||||
|
"particleCompoundShapeURL": {
|
||||||
|
"tooltip": "The model file to use for the particle emitter if Shape Type is \"Use Compound Shape URL\".",
|
||||||
|
"jsPropertyName": "compoundShapeURL"
|
||||||
|
},
|
||||||
"emitDimensions": {
|
"emitDimensions": {
|
||||||
"tooltip": "The outer limit radius in dimensions that the particles can be emitted from."
|
"tooltip": "The outer limit radius in dimensions that the particles can be emitted from."
|
||||||
},
|
},
|
||||||
|
|
|
@ -807,6 +807,21 @@ const GROUPS = [
|
||||||
decimals: 2,
|
decimals: 2,
|
||||||
propertyID: "speedSpread",
|
propertyID: "speedSpread",
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
label: "Shape Type",
|
||||||
|
type: "dropdown",
|
||||||
|
options: { "box": "Box", "ellipsoid": "Ellipsoid",
|
||||||
|
"cylinder-y": "Cylinder", "circle": "Circle", "plane": "Plane",
|
||||||
|
"compound": "Use Compound Shape URL" },
|
||||||
|
propertyID: "particleShapeType",
|
||||||
|
propertyName: "shapeType",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: "Compound Shape URL",
|
||||||
|
type: "string",
|
||||||
|
propertyID: "particleCompoundShapeURL",
|
||||||
|
propertyName: "compoundShapeURL",
|
||||||
|
},
|
||||||
{
|
{
|
||||||
label: "Emit Dimensions",
|
label: "Emit Dimensions",
|
||||||
type: "vec3",
|
type: "vec3",
|
||||||
|
|
Loading…
Reference in a new issue