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