From 3ff0770441a2ce24063063eff9b07264715bc5f9 Mon Sep 17 00:00:00 2001 From: SamGondelman Date: Wed, 20 Mar 2019 21:14:54 -0700 Subject: [PATCH] model emitters! --- .../RenderableParticleEffectEntityItem.cpp | 173 +++++++++++++++++- .../src/RenderableParticleEffectEntityItem.h | 12 +- .../entities/src/EntityItemProperties.cpp | 21 ++- libraries/entities/src/LineEntityItem.h | 2 - libraries/entities/src/ModelEntityItem.h | 2 +- libraries/entities/src/ShapeEntityItem.h | 2 +- libraries/entities/src/ZoneEntityItem.cpp | 2 +- libraries/entities/src/ZoneEntityItem.h | 2 +- libraries/shared/src/GeometryUtil.cpp | 6 + libraries/shared/src/GeometryUtil.h | 1 + .../system/assets/data/createAppTooltips.json | 8 + scripts/system/html/js/entityProperties.js | 15 ++ 12 files changed, 222 insertions(+), 24 deletions(-) diff --git a/libraries/entities-renderer/src/RenderableParticleEffectEntityItem.cpp b/libraries/entities-renderer/src/RenderableParticleEffectEntityItem.cpp index 8883108f75..d517ecd026 100644 --- a/libraries/entities-renderer/src/RenderableParticleEffectEntityItem.cpp +++ b/libraries/entities-renderer/src/RenderableParticleEffectEntityItem.cpp @@ -1,4 +1,4 @@ -// +// // RenderableParticleEffectEntityItem.cpp // interface/src // @@ -14,6 +14,8 @@ #include #include +#include + using namespace render; using namespace render::entities; @@ -111,6 +113,7 @@ void ParticleEffectEntityRenderer::doRenderUpdateSynchronousTyped(const ScenePoi QString compoundShapeURL = entity->getCompoundShapeURL(); if (_compoundShapeURL != compoundShapeURL) { _compoundShapeURL = compoundShapeURL; + _hasComputedTriangles = false; fetchGeometryResource(); } }); @@ -217,7 +220,8 @@ float importanceSample3DDimension(float startDim) { } ParticleEffectEntityRenderer::CpuParticle ParticleEffectEntityRenderer::createParticle(uint64_t now, const Transform& baseTransform, const particle::Properties& particleProperties, - const ShapeType& shapeType, const GeometryResource::Pointer& geometryResource) { + const ShapeType& shapeType, const GeometryResource::Pointer& geometryResource, + const TriangleInfo& triangleInfo) { CpuParticle particle; const auto& accelerationSpread = particleProperties.emission.acceleration.spread; @@ -259,7 +263,7 @@ ParticleEffectEntityRenderer::CpuParticle ParticleEffectEntityRenderer::createPa } else { azimuth = azimuthStart + (TWO_PI + azimuthFinish - azimuthStart) * randFloat(); } - // TODO: azimuth and elevation are only used for ellipsoids, but could be used for other shapes too + // TODO: azimuth and elevation are only used for ellipsoids/circles, but could be used for other shapes too if (emitDimensions == Vectors::ZERO) { // Point @@ -301,7 +305,7 @@ ParticleEffectEntityRenderer::CpuParticle ParticleEffectEntityRenderer::createPa break; } - case SHAPE_TYPE_CIRCLE: { // FIXME: SHAPE_TYPE_CIRCLE is not exposed to scripts in buildStringToShapeTypeLookup() + 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); @@ -326,7 +330,43 @@ ParticleEffectEntityRenderer::CpuParticle ParticleEffectEntityRenderer::createPa break; } - case SHAPE_TYPE_COMPOUND: + 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: @@ -374,13 +414,16 @@ void ParticleEffectEntityRenderer::stepSimulation() { const auto& modelTransform = getModelTransform(); if (_emitting && particleProperties.emitting() && - (_shapeType != SHAPE_TYPE_COMPOUND || (_geometryResource && _geometryResource->isLoaded()))) { + (shapeType != SHAPE_TYPE_COMPOUND || (geometryResource && geometryResource->isLoaded()))) { uint64_t emitInterval = particleProperties.emitIntervalUsecs(); if (emitInterval > 0 && interval >= _timeUntilNextEmit) { auto timeRemaining = interval; while (timeRemaining > _timeUntilNextEmit) { + if (_shapeType == SHAPE_TYPE_COMPOUND && !_hasComputedTriangles) { + computeTriangles(geometryResource->getHFMModel()); + } // emit particle - _cpuParticles.push_back(createParticle(now, modelTransform, particleProperties, shapeType, geometryResource)); + _cpuParticles.push_back(createParticle(now, modelTransform, particleProperties, shapeType, geometryResource, _triangleInfo)); _timeUntilNextEmit = emitInterval; if (emitInterval < timeRemaining) { timeRemaining -= emitInterval; @@ -467,4 +510,120 @@ void ParticleEffectEntityRenderer::fetchGeometryResource() { } else { _geometryResource = DependencyManager::get()->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 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()); } \ No newline at end of file diff --git a/libraries/entities-renderer/src/RenderableParticleEffectEntityItem.h b/libraries/entities-renderer/src/RenderableParticleEffectEntityItem.h index 4a4e5e5cbc..d13c966e96 100644 --- a/libraries/entities-renderer/src/RenderableParticleEffectEntityItem.h +++ b/libraries/entities-renderer/src/RenderableParticleEffectEntityItem.h @@ -81,8 +81,18 @@ private: glm::vec2 spare; }; + void computeTriangles(const hfm::Model& hfmModel); + bool _hasComputedTriangles{ false }; + struct TriangleInfo { + std::vector triangles; + std::vector 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 ShapeType& shapeType, const GeometryResource::Pointer& geometryResource, + const TriangleInfo& triangleInfo); void stepSimulation(); particle::Properties _particleProperties; diff --git a/libraries/entities/src/EntityItemProperties.cpp b/libraries/entities/src/EntityItemProperties.cpp index 0a6875b63d..5958af66dd 100644 --- a/libraries/entities/src/EntityItemProperties.cpp +++ b/libraries/entities/src/EntityItemProperties.cpp @@ -127,6 +127,7 @@ void buildStringToShapeTypeLookup() { addShapeType(SHAPE_TYPE_SIMPLE_COMPOUND); addShapeType(SHAPE_TYPE_STATIC_MESH); addShapeType(SHAPE_TYPE_ELLIPSOID); + addShapeType(SHAPE_TYPE_CIRCLE); } QHash stringToMaterialMappingModeLookup; @@ -1121,21 +1122,21 @@ EntityPropertyFlags EntityItemProperties::getChangedProperties() const { * Particles are emitted from the portion of the shape that lies between emitRadiusStart 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 - * within the ellipsoid; range 0Math.PI. Particles are emitted from the portion of the - * ellipsoid that lies between polarStart and polarFinish. Only used if shapeType is - * ellipsoid. + * within the shape; range 0Math.PI. Particles are emitted from the portion of the + * shape that lies between polarStart and polarFinish. Only used if shapeType is + * ellipsoid or sphere. * @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 0Math.PI. Particles are emitted from the portion of the - * ellipsoid that lies between polarStart and polarFinish. Only used if shapeType is - * ellipsoid. + * within the shape; range 0Math.PI. Particles are emitted from the portion of the + * shape that lies between polarStart and polarFinish. Only used if shapeType is + * ellipsoid or sphere. * @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 -Math.PIMath.PI. Particles are - * emitted from the portion of the ellipsoid that lies between azimuthStart and azimuthFinish. - * Only used if shapeType is ellipsoid. + * emitted from the portion of the shape that lies between azimuthStart and azimuthFinish. + * Only used if shapeType is ellipsoid, sphere, or circle. * @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 -Math.PIMath.PI. Particles are - * emitted from the portion of the ellipsoid that lies between azimuthStart and azimuthFinish. - * Only used if shapeType is ellipsoid. + * emitted from the portion of the shape that lies between azimuthStart and azimuthFinish. + * Only used if shapeType is ellipsoid, sphere, or circle.. * * @property {string} textures="" - The URL of a JPG or PNG image file to display for each particle. If you want transparency, * use PNG format. diff --git a/libraries/entities/src/LineEntityItem.h b/libraries/entities/src/LineEntityItem.h index 86ca6065bb..098183299f 100644 --- a/libraries/entities/src/LineEntityItem.h +++ b/libraries/entities/src/LineEntityItem.h @@ -49,8 +49,6 @@ class LineEntityItem : public EntityItem { QVector getLinePoints() const; - virtual ShapeType getShapeType() const override { return SHAPE_TYPE_NONE; } - // never have a ray intersection pick a LineEntityItem. virtual bool supportsDetailedIntersection() const override { return true; } virtual bool findDetailedRayIntersection(const glm::vec3& origin, const glm::vec3& direction, diff --git a/libraries/entities/src/ModelEntityItem.h b/libraries/entities/src/ModelEntityItem.h index 08468617ba..8f5e24ad76 100644 --- a/libraries/entities/src/ModelEntityItem.h +++ b/libraries/entities/src/ModelEntityItem.h @@ -175,7 +175,7 @@ protected: QString _textures; - ShapeType _shapeType = SHAPE_TYPE_NONE; + ShapeType _shapeType { SHAPE_TYPE_NONE } ; private: uint64_t _lastAnimated{ 0 }; diff --git a/libraries/entities/src/ShapeEntityItem.h b/libraries/entities/src/ShapeEntityItem.h index 363a7f39d1..fc590e06a4 100644 --- a/libraries/entities/src/ShapeEntityItem.h +++ b/libraries/entities/src/ShapeEntityItem.h @@ -112,7 +112,7 @@ protected: //! This is SHAPE_TYPE_ELLIPSOID rather than SHAPE_TYPE_NONE to maintain //! prior functionality where new or unsupported shapes are treated as //! ellipsoids. - ShapeType _collisionShapeType{ ShapeType::SHAPE_TYPE_ELLIPSOID }; + ShapeType _collisionShapeType { ShapeType::SHAPE_TYPE_ELLIPSOID }; }; #endif // hifi_ShapeEntityItem_h diff --git a/libraries/entities/src/ZoneEntityItem.cpp b/libraries/entities/src/ZoneEntityItem.cpp index f243d59da0..0771d9ad54 100644 --- a/libraries/entities/src/ZoneEntityItem.cpp +++ b/libraries/entities/src/ZoneEntityItem.cpp @@ -352,7 +352,7 @@ bool ZoneEntityItem::contains(const glm::vec3& point) const { Extents meshExtents = hfmModel.getMeshExtents(); 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::mat4 hfmToEntityMatrix = glm::scale(scale) * glm::translate(offset); diff --git a/libraries/entities/src/ZoneEntityItem.h b/libraries/entities/src/ZoneEntityItem.h index df6ce50fd6..69e3227135 100644 --- a/libraries/entities/src/ZoneEntityItem.h +++ b/libraries/entities/src/ZoneEntityItem.h @@ -133,7 +133,7 @@ protected: KeyLightPropertyGroup _keyLightProperties; AmbientLightPropertyGroup _ambientLightProperties; - ShapeType _shapeType = DEFAULT_SHAPE_TYPE; + ShapeType _shapeType { DEFAULT_SHAPE_TYPE }; QString _compoundShapeURL; // The following 3 values are the defaults for zone creation diff --git a/libraries/shared/src/GeometryUtil.cpp b/libraries/shared/src/GeometryUtil.cpp index 3f2f7cd7fb..b6fca03403 100644 --- a/libraries/shared/src/GeometryUtil.cpp +++ b/libraries/shared/src/GeometryUtil.cpp @@ -358,6 +358,12 @@ glm::vec3 Triangle::getNormal() const { 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 { return { glm::vec3(transform * glm::vec4(v0, 1.0f)), diff --git a/libraries/shared/src/GeometryUtil.h b/libraries/shared/src/GeometryUtil.h index 8ec75f71bd..04c54fc32e 100644 --- a/libraries/shared/src/GeometryUtil.h +++ b/libraries/shared/src/GeometryUtil.h @@ -125,6 +125,7 @@ public: glm::vec3 v1; glm::vec3 v2; glm::vec3 getNormal() const; + float getArea() const; Triangle operator*(const glm::mat4& transform) const; }; diff --git a/scripts/system/assets/data/createAppTooltips.json b/scripts/system/assets/data/createAppTooltips.json index 7201cdecad..73c7504089 100644 --- a/scripts/system/assets/data/createAppTooltips.json +++ b/scripts/system/assets/data/createAppTooltips.json @@ -227,6 +227,14 @@ "speedSpread": { "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": { "tooltip": "The outer limit radius in dimensions that the particles can be emitted from." }, diff --git a/scripts/system/html/js/entityProperties.js b/scripts/system/html/js/entityProperties.js index f501df7933..15ab40e5ea 100644 --- a/scripts/system/html/js/entityProperties.js +++ b/scripts/system/html/js/entityProperties.js @@ -807,6 +807,21 @@ const GROUPS = [ decimals: 2, 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", type: "vec3",