mirror of
https://github.com/JulianGro/overte.git
synced 2025-04-13 22:27:13 +02:00
model emitters!
This commit is contained in:
parent
38864e5b6e
commit
3ff0770441
12 changed files with 222 additions and 24 deletions
|
@ -1,4 +1,4 @@
|
|||
//
|
||||
//
|
||||
// RenderableParticleEffectEntityItem.cpp
|
||||
// interface/src
|
||||
//
|
||||
|
@ -14,6 +14,8 @@
|
|||
#include <GeometryCache.h>
|
||||
#include <shaders/Shaders.h>
|
||||
|
||||
#include <glm/gtx/transform.hpp>
|
||||
|
||||
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<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,8 +81,18 @@ private:
|
|||
glm::vec2 spare;
|
||||
};
|
||||
|
||||
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 ShapeType& shapeType, const GeometryResource::Pointer& geometryResource,
|
||||
const TriangleInfo& triangleInfo);
|
||||
void stepSimulation();
|
||||
|
||||
particle::Properties _particleProperties;
|
||||
|
|
|
@ -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<QString, MaterialMappingMode> stringToMaterialMappingModeLookup;
|
||||
|
@ -1121,21 +1122,21 @@ EntityPropertyFlags EntityItemProperties::getChangedProperties() const {
|
|||
* 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
|
||||
* within the ellipsoid; 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>. Only used if <code>shapeType</code> is
|
||||
* <code>ellipsoid</code>.
|
||||
* within the shape; range <code>0</code> – <code>Math.PI</code>. Particles are emitted from the portion of the
|
||||
* 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
|
||||
* within the ellipsoid; 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>. Only used if <code>shapeType</code> is
|
||||
* <code>ellipsoid</code>.
|
||||
* within the shape; range <code>0</code> – <code>Math.PI</code>. Particles are emitted from the portion of the
|
||||
* 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
|
||||
* 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>.
|
||||
* Only used if <code>shapeType</code> is <code>ellipsoid</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
|
||||
* 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>.
|
||||
* Only used if <code>shapeType</code> is <code>ellipsoid</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,
|
||||
* use PNG format.
|
||||
|
|
|
@ -49,8 +49,6 @@ class LineEntityItem : public EntityItem {
|
|||
|
||||
QVector<glm::vec3> 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,
|
||||
|
|
|
@ -175,7 +175,7 @@ protected:
|
|||
|
||||
QString _textures;
|
||||
|
||||
ShapeType _shapeType = SHAPE_TYPE_NONE;
|
||||
ShapeType _shapeType { SHAPE_TYPE_NONE } ;
|
||||
|
||||
private:
|
||||
uint64_t _lastAnimated{ 0 };
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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)),
|
||||
|
|
|
@ -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;
|
||||
};
|
||||
|
||||
|
|
|
@ -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."
|
||||
},
|
||||
|
|
|
@ -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",
|
||||
|
|
Loading…
Reference in a new issue