tawasuyu

sergio/humanoid

HTML 13TOML 10 Definiciones

⎇ 8ab12b6800f7b28dfd3aa1a0ee97dff7b1ca41b7 launcher/src/main/java/com/rizoma/orbita/ui/DynamicBackground.kt 84 líneas · 3.3 KB Historia crudo

Órbita: alejar + fondo dinámico · Trazo: aprendizaje con umbral y bilingüe 8ab12b680 · sergio ·

1package com.rizoma.orbita.ui
2
3import androidx.compose.animation.core.LinearEasing
4import androidx.compose.animation.core.RepeatMode
5import androidx.compose.animation.core.animateFloat
6import androidx.compose.animation.core.infiniteRepeatable
7import androidx.compose.animation.core.rememberInfiniteTransition
8import androidx.compose.animation.core.tween
9import androidx.compose.foundation.Canvas
10import androidx.compose.foundation.layout.fillMaxSize
11import androidx.compose.runtime.Composable
12import androidx.compose.runtime.getValue
13import androidx.compose.ui.Modifier
14import androidx.compose.ui.geometry.Offset
15import androidx.compose.ui.graphics.BlendMode
16import androidx.compose.ui.graphics.Brush
17import androidx.compose.ui.graphics.Color
18import com.rizoma.orbita.ui.theme.NeonCian
19import com.rizoma.orbita.ui.theme.NeonRosa
20import com.rizoma.orbita.ui.theme.NeonVerde
21import com.rizoma.orbita.ui.theme.Vacio
22import kotlin.math.PI
23import kotlin.math.cos
24import kotlin.math.sin
25
26private data class Blob(
27    val color: Color,
28    val periodMs: Int,
29    val ax: Float, val ay: Float,   // amplitud relativa del vaivén
30    val ox: Float, val oy: Float,   // origen relativo
31    val freqY: Float,               // relación de frecuencia vertical (Lissajous)
32    val phase: Float,
33)
34
35private val blobs = listOf(
36    Blob(NeonVerde, 49_000, 0.34f, 0.30f, 0.42f, 0.40f, 1.30f, 0f),
37    Blob(NeonRosa, 67_000, 0.38f, 0.26f, 0.58f, 0.62f, 0.80f, 2.1f),
38    Blob(NeonCian, 83_000, 0.30f, 0.34f, 0.50f, 0.30f, 1.70f, 4.0f),
39)
40
41/**
42 * Fondo dinámico congruente con el rizoma: manchas de luz neón que derivan en
43 * trayectorias de Lissajous y laten suavemente, mezcladas de forma aditiva sobre el vacío.
44 */
45@Composable
46fun DynamicBackground(modifier: Modifier = Modifier) {
47    val transition = rememberInfiniteTransition(label = "fondo")
48    val tau = (2 * PI).toFloat()
49    // Una fase por mancha (llamadas directas: animateFloat es @Composable, no cabe en un map/función local).
50    val p0 by transition.animateFloat(
51        0f, tau, infiniteRepeatable(tween(blobs[0].periodMs, easing = LinearEasing), RepeatMode.Restart), label = "p0",
52    )
53    val p1 by transition.animateFloat(
54        0f, tau, infiniteRepeatable(tween(blobs[1].periodMs, easing = LinearEasing), RepeatMode.Restart), label = "p1",
55    )
56    val p2 by transition.animateFloat(
57        0f, tau, infiniteRepeatable(tween(blobs[2].periodMs, easing = LinearEasing), RepeatMode.Restart), label = "p2",
58    )
59    val phases = floatArrayOf(p0, p1, p2)
60
61    Canvas(modifier.fillMaxSize()) {
62        drawRect(Vacio)
63        val w = size.width
64        val h = size.height
65        val base = maxOf(w, h)
66        blobs.forEachIndexed { i, blob ->
67            val t = phases[i] + blob.phase
68            val cx = w * (blob.ox + blob.ax * cos(t))
69            val cy = h * (blob.oy + blob.ay * sin(t * blob.freqY))
70            val pulse = 0.82f + 0.18f * sin(t * 1.7f)
71            val radius = base * 0.62f * pulse
72            drawCircle(
73                brush = Brush.radialGradient(
74                    colors = listOf(blob.color.copy(alpha = 0.16f), Color.Transparent),
75                    center = Offset(cx, cy),
76                    radius = radius,
77                ),
78                radius = radius,
79                center = Offset(cx, cy),
80                blendMode = BlendMode.Plus,
81            )
82        }
83    }
84}