티스토리 뷰
ARCore관련해서 좀 더 공부를 하기 위하여 자료를 조사하던 중 GitHub에 올라온 것들 중 배우고 싶은 것이 있는 것들 위주로 공부를 하게되었습니다.
첫 번 째는 skydoves 님의 Pokedex-AR입니다.
https://github.com/skydoves/Pokedex-AR
ARCore를 활용하여 포켓몬 GO를 비슷하게 만든 프로젝트인 것 같습니다.
포켓몬을 눌렀을 때에는 점프하는 동작을 하고 몬스터볼을 눌렀을 때에는 몬스터볼이 날라가 포켓몬을 잡게 됩니다.
제가 이 프로젝트에서 배우고 싶었던 것은 모델의 움직임과 애니메이션을 주는 방법입니다.
private fun initializeModels(arFragment: ArFragment, session: Session) {
if (session.allAnchors.isEmpty() && !viewModel.isCaught) {
val pose = Pose(floatArrayOf(0f, 0f, -1f), floatArrayOf(0f, 0f, 0f, 1f))
session.createAnchor(pose).apply {
val pokemon = PokemonModels.getRandomPokemon()
ModelRenderer.renderObject(this@SceneActivity, pokemon) { renderable ->
ModelRenderer.addPokemonOnScene(arFragment, this, renderable, pokemon)
}
val pokeBall = PokemonModels.getPokeball()
ModelRenderer.renderObject(this@SceneActivity, pokeBall) { renderable ->
ModelRenderer.addPokeBallOnScene(arFragment, this, this, renderable, pokeBall, pokemon) {
viewModel.insertPokemonModel(pokemon)
}
}
}
}
}
이 프로젝트에서는 앱을 최초 실행할 때 포켓몬과 몬스터볼의 위치를 지정하여 화면에 뿌려줍니다.
private fun getEevee() = RenderingModel(
name = "eevee",
model = "eevee.sfb",
localPosition = DEFAULT_POSITION_POKEMON
)
getRandomPokemon()함수를 이용하면 위와 같이 정의된 함수중 한개가 랜덤으로 실행되게 됩니다.
data class RenderingModel(
val name: String,
val model: String,
val direction: Vector3 = Vector3(0f, 0f, 1f),
val scale: Float = 1f,
val localPosition: Vector3 = Vector3(0.5f, 0.5f, 0.5f)
)
RenderingModel에서 Vector3 값으로 방향과 위치값을 설정하게 됩니다.
fun addPokemonOnScene(
fragment: ArFragment,
anchor: Anchor,
renderable: Renderable,
renderingModel: RenderingModel
) {
val anchorNode = AnchorNode(anchor)
TransformableNode(fragment.transformationSystem).apply {
.. (중략) ..
setOnTouchListener { hitTestResult, motionEvent ->
if (motionEvent.action == MotionEvent.ACTION_UP) {
hitTestResult.node?.let { node ->
node.setLookDirection(Vector3(0f, 0f, 1f))
ModelAnimations.translateModel(
anchorNode = node,
targetPosition = Vector3(
localPosition.x,
localPosition.y + 0.25f,
localPosition.z
),
doWhenFinish = {
val localPosition = renderingModel.localPosition
ModelAnimations.translateModel(node, localPosition)
}
)
}
}
true
}
}
}
TransformableNode를 생성하고 터치 시에 ModelAnimations.translateModel() 함수를 실행하게 됩니다.
object ModelAnimations {
inline fun translateModel(
anchorNode: Node,
targetPosition: Vector3,
durationTime: Long = 150L,
crossinline doWhenFinish: () -> Unit = {}
) {
ObjectAnimator().apply {
setAutoCancel(false)
target = anchorNode
duration = durationTime
setObjectValues(
anchorNode.localPosition,
targetPosition
)
setPropertyName("localPosition")
setEvaluator(Vector3Evaluator())
interpolator = AccelerateDecelerateInterpolator()
start()
}.doWhenFinish { doWhenFinish() }
}
}
ObjectAnimator를 이용하여 모델의 현위치 -> targetPosition 위치로 이동하게 됩니다.
inline fun ObjectAnimator.doWhenFinish(
crossinline block: () -> Unit
) {
addListener(object : Animator.AnimatorListener {
override fun onAnimationEnd(animation: Animator?) = block()
override fun onAnimationStart(animation: Animator?) = Unit
override fun onAnimationCancel(animation: Animator?) = Unit
override fun onAnimationRepeat(animation: Animator?) = Unit
})
}
그 다음 doWhenFinish를 통해 변한 위치 -> 기존 위치의 값으로 다시 ModelAnimations.translateModel() 수행함으로써
점프하는 효과를 주었습니다.
val targetPosition = Vector3(
pokemonPosition.x + getRandomPosition(),
pokemonPosition.y + getRandomPosition(),
pokemonPosition.z + getRandomPosition()
)
private fun getRandomPosition(): Float {
val position = Random.nextFloat()
return if (position <= 0.5f) {
position
} else {
position - 1
}
}
몬스터볼의 경우에는 getRandomPosition()함수를 활용해서 랜덤으로 위치 targetPosition을 설정하여 랜던방향으로 날아가게 됩니다.
두 번째는 SimonMarquis님의 AR-Toolbox입니다.
https://github.com/SimonMarquis/AR-Toolbox
이 프로젝트에서는 ARCore로 할 수 있는 대부분의 기능이 다 구현되어 있었습니다.
그 중 제일 궁금했던 부분은 Color, Metallic, Roughness, Reflectance 와 같은 효과를 주는 방법과
AR 그리기, 거리재기 기능 구현입니다.
코드가 좀 어려워서 완벽하게 이해하진 못 했지만 제가 분석한 것 위주로 작성하겠습니다~
1) 모델에 [ Color, Metallic, Roughness, Reflectance ] 속성 값 주기
sealed class MaterialNode(
name: String,
val properties: MaterialProperties,
coordinator: Coordinator,
settings: Settings
) : Nodes(name, coordinator, settings) {
init {
update()
}
fun update(block: (MaterialProperties.() -> Unit) = {}) {
properties.update(renderable?.material, block)
}
}
Nodes, Coordinator, Settings 모두 프로젝트 내부에서 정의된 클래스이지만 이 기능과는 관계가 별로 없습니다.
update 함수를 통해 MaterialProperties의 속성을 update 해줍니다.
class MaterialProperties(
@field:ColorInt var color: Int = DEFAULT_COLOR,
@field:IntRange(from = 0, to = 100) var metallic: Int = DEFAULT_METALLIC,
@field:IntRange(from = 0, to = 100) var roughness: Int = DEFAULT_ROUGHNESS,
@field:IntRange(from = 0, to = 100) var reflectance: Int = DEFAULT_REFLECTANCE
) {
companion object {
private const val DEFAULT_COLOR = Color.WHITE
private const val DEFAULT_METALLIC = 0
private const val DEFAULT_ROUGHNESS = 40
private const val DEFAULT_REFLECTANCE = 50
val DEFAULT = MaterialProperties()
}
fun update(material: Material?, block: (MaterialProperties.() -> Unit) = {}) {
block(this)
material?.apply {
setFloat3(MATERIAL_COLOR, color.toArColor())
setFloat(MATERIAL_METALLIC, metallic / 100F)
setFloat(MATERIAL_ROUGHNESS, roughness / 100F)
setFloat(MATERIAL_REFLECTANCE, reflectance / 100F)
}
}
}
setFloat와 setFloat3 함수 매개변수로 MATERIAL_ 로 시작하는 값을 확인할 수 있습니다.
package com.google.ar.sceneform.rendering;
import android.content.Context;
import androidx.annotation.RequiresApi;
import com.google.ar.sceneform.rendering.R.raw;
import java.util.concurrent.CompletableFuture;
@RequiresApi(
api = 24
)
public final class MaterialFactory {
public static final String MATERIAL_COLOR = "color";
public static final String MATERIAL_TEXTURE = "texture";
public static final String MATERIAL_METALLIC = "metallic";
public static final String MATERIAL_ROUGHNESS = "roughness";
public static final String MATERIAL_REFLECTANCE = "reflectance";
.. 이하 생략 ..
}
MaterialFactory에 정의되어있는 name과 setFloat와 setFloat3 Material 속성 값을 변경할 수 있습니다.
Material 속성을 가지고 있다면 위의 다섯가지 속성을 언제든 바꿀 수 있습니다.
가장 간단하게 Material 속성을 적용하려면 ShapeFactory을 이용하여 ModelRenderable을 생성 하는 방법입니다.
ShapeFactory에서는 makeCube, makeSphere, makeCylinder 이 3가지 함수를 제공합니다.
그래서 이 프로젝트에서도 큐브, 구, 원기둥을 활용해서 속성을 적용하는걸 확인할 수 있습니다.
2) 드로잉
when (motionEvent.action) {
MotionEvent.ACTION_DOWN -> drawing = Drawing.create(x, y, true, materialProperties(), this, coordinator, settings)
MotionEvent.ACTION_MOVE -> drawing?.extend(x, y)
MotionEvent.ACTION_UP, MotionEvent.ACTION_CANCEL -> drawing = drawing?.deleteIfEmpty().let { null }
}
모션 이벤트를 통해서
DOWN 상태일때 Drawing을 생성
MOVE 상태일때 extend
UP, CANCEL 상태일때 마치게 됩니다.
class Drawing(
val isFromTouch: Boolean,
private val plane: CollisionPlane?,
properties: MaterialProperties,
coordinator: Coordinator,
settings: Settings
) : MaterialNode("Drawing", properties, coordinator, settings) {
companion object {
..(중략)..
fun create(
x: Float,
y: Float,
fromTouch: Boolean,
properties: MaterialProperties,
ar: ArSceneView,
coordinator: Coordinator,
settings: Settings
): Drawing? {
..(중략)..
return Drawing(fromTouch, plane, properties, coordinator, settings).apply {
makeOpaqueWithColor(context.applicationContext, properties.color.toArColor()).thenAccept { material = it }
attach(anchor, scene)
extend(x, y)
}
}
}
}
makerOpaueWitchColor은 MaterialFactory에 정의된 함수로 Material 생성과 동시에 색상을 지정할 수 있는 함수입니다.
private val line = LineSimplifier()
private var material: Material? = null
set(value) {
field = value?.apply { properties.update(this) }
render()
}
private fun append(pointInWorld: Vector3) {
val pointInLocal = (parent as AnchorNode).worldToLocalPoint(pointInWorld)
line.append(pointInLocal)
render()
}
private fun render() {
val definition = ExtrudedCylinder.makeExtrudedCylinder(RADIUS, line.points, material ?: return) ?: return
if (renderable == null) {
ModelRenderable.builder().setSource(definition).build().thenAccept { renderable = it }
} else {
renderable?.updateFromDefinition(definition)
}
}
fun extend(x: Float, y: Float) {
val ray = scene?.camera?.screenPointToRay(x, y) ?: return
if (plane != null) {
val rayHit = RayHit()
if (plane.rayIntersection(ray, rayHit)) {
append(rayHit.point)
}
} else {
append(ray.getPoint(DEFAULT_DRAWING_DISTANCE))
}
}
fun deleteIfEmpty() = if (line.points.size < 2) detach() else Unit
LineSimplifier와 ExtrudedCylinder은 프로젝트에서 정의된 클래스인데 이 부분은 아직 분석 중입니다.
ACTION_DOWN이 호출될 때 위의 과정을 거쳐서 Drawing 객체와 모델을 생성하게 됩니다.
ACTION_MOVE가 호출될 때 위의 과정을 거쳐 기존의 ExtrudedCylinder에 연장해서 그리게 됩니다.
ACTION_UP, ACTION_CANCEL이 호출 될 때 deleteIfEmpty가 호출되어 그리기를 종료하게 됩니다.
Measure 일차적으로 분석은 하였으나 정리할 필요가 있어서 다음에 올리도록 하겠습니다~
'메타버스 스터디' 카테고리의 다른 글
ARCore 정리 (0) | 2021.08.06 |
---|---|
AR Core 4주차 (0) | 2021.07.16 |
ARCore 3주차 (0) | 2021.07.11 |
ARCore 2주차 (0) | 2021.07.04 |
ARCore (0) | 2021.06.27 |
- Total
- Today
- Yesterday
- Worker
- 안드로이드
- Compose Naver Map
- Compose MotionLayout
- WorkManager
- LazyColumn
- Kotlin
- Android
- Compose BottomSheetScaffold
- WebView
- Compose BottomSheetDialog
- 포켓몬 도감
- 웹뷰
- compose
- Retrofit
- Row
- Duplicate class found error
- Compose 네이버 지도 api
- Compose QRCode Scanner
- column
- Gradient
- 안드로이드 구글 지도
- Fast api
- Compose ConstraintLayout
- Pokedex
- Compose BottomSheet
- Duplicate class fond 에러
- Compose 네이버 지도
- Android Compose
- Compose ModalBottomSheetLayout
일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | 2 | 3 | 4 | 5 | 6 | 7 |
8 | 9 | 10 | 11 | 12 | 13 | 14 |
15 | 16 | 17 | 18 | 19 | 20 | 21 |
22 | 23 | 24 | 25 | 26 | 27 | 28 |
29 | 30 | 31 |