<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>Forem: Denis</title>
    <description>The latest articles on Forem by Denis (@den4ic).</description>
    <link>https://forem.com/den4ic</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F1935678%2F057c01dc-35b2-4599-accd-d3dbc475fcfa.png</url>
      <title>Forem: Denis</title>
      <link>https://forem.com/den4ic</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://forem.com/feed/den4ic"/>
    <language>en</language>
    <item>
      <title>Morphing Geometric Shapes with SDF in GLSL Fragment Shaders and Visualization in Jetpack Compose</title>
      <dc:creator>Denis</dc:creator>
      <pubDate>Fri, 10 Jan 2025 21:15:01 +0000</pubDate>
      <link>https://forem.com/den4ic/morphing-geometric-shapes-with-sdf-in-glsl-fragment-shaders-and-visualization-in-jetpack-compose-5db8</link>
      <guid>https://forem.com/den4ic/morphing-geometric-shapes-with-sdf-in-glsl-fragment-shaders-and-visualization-in-jetpack-compose-5db8</guid>
      <description>&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F1g00id24z7w8do8jdrpk.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F1g00id24z7w8do8jdrpk.png" alt="Image description" width="800" height="423"&gt;&lt;/a&gt;&lt;br&gt;
Creating dynamic visual effects for mobile applications requires developers not only to have a creative approach but also to meet performance requirements. One of the most efficient techniques for implementing smooth transitions and transformations of objects is the use of shaders, which allow complex parallel computations to be performed on the GPU. This not only ensures smooth animations but can also reduce the load on the CPU by offloading resource-intensive tasks to the graphics processor in certain scenarios, which is especially important for mobile devices with limited resources.&lt;br&gt;
This article will explore an example of implementing smooth morphing animation of geometric shapes using SDF (Signed Distance Functions) and GLSL for graphical rendering.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Basics of SDF&lt;/strong&gt;&lt;br&gt;
Before diving into the implementation of smooth morphing between geometric shapes, it's important to first understand the principles of working with SDF. &lt;strong&gt;SDF (Signed Distance Function)&lt;/strong&gt; is a mathematical model that defines the distance from a point to the nearest surface of an object. Each shape, whether it is a circle, square, or polygon, can be described by its unique SDF, which allows for operations like intersections, unions, and differences of shapes. In the context of morphing between two shapes, this allows for the creation of a continuous transition, integrating additional effects like deformation or smoothing.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Shapes Using SDF&lt;/strong&gt;&lt;br&gt;
To illustrate the workings of SDF, let's consider a few examples of geometric shapes that can be used:&lt;br&gt;
&lt;strong&gt;1. Circle (sdCircle)&lt;/strong&gt;&lt;br&gt;
The &lt;code&gt;sdCircle&lt;/code&gt; function calculates the distance from a point to a circle with radius &lt;code&gt;r&lt;/code&gt;. The distance is calculated as the difference between the length of the vector from the point to the center of the circle and the radius.&lt;br&gt;
&lt;/p&gt;
&lt;div class="ltag_gist-liquid-tag"&gt;
  
&lt;/div&gt;
&lt;br&gt;
• &lt;code&gt;length(p)&lt;/code&gt; — is the length of the vector &lt;code&gt;p&lt;/code&gt;, which represents the point's coordinates relative to the circle's center.&lt;br&gt;
• &lt;code&gt;r&lt;/code&gt; — is the radius of the circle.&lt;br&gt;
If the point is on the circle, the distance will be zero. If the point is inside the circle, the result will be negative, and if outside, the result will be positive.&lt;br&gt;
&lt;strong&gt;2. Square (sdSquare)&lt;/strong&gt;&lt;br&gt;
The &lt;code&gt;sdSquare&lt;/code&gt;function calculates the distance from a point to the nearest edge of a square with side length &lt;code&gt;2 * r,&lt;/code&gt; where the square's center is at the origin.&lt;br&gt;
&lt;div class="ltag_gist-liquid-tag"&gt;
  
