Skip to content

Commit 2de9a2d

Browse files
Added comments and specs explaining the ballistic motion physics behind the confetti burst
1 parent 0fcd305 commit 2de9a2d

1 file changed

Lines changed: 29 additions & 0 deletions

File tree

  • app/src/main/java/com/cornellappdev/uplift/ui/components/general

app/src/main/java/com/cornellappdev/uplift/ui/components/general/ConfettiBurst.kt

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,19 @@ private data class ConfettiParticle2D(
4747
val rotation: Float
4848
)
4949

50+
/**
51+
* Renders a confetti burst anchored to a given popup rectangle.(checkinpopup).
52+
*
53+
* Reads 'showing' from [ConfettiViewModel]. If false or rect is null, renders nothing.
54+
* When shown, spawns particles inside the bounds of 'originRectInRoot'.
55+
* Animates progress from 0 to 1 in 1.2 seconds with linear easing.
56+
* Applies simple ballistic motion to each particle:
57+
* x(t) = x0 + vx * t
58+
* y(t) = y0 +vy0 * t + 0.5 * g * t^2
59+
* where g = 1750 px/s^2 to pull particles downward.
60+
* Fades particles out as progress approaches 1.
61+
* When complete calls [ConfettiViewModel.onAnimationFinished] to hide the confetti.
62+
*/
5063
@Composable
5164
fun ConfettiBurst(
5265
confettiViewModel: ConfettiViewModel,
@@ -73,22 +86,31 @@ fun ConfettiBurst(
7386
LaunchedEffect(uiState.showing) {
7487
if (uiState.showing) started = true
7588
}
89+
90+
// Progress 0 to 1 over 1.2s, used as time 't' in the physics below
7691
val progress by animateFloatAsState(
7792
targetValue = if (started) 1f else 0f,
7893
animationSpec = tween(durationMillis = 1200, easing = LinearEasing),
7994
label = "confettiProgress"
8095
)
8196

97+
//build particles each with spawn, shape, size and velocity
8298
val particles = remember((uiState.showing)) {
8399
List(particleCount) {
100+
//spawn uniformly inside the rect bounds
84101
val x = Random.nextFloat() * rect.width + rect.left
85102
val y = Random.nextFloat() * rect.height + rect.top
86103
val start: Offset = Offset(x,y)
104+
//angled straight up with a random right skew to look more natural
87105
val angle = ((-90f + Random.nextFloat() * 110f) * Math.PI / 180f).toFloat()
106+
//initial speed
88107
val speed = Random.nextFloat() * 700f + 400f
108+
//velocity compontents
89109
val vx = cos(angle) * speed
90110
val vy0 = sin(angle) * speed
111+
//size in px
91112
val size = Random.nextInt(18, 34).toFloat()
113+
92114
ConfettiParticle2D(
93115
start = start,
94116
vx = vx,
@@ -109,23 +131,29 @@ fun ConfettiBurst(
109131
}
110132
}
111133

134+
//Renders ballisitc motion + fade out for each particle
112135
Canvas(modifier = modifier.fillMaxSize()) {
136+
//gravity and seconds t
113137
val g = 1750f
114138
val t = progress * 1.2f
115139

116140
particles.forEach { particle ->
141+
//position at time t
117142
val x = particle.start.x + particle.vx * t
118143
val y = particle.start.y + (particle.vy0 * t + 0.5f * g * t * t)
119144

145+
// fade progress
120146
val alpha = 1f - progress
121147

148+
//gradient brush
122149
val brush = Brush.linearGradient(
123150
colors = colors,
124151
start = Offset(x - particle.size * 0.8f, y- particle.size * 0.8f),
125152
end = Offset(x + particle.size* 0.8f, y+ particle.size * 0.8f)
126153
)
127154

128155
when (particle.shape) {
156+
//draws circle
129157
ConfettiShape.CIRCLE -> {
130158
drawCircle(
131159
brush = brush,
@@ -135,6 +163,7 @@ fun ConfettiBurst(
135163
)
136164
}
137165

166+
//draws rectangle
138167
ConfettiShape.RECTANGLE -> {
139168
withTransform({
140169
rotate(degrees = particle.rotation, pivot = Offset(x, y))

0 commit comments

Comments
 (0)