1 /*
2 * Copyright (c) 2024 Huawei Device Co., Ltd.
3 * Licensed under the Apache License, Version 2.0 (the "License");
4 * you may not use this file except in compliance with the License.
5 * You may obtain a copy of the License at
6 *
7 * http://www.apache.org/licenses/LICENSE-2.0
8 *
9 * Unless required by applicable law or agreed to in writing, software
10 * distributed under the License is distributed on an "AS IS" BASIS,
11 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 * See the License for the specific language governing permissions and
13 * limitations under the License.
14 */
15
16 #include "animation/rs_particle_noise_field.h"
17
18 namespace OHOS {
19 namespace Rosen {
20 constexpr float EPSILON = 1e-3;
21 constexpr float HALF = 0.5f;
22 constexpr float FEATHERMAX = 100.f;
23 constexpr int SQUARE_NUM = 2;
IsPointInField(const Vector2f & point,const ShapeType & fieldShape,const Vector2f & fieldCenter,float width,float height)24 bool ParticleNoiseField::IsPointInField(
25 const Vector2f& point, const ShapeType& fieldShape, const Vector2f& fieldCenter, float width, float height)
26 {
27 if (fieldShape == ShapeType::RECT) {
28 return ((point.x_ > fieldCenter_.x_ - width * HALF) && (point.x_ < fieldCenter_.x_ + width * HALF) &&
29 (point.y_ >= fieldCenter_.y_ - height * HALF) && (point.y_ < fieldCenter_.y_ + height * HALF));
30 } else {
31 double normX = (point.x_ - fieldCenter_.x_) * (point.x_ - fieldCenter_.x_);
32 double normY = (point.y_ - fieldCenter_.y_) * (point.y_ - fieldCenter_.y_);
33 return ROSEN_EQ(width, 0.f) || ROSEN_EQ(height, 0.f) ? false :
34 (normX / (width * HALF * width * HALF) + normY / (height * HALF * height * HALF) <= 1.0);
35 }
36 return false;
37 }
38
CalculateDistanceToRectangleEdge(const Vector2f & position,const Vector2f & direction,const Vector2f & center,const Vector2f & size)39 float ParticleNoiseField::CalculateDistanceToRectangleEdge(
40 const Vector2f& position, const Vector2f& direction, const Vector2f& center, const Vector2f& size)
41 {
42 if (ROSEN_EQ(direction.x_, 0.f) && ROSEN_EQ(direction.y_, 0.f)) {
43 return 0.0f;
44 }
45 // Calculates the four boundaries of a rectangle.
46 float left = center.x_ - size.x_ * HALF;
47 float right = center.x_ + size.x_ * HALF;
48 float top = center.y_ - size.y_ * HALF;
49 float bottom = center.y_ + size.y_ * HALF;
50 // Calculate the time required for reaching each boundary.
51 float tLeft = ROSEN_EQ(direction.x_, 0.f) ? -1.f : (left - position.x_) / direction.x_;
52 float tRight = ROSEN_EQ(direction.x_, 0.f) ? -1.f : (right - position.x_) / direction.x_;
53 float tTop = ROSEN_EQ(direction.y_, 0.f) ? -1.f : (top - position.y_) / direction.y_;
54 float tBottom = ROSEN_EQ(direction.y_, 0.f) ? -1.f : (bottom - position.y_) / direction.y_;
55
56 // Particles advance to the boundary only if t is a positive number.
57 std::vector<float> times;
58 if (tLeft > 0.f) {
59 times.push_back(tLeft);
60 }
61 if (tRight > 0.f) {
62 times.push_back(tRight);
63 }
64 if (tTop > 0.f) {
65 times.push_back(tTop);
66 }
67 if (tBottom > 0.f) {
68 times.push_back(tBottom);
69 }
70
71 if (times.empty()) {
72 return 0.f;
73 }
74 // The smallest value of t, which is the first time the particle will reach the boundary.
75 float tEdge = *std::min_element(times.begin(), times.end());
76
77 // Calculates the distance to the border.
78 float distance = std::sqrt(std::pow(tEdge * direction.x_, 2) + std::pow(tEdge * direction.y_, 2));
79 return distance;
80 }
81
CalculateDistanceToEllipseEdge(const Vector2f & direction,const Vector2f & center,const Vector2f & axes)82 float ParticleNoiseField::CalculateDistanceToEllipseEdge(const Vector2f& direction,
83 const Vector2f& center, const Vector2f& axes)
84 {
85 if (ROSEN_EQ(direction.x_, 0.f) && ROSEN_EQ(direction.y_, 0.f)) {
86 return 0.0f;
87 }
88
89 float a = axes.x_ / 2;
90 float b = axes.y_ / 2;
91
92 float t = std::atan2(direction.y_, direction.x_);
93 float x = a * std::cos(t);
94 float y = b * std::sin(t);
95
96 return std::sqrt(std::pow(x, SQUARE_NUM) + std::pow(y, SQUARE_NUM));
97 }
98
CalculateFeatherEffect(float distanceToEdge,float featherWidth)99 float ParticleNoiseField::CalculateFeatherEffect(float distanceToEdge, float featherWidth)
100 {
101 float normalizedDistance = 1.0f;
102 if (featherWidth > 0.f && !ROSEN_EQ(featherWidth, 0.f) && distanceToEdge >= 0) {
103 normalizedDistance = distanceToEdge / featherWidth;
104 }
105 if (normalizedDistance >= 1.0f - std::numeric_limits<float>::epsilon()) {
106 return 1.0f;
107 }
108 return normalizedDistance;
109 }
110
ApplyField(const Vector2f & position,float deltaTime)111 Vector2f ParticleNoiseField::ApplyField(const Vector2f& position, float deltaTime)
112 {
113 if (fieldShape_ == ShapeType::CIRCLE) {
114 fieldSize_.x_ = std::min(fieldSize_.x_, fieldSize_.y_);
115 fieldSize_.y_ = fieldSize_.x_;
116 }
117 if (IsPointInField(position, fieldShape_, fieldCenter_, fieldSize_.x_, fieldSize_.y_) && fieldStrength_ != 0) {
118 Vector2f direction = position - fieldCenter_;
119 float distance = direction.GetLength();
120 float forceMagnitude = static_cast<float>(fieldStrength_);
121 float featherWidth = fieldSize_.x_ * (fieldFeather_ / FEATHERMAX);
122 float edgeDistance = 0.f;
123 if (fieldShape_ == ShapeType::CIRCLE) {
124 edgeDistance = fieldSize_.x_;
125 } else if (fieldShape_ == ShapeType::ELLIPSE) {
126 edgeDistance = CalculateDistanceToEllipseEdge(direction, fieldCenter_, fieldSize_);
127 } else {
128 edgeDistance = CalculateDistanceToRectangleEdge(position, direction, fieldCenter_, fieldSize_);
129 }
130
131 if (fieldStrength_ < 0 && !ROSEN_EQ(deltaTime, 0.f)) {
132 forceMagnitude = std::max(forceMagnitude, -1.f * distance / deltaTime);
133 } else if (fieldStrength_ > 0 && !ROSEN_EQ(deltaTime, 0.f)) {
134 forceMagnitude = std::min(forceMagnitude, edgeDistance / deltaTime);
135 }
136 float featherEffect = CalculateFeatherEffect(edgeDistance, featherWidth);
137 forceMagnitude *= featherEffect;
138 Vector2f force = direction.Normalized() * forceMagnitude;
139 return force;
140 }
141 return Vector2f { 0.f, 0.f };
142 }
143
ApplyCurlNoise(const Vector2f & position)144 Vector2f ParticleNoiseField::ApplyCurlNoise(const Vector2f& position)
145 {
146 if (IsPointInField(position, fieldShape_, fieldCenter_, fieldSize_.x_, fieldSize_.y_)) {
147 PerlinNoise2D noise(noiseScale_, noiseFrequency_, noiseAmplitude_);
148 return noise.Curl(position.x_, position.y_) * noiseScale_;
149 }
150 return Vector2f { 0.f, 0.f };
151 }
152
Dump(std::string & out) const153 void ParticleNoiseFields::Dump(std::string& out) const
154 {
155 out += '[';
156 bool found = false;
157 for (auto& field : fields_) {
158 if (field != nullptr) {
159 found = true;
160 out += "field[fieldStrength:" + std::to_string(field->fieldStrength_);
161 out += " fieldShape:" + std::to_string(static_cast<int>(field->fieldShape_));
162 out += " fieldSize[x:" + std::to_string(field->fieldSize_.x_) + " y:";
163 out += std::to_string(field->fieldSize_.y_) + "]";
164 out += " fieldCenter[x:" + std::to_string(field->fieldCenter_.x_) + " y:";
165 out += std::to_string(field->fieldCenter_.y_) + "] fieldFeather:" + std::to_string(field->fieldFeather_);
166 out += " noiseScale:" + std::to_string(field->noiseScale_);
167 out += " noiseFrequency:" + std::to_string(field->noiseFrequency_);
168 out += " noiseAmplitude:" + std::to_string(field->noiseAmplitude_) + "] ";
169 }
170 }
171 if (found) {
172 out.pop_back();
173 }
174 out += ']';
175 }
176
Fade(float t)177 float PerlinNoise2D::Fade(float t)
178 {
179 // Fade function as defined by Ken Perlin: 6t^5 - 15t^4 + 10t^3
180 return t * t * t * (t * (t * 6 - 15) + 10);
181 }
182
Lerp(float t,float a,float b)183 float PerlinNoise2D::Lerp(float t, float a, float b)
184 {
185 // Linear interpolate between a and b
186 return a + t * (b - a);
187 }
188
Grad(int hash,float x,float y)189 float PerlinNoise2D::Grad(int hash, float x, float y)
190 {
191 // Convert low 4 bits of hash code into 12 gradient directions.
192 // 15 mins use a bitwise AND operation (&) to get the lowest 4 bits of the hash value.
193 uint32_t h = static_cast<uint32_t>(hash) & 15;
194 // The value of h determines whether the first component of the gradient vector is x or y.
195 // If the value of h is less than 8, u is assigned the value x, otherwise y
196 double u = h < 8 ? x : y;
197 // Selects the second component of the gradient vector.
198 // If h is less than 4, v is assigned the value y; If h is equal to 12 or 14, v is assigned to x; Otherwise, v is 0.
199 // This allocation ensures diversity in the gradient vectors and helps to produce a more natural noise texture.
200 double v = h < 4 ? y : h == 12 || h == 14 ? x : 0;
201 return ((h & 1) == 0 ? u : -u) + ((h & 2) == 0 ? v : -v); // h & 2 check the h's second least bit
202 }
203
PerlinNoise2D(float noiseScale,float noiseFrequency,float noiseAmplitude)204 PerlinNoise2D::PerlinNoise2D(float noiseScale, float noiseFrequency, float noiseAmplitude)
205 {
206 noiseScale_ = noiseScale;
207 noiseFrequency_ = noiseFrequency;
208 noiseAmplitude_ = noiseAmplitude;
209 // Initialize permutation vector with values from 0 to 255
210 p.resize(256); // 256 is the vector size
211 std::iota(p.begin(), p.end(), 0);
212
213 // Shuffle using a default random engine
214 std::default_random_engine engine;
215 std::shuffle(p.begin(), p.end(), engine);
216
217 // Duplicate the permutation vector
218 p.insert(p.end(), p.begin(), p.end());
219 }
220
Noise(float x,float y)221 float PerlinNoise2D::Noise(float x, float y)
222 {
223 x *= noiseFrequency_;
224 y *= noiseFrequency_;
225 // Find the unit square that contains the point, 255 is vector max index
226 uint32_t X = static_cast<uint32_t>(static_cast<int>(floor(x))) & 255;
227 uint32_t Y = static_cast<uint32_t>(static_cast<int>(floor(y))) & 255;
228
229 // Find relative x, y of point in square
230 x -= floor(x);
231 y -= floor(y);
232
233 // Compute fade curves for each of x, y
234 float u = Fade(x);
235 float v = Fade(y);
236
237 // Hash coordinates of the 4 square corners
238 int A = p[X] + static_cast<int>(Y);
239 int AA = p[A];
240 int AB = p[A + 1];
241 int B = p[X + 1] + static_cast<int>(Y);
242 int BA = p[B];
243 int BB = p[B + 1];
244
245 // And add blended results from the 4 corners of the square
246 float res = Lerp(v, Lerp(u, Grad(p[AA], x, y), Grad(p[BA], x - 1, y)),
247 Lerp(u, Grad(p[AB], x, y - 1), Grad(p[BB], x - 1, y - 1)));
248
249 return noiseAmplitude_ * (res + 1.f) / 2.f; // Normalize the result
250 }
251
252 // In the two-dimensional mode, curl actually returns a vector instead of a scalar.
Curl(float x,float y)253 Vector2f PerlinNoise2D::Curl(float x, float y)
254 {
255 // Calculate the partial derivative of the noise field.
256 float noise_dx = Noise(x + EPSILON, y) - Noise(x - EPSILON, y);
257 float noise_dy = Noise(x, y + EPSILON) - Noise(x, y - EPSILON);
258
259 // The result of the two-dimensional curl is the vector obtained by rotating the gradient by 90 degrees.
260 // Assume that Curl has a value only in the Z component (rotation in the two-dimensional plane).
261 float curlx = -noise_dy / (2 * EPSILON);
262 float curly = noise_dx / (2 * EPSILON);
263
264 return Vector2f(curlx, curly);
265 }
266 } // namespace Rosen
267 } // namespace OHOS