&lt;/div&gt;
&lt;br&gt;
• &lt;code&gt;abs(p)&lt;/code&gt; — function that converts coordinates to their absolute values, symmetrically mapping them to the first quadrant of the coordinate system.&lt;br&gt;
• &lt;code&gt;max(p.x, p.y)&lt;/code&gt; — returns the greatest of the &lt;code&gt;x&lt;/code&gt; or &lt;code&gt;y&lt;/code&gt; coordinates, which corresponds to the farthest point from the center within the square.&lt;br&gt;
• &lt;code&gt;r&lt;/code&gt; — is half the side length of the square.&lt;br&gt;
&lt;strong&gt;3. Diamond (sdDiamond)&lt;/strong&gt;&lt;br&gt;
The &lt;code&gt;sdDiamond&lt;/code&gt; function calculates the distance to a diamond-shaped figure with size &lt;code&gt;r&lt;/code&gt; and an aspect ratio &lt;code&gt;aspect&lt;/code&gt;, which controls the stretching of the diamond along one axis.&lt;br&gt;
&lt;div class="ltag_gist-liquid-tag"&gt;
  
&lt;/div&gt;
&lt;br&gt;
• &lt;code&gt;abs(p)&lt;/code&gt; — function that converts coordinates to their absolute values, symmetrically mapping them to the first quadrant of the coordinate system.&lt;br&gt;
• &lt;code&gt;p.x *= aspect&lt;/code&gt; — stretches the x-coordinate based on the aspect parameter, changing the shape of the diamond.&lt;br&gt;
• The expression &lt;code&gt;p.x + p.y - r&lt;/code&gt; calculates the boundary of the diamond.&lt;br&gt;
&lt;strong&gt;4. Regular Polygon (sdRegularPolygon)&lt;/strong&gt;&lt;br&gt;
The &lt;code&gt;sdRegularPolygon&lt;/code&gt;function computes the distance to a regular polygon with &lt;code&gt;n&lt;/code&gt; sides and a radius defining the distance from the center to the vertices.&lt;br&gt;
&lt;div class="ltag_gist-liquid-tag"&gt;
  
&lt;/div&gt;
&lt;br&gt;
• &lt;code&gt;float an = 3.141593 / float(n)&lt;/code&gt; calculates the angular value for each side of the polygon.&lt;br&gt;
• &lt;code&gt;vec2 acs = vec2(cos(an), sin(an))&lt;/code&gt; is the directional vector for one side of the polygon.&lt;br&gt;
• The expression &lt;code&gt;atan(p.x, p.y)&lt;/code&gt; calculates the angle of the point relative to the X-axis, and &lt;code&gt;mod(atan(p.x, p.y), 2.0 * an) - an&lt;/code&gt; returns the angle relative to the nearest side of the polygon.&lt;br&gt;
• The transformation &lt;code&gt;p = length(p) * vec2(cos(bn), abs(sin(bn)))&lt;/code&gt; normalizes the point in the new coordinate system aligned with the polygon's side.&lt;br&gt;
In addition to the previously discussed functions for various geometric shapes, it is worth noting that a square can also be considered a regular polygon with four sides &lt;code&gt;(n = 4)&lt;/code&gt;. In this case, we use the sdRegularPolygon function but need to scale the coordinates to match the square.&lt;br&gt;
&lt;code&gt;5. Star (sdStar)&lt;/code&gt;&lt;br&gt;
The &lt;code&gt;sdStar&lt;/code&gt; function calculates the distance from a point to a 5-ray star. Unlike other shapes such as circles, squares, and diamonds, the star has a more complex structure due to the multiple rays that need to be precisely computed.&lt;br&gt;
&lt;div class="ltag_gist-liquid-tag"&gt;
  
