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