Add Physics sample

This commit is contained in:
Denis Andrasec 2024-07-04 17:48:13 +02:00
parent 2bae3023e4
commit 8df1b3ee27
5 changed files with 305 additions and 1 deletions

View File

@ -0,0 +1,174 @@
celestial-circus-pma.png
size: 1024, 1024
filter: Linear, Linear
pma: true
scale: 0.4
arm-back-down
bounds: 324, 401, 38, 82
rotate: 90
arm-back-up
bounds: 290, 44, 83, 116
rotate: 90
arm-front-down
bounds: 706, 2, 36, 78
rotate: 90
arm-front-up
bounds: 860, 138, 77, 116
bench
bounds: 725, 256, 189, 48
body-bottom
bounds: 879, 868, 154, 124
rotate: 90
body-top
bounds: 725, 128, 126, 133
rotate: 90
chest
bounds: 408, 26, 104, 93
cloud-back
bounds: 752, 378, 202, 165
cloud-front
bounds: 2, 2, 325, 196
rotate: 90
collar
bounds: 786, 13, 47, 26
ear
bounds: 1002, 643, 20, 28
eye-back-shadow
bounds: 428, 395, 14, 10
eye-front-shadow
bounds: 704, 529, 24, 14
eye-reflex-back
bounds: 860, 128, 8, 7
rotate: 90
eye-reflex-front
bounds: 726, 386, 10, 7
eye-white-back
bounds: 835, 23, 13, 16
eye-white-front
bounds: 1005, 1000, 22, 17
rotate: 90
eyelashes-down-back
bounds: 232, 329, 11, 6
rotate: 90
eyelashes-down-front
bounds: 913, 851, 15, 6
rotate: 90
eyelashes-top-back
bounds: 408, 395, 18, 10
eyelashes-top-front
bounds: 702, 179, 30, 16
rotate: 90
face
bounds: 514, 26, 93, 102
rotate: 90
feathers-back
bounds: 954, 625, 46, 46
feathers-front
bounds: 706, 40, 72, 86
fringe-middle-back
bounds: 200, 6, 33, 52
rotate: 90
fringe-middle-front
bounds: 878, 76, 60, 50
rotate: 90
fringe-side-back
bounds: 780, 41, 27, 94
rotate: 90
fringe-side-front
bounds: 939, 161, 26, 93
glove-bottom-back
bounds: 954, 572, 51, 41
rotate: 90
glove-bottom-front
bounds: 916, 256, 47, 48
hair-back-1
bounds: 444, 395, 132, 306
rotate: 90
hair-back-2
bounds: 438, 211, 80, 285
rotate: 90
hair-back-3
bounds: 719, 306, 70, 268
rotate: 90
hair-back-4
bounds: 438, 121, 88, 262
rotate: 90
hair-back-5
bounds: 438, 293, 88, 279
rotate: 90
hair-back-6
bounds: 200, 41, 88, 286
hair-hat-shadow
bounds: 232, 398, 90, 41
hand-back
bounds: 954, 673, 60, 47
rotate: 90
hand-front
bounds: 967, 172, 53, 60
hat-back
bounds: 954, 802, 64, 45
rotate: 90
hat-front
bounds: 780, 70, 96, 56
head-back
bounds: 618, 17, 102, 86
rotate: 90
jabot
bounds: 967, 234, 70, 55
rotate: 90
leg-back
bounds: 232, 441, 210, 333
leg-front
bounds: 444, 529, 258, 320
logo-brooch
bounds: 954, 545, 16, 25
mouth
bounds: 408, 121, 22, 6
neck
bounds: 232, 342, 39, 56
rotate: 90
nose
bounds: 742, 529, 6, 7
rotate: 90
nose-highlight
bounds: 719, 300, 4, 4
nose-shadow
bounds: 869, 128, 7, 8
pupil-back
bounds: 730, 529, 10, 14
pupil-front
bounds: 254, 21, 12, 18
rope-back
bounds: 232, 383, 10, 492
rotate: 90
rope-front
bounds: 232, 383, 10, 492
rotate: 90
rope-front-bottom
bounds: 954, 735, 42, 65
skirt
bounds: 2, 776, 440, 246
sock-bow
bounds: 408, 407, 33, 32
spine-logo-body
bounds: 879, 853, 13, 32
rotate: 90
star-big
bounds: 939, 141, 18, 24
rotate: 90
star-medium
bounds: 742, 537, 6, 8
rotate: 90
star-small
bounds: 719, 378, 3, 4
rotate: 90
underskirt
bounds: 2, 329, 445, 228
rotate: 90
underskirt-back
bounds: 444, 851, 433, 171
wing-back
bounds: 290, 129, 146, 252
wing-front
bounds: 704, 545, 304, 248
rotate: 90

