@@ -9,6 +9,7 @@ import androidx.compose.ui.ExperimentalComposeUiApi
99import androidx.compose.ui.Modifier
1010import androidx.compose.ui.geometry.Offset
1111import androidx.compose.ui.graphics.Color
12+ import androidx.compose.ui.graphics.Path
1213import androidx.compose.ui.graphics.drawscope.DrawScope
1314import androidx.compose.ui.platform.LocalDensity
1415import androidx.compose.ui.unit.dp
@@ -28,10 +29,36 @@ private class MutableParticle(
2829 val alpha : Float
2930)
3031
32+ /* *
33+ * Performance-optimized connection representation using value class.
34+ * Packs two integers into a single Long to avoid object allocation overhead
35+ * compared to Pair<Int, Int>. This reduces GC pressure in the animation loop
36+ * where connections are created frequently for triangle detection.
37+ */
38+ @Immutable
39+ @Stable
40+ private value class Connection (private val value : Long ) {
41+ constructor (first: Int , second: Int ) : this ((first.toLong() shl 32 ) or (second.toLong() and 0xFFFFFFFF ))
42+
43+ @Stable
44+ inline val first: Int get() = (value shr 32 ).toInt()
45+
46+ @Stable
47+ inline val second: Int get() = value.toInt()
48+
49+ @Stable
50+ @Suppress(" NOTHING_TO_INLINE" )
51+ inline operator fun component1 (): Int = first
52+
53+ @Stable
54+ @Suppress(" NOTHING_TO_INLINE" )
55+ inline operator fun component2 (): Int = second
56+ }
57+
3158/* *
3259 * Ultra-optimized animated background with theme-aware performance scaling:
3360 * 1. Eliminated O(n²) algorithm with distance culling
34- * 2. Replaced expensive sqrt() with squared distance comparisons
61+ * 2. Replaced expensive sqrt() with squared distance comparisons
3562 * 3. Fixed excessive recomposition issues
3663 * 4. Used mutable particles to reduce object allocation
3764 * 5. Cached color objects to reduce GC pressure
@@ -49,7 +76,7 @@ fun OptimizedAnimatedBackground(
4976) {
5077 val density = LocalDensity .current
5178 val colorScheme = MaterialTheme .colorScheme
52-
79+
5380 // Theme-aware performance scaling - reduce animation during theme transitions
5481 val performanceScale = remember(themeEffects?.isTransitioning) {
5582 if (themeEffects?.isTransitioning == true ) 0.3f else 1f // 70% reduction during transitions
@@ -64,14 +91,15 @@ fun OptimizedAnimatedBackground(
6491 // Mutable particles list for better performance
6592 val particles = remember { mutableListOf<MutableParticle >() }
6693 var canvasSize by remember { mutableStateOf(Offset .Zero ) }
67-
94+
6895 // Theme-aware cached colors with sci-fi glow effects
6996 val cachedColors = remember(colorScheme, themeEffects?.glowIntensity) {
7097 val glowIntensity = themeEffects?.glowIntensity ? : 0f
7198 ParticleColors (
7299 particleColor = colorScheme.primary.copy(alpha = 0.65f + glowIntensity * 0.2f ),
73100 connectionColor = colorScheme.secondary.copy(alpha = 0.4f + glowIntensity * 0.15f ),
74- mouseConnectionColor = colorScheme.secondary.copy(alpha = 0.48f + glowIntensity * 0.25f )
101+ mouseConnectionColor = colorScheme.secondary.copy(alpha = 0.48f + glowIntensity * 0.25f ),
102+ triangleFillColor = colorScheme.secondary.copy(alpha = 0.08f + glowIntensity * 0.03f )
75103 )
76104 }
77105
@@ -91,15 +119,15 @@ fun OptimizedAnimatedBackground(
91119 // Consolidated LaunchedEffect for both initialization and updates with theme-aware scaling
92120 LaunchedEffect (canvasSize, scaledParticleCount, animationTime) {
93121 if (canvasSize == Offset .Zero ) return @LaunchedEffect
94-
122+
95123 // Initialize particles if needed with scaled count
96124 if (particles.size != scaledParticleCount) {
97125 particles.clear()
98126 repeat(scaledParticleCount) {
99127 particles.add(createRandomMutableParticle(canvasSize.x, canvasSize.y, scaledParticleSpeed))
100128 }
101129 }
102-
130+
103131 // Update existing particles in-place with scaled speed
104132 particles.forEach { particle ->
105133 updateMutableParticle(particle, canvasSize.x, canvasSize.y, scaledParticleSpeed)
@@ -138,7 +166,8 @@ fun OptimizedAnimatedBackground(
138166private data class ParticleColors (
139167 val particleColor : Color ,
140168 val connectionColor : Color ,
141- val mouseConnectionColor : Color
169+ val mouseConnectionColor : Color ,
170+ val triangleFillColor : Color
142171)
143172
144173/* *
@@ -158,7 +187,12 @@ private fun createRandomMutableParticle(canvasWidth: Float, canvasHeight: Float,
158187/* *
159188 * Update mutable particle in-place (no object allocation)
160189 */
161- private fun updateMutableParticle (particle : MutableParticle , canvasWidth : Float , canvasHeight : Float , baseSpeed : Float ) {
190+ private fun updateMutableParticle (
191+ particle : MutableParticle ,
192+ canvasWidth : Float ,
193+ canvasHeight : Float ,
194+ baseSpeed : Float
195+ ) {
162196 // Update position
163197 particle.x + = particle.vx
164198 particle.y + = particle.vy
@@ -183,39 +217,204 @@ private fun updateMutableParticle(particle: MutableParticle, canvasWidth: Float,
183217 particle.vy = particle.vy.coerceIn(- maxSpeed, maxSpeed)
184218}
185219
220+ /* *
221+ * Draw glass shard triangles formed by connected particles and mouse
222+ */
223+ private fun DrawScope.drawTriangles (
224+ particles : List <MutableParticle >,
225+ connections : List <Connection >,
226+ mouseConnections : List <Int >,
227+ mousePosition : Offset ? ,
228+ connectionDistanceSquared : Float ,
229+ colors : ParticleColors
230+ ) {
231+
232+ // Find triangles formed by particle-to-particle connections
233+ for (i in connections.indices) {
234+ for (j in i + 1 until connections.size) {
235+ val conn1 = connections[i]
236+ val conn2 = connections[j]
237+
238+ // Check if two connections share a common particle (form a triangle)
239+ val sharedParticle = when {
240+ conn1.first == conn2.first -> conn1.first
241+ conn1.first == conn2.second -> conn1.first
242+ conn1.second == conn2.first -> conn1.second
243+ conn1.second == conn2.second -> conn1.second
244+ else -> null
245+ }
246+
247+ if (sharedParticle != null ) {
248+ // Get the three particles forming the triangle
249+ val p1Index = if (conn1.first == sharedParticle) conn1.second else conn1.first
250+ val p2Index = sharedParticle
251+ val p3Index = if (conn2.first == sharedParticle) conn2.second else conn2.first
252+
253+ // Verify the third edge exists (p1 to p3)
254+ val p1 = particles[p1Index]
255+ val p3 = particles[p3Index]
256+ val dx = p1.x - p3.x
257+ val dy = p1.y - p3.y
258+ val distanceSquared = dx * dx + dy * dy
259+
260+ if (distanceSquared <= connectionDistanceSquared) {
261+ drawGlassTriangle(
262+ particles[p1Index],
263+ particles[p2Index],
264+ particles[p3Index],
265+ connectionDistanceSquared,
266+ colors.triangleFillColor
267+ )
268+ }
269+ }
270+ }
271+ }
272+
273+ // Find triangles involving the mouse position
274+ mousePosition?.let { mouse ->
275+ if (mouseConnections.size >= 2 ) {
276+ // Check each pair of mouse-connected particles
277+ for (i in mouseConnections.indices) {
278+ for (j in i + 1 until mouseConnections.size) {
279+ val p1Index = mouseConnections[i]
280+ val p2Index = mouseConnections[j]
281+
282+ // Check if these two particles are also connected to each other
283+ if (connections.any {
284+ (it.first == p1Index && it.second == p2Index) ||
285+ (it.first == p2Index && it.second == p1Index)
286+ }) {
287+ drawGlassTriangleWithMouse(
288+ particles[p1Index],
289+ particles[p2Index],
290+ mouse,
291+ connectionDistanceSquared,
292+ colors.triangleFillColor
293+ )
294+ }
295+ }
296+ }
297+ }
298+ }
299+ }
300+
301+ /* *
302+ * Draw a glass shard triangle between three particles
303+ */
304+ private fun DrawScope.drawGlassTriangle (
305+ p1 : MutableParticle ,
306+ p2 : MutableParticle ,
307+ p3 : MutableParticle ,
308+ connectionDistanceSquared : Float ,
309+ baseColor : Color
310+ ) {
311+ // Calculate distances of all three edges to find the longest
312+ val dist1Sq = (p1.x - p2.x) * (p1.x - p2.x) + (p1.y - p2.y) * (p1.y - p2.y)
313+ val dist2Sq = (p2.x - p3.x) * (p2.x - p3.x) + (p2.y - p3.y) * (p2.y - p3.y)
314+ val dist3Sq = (p3.x - p1.x) * (p3.x - p1.x) + (p3.y - p1.y) * (p3.y - p1.y)
315+
316+ // Use the longest edge for alpha calculation
317+ val longestDistanceSquared = maxOf(dist1Sq, dist2Sq, dist3Sq)
318+ val longestDistance = kotlin.math.sqrt(longestDistanceSquared)
319+ val maxDistance = kotlin.math.sqrt(connectionDistanceSquared)
320+
321+ // Make triangles fainter and decay faster than connection lines
322+ // Base alpha factor is 0.3 (much fainter than lines) and use squared ratio for faster decay
323+ val distanceRatio = (longestDistance / maxDistance).coerceIn(0f , 1f )
324+ val alpha = (1 - distanceRatio * distanceRatio) * baseColor.alpha * 0.3f
325+
326+ val trianglePath = Path ().apply {
327+ moveTo(p1.x, p1.y)
328+ lineTo(p2.x, p2.y)
329+ lineTo(p3.x, p3.y)
330+ close()
331+ }
332+
333+ drawPath(
334+ path = trianglePath,
335+ color = baseColor.copy(alpha = alpha)
336+ )
337+ }
338+
339+ /* *
340+ * Draw a glass shard triangle involving mouse position
341+ */
342+ private fun DrawScope.drawGlassTriangleWithMouse (
343+ p1 : MutableParticle ,
344+ p2 : MutableParticle ,
345+ mousePos : Offset ,
346+ connectionDistanceSquared : Float ,
347+ baseColor : Color
348+ ) {
349+ // Calculate distances of all three edges to find the longest
350+ val dist1Sq = (p1.x - p2.x) * (p1.x - p2.x) + (p1.y - p2.y) * (p1.y - p2.y)
351+ val dist2Sq = (p2.x - mousePos.x) * (p2.x - mousePos.x) + (p2.y - mousePos.y) * (p2.y - mousePos.y)
352+ val dist3Sq = (mousePos.x - p1.x) * (mousePos.x - p1.x) + (mousePos.y - p1.y) * (mousePos.y - p1.y)
353+
354+ // Use the longest edge for alpha calculation
355+ val longestDistanceSquared = maxOf(dist1Sq, dist2Sq, dist3Sq)
356+ val longestDistance = kotlin.math.sqrt(longestDistanceSquared)
357+ val maxDistance = kotlin.math.sqrt(connectionDistanceSquared)
358+
359+ // Make triangles fainter and decay faster than connection lines
360+ // Mouse triangles get slightly higher intensity (0.4 vs 0.3) but still much fainter than lines
361+ val distanceRatio = (longestDistance / maxDistance).coerceIn(0f , 1f )
362+ val alpha = (1 - distanceRatio * distanceRatio) * baseColor.alpha * 0.4f
363+
364+ val trianglePath = Path ().apply {
365+ moveTo(p1.x, p1.y)
366+ lineTo(p2.x, p2.y)
367+ lineTo(mousePos.x, mousePos.y)
368+ close()
369+ }
370+
371+ drawPath(
372+ path = trianglePath,
373+ color = baseColor.copy(alpha = alpha)
374+ )
375+ }
376+
186377/* *
187378 * Optimized drawing function with major performance improvements:
188379 * 1. Eliminated nested loop O(n²) algorithm using distance culling
189380 * 2. Uses squared distance comparison (no expensive sqrt)
190381 * 3. Early termination for distant particles
191382 * 4. Cached color objects
192383 * 5. Reduced temporary object allocation
384+ * 6. Glass shard triangles when three points are connected
193385 */
194386private fun DrawScope.drawOptimizedParticlesAndConnections (
195387 particles : List <MutableParticle >,
196388 connectionDistanceSquared : Float ,
197389 colors : ParticleColors ,
198390 mousePosition : Offset ? = null
199391) {
200- // Optimized connection drawing with distance culling
392+ // Collect all connected pairs for triangle detection
393+ val connections = mutableListOf<Connection >()
394+ val mouseConnections = mutableListOf<Int >()
395+
396+ // Optimized connection drawing with distance culling and connection tracking
201397 // Still O(n²) but with early termination for better average case performance
202398 for (i in particles.indices) {
203399 val particle1 = particles[i]
204-
400+
205401 for (j in i + 1 until particles.size) {
206402 val particle2 = particles[j]
207-
403+
208404 // Fast squared distance calculation (no sqrt needed)
209405 val dx = particle1.x - particle2.x
210406 val dy = particle1.y - particle2.y
211407 val distanceSquared = dx * dx + dy * dy
212-
408+
213409 // Early termination if too far apart
214410 if (distanceSquared <= connectionDistanceSquared) {
411+ // Track connection for triangle detection
412+ connections.add(Connection (i, j))
413+
215414 // Only calculate sqrt when we know we need to draw
216415 val distance = kotlin.math.sqrt(distanceSquared)
217416 val alpha = (1 - distance / kotlin.math.sqrt(connectionDistanceSquared)) * colors.connectionColor.alpha
218-
417+
219418 drawLine(
220419 color = colors.connectionColor.copy(alpha = alpha),
221420 start = Offset (particle1.x, particle1.y),
@@ -226,17 +425,21 @@ private fun DrawScope.drawOptimizedParticlesAndConnections(
226425 }
227426 }
228427
229- // Optimized mouse connections
428+ // Optimized mouse connections with tracking for triangle detection
230429 mousePosition?.let { mouse ->
231- particles.forEach { particle ->
430+ particles.forEachIndexed { index, particle ->
232431 val dx = particle.x - mouse.x
233432 val dy = particle.y - mouse.y
234433 val distanceSquared = dx * dx + dy * dy
235-
434+
236435 if (distanceSquared <= connectionDistanceSquared) {
436+ // Track mouse connection for triangle detection
437+ mouseConnections.add(index)
438+
237439 val distance = kotlin.math.sqrt(distanceSquared)
238- val alpha = (1 - distance / kotlin.math.sqrt(connectionDistanceSquared)) * colors.mouseConnectionColor.alpha
239-
440+ val alpha =
441+ (1 - distance / kotlin.math.sqrt(connectionDistanceSquared)) * colors.mouseConnectionColor.alpha
442+
240443 drawLine(
241444 color = colors.mouseConnectionColor.copy(alpha = alpha),
242445 start = Offset (particle.x, particle.y),
@@ -247,6 +450,9 @@ private fun DrawScope.drawOptimizedParticlesAndConnections(
247450 }
248451 }
249452
453+ // Draw glass shard triangles
454+ drawTriangles(particles, connections, mouseConnections, mousePosition, connectionDistanceSquared, colors)
455+
250456 // Draw particles with cached colors
251457 particles.forEach { particle ->
252458 drawCircle(
@@ -255,4 +461,4 @@ private fun DrawScope.drawOptimizedParticlesAndConnections(
255461 center = Offset (particle.x, particle.y)
256462 )
257463 }
258- }
464+ }
0 commit comments