Skip to content

Commit

Permalink
27 Prototype character portarit (#28)
Browse files Browse the repository at this point in the history
  • Loading branch information
daniil-shevtsov committed Jun 19, 2022
1 parent 3bc69de commit b6b699c
Show file tree
Hide file tree
Showing 15 changed files with 1,263 additions and 17 deletions.
7 changes: 4 additions & 3 deletions app/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ android {
}

lint {
isAbortOnError = false
abortOnError = false
}

kotlinOptions {
Expand All @@ -53,7 +53,7 @@ android {
}

lint {
isAbortOnError = false
abortOnError = false
}

composeOptions {
Expand Down Expand Up @@ -96,7 +96,8 @@ dependencies {
with(Deps.Compose) {
implementation(ui)
implementation(uiGraphics)
implementation(uiTooling)
debugImplementation(uiTooling)
implementation(uiToolingPreview)
implementation(foundationLayout)
implementation(materialExtended)
implementation(material)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,9 +11,9 @@ import kotlinx.coroutines.flow.collect
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.onEach
import kotlinx.coroutines.flow.scan
import timber.log.Timber
import javax.inject.Inject
import kotlin.time.Duration
import kotlin.time.Duration.Companion.milliseconds

class IdleGameViewModel @Inject constructor(
private val balanceConfig: BalanceConfig,
Expand All @@ -32,7 +32,7 @@ class IdleGameViewModel @Inject constructor(
private suspend fun startTime(until: Duration) {
TimeBehavior.startEmitingTime(
timeStorage = timeStorage,
interval = Duration.milliseconds(balanceConfig.tickRateMillis),
interval = balanceConfig.tickRateMillis.milliseconds,
until = until,
)
}
Expand All @@ -44,7 +44,6 @@ class IdleGameViewModel @Inject constructor(
.scan(0L to 0L) { previousPair, newTime -> previousPair.second to newTime }
.map { (previous, new) ->
val difference = new - previous
Timber.d("previous $previous new $new")
Time(difference)
}
.onEach { time ->
Expand All @@ -54,7 +53,6 @@ class IdleGameViewModel @Inject constructor(
currentState.resources.find { it.key == ResourceKey.Blood }!!.value
val resourceChange =
oldResourceValue + time.value * balanceConfig.resourcePerMillisecond
Timber.d("time: $time oldResourceValue: $oldResourceValue balanceCOnfig: ${balanceConfig.resourcePerMillisecond} resource change: $resourceChange")
imperativeShell.updateState(
newState = currentState.copy(
resources = currentState.resources.map { resource ->
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
package com.daniil.shevtsov.idle.feature.portrait.view

import androidx.compose.ui.geometry.Offset

data class BezierState(
val start: Offset,
val support: Offset,
val support2: Offset? = null,
val finish: Offset,
)

fun BezierState.points() = listOfNotNull(
start,
finish,
support,
support2,
)

fun List<Offset>.toBezierState() = BezierState(
start = get(0),
finish = get(1),
support = get(2),
support2 = getOrNull(3),
)

fun BezierState.multiply(
x: Float = 1f,
y: Float = 1f
): BezierState = points().map { point ->
point.copy(
x = point.x.times(x),
y = point.y.times(y)
)
}.toBezierState()

fun BezierState.add(
x: Float = 0f,
y: Float = 0f
): BezierState = points().map { point ->
point.copy(
x = point.x + x,
y = point.y + y,
)
}.toBezierState()
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
package com.daniil.shevtsov.idle.feature.portrait.view

import androidx.compose.ui.geometry.Offset
import androidx.compose.ui.geometry.Rect
import androidx.compose.ui.geometry.Size
import kotlin.math.pow
import kotlin.math.sqrt
import kotlin.random.Random

fun Rect.shrink(percent: Float): Rect {
return shrink(
widthPercent = percent, heightPercent = percent
)
}

enum class Anchor {
Center
}

fun Rect.move(
position: Offset,
anchor: Anchor = Anchor.Center,
) = Rect(
offset = position.translate(
x = -width / 2,
y = -height / 2,
),
size = size,
)

fun Rect.shrink(
widthPercent: Float = 1f,
heightPercent: Float = 1f,
): Rect {
val newWidth = width * widthPercent
val newHeight = height * heightPercent

return Rect(
offset = topLeft.translate(
x = (width - newWidth) / 2,
y = (height - newHeight) / 2,
),
size = Size(newWidth, newHeight)
)
}

fun Offset.translate(
value: Float,
) = translate(
x = value,
y = value,
)

fun Offset.translate(
x: Float = 0f,
y: Float = 0f,
) = copy(
x = this.x + x,
y = this.y + y,
)

fun Offset.distanceTo(offset: Offset) = sqrt((offset.x - x).pow(2) + (offset.y - y).pow(2))

fun Offset.coerceIn(bounds: Rect) = Offset(
x = x.coerceIn(bounds.left, bounds.right),
y = y.coerceIn(bounds.top, bounds.bottom)
)

fun Offset.times(
x: Float = 1f,
y: Float = 1f
) = Offset(
x = this.x * x,
y = this.y * y,
)

fun Offset.div(
x: Float = 1f,
y: Float = 1f
) = Offset(
x = this.x / x,
y = this.y / y,
)

fun Random.nextFloatInRange(
min: Float = Float.MIN_VALUE,
max: Float = Float.MAX_VALUE,
) = min + nextFloat() * (max - min)

fun BodyPart.toRect() = Rect(
position,
size,
)
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
package com.daniil.shevtsov.idle.feature.portrait.view

import androidx.compose.ui.geometry.Rect
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.Path
import androidx.compose.ui.graphics.drawscope.DrawScope
import androidx.compose.ui.graphics.drawscope.Stroke

fun DrawScope.drawArea(
area: Rect,
color: Color = Color.Red,
strokeWidth: Float = 3f,
) {
drawRect(
color,
style = Stroke(width = strokeWidth),
topLeft = area.topLeft,
size = area.size
)
}

fun DrawScope.drawBodyPart(part: BodyPart) {
drawRect(part.color, topLeft = part.position, size = part.size)
}

fun DrawScope.drawBezierPoints(
bezierState: BezierState,
pointColor: Color = Color.Green,
supportColor: Color = Color.Red,
pointRadius: Float = 16f,
) {
drawCircle(pointColor, center = bezierState.start, radius = pointRadius)
drawCircle(pointColor, center = bezierState.finish, radius = pointRadius)
drawCircle(supportColor, center = bezierState.support, radius = pointRadius)
if (bezierState.support2 != null) {
drawCircle(supportColor, center = bezierState.support2, radius = pointRadius)
}
}

fun Path.drawQuadraticBezier(state: BezierState) {
with(state) {
moveTo(start.x, start.y)
if (state.support2 == null) {
quadraticBezierTo(
support.x,
support.y,
finish.x,
finish.y,
)
} else {
cubicTo(
support.x,
support.y,
support2!!.x,
support2.y,
finish.x,
finish.y,
)
}

}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,115 @@
package com.daniil.shevtsov.idle.feature.portrait.view

import androidx.compose.foundation.Canvas
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.size
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.geometry.Offset
import androidx.compose.ui.geometry.Rect
import androidx.compose.ui.geometry.Size
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import com.daniil.shevtsov.idle.feature.portrait.view.face.drawNose
import com.daniil.shevtsov.idle.feature.portrait.view.face.drawPortrait

@Preview(
widthDp = 800,
heightDp = 800
)
@Composable
fun PortraitPreview() {
val previewSize = 400.dp

val previewMode = PreviewMode.Portaits

val state = PreviewState(
mode = previewMode,
shouldShowFaceAreas = false,
shouldShowNoseAreas = false,
shouldShowEyeAreas = false,
)

when (state.mode) {
PreviewMode.Nose -> {
Canvas(modifier = Modifier, onDraw = {
val nose = BodyPart(
position = Offset(size.width / 2 - size.width / 4, 0f),
size = Size(size.width / 2, size.height),
color = Color.Gray
)
drawNose(nose, state)
})
}
PreviewMode.Portaits -> {
Column {
repeat(2) {
Row {
repeat(2) {
Portrait(
previewState = state,
modifier = Modifier.size(previewSize)
)
}
}
}
}
}
}
}

@Composable
fun Portrait(
previewState: PreviewState,
modifier: Modifier = Modifier,
) {
Canvas(modifier = modifier, onDraw = {
drawPortrait(previewState)
})
}

data class GeneratingConfig(
val faceArea: Rect,
val eyesArea: Rect,
val noseArea: Rect,
val mouthArea: Rect,
)

data class BodyPart(
val position: Offset,
val size: Size,
val color: Color,
)

data class FacePartsSize(
val eye: Size,
val nose: Size,
val mouth: Size,
)

data class PortraitState(
val head: BodyPart,
val leftEye: BodyPart,
val rightEye: BodyPart,
val nose: BodyPart,
val mouth: BodyPart,
)


enum class PreviewMode {
Portaits,
Nose,
}

data class PreviewState(
val mode: PreviewMode,
val shouldShowFaceAreas: Boolean,
val shouldShowNoseAreas: Boolean,
val shouldShowEyeAreas: Boolean,
)




Loading

0 comments on commit b6b699c

Please sign in to comment.