Binary file not shown.

After

Width:  |  Height:  |  Size: 799 KiB

View File

@ -61,7 +61,8 @@ fun AppContent() {
Destination.SimpleAnimation,
Destination.PlayPause,
Destination.AnimationStateEvents,
Destination.IKFollowing
Destination.IKFollowing,
Destination.Physics
),
paddingValues
)
@ -91,6 +92,12 @@ fun AppContent() {
) {
IKFollowing(navController)
}
composable(
Destination.Physics.route
) {
Physics(navController)
}
}
}
}
@ -129,4 +136,5 @@ sealed class Destination(val route: String, val title: String) {
data object PlayPause : Destination("playPause", "Play/Pause")
data object AnimationStateEvents : Destination("animationStateEvents", "Animation State Listener")
data object IKFollowing : Destination("ikFollowing", "IK Following")
data object Physics: Destination("physics", "Physics (drag anywhere)")
}

View File

@ -0,0 +1,122 @@
package com.esotericsoftware.spine
import android.graphics.Point
import androidx.compose.foundation.gestures.detectDragGestures
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.padding
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.rounded.ArrowBack
import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.Icon
import androidx.compose.material3.IconButton
import androidx.compose.material3.Scaffold
import androidx.compose.material3.Text
import androidx.compose.material3.TopAppBar
import androidx.compose.runtime.Composable
import androidx.compose.runtime.mutableIntStateOf
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.ui.Modifier
import androidx.compose.ui.input.pointer.pointerInput
import androidx.compose.ui.layout.onGloballyPositioned
import androidx.compose.ui.viewinterop.AndroidView
import androidx.navigation.NavHostController
import com.esotericsoftware.spine.android.SpineController
import com.esotericsoftware.spine.android.SpineView
@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun Physics(nav: NavHostController) {
val containerHeight = remember { mutableIntStateOf(0) }
val dragPosition = remember { mutableStateOf(Point(0, 0)) }
val mousePosition = remember { mutableStateOf<Point?>(null) }
val lastMousePosition = remember { mutableStateOf<Point?>(null) }
val controller = remember {
SpineController.Builder()
.setOnInitialized { controller ->
controller.animationState.setAnimation(0, "eyeblink-long", true)
controller.animationState.setAnimation(1, "wings-and-feet", true)
}
.setOnAfterUpdateWorldTransforms { controller ->
val lastMousePositionValue = lastMousePosition.value
if (lastMousePositionValue == null) {
lastMousePosition.value = mousePosition.value
return@setOnAfterUpdateWorldTransforms
}
val mousePositionValue = mousePosition.value ?: return@setOnAfterUpdateWorldTransforms
val dx = mousePositionValue.x - lastMousePositionValue.x
val dy = mousePositionValue.y - lastMousePositionValue.y
val position = Point(
controller.skeleton.x.toInt(),
controller.skeleton.y.toInt()
)
position.x += dx
position.y += dy
controller.skeleton.setPosition(position.x.toFloat(), position.y.toFloat());
lastMousePosition.value = mousePositionValue
}
.build()
}
Scaffold(
topBar = {
TopAppBar(
title = { Text(text = Destination.SimpleAnimation.title) },
navigationIcon = {
IconButton({ nav.navigateUp() }) {
Icon(
Icons.Rounded.ArrowBack,
null,
)
}
}
)
}
) { paddingValues ->
Box(modifier = Modifier
.fillMaxSize()
.padding(paddingValues)
.onGloballyPositioned { coordinates ->
containerHeight.intValue = coordinates.size.height
}
.pointerInput(Unit) {
detectDragGestures(
onDragStart = { offset ->
dragPosition.value = Point(offset.x.toInt(), offset.y.toInt())
},
onDrag = { _, dragAmount ->
dragPosition.value = Point(
(dragPosition.value.x + dragAmount.x).toInt(),
(dragPosition.value.y + dragAmount.y).toInt()
)
val invertedYDragPosition = Point(
dragPosition.value.x,
containerHeight.intValue - dragPosition.value.y,
)
mousePosition.value = controller.toSkeletonCoordinates(
invertedYDragPosition
)
},
)
}
) {
AndroidView(
factory = { ctx ->
SpineView(ctx).apply {
loadFromAsset(
"celestial-circus-pma.atlas",
"celestial-circus-pro.skel",
controller
)
}
},
modifier = Modifier.padding(paddingValues)
)
}
}
}