&lt;/div&gt;
&lt;br&gt;
• &lt;code&gt;const vec2 k1 = vec2(0.809016994375, -0.587785252292)&lt;/code&gt; defines the first directional vector &lt;code&gt;k1&lt;/code&gt; corresponding to a 36° angle (one of the star's rays).&lt;br&gt;
• &lt;code&gt;const vec2 k2 = vec2(-k1.x, k1.y)&lt;/code&gt; defines the second directional vector &lt;code&gt;k2&lt;/code&gt;, the mirror reflection of &lt;code&gt;k1&lt;/code&gt; along the &lt;code&gt;Y&lt;/code&gt;-axis.&lt;br&gt;
• &lt;code&gt;p.x = abs(p.x)&lt;/code&gt; ensures that the &lt;code&gt;x&lt;/code&gt;-coordinate is positive.&lt;br&gt;
• &lt;code&gt;p -= 2.0 * max(dot(k1, p), 0.0) * k1&lt;/code&gt; adjusts the point relative to the first directional ray. If the point lies beyond the ray, it is reflected relative to the ray. The same transformation applies for &lt;code&gt;k2&lt;/code&gt;.&lt;br&gt;
• &lt;code&gt;p.y -= r&lt;/code&gt; adjusts the y-coordinate by subtracting the star's radius &lt;code&gt;r&lt;/code&gt; to account for the star's size.&lt;br&gt;
• &lt;code&gt;vec2 ba = rf * vec2(-k1.y, k1.x) - vec2(0, 1)&lt;/code&gt; defines the vector used for further reflections based on the radius and scaling factor &lt;code&gt;rf&lt;/code&gt;.&lt;br&gt;
• The final calculation computes the distance from the point to the star, considering all transformations and reflections, and uses the sign to determine the point's position relative to the star.&lt;br&gt;
The magic numbers in vectors &lt;code&gt;k1&lt;/code&gt; and &lt;code&gt;k2&lt;/code&gt; define the key angles of the star, ensuring the rays are oriented correctly. These values are tied to the 36° and 72° angles used to form the star properly.

&lt;p&gt;&lt;strong&gt;Morphing Shapes&lt;/strong&gt;&lt;br&gt;
Based on the principles of working with Signed Distance Functions (SDF), can proceed with morphing between shapes. During the morphing process, the shader calculates two distance values (for the first and second shapes) for each point on the screen (pixel), and then performs interpolation using the &lt;code&gt;morphFactor&lt;/code&gt; parameter. Depending on the value of this factor, obtain an intermediate form that is a mixture of the two shapes.&lt;br&gt;
Here’s an example of a function with transformation:&lt;br&gt;
&lt;/p&gt;
&lt;div class="ltag_gist-liquid-tag"&gt;
  
&lt;/div&gt;
&lt;br&gt;
Where:&lt;br&gt;
• &lt;code&gt;getShapeDistance&lt;/code&gt; is a function that returns the distance to the selected shape (circle, square, etc.), depending on the passed shape indices.&lt;br&gt;
• &lt;code&gt;p&lt;/code&gt; represents the normalized fragment coordinates in space, where the center of the screen is &lt;code&gt;(0, 0)&lt;/code&gt; and the coordinate ranges for both axes (&lt;code&gt;X&lt;/code&gt; and &lt;code&gt;Y&lt;/code&gt;) are from -1 to 1.&lt;br&gt;
• &lt;code&gt;d1&lt;/code&gt; and &lt;code&gt;d2&lt;/code&gt; are the SDF values for two shapes.&lt;br&gt;
• &lt;code&gt;morphFactor&lt;/code&gt; is the morphing factor. When &lt;code&gt;morphFactor&lt;/code&gt; is 0, the first shape is displayed, and when it is 1, the second shape is displayed. Gradually changing this factor creates a smooth transition between the two shapes.

&lt;p&gt;&lt;strong&gt;Color Effects in Shaders&lt;/strong&gt;&lt;br&gt;
An important aspect of morphing is working with color. Color influences the perception of objects and enhances visual effects by creating smooth transitions between states. It’s important to note that color can be used to enhance depth and lighting perception during the morphing animation. To achieve this, various visual effects are applied to integrate color into the shape transformation process and create a smooth and natural transition between different states.&lt;br&gt;
Here’s how color effects can be integrated into the morphing process, enhancing visual perception:&lt;br&gt;
&lt;/p&gt;
&lt;div class="ltag_gist-liquid-tag"&gt;
  
&lt;/div&gt;
&lt;br&gt;
In this fragment of code:&lt;br&gt;
• The variable &lt;code&gt;col&lt;/code&gt; of type &lt;code&gt;vec3&lt;/code&gt; stores the color.&lt;br&gt;
• Inside the &lt;code&gt;if (isColorMode)&lt;/code&gt; block, it checks if the color mode is active. If enabled, it selects either the external or internal color based on the value of &lt;code&gt;d&lt;/code&gt; (the distance to the object's surface). If the point is outside the object &lt;code&gt;(d &amp;gt; 0.0)&lt;/code&gt;, the external color &lt;code&gt;(externalColor)&lt;/code&gt; is used; otherwise, the internal color &lt;code&gt;(internalColor)&lt;/code&gt; is used.&lt;br&gt;
• The color intensity is adjusted using the formula &lt;code&gt;col *= 1.05 - exp(-6.0 * abs(d));&lt;/code&gt; creating a fading effect. The color becomes brighter as it approaches the surface and fades as the distance increases.&lt;br&gt;
• A dynamic color hue shift is applied with &lt;code&gt;col *= 0.8 + 0.2 * cos(110.0 * d);&lt;/code&gt; creating a pulsating or lighting change effect.&lt;br&gt;
• The &lt;code&gt;mix(col, vec3(1.0), 1.0 - smoothstep(0.0, 0.01, abs(d)));&lt;/code&gt; function smoothly transitions the color towards white near the surface, particularly when d is small.&lt;br&gt;
• If the color mode is off, the standard white &lt;code&gt;vec3(1.0)&lt;/code&gt; is used for points outside the shape and black &lt;code&gt;vec3(0.0)&lt;/code&gt; for points inside.

&lt;p&gt;&lt;strong&gt;Integrating the Shader on Android with OpenGL ES&lt;/strong&gt;&lt;br&gt;
After explaining the principles of shader construction, the next step for integrating it into Android is setting up the rendering of geometric shapes with morphing and color effects. An important step in this process is creating the OpenGL environment, loading shaders, and passing parameters to these shaders. All of this can be achieved using the &lt;code&gt;GLSurfaceView&lt;/code&gt; component for rendering, which provides access to OpenGL ES on Android devices. As an example, we will create a class &lt;code&gt;MorphGLSurfaceView&lt;/code&gt;, inherited from &lt;code&gt;GLSurfaceView&lt;/code&gt;, where the renderer is initialized using the &lt;code&gt;setRenderer&lt;/code&gt; method. Additionally, a method &lt;code&gt;updateShaderValue&lt;/code&gt; will be created to update shader values, allowing parameters such as the morphing slider value, selected shapes, color mode, and object colors to be passed:&lt;br&gt;
&lt;/p&gt;
&lt;div class="ltag_gist-liquid-tag"&gt;
  
&lt;/div&gt;
&lt;br&gt;
To prepare the rendering process, we define a &lt;code&gt;MorphRenderer&lt;/code&gt; class, which will initialize the shaders, pass values, and handle updates for displaying the morphing effect. The shaders are loaded using the &lt;code&gt;loadShaderFromRawResource&lt;/code&gt; function within the &lt;code&gt;onSurfaceCreated&lt;/code&gt; method, sourced from raw resources and compiled via the &lt;code&gt;loadShader&lt;/code&gt; function. Additionally, the class will include an array &lt;code&gt;vertices&lt;/code&gt; that contains the vertex coordinates defining the geometry of the object. These coordinates are stored in an array, with each element corresponding to a single vertex. Each vertex is represented by a triplet of numbers, where each triplet specifies spatial coordinates (&lt;code&gt;X, Y, Z&lt;/code&gt;). The shaders will later use these coordinates for transforming and visualizing the object.&lt;br&gt;
&lt;div class="ltag_gist-liquid-tag"&gt;
  
&lt;/div&gt;
&lt;br&gt;
When the surface changes (in the &lt;code&gt;onSurfaceChanged&lt;/code&gt; method), it is necessary to set the viewport for OpenGL:&lt;br&gt;
&lt;div class="ltag_gist-liquid-tag"&gt;
  
&lt;/div&gt;
&lt;br&gt;
Then, in the &lt;code&gt;onDrawFrame&lt;/code&gt; method, rendering takes place. First, the screen is cleared using &lt;code&gt;GLES30.glClear&lt;/code&gt;. After that, the shader program is activated. Next, all the required parameters, such as resolution, the slider value for morphing, shape selection, and color parameters, are passed via uniforms:&lt;br&gt;
&lt;div class="ltag_gist-liquid-tag"&gt;
  
&lt;/div&gt;
&lt;br&gt;
To update values during runtime, methods such as &lt;code&gt;updateSliderValue&lt;/code&gt;, &lt;code&gt;updateSelectedShape&lt;/code&gt;, &lt;code&gt;updateColorMode&lt;/code&gt;, and &lt;code&gt;updateColors&lt;/code&gt; are used.&lt;br&gt;
&lt;div class="ltag_gist-liquid-tag"&gt;
  
&lt;/div&gt;
&lt;br&gt;
Next, using the &lt;code&gt;AndroidView&lt;/code&gt; component from Compose, shaders can be dynamically updated as parameters change. This enables direct interaction with rendering from the UI layer of the application, creating smooth and fast transitions between morphing states and color effects:&lt;br&gt;
&lt;div class="ltag_gist-liquid-tag"&gt;
  
&lt;/div&gt;


&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F17gj10hjk5potk37ldvf.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F17gj10hjk5potk37ldvf.gif" alt="Image description" width="237" height="500"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;As a result of integrating the shader with OpenGL ES on Android, a system is created where the morphing of geometric shapes and the dynamic color changes of objects occur in real time using the GPU.&lt;br&gt;
&lt;a href="https://github.com/den4ic/ShaderMorph" rel="noopener noreferrer"&gt;The full code is available via the link on GitHub.&lt;/a&gt;&lt;/p&gt;

</description>
      <category>tutorial</category>
      <category>android</category>
      <category>mobile</category>
    </item>
    <item>
      <title>Data Transfer Between Fragment and BottomSheetDialogFragment Using Dagger and Navigation Component</title>
      <dc:creator>Denis</dc:creator>
      <pubDate>Fri, 16 Aug 2024 00:31:17 +0000</pubDate>
      <link>https://forem.com/den4ic/data-transfer-between-fragment-and-bottomsheetdialogfragment-using-dagger-and-navigation-component-1ml3</link>
      <guid>https://forem.com/den4ic/data-transfer-between-fragment-and-bottomsheetdialogfragment-using-dagger-and-navigation-component-1ml3</guid>
      <description>&lt;p&gt;Data transfer between a Fragment and a &lt;code&gt;BottomSheetDialogFragment&lt;/code&gt; can be effectively managed using &lt;code&gt;Dagger&lt;/code&gt;and the &lt;code&gt;Navigation Component&lt;/code&gt;, avoiding the use of data transfer through constructors or interfaces, as well as &lt;code&gt;SharedViewModel&lt;/code&gt;and &lt;code&gt;Hilt&lt;/code&gt;. This approach allows for a focus on dependency injection and state management through standard tools.&lt;/p&gt;




&lt;p&gt;In our example, the key component is &lt;code&gt;DaggerBottomSheetDialogFragment&lt;/code&gt;, which provides dependency injection and state management, offering flexibility and control over the process.&lt;br&gt;
&lt;/p&gt;
&lt;div class="ltag_gist-liquid-tag"&gt;
  
&lt;/div&gt;
&lt;br&gt;
&lt;em&gt;Using &lt;code&gt;Dagger&lt;/code&gt; in this context allows for dependency injection, which opens up possibilities for extending functionality. When dependencies need to be injected into &lt;code&gt;BottomSheetDialogFragment&lt;/code&gt; and other components of the application, &lt;code&gt;Dagger&lt;/code&gt; helps maintain architectural cleanliness and flexibility.&lt;/em&gt;

&lt;p&gt;&lt;strong&gt;Data Transfer&lt;/strong&gt;&lt;br&gt;
Data transfer between &lt;code&gt;FirstFragment&lt;/code&gt; and &lt;code&gt;SecondBottomSheetFragment&lt;/code&gt; is organized with a focus on bidirectional data flow as follows:&lt;br&gt;
• &lt;strong&gt;Sending Data:&lt;/strong&gt; &lt;code&gt;FirstFragment&lt;/code&gt; sends data to &lt;code&gt;SecondBottomSheetFragment&lt;/code&gt;using the &lt;code&gt;openBottomFragment&lt;/code&gt; method. This method sets the arguments and uses &lt;code&gt;findNavController()&lt;/code&gt; for navigation.&lt;br&gt;
• &lt;strong&gt;Receiving Data:&lt;/strong&gt; Values obtained from &lt;code&gt;SecondBottomSheetFragment&lt;/code&gt; are observed in &lt;code&gt;observeBackStack&lt;/code&gt;, where &lt;code&gt;currentBackStackEntryFlow&lt;/code&gt; is used to track changes in the navigation stack state, followed by updating data in the &lt;code&gt;ViewModel&lt;/code&gt;.&lt;br&gt;
Let’s look at how this is implemented in code:&lt;br&gt;
&lt;/p&gt;
&lt;div class="ltag_gist-liquid-tag"&gt;
  
&lt;/div&gt;
&lt;br&gt;
&lt;em&gt;Note that in this example, the &lt;code&gt;openBottomFragment&lt;/code&gt; method uses a check before performing a navigation action. This is necessary because rapid opening or closing of windows may result in an error related to the incorrect state of navigation. This error manifests as &lt;code&gt;java.lang.IllegalArgumentException&lt;/code&gt; if the &lt;code&gt;NavController&lt;/code&gt; attempts to perform navigation when the current fragment location does not support the transition.&lt;/em&gt;

&lt;p&gt;Next, let’s consider the example in &lt;code&gt;SecondBottomSheetFragment&lt;/code&gt;, where data from &lt;code&gt;FirstFragment&lt;/code&gt; is received via &lt;code&gt;initArgs&lt;/code&gt;, which initializes the &lt;code&gt;ViewModel&lt;/code&gt;. The processed data is then sent back to &lt;code&gt;FirstFragment&lt;/code&gt;through the &lt;code&gt;sendDataToParentFragment&lt;/code&gt; method. This method allows for updating the user interface in &lt;code&gt;FirstFragment&lt;/code&gt;, ensuring it reflects the changes made in &lt;code&gt;SecondBottomSheetFragment&lt;/code&gt;.&lt;br&gt;
&lt;/p&gt;
&lt;div class="ltag_gist-liquid-tag"&gt;
  
&lt;/div&gt;
&lt;br&gt;
&lt;div class="ltag_gist-liquid-tag"&gt;
  
&lt;/div&gt;
&lt;br&gt;
This example demonstrates how to use &lt;code&gt;Dagger&lt;/code&gt; and the &lt;code&gt;Navigation Component&lt;/code&gt; for managing state and transferring data between fragments and a BottomSheetDialogFragment. &lt;a href="https://github.com/den4ic/DaggerBottomSheetNav" rel="noopener noreferrer"&gt;The complete code is available at the GitHub link.&lt;/a&gt;

</description>
      <category>android</category>
      <category>kotlin</category>
      <category>tutorial</category>
    </item>
  </channel>
</rss>
