Particles: Removed emitters, added more full-featured position affectors

Bug: skia:
Change-Id: Ie6485a11bb57fecef470d727dcf3b4fe5dff0b90
Reviewed-on: https://skia-review.googlesource.com/c/195582
Reviewed-by: Brian Osman <brianosman@google.com>
Commit-Queue: Brian Osman <brianosman@google.com>
This commit is contained in:
Brian Osman 2019-02-28 15:48:05 -05:00 committed by Skia Commit-Bot
parent 09ff13c42d
commit 3d76d1bf46
14 changed files with 315 additions and 242 deletions

View File

@ -21,7 +21,6 @@ class SkCanvas;
class SkFieldVisitor;
class SkParticleAffector;
class SkParticleDrawable;
class SkParticleEmitter;
class SkParticleEffectParams : public SkRefCnt {
public:
@ -33,9 +32,6 @@ public:
// Drawable (image, sprite sheet, etc.)
sk_sp<SkParticleDrawable> fDrawable;
// Emitter shape & parameters
sk_sp<SkParticleEmitter> fEmitter;
// Rules that configure particles at spawn time
SkTArray<sk_sp<SkParticleAffector>> fSpawnAffectors;

View File

@ -1,30 +0,0 @@
/*
* Copyright 2019 Google LLC
*
* Use of this source code is governed by a BSD-style license that can be
* found in the LICENSE file.
*/
#ifndef SkParticleEmitter_DEFINED
#define SkParticleEmitter_DEFINED
#include "SkReflected.h"
struct SkParticlePose;
struct SkPoint;
class SkRandom;
class SkParticleEmitter : public SkReflected {
public:
REFLECTED_ABSTRACT(SkParticleEmitter, SkReflected)
virtual SkParticlePose emit(SkRandom&) const = 0;
static void RegisterEmitterTypes();
static sk_sp<SkParticleEmitter> MakeCircle(SkPoint center, SkScalar radius);
static sk_sp<SkParticleEmitter> MakeLine(SkPoint p1, SkPoint p2);
static sk_sp<SkParticleEmitter> MakeText(const char* text, SkScalar fontSize);
};
#endif // SkParticleEmitter_DEFINED

View File

@ -11,6 +11,5 @@ skia_particle_sources = [
"$_src/SkParticleAffector.cpp",
"$_src/SkParticleDrawable.cpp",
"$_src/SkParticleEffect.cpp",
"$_src/SkParticleEmitter.cpp",
"$_src/SkReflected.cpp",
]

View File

@ -7,9 +7,15 @@
#include "SkParticleAffector.h"
#include "SkContourMeasure.h"
#include "SkCurve.h"
#include "SkParsePath.h"
#include "SkParticleData.h"
#include "SkPath.h"
#include "SkRandom.h"
#include "SkTextUtils.h"
#include "sk_tool_utils.h"
constexpr SkFieldVisitor::EnumStringMapping gParticleFrameMapping[] = {
{ kWorld_ParticleFrame, "World" },
@ -180,6 +186,208 @@ private:
int fFrame;
};
class SkPositionInCircleAffector : public SkParticleAffector {
public:
SkPositionInCircleAffector(const SkCurve& x = 0.0f, const SkCurve& y = 0.0f,
const SkCurve& radius = 0.0f, bool setHeading = true)
: fX(x)
, fY(y)
, fRadius(radius)
, fSetHeading(setHeading) {}
REFLECTED(SkPositionInCircleAffector, SkParticleAffector)
void onApply(SkParticleUpdateParams& params, SkParticleState ps[], int count) override {
for (int i = 0; i < count; ++i) {
SkVector v;
do {
v.fX = ps[i].fRandom.nextSScalar1();
v.fY = ps[i].fRandom.nextSScalar1();
} while (v.dot(v) > 1);
SkPoint center = { fX.eval(ps[i].fAge, ps[i].fRandom),
fY.eval(ps[i].fAge, ps[i].fRandom) };
SkScalar radius = fRadius.eval(ps[i].fAge, ps[i].fRandom);
ps[i].fPose.fPosition = center + (v * radius);
if (fSetHeading) {
if (!v.normalize()) {
v.set(0, -1);
}
ps[i].fPose.fHeading = v;
}
}
}
void visitFields(SkFieldVisitor* v) override {
SkParticleAffector::visitFields(v);
v->visit("SetHeading", fSetHeading);
v->visit("X", fX);
v->visit("Y", fY);
v->visit("Radius", fRadius);
}
private:
SkCurve fX;
SkCurve fY;
SkCurve fRadius;
bool fSetHeading;
};
class SkPositionOnPathAffector : public SkParticleAffector {
public:
SkPositionOnPathAffector(const char* path = "", bool setHeading = true, bool random = true)
: fPath(path)
, fSetHeading(setHeading)
, fRandom(random) {
this->rebuild();
}
REFLECTED(SkPositionOnPathAffector, SkParticleAffector)
void onApply(SkParticleUpdateParams& params, SkParticleState ps[], int count) override {
if (fContours.empty()) {
return;
}
for (int i = 0; i < count; ++i) {
float t = fRandom ? ps[i].fRandom.nextF() : ps[i].fAge;
SkScalar len = fTotalLength * t;
int idx = 0;
while (idx < fContours.count() && len > fContours[idx]->length()) {
len -= fContours[idx++]->length();
}
SkVector localXAxis;
if (!fContours[idx]->getPosTan(len, &ps[i].fPose.fPosition, &localXAxis)) {
ps[i].fPose.fPosition = { 0, 0 };
localXAxis = { 1, 0 };
}
if (fSetHeading) {
ps[i].fPose.fHeading.set(localXAxis.fY, -localXAxis.fX);
}
}
}
void visitFields(SkFieldVisitor* v) override {
SkString oldPath = fPath;
SkParticleAffector::visitFields(v);
v->visit("SetHeading", fSetHeading);
v->visit("Random", fRandom);
v->visit("Path", fPath);
if (fPath != oldPath) {
this->rebuild();
}
}
private:
SkString fPath;
bool fSetHeading;
bool fRandom;
void rebuild() {
SkPath path;
if (!SkParsePath::FromSVGString(fPath.c_str(), &path)) {
return;
}
fTotalLength = 0;
fContours.reset();
SkContourMeasureIter iter(path, false);
while (auto contour = iter.next()) {
fContours.push_back(contour);
fTotalLength += contour->length();
}
}
// Cached
SkScalar fTotalLength;
SkTArray<sk_sp<SkContourMeasure>> fContours;
};
class SkPositionOnTextAffector : public SkParticleAffector {
public:
SkPositionOnTextAffector(const char* text = "", SkScalar fontSize = 96, bool setHeading = true,
bool random = true)
: fText(text)
, fFontSize(fontSize)
, fSetHeading(setHeading)
, fRandom(random) {
this->rebuild();
}
REFLECTED(SkPositionOnTextAffector, SkParticleAffector)
void onApply(SkParticleUpdateParams& params, SkParticleState ps[], int count) override {
if (fContours.empty()) {
return;
}
// TODO: Refactor to share code with PositionOnPathAffector
for (int i = 0; i < count; ++i) {
float t = fRandom ? ps[i].fRandom.nextF() : ps[i].fAge;
SkScalar len = fTotalLength * t;
int idx = 0;
while (idx < fContours.count() && len > fContours[idx]->length()) {
len -= fContours[idx++]->length();
}
SkVector localXAxis;
if (!fContours[idx]->getPosTan(len, &ps[i].fPose.fPosition, &localXAxis)) {
ps[i].fPose.fPosition = { 0, 0 };
localXAxis = { 1, 0 };
}
if (fSetHeading) {
ps[i].fPose.fHeading.set(localXAxis.fY, -localXAxis.fX);
}
}
}
void visitFields(SkFieldVisitor* v) override {
SkString oldText = fText;
SkScalar oldSize = fFontSize;
SkParticleAffector::visitFields(v);
v->visit("SetHeading", fSetHeading);
v->visit("Random", fRandom);
v->visit("Text", fText);
v->visit("FontSize", fFontSize);
if (fText != oldText || fFontSize != oldSize) {
this->rebuild();
}
}
private:
SkString fText;
SkScalar fFontSize;
bool fSetHeading;
bool fRandom;
void rebuild() {
fTotalLength = 0;
fContours.reset();
if (fText.isEmpty()) {
return;
}
SkFont font(sk_tool_utils::create_portable_typeface());
font.setSize(fFontSize);
SkPath path;
SkTextUtils::GetPath(fText.c_str(), fText.size(), kUTF8_SkTextEncoding, 0, 0, font, &path);
SkContourMeasureIter iter(path, false);
while (auto contour = iter.next()) {
fContours.push_back(contour);
fTotalLength += contour->length();
}
}
// Cached
SkScalar fTotalLength;
SkTArray<sk_sp<SkContourMeasure>> fContours;
};
class SkSizeAffector : public SkParticleAffector {
public:
SkSizeAffector(const SkCurve& curve = 1.0f) : fCurve(curve) {}
@ -250,6 +458,9 @@ void SkParticleAffector::RegisterAffectorTypes() {
REGISTER_REFLECTED(SkAngularVelocityAffector);
REGISTER_REFLECTED(SkPointForceAffector);
REGISTER_REFLECTED(SkOrientationAffector);
REGISTER_REFLECTED(SkPositionInCircleAffector);
REGISTER_REFLECTED(SkPositionOnPathAffector);
REGISTER_REFLECTED(SkPositionOnTextAffector);
REGISTER_REFLECTED(SkSizeAffector);
REGISTER_REFLECTED(SkFrameAffector);
REGISTER_REFLECTED(SkColorAffector);

View File

@ -14,7 +14,6 @@
#include "SkPaint.h"
#include "SkParticleAffector.h"
#include "SkParticleDrawable.h"
#include "SkParticleEmitter.h"
#include "SkReflected.h"
#include "SkRSXform.h"
@ -25,7 +24,6 @@ void SkParticleEffectParams::visitFields(SkFieldVisitor* v) {
v->visit("Life", fLifetime);
v->visit("Drawable", fDrawable);
v->visit("Emitter", fEmitter);
v->visit("Spawn", fSpawnAffectors);
v->visit("Update", fUpdateAffectors);
@ -86,7 +84,7 @@ void SkParticleEffect::update(const SkAnimTimer& timer) {
int numToSpawn = sk_float_round2int(desired);
fSpawnRemainder = desired - numToSpawn;
numToSpawn = SkTPin(numToSpawn, 0, fParams->fMaxCount - fCount);
if (fParams->fEmitter) {
if (numToSpawn) {
// This isn't "particle" t, it's effect t.
float t = static_cast<float>((now - fSpawnTime) / fParams->fEffectDuration);
t = fLooping ? fmodf(t, 1.0f) : SkTPin(t, 0.0f, 1.0f);
@ -99,7 +97,9 @@ void SkParticleEffect::update(const SkAnimTimer& timer) {
fParticles[fCount].fAge = t;
fParticles[fCount].fInvLifetime =
sk_ieee_float_divide(1.0f, fParams->fLifetime.eval(t, fRandom));
fParticles[fCount].fPose = fParams->fEmitter->emit(fRandom);
fParticles[fCount].fPose.fPosition = { 0.0f, 0.0f };
fParticles[fCount].fPose.fHeading = { 0.0f, -1.0f };
fParticles[fCount].fPose.fScale = 1.0f;
fParticles[fCount].fVelocity.fLinear = { 0.0f, 0.0f };
fParticles[fCount].fVelocity.fAngular = 0.0f;
fParticles[fCount].fColor = { 1.0f, 1.0f, 1.0f, 1.0f };

View File

@ -1,163 +0,0 @@
/*
* Copyright 2019 Google LLC
*
* Use of this source code is governed by a BSD-style license that can be
* found in the LICENSE file.
*/
#include "SkParticleEmitter.h"
#include "SkContourMeasure.h"
#include "SkParticleData.h"
#include "SkRandom.h"
#include "SkTextUtils.h"
#include "sk_tool_utils.h"
class SkCircleEmitter : public SkParticleEmitter {
public:
SkCircleEmitter(SkPoint center = { 0.0f, 0.0f }, SkScalar radius = 0.0f)
: fCenter(center), fRadius(radius) {}
REFLECTED(SkCircleEmitter, SkParticleEmitter)
SkParticlePose emit(SkRandom& random) const override {
SkVector v;
do {
v.fX = random.nextSScalar1();
v.fY = random.nextSScalar1();
} while (v.distanceToOrigin() > 1);
SkParticlePose pose = { fCenter + (v * fRadius), v, 1.0f };
if (!pose.fHeading.normalize()) {
pose.fHeading.set(0, -1);
}
return pose;
}
void visitFields(SkFieldVisitor* v) override {
v->visit("Center", fCenter);
v->visit("Radius", fRadius);
}
private:
SkPoint fCenter;
SkScalar fRadius;
};
class SkLineEmitter : public SkParticleEmitter {
public:
SkLineEmitter(SkPoint p1 = { 0.0f, 0.0f }, SkPoint p2 = { 0.0f, 0.0f }) : fP1(p1), fP2(p2) {}
REFLECTED(SkLineEmitter, SkParticleEmitter)
SkParticlePose emit(SkRandom& random) const override {
SkVector localXAxis = (fP2 - fP1);
SkParticlePose pose = { fP1 + (fP2 - fP1) * random.nextUScalar1(),
{ localXAxis.fY, -localXAxis.fX },
1.0f };
if (!pose.fHeading.normalize()) {
pose.fHeading.set(0, -1);
}
return pose;
}
void visitFields(SkFieldVisitor* v) override {
v->visit("P1", fP1);
v->visit("P2", fP2);
}
private:
SkPoint fP1;
SkPoint fP2;
};
class SkTextEmitter : public SkParticleEmitter {
public:
SkTextEmitter(const char* text = "", SkScalar fontSize = 96)
: fText(text), fFontSize(fontSize) {
this->rebuild();
}
REFLECTED(SkTextEmitter, SkParticleEmitter)
SkParticlePose emit(SkRandom& random) const override {
if (fContours.count() == 0) {
return SkParticlePose{ { 0, 0 }, { 0, -1 }, 0.0f };
}
SkScalar len = random.nextRangeScalar(0, fTotalLength);
int idx = 0;
while (idx < fContours.count() && len > fContours[idx]->length()) {
len -= fContours[idx++]->length();
}
SkParticlePose pose;
SkVector localXAxis;
if (!fContours[idx]->getPosTan(len, &pose.fPosition, &localXAxis)) {
pose.fPosition = { 0, 0 };
localXAxis = { 1, 0 };
}
pose.fHeading = { localXAxis.fY, -localXAxis.fX };
pose.fScale = 1.0f;
return pose;
}
void visitFields(SkFieldVisitor* v) override {
SkString oldText = fText;
SkScalar oldSize = fFontSize;
v->visit("Text", fText);
v->visit("FontSize", fFontSize);
if (fText != oldText || fFontSize != oldSize) {
this->rebuild();
}
}
private:
SkString fText;
SkScalar fFontSize;
void rebuild() {
fTotalLength = 0;
fContours.reset();
if (fText.isEmpty()) {
return;
}
SkFont font(sk_tool_utils::create_portable_typeface());
font.setSize(fFontSize);
SkPath path;
SkTextUtils::GetPath(fText.c_str(), fText.size(), kUTF8_SkTextEncoding, 0, 0, font, &path);
fIter.reset(path, false);
while (auto contour = fIter.next()) {
fContours.push_back(contour);
fTotalLength += contour->length();
}
}
// Cached
SkScalar fTotalLength;
SkContourMeasureIter fIter;
SkTArray<sk_sp<SkContourMeasure>> fContours;
};
void SkParticleEmitter::RegisterEmitterTypes() {
// Register types for serialization
REGISTER_REFLECTED(SkParticleEmitter);
REGISTER_REFLECTED(SkCircleEmitter);
REGISTER_REFLECTED(SkLineEmitter);
REGISTER_REFLECTED(SkTextEmitter);
}
sk_sp<SkParticleEmitter> SkParticleEmitter::MakeCircle(SkPoint center, SkScalar radius) {
return sk_sp<SkParticleEmitter>(new SkCircleEmitter(center, radius));
}
sk_sp<SkParticleEmitter> SkParticleEmitter::MakeLine(SkPoint p1, SkPoint p2) {
return sk_sp<SkParticleEmitter>(new SkLineEmitter(p1, p2));
}
sk_sp<SkParticleEmitter> SkParticleEmitter::MakeText(const char* text, SkScalar fontSize) {
return sk_sp<SkParticleEmitter>(new SkTextEmitter(text, fontSize));
}

View File

@ -18,11 +18,6 @@
"Type": "SkCircleDrawable",
"Radius": 1
},
"Emitter": {
"Type": "SkTextEmitter",
"Text": "SKIA",
"FontSize": 96
},
"Spawn": [
{
"Type": "SkLinearVelocityAffector",
@ -53,6 +48,13 @@
}
]
}
},
{
"Type": "SkPositionOnTextAffector",
"Enabled": true,
"SetHeading": true,
"Text": "SKIA",
"FontSize": 96
}
],
"Update": [

View File

@ -20,12 +20,46 @@
"Columns": 4,
"Rows": 4
},
"Emitter": {
"Type": "SkCircleEmitter",
"Center": { "x": 200, "y": 200 },
"Radius": 60
},
"Spawn": [],
"Spawn": [
{
"Type": "SkPositionInCircleAffector",
"Enabled": true,
"SetHeading": true,
"X": {
"XValues": [],
"Segments": [
{
"Type": "Constant",
"Ranged": false,
"Bidirectional": false,
"A0": 0
}
]
},
"Y": {
"XValues": [],
"Segments": [
{
"Type": "Constant",
"Ranged": false,
"Bidirectional": false,
"A0": 0
}
]
},
"Radius": {
"XValues": [],
"Segments": [
{
"Type": "Constant",
"Ranged": false,
"Bidirectional": false,
"A0": 60
}
]
}
}
],
"Update": [
{
"Type": "SkPointForceAffector",

View File

@ -19,11 +19,6 @@
"Columns": 1,
"Rows": 1
},
"Emitter": {
"Type": "SkLineEmitter",
"P1": { "x": 237, "y": 396 },
"P2": { "x": 214, "y": 398 }
},
"Spawn": [
{
"Type": "SkLinearVelocityAffector",

View File

@ -17,11 +17,6 @@
"Type": "SkCircleDrawable",
"Radius": 1
},
"Emitter": {
"Type": "SkLineEmitter",
"P1": { "x": 61, "y": 34 },
"P2": { "x": 606, "y": 32 }
},
"Spawn": [
{
"Type": "SkLinearVelocityAffector",
@ -52,6 +47,12 @@
}
]
}
},
{
"Type": "SkPositionOnPathAffector",
"Enabled": true,
"SetHeading": false,
"Path": "h500"
}
],
"Update": [

View File

@ -18,11 +18,6 @@
"Type": "SkCircleDrawable",
"Radius": 2
},
"Emitter": {
"Type": "SkCircleEmitter",
"Center": { "x": 0, "y": 0 },
"Radius": 0
},
"Spawn": [
{
"Type": "SkLinearVelocityAffector",

View File

@ -18,12 +18,13 @@
"Type": "SkCircleDrawable",
"Radius": 2
},
"Emitter": {
"Type": "SkLineEmitter",
"P1": { "x": 200, "y": 200 },
"P2": { "x": 250, "y": 200 }
},
"Spawn": [
{
"Type": "SkPositionOnPathAffector",
"Enabled": true,
"SetHeading": true,
"Path": "h50"
},
{
"Type": "SkLinearVelocityAffector",
"Enabled": true,

View File

@ -17,17 +17,51 @@
"Type": "SkCircleDrawable",
"Radius": 2
},
"Emitter": {
"Type": "SkCircleEmitter",
"Center": { "x": 380.8, "y": 273.92 },
"Radius": 43
},
"Spawn": [],
"Spawn": [
{
"Type": "SkPositionInCircleAffector",
"Enabled": true,
"SetHeading": true,
"X": {
"XValues": [],
"Segments": [
{
"Type": "Constant",
"Ranged": false,
"Bidirectional": false,
"A0": 0
}
]
},
"Y": {
"XValues": [],
"Segments": [
{
"Type": "Constant",
"Ranged": false,
"Bidirectional": false,
"A0": 0
}
]
},
"Radius": {
"XValues": [],
"Segments": [
{
"Type": "Constant",
"Ranged": false,
"Bidirectional": false,
"A0": 40
}
]
}
}
],
"Update": [
{
"Type": "SkPointForceAffector",
"Enabled": true,
"Point": { "x": 375, "y": 273 },
"Point": { "x": 0, "y": 0 },
"Constant": -10,
"InvSquare": 0
},

View File

@ -11,7 +11,6 @@
#include "SkParticleAffector.h"
#include "SkParticleDrawable.h"
#include "SkParticleEffect.h"
#include "SkParticleEmitter.h"
#include "SkParticleSerialization.h"
#include "SkReflected.h"
@ -197,7 +196,6 @@ ParticlesSlide::ParticlesSlide() {
REGISTER_REFLECTED(SkReflected);
SkParticleAffector::RegisterAffectorTypes();
SkParticleDrawable::RegisterDrawableTypes();
SkParticleEmitter::RegisterEmitterTypes();
fName = "Particles";
}