2019-08-30 14:59:47 +00:00
|
|
|
/*
|
|
|
|
* 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 "modules/particles/include/SkParticleBinding.h"
|
|
|
|
|
|
|
|
#include "include/core/SkContourMeasure.h"
|
|
|
|
#include "include/core/SkPath.h"
|
|
|
|
#include "include/utils/SkParsePath.h"
|
|
|
|
#include "include/utils/SkRandom.h"
|
|
|
|
#include "include/utils/SkTextUtils.h"
|
Particles: Sub-effect spawning and some slight refactoring
* Added a new binding type, SkEffectBinding. This stores another
entire effect params structure (so the JSON is just nested).
The name is a callable value that spawns a new instance of
that effect, inheriting the parameters of the spawning effect
or particle (depending on which kind of script made the call).
* Broke up the monolithic update function into some helpers,
got some code reuse with the script calling logic.
* Unlike particle capacity, there is no upper limit on child
effects (yet), so it's easy to trigger runaway memory and
CPU consumption. Be careful.
* Added death scripts to effects and particles, which are a
common place to want to spawn sub-effects. Like spawn,
these run on each loop, but for one-shots they play at the
end. Even with loops, this is helpful for timing sub-effects
(see fireworks2.json).
* Finally, added a much more comprehensive example effect,
raincloud.json. This includes a total of three effects, to
generate a cloud, raindrops, and splashes when those drops
hit "the ground".
Change-Id: I3d7b72bcbb684642cd9723518b67ab1c7d7a538a
Reviewed-on: https://skia-review.googlesource.com/c/skia/+/242479
Reviewed-by: Michael Ludwig <michaelludwig@google.com>
Commit-Queue: Brian Osman <brianosman@google.com>
2019-09-19 14:06:36 +00:00
|
|
|
#include "modules/particles/include/SkParticleEffect.h"
|
2019-08-30 14:59:47 +00:00
|
|
|
#include "modules/particles/include/SkReflected.h"
|
|
|
|
#include "src/sksl/SkSLCompiler.h"
|
|
|
|
|
|
|
|
void SkParticleBinding::visitFields(SkFieldVisitor* v) {
|
|
|
|
v->visit("Name", fName);
|
|
|
|
}
|
|
|
|
|
Particles: Sub-effect spawning and some slight refactoring
* Added a new binding type, SkEffectBinding. This stores another
entire effect params structure (so the JSON is just nested).
The name is a callable value that spawns a new instance of
that effect, inheriting the parameters of the spawning effect
or particle (depending on which kind of script made the call).
* Broke up the monolithic update function into some helpers,
got some code reuse with the script calling logic.
* Unlike particle capacity, there is no upper limit on child
effects (yet), so it's easy to trigger runaway memory and
CPU consumption. Be careful.
* Added death scripts to effects and particles, which are a
common place to want to spawn sub-effects. Like spawn,
these run on each loop, but for one-shots they play at the
end. Even with loops, this is helpful for timing sub-effects
(see fireworks2.json).
* Finally, added a much more comprehensive example effect,
raincloud.json. This includes a total of three effects, to
generate a cloud, raindrops, and splashes when those drops
hit "the ground".
Change-Id: I3d7b72bcbb684642cd9723518b67ab1c7d7a538a
Reviewed-on: https://skia-review.googlesource.com/c/skia/+/242479
Reviewed-by: Michael Ludwig <michaelludwig@google.com>
Commit-Queue: Brian Osman <brianosman@google.com>
2019-09-19 14:06:36 +00:00
|
|
|
class SkEffectExternalValue : public SkParticleExternalValue {
|
|
|
|
public:
|
|
|
|
SkEffectExternalValue(const char* name, SkSL::Compiler& compiler,
|
|
|
|
sk_sp<SkParticleEffectParams> params)
|
|
|
|
: SkParticleExternalValue(name, compiler, *compiler.context().fVoid_Type)
|
|
|
|
, fParams(std::move(params)) {}
|
|
|
|
|
|
|
|
bool canCall() const override { return true; }
|
|
|
|
int callParameterCount() const override { return 1; }
|
|
|
|
void getCallParameterTypes(const SkSL::Type** outTypes) const override {
|
|
|
|
outTypes[0] = fCompiler.context().fBool_Type.get();
|
|
|
|
}
|
|
|
|
|
|
|
|
void call(int index, float* arguments, float* outReturn) override {
|
|
|
|
bool loop = ((int*)arguments)[0] != 0;
|
|
|
|
fEffect->addSpawnRequest(index, loop, fParams);
|
|
|
|
}
|
|
|
|
|
|
|
|
private:
|
|
|
|
sk_sp<SkParticleEffectParams> fParams;
|
|
|
|
};
|
|
|
|
|
|
|
|
class SkEffectBinding : public SkParticleBinding {
|
|
|
|
public:
|
|
|
|
SkEffectBinding(const char* name = "", sk_sp<SkParticleEffectParams> params = nullptr)
|
|
|
|
: SkParticleBinding(name)
|
|
|
|
, fParams(std::move(params)) {
|
|
|
|
if (!fParams) {
|
|
|
|
fParams.reset(new SkParticleEffectParams());
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
REFLECTED(SkEffectBinding, SkParticleBinding)
|
|
|
|
|
|
|
|
void visitFields(SkFieldVisitor* v) override {
|
|
|
|
SkParticleBinding::visitFields(v);
|
|
|
|
fParams->visitFields(v);
|
|
|
|
}
|
|
|
|
|
|
|
|
std::unique_ptr<SkParticleExternalValue> toValue(SkSL::Compiler& compiler) override {
|
|
|
|
return std::unique_ptr<SkParticleExternalValue>(
|
|
|
|
new SkEffectExternalValue(fName.c_str(), compiler, fParams));
|
|
|
|
}
|
|
|
|
|
|
|
|
private:
|
|
|
|
sk_sp<SkParticleEffectParams> fParams;
|
|
|
|
};
|
|
|
|
|
2019-08-30 14:59:47 +00:00
|
|
|
struct SkPathContours {
|
|
|
|
SkScalar fTotalLength;
|
|
|
|
SkTArray<sk_sp<SkContourMeasure>> fContours;
|
|
|
|
|
|
|
|
void rebuild(const SkPath& path) {
|
|
|
|
fTotalLength = 0;
|
|
|
|
fContours.reset();
|
|
|
|
|
|
|
|
SkContourMeasureIter iter(path, false);
|
|
|
|
while (auto contour = iter.next()) {
|
|
|
|
fContours.push_back(contour);
|
|
|
|
fTotalLength += contour->length();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
// Exposes an SkPath as an external, callable value. p(x) returns a float4 { pos.xy, normal.xy }
|
|
|
|
class SkPathExternalValue : public SkParticleExternalValue {
|
|
|
|
public:
|
|
|
|
SkPathExternalValue(const char* name, SkSL::Compiler& compiler, const SkPathContours* path)
|
|
|
|
: SkParticleExternalValue(name, compiler, *compiler.context().fFloat4_Type)
|
|
|
|
, fPath(path) { }
|
|
|
|
|
|
|
|
bool canCall() const override { return true; }
|
|
|
|
int callParameterCount() const override { return 1; }
|
|
|
|
void getCallParameterTypes(const SkSL::Type** outTypes) const override {
|
|
|
|
outTypes[0] = fCompiler.context().fFloat_Type.get();
|
|
|
|
}
|
|
|
|
|
|
|
|
void call(int index, float* arguments, float* outReturn) override {
|
|
|
|
SkScalar len = fPath->fTotalLength * arguments[0];
|
|
|
|
int idx = 0;
|
2019-10-17 14:28:46 +00:00
|
|
|
while (idx < fPath->fContours.count() - 1 && len > fPath->fContours[idx]->length()) {
|
2019-08-30 14:59:47 +00:00
|
|
|
len -= fPath->fContours[idx++]->length();
|
|
|
|
}
|
|
|
|
SkVector localXAxis;
|
2019-10-17 14:28:46 +00:00
|
|
|
if (idx >= fPath->fContours.count() ||
|
|
|
|
!fPath->fContours[idx]->getPosTan(len, (SkPoint*)outReturn, &localXAxis)) {
|
2019-08-30 14:59:47 +00:00
|
|
|
outReturn[0] = outReturn[1] = 0.0f;
|
|
|
|
localXAxis = { 1, 0 };
|
|
|
|
}
|
|
|
|
outReturn[2] = localXAxis.fY;
|
|
|
|
outReturn[3] = -localXAxis.fX;
|
|
|
|
}
|
|
|
|
|
|
|
|
private:
|
|
|
|
const SkPathContours* fPath;
|
|
|
|
};
|
|
|
|
|
|
|
|
class SkPathBinding : public SkParticleBinding {
|
|
|
|
public:
|
|
|
|
SkPathBinding(const char* name = "", const char* path = "")
|
|
|
|
: SkParticleBinding(name)
|
|
|
|
, fPath(path) {
|
|
|
|
this->rebuild();
|
|
|
|
}
|
|
|
|
|
|
|
|
REFLECTED(SkPathBinding, SkParticleBinding)
|
|
|
|
|
|
|
|
void visitFields(SkFieldVisitor* v) override {
|
|
|
|
SkString oldPath = fPath;
|
|
|
|
|
|
|
|
SkParticleBinding::visitFields(v);
|
|
|
|
v->visit("Path", fPath);
|
|
|
|
|
|
|
|
if (fPath != oldPath) {
|
|
|
|
this->rebuild();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
std::unique_ptr<SkParticleExternalValue> toValue(SkSL::Compiler& compiler) override {
|
|
|
|
return std::unique_ptr<SkParticleExternalValue>(
|
|
|
|
new SkPathExternalValue(fName.c_str(), compiler, &fContours));
|
|
|
|
}
|
|
|
|
|
|
|
|
private:
|
|
|
|
SkString fPath;
|
|
|
|
|
|
|
|
void rebuild() {
|
|
|
|
SkPath path;
|
|
|
|
if (SkParsePath::FromSVGString(fPath.c_str(), &path)) {
|
|
|
|
fContours.rebuild(path);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Cached
|
|
|
|
SkPathContours fContours;
|
|
|
|
};
|
|
|
|
|
|
|
|
class SkTextBinding : public SkParticleBinding {
|
|
|
|
public:
|
|
|
|
SkTextBinding(const char* name = "", const char* text = "", SkScalar fontSize = 96)
|
|
|
|
: SkParticleBinding(name)
|
|
|
|
, fText(text)
|
|
|
|
, fFontSize(fontSize) {
|
|
|
|
this->rebuild();
|
|
|
|
}
|
|
|
|
|
|
|
|
REFLECTED(SkTextBinding, SkParticleBinding)
|
|
|
|
|
|
|
|
void visitFields(SkFieldVisitor* v) override {
|
|
|
|
SkString oldText = fText;
|
|
|
|
SkScalar oldSize = fFontSize;
|
|
|
|
|
|
|
|
SkParticleBinding::visitFields(v);
|
|
|
|
v->visit("Text", fText);
|
|
|
|
v->visit("FontSize", fFontSize);
|
|
|
|
|
|
|
|
if (fText != oldText || fFontSize != oldSize) {
|
|
|
|
this->rebuild();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
std::unique_ptr<SkParticleExternalValue> toValue(SkSL::Compiler& compiler) override {
|
|
|
|
return std::unique_ptr<SkParticleExternalValue>(
|
|
|
|
new SkPathExternalValue(fName.c_str(), compiler, &fContours));
|
|
|
|
}
|
|
|
|
|
|
|
|
private:
|
|
|
|
SkString fText;
|
|
|
|
SkScalar fFontSize;
|
|
|
|
|
|
|
|
void rebuild() {
|
|
|
|
if (fText.isEmpty()) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
SkFont font(nullptr, fFontSize);
|
|
|
|
SkPath path;
|
|
|
|
SkTextUtils::GetPath(fText.c_str(), fText.size(), SkTextEncoding::kUTF8, 0, 0, font, &path);
|
|
|
|
fContours.rebuild(path);
|
|
|
|
}
|
|
|
|
|
|
|
|
// Cached
|
|
|
|
SkPathContours fContours;
|
|
|
|
};
|
|
|
|
|
Particles: Sub-effect spawning and some slight refactoring
* Added a new binding type, SkEffectBinding. This stores another
entire effect params structure (so the JSON is just nested).
The name is a callable value that spawns a new instance of
that effect, inheriting the parameters of the spawning effect
or particle (depending on which kind of script made the call).
* Broke up the monolithic update function into some helpers,
got some code reuse with the script calling logic.
* Unlike particle capacity, there is no upper limit on child
effects (yet), so it's easy to trigger runaway memory and
CPU consumption. Be careful.
* Added death scripts to effects and particles, which are a
common place to want to spawn sub-effects. Like spawn,
these run on each loop, but for one-shots they play at the
end. Even with loops, this is helpful for timing sub-effects
(see fireworks2.json).
* Finally, added a much more comprehensive example effect,
raincloud.json. This includes a total of three effects, to
generate a cloud, raindrops, and splashes when those drops
hit "the ground".
Change-Id: I3d7b72bcbb684642cd9723518b67ab1c7d7a538a
Reviewed-on: https://skia-review.googlesource.com/c/skia/+/242479
Reviewed-by: Michael Ludwig <michaelludwig@google.com>
Commit-Queue: Brian Osman <brianosman@google.com>
2019-09-19 14:06:36 +00:00
|
|
|
sk_sp<SkParticleBinding> SkParticleBinding::MakeEffectBinding(
|
|
|
|
const char* name, sk_sp<SkParticleEffectParams> params) {
|
|
|
|
return sk_sp<SkParticleBinding>(new SkEffectBinding(name, params));
|
|
|
|
}
|
|
|
|
|
2019-08-30 14:59:47 +00:00
|
|
|
sk_sp<SkParticleBinding> SkParticleBinding::MakePathBinding(const char* name, const char* path) {
|
|
|
|
return sk_sp<SkParticleBinding>(new SkPathBinding(name, path));
|
|
|
|
}
|
|
|
|
|
|
|
|
void SkParticleBinding::RegisterBindingTypes() {
|
|
|
|
REGISTER_REFLECTED(SkParticleBinding);
|
Particles: Sub-effect spawning and some slight refactoring
* Added a new binding type, SkEffectBinding. This stores another
entire effect params structure (so the JSON is just nested).
The name is a callable value that spawns a new instance of
that effect, inheriting the parameters of the spawning effect
or particle (depending on which kind of script made the call).
* Broke up the monolithic update function into some helpers,
got some code reuse with the script calling logic.
* Unlike particle capacity, there is no upper limit on child
effects (yet), so it's easy to trigger runaway memory and
CPU consumption. Be careful.
* Added death scripts to effects and particles, which are a
common place to want to spawn sub-effects. Like spawn,
these run on each loop, but for one-shots they play at the
end. Even with loops, this is helpful for timing sub-effects
(see fireworks2.json).
* Finally, added a much more comprehensive example effect,
raincloud.json. This includes a total of three effects, to
generate a cloud, raindrops, and splashes when those drops
hit "the ground".
Change-Id: I3d7b72bcbb684642cd9723518b67ab1c7d7a538a
Reviewed-on: https://skia-review.googlesource.com/c/skia/+/242479
Reviewed-by: Michael Ludwig <michaelludwig@google.com>
Commit-Queue: Brian Osman <brianosman@google.com>
2019-09-19 14:06:36 +00:00
|
|
|
REGISTER_REFLECTED(SkEffectBinding);
|
2019-08-30 14:59:47 +00:00
|
|
|
REGISTER_REFLECTED(SkPathBinding);
|
|
|
|
REGISTER_REFLECTED(SkTextBinding);
|
|
|
|
}
|