<?xml version="1.0" encoding="utf-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
    <channel>
        <title> 개발 정보 서고</title>
        <link>https://velog.io/</link>
        <description>안드로이드  네이티브 앱 개발자를 지망하는 대학생입니다.</description>
        <lastBuildDate>Wed, 04 Feb 2026 04:08:24 GMT</lastBuildDate>
        <docs>https://validator.w3.org/feed/docs/rss2.html</docs>
        <generator>https://github.com/jpmonette/feed</generator>
        <image>
            <title> 개발 정보 서고</title>
            <url>https://velog.velcdn.com/images/ksh-g001/profile/5fdc732a-b03e-43c3-a1e3-007ed0440fbe/image.png</url>
            <link>https://velog.io/</link>
        </image>
        <copyright>Copyright (C) 2019.  개발 정보 서고. All rights reserved.</copyright>
        <atom:link href="https://v2.velog.io/rss/ksh-g001" rel="self" type="application/rss+xml"/>
        <item>
            <title><![CDATA[kt cloud TECH UP 프론트엔드 참여 후기]]></title>
            <link>https://velog.io/@ksh-g001/kt-cloud-TECH-UP-%ED%94%84%EB%A1%A0%ED%8A%B8%EC%97%94%EB%93%9C-%EC%B0%B8%EC%97%AC-%ED%9B%84%EA%B8%B0</link>
            <guid>https://velog.io/@ksh-g001/kt-cloud-TECH-UP-%ED%94%84%EB%A1%A0%ED%8A%B8%EC%97%94%EB%93%9C-%EC%B0%B8%EC%97%AC-%ED%9B%84%EA%B8%B0</guid>
            <pubDate>Wed, 04 Feb 2026 04:08:24 GMT</pubDate>
            <description><![CDATA[<blockquote>
<p>2025년 경기도 청년사다리를 마치고 어슬렁거리다가 kt cloud TECH UP을 발견했다. <strong>&quot;안드로이드 네이티브 앱개발자 지망생이지만 웹 프레임워크도 알면 좋지 않을까, 웹앱까지 커버할 수 있는 개발자가 될려면 웹의 환경도 이해를 해야 좋지 않을까?!&quot;</strong> 하는 마음으로 지원하게됐다.</p>
</blockquote>
<h2 id="케클업이란-무엇인가">케클업이란 무엇인가</h2>
<p><a href="https://ktcloud-techup.com/">kt cloud TECH UP 공식 홈페이지에서 자세한 내용을 확인할 수 있다.</a>
일단, 공식적인 kt cloud TECH UP의 줄임말은 케클업이다.</p>
<p>케클업의 장점은 기본적인 과정별 교육 이후 프로젝트에 집중해서 진행하기에 포트폴리오로 쓸만한 프로젝트가 없다면 기본, 심화, 실무 통합 프로젝트로 얻을 수 있다.</p>
<p>운영진의 지향점은 실제 회사 생활과 비슷한 부트캠프 생활이라서 </p>
<p>타과정과의 교류도 이제 막 시작한 참이라서 타 과정은 잘 모르지만, 일단 프론트엔드 과정에서는 React + Vite, Next.js 기반으로 웹 중심으로 수업이 약 2개월간 진행되고 기본 프로젝트 1개월, 심화 프로젝트 1개월, 실무통합 프로젝트 4개월을 진행한다.</p>
<p>실무통합 프로젝트 전까지는 각 과정 안에서 과정이 진행되고 실무통합 프로젝트 이후부터는</p>
<ul>
<li>프로덕트 매니지먼트(PM)</li>
<li>프로덕트 디자인(PD)</li>
<li>프론트엔드</li>
<li>풀스택</li>
<li>백엔드</li>
<li>생성형 AI</li>
<li>사이버 보안</li>
<li>클라우드 인프라</li>
<li>클라우드 네이티브</li>
</ul>
<p>총 9개의 분야와 함께 약 18명의 스타트업 규모로 프로젝트를 진행한다.</p>
<p>가장 큰 장점은 실제 회사 프로세스를 모방하는 부트캠프에서 회사에 들어가지 않으면 만나기 어려운 규모의 인원으로 프로젝트를 진행할 수 있다는 것이라고 판단해서 개인적으로 이 부분을 확인하자마자 지원을 넣게 됐다.</p>
<h2 id="모집-지원">모집 지원</h2>
<p>1기는 여러 트랙으로 지원자를 받았는데 사실상 모바일 관련은 모집 분야에 없고 프론트엔드 과정이 모바일을 고려하는 멀티플랫폼 과정도 아니라서 개인적으로 포트폴리오 전형이 아닌 면접 전형으로 지원했다</p>
<p>사실상 면접 전형밖에 경험해보지 않아서 더 쓸 말이 없지만, 부트캠프 특성상 케클업 운영측도 열정과 &#39;아, 이 사람이면 도중에 그만둘리가 없다&#39;라는 기준으로 지원자를 모집한다고 느꼈다</p>
<p>당시에 제출한 자소서는 이제는 남은 기록이 없지만 결국에 자소서의 이력도 부트캠프로 어떻게 레벨업을 해서 나아갈지에 대한 미래 계획, 열정, 그리고 과거에 어떤 선택을 했고 현재 취업시장에서 무엇을 원하기에 이 과정을 선택했다는 설명이 중요했던 것 같다.</p>
<h2 id="진행-방식">진행 방식</h2>
<p><img src="https://velog.velcdn.com/images/ksh-g001/post/5e1f3899-562e-4609-ac6e-76382f1d01d8/image.png" alt="케클업 디스코드 Warm up day 공지사항">
케클업은 100% zep 온라인 진행이지만, 가끔 커뮤니티 데이라고 해서 오프라인 이벤트도 실시한다. 커뮤니티 데이는 참여 방식이 자율이라서 각 분야 분위기에 따라서 참여율이 달랐다.</p>
<p>다른 부트캠프보다 케클업은 많이 지원해준다고 느꼈다. 지원받은 내용만 정리하면</p>
<ul>
<li>AWS 비용 지원(기본에서는 10만원, 실무 통합에서는 100만원을 지원한다)</li>
<li>교보문고 책 지원금 지원</li>
<li>13만 원 맥북 대여 지원</li>
<li>인프런 강의 한달 지원</li>
<li>kt cloud TECH UP 전용 AI 에이전트 무료 지원</li>
<li>제주도 런케이션(교육 진행 중에 우수 학습자 대상으로 제주도로 여행을 보내준다 물론, 교육 출석은 해야 한다)</li>
<li>kt cloud 데이터 센터 탐방</li>
<li>프로젝트 기간 내내 거의 매번하는 kt cloud 현직자 멘토링 및 커피챗</li>
<li>구름 exp 사이트를 통한 상품 교환(배민 교환권, 네이버 페이 교환권, 다이소 교환권, 교촌치킨 고추바사삭 등등)</li>
<li>포트폴리오 및 이력서 전략 수립 </li>
<li>KT Cloud TECH UP 가방, 후드티, 텀블럭 등이 들어간 웰컴키트(?)</li>
<li>기타 등등</li>
</ul>
<p>실무통합 프로젝트 전까지는 각 분야별로 강사분들께서 비교적 자율적으로 진행하는 느낌이 있다.</p>
<h3 id="교육-기간">교육 기간</h3>
<p><img src="https://velog.velcdn.com/images/ksh-g001/post/1bb0900f-ca93-4550-b7de-21f55275d940/image.png" alt="교육기간 당시에 홀로 공부해봤던 Next.js 연습용 웹사이트">
수강 중인 프론트엔드 과정을 기준으로 말하면 첫날은 자기소개이후 첫 한달은 커리큘럼에 따라서 오전에는 교육을 진행하고 오후에는 생활조라고 해서 같이 공부하는 조로 나뉘어 복습 및 교류를 했다</p>
<p>조마다 생활조 팀장을 뽑아서 자율적으로 진행하되 생활조도 생활조이지만 강사님께서 해주시는 강의가 주 내용이었던 것 같다.</p>
<p>1개월은 React와 Vite 중심이었다면 2개월때는 최근 풀스택 프레임워크가 대두됐기에 Next.js를 중점으로 교육이 진행됐다.</p>
<p>CSR, SSR과 Next.js Route API를 통한 백엔드 API 구현도 했지만 결국 프론트엔드 과정이기에 프론트 관련된 내용을 위주로 다룬다.</p>
<p>기본 교육기간에는 이미 내용을 아는 이들에게는 지루할 수도 있지만, 처음 도전하는 이들에게는 가장 중요한 시간이다. 기본 프레임워크 사용법이나 기초적인 지식을 다루면서 인프런까지 섭렵할 수 있는 기간이기에 최대한 지식을 흡수할 수 있으면 흡수해야한다.</p>
<p>프론트엔드를 처음했던 나의 경우 이 시간이 상당히 소중했다. 그리고 구름 exp에서 포인트 얻기 가장 좋은 기간이기도 하다.</p>
<h3 id="기본-프로젝트-기간">기본 프로젝트 기간</h3>
<p>교육을 대략 2개월동안 하고서 기본 프로젝트를 시작한다. 팀은 운영진측에서 발표를 해준다. 아마도 교육기간 동안 진행된 평가나 성적으로 적당히 각 팀의 평균을 맞춰준 듯하다.</p>
<p>기본 프로젝트에서는 운영진측에서 주제를 정해준다. 기본 CRUD에 결제 연결 정도인 프로젝트로 배정해주는데 운영진 측에 제공한 주제에서 자율주제를 선택해 본인만의 아이디어로 프로젝트 진행할 수도 있다.</p>
<p>내가 참여한 팀에서는 화장품 유목민을 위한 작은 용량&amp;값싼 화장품 판매 플랫폼을 주제로 잡고 진행을 했다.
<img src="https://velog.velcdn.com/images/ksh-g001/post/25cc1c22-c96e-4d62-9f1b-4309c8e232ec/image.png" alt="기본프로젝트에서 만들었던 주문완료 화면">
개인적으로 토스페이 API를 연동하고 장바구니와 좋아요 기능을 담당해서 로직상으로 CRUD를 넘어서 SSR 영역과 CSR 영역을 넘나들 때 Firebase 기반 인증 토큰의 유효성 검사 방식 등등을 고민해야 할 점이 있었다.</p>
<h2 id="심화-프로젝트-기간">심화 프로젝트 기간</h2>
<p>심화 프로젝트때도 운영진측에서 주제를 제시해주지만 그게 싫다면 자율 주제를 선택해 진행할 수 있다. 실제로 프론트엔드에서는 대부분 자율주제를 선택했던 것 같다. 한 팀은 zep같은 오픈월드 2d 멀티 실시간 미니게임 웹사이트를 만들었고 다른 팀에서는 본인의 기술 로드맵을 그래프 형태로 설계하고 AI를 통해서 연관 기술스택을 추천받을 수 있는 프로젝트를 진행했다.</p>
<p><img src="https://velog.velcdn.com/images/ksh-g001/post/45801574-d942-4f0a-86ee-f85df2891bc4/image.png" alt="MyWay 아키텍처">
실제 심화 프로젝트 때 4인구성 프론트엔드 팀에서 한 프로젝트의 아키텍처다. 기본 CRUD+결제였던 기본에 비해서 실시간 채팅 서버를 비롯해 지도기능까지 추가되어서 상당히 프론트엔드의 아키텍처나 기술적으로 성장을 할 수 있었다.</p>
<p>특히나 길찾기 기능을 메인으로 맡게 되어서 안드로이드 프로젝트때는 안해봤던 네이버 지도나 Odsay API같은 길찾기 API를 통해 지도 위에 경로를 표시하는 등 참 좋은 경험을 했다고 생각한다.
<img src="https://velog.velcdn.com/images/ksh-g001/post/5376668d-a36a-45ad-97e4-85441c30facb/image.png" alt="길찾기 구현 화면">
길찾기를 구현할 때 도보에서는 Tmap API가 필요해서 도중에 추가하기도 했는데 다행히 ODsay API와 같이 1일 1000회 무료로 제공된다.</p>
<p>그리고 이쯤에 구름 exp에서 미션이 열려 하나만 하면 300p(배민 1만원권 정도)를 얻을 수 있게 된다.</p>
<h2 id="실무-통합-프로젝트-기간">실무 통합 프로젝트 기간</h2>
<p>실무 통합 프로젝트에서는 운영진측에서 주제를 2개를 제공하고 아직도 진행 중이다. 1기에서의 주제는 </p>
<ol>
<li>AI를 활용한 금융권 수준의 보안이 갖춰진 심리상담서비스</li>
<li>AI를 활용한 대규모 트래픽 공격 및 방어 시스템이 갖춰진 플랫폼</li>
</ol>
<p>였다. 여기서 2번이 조금 설왕설래가 많았는데 처음에는 티켓팅이었다가 티켓팅에 한정되어 있으면 다양한 결과가 나오기 어렵다고 생각되어 티켓팅이라는 말이 빠졌다.</p>
<p>실무 통합 프로젝트 전에 해커톤이라고 해서 아이디어톤을 실시하는데 이때 가장 PM과 PD가 바빠진다. 프론트엔드까지 바빠지는 경우는 아이디어톤 발표에 사용할 회사 홈페이지를 구축했을 때이고 대부분 Figma에서 디자이너가 간단한 flow를 설정해줘서 figma상에서 nav만 작동하도록 한다.</p>
<p>하지만, 개발직군이 확실히 기획/디자인팀에게 좀 기대야 하는(?) 그런 느낌이 있기에 이때 도움이 되고 싶다면 Figma를 깍을 수 있다면 깍는 것도 나쁘지 않다.</p>
<p>특히나 프론트엔드면 PD와 계속 논의하고 협의하면서 PM측과도 논의할 내용도 많아서 Figma를 잘 다뤄서 PD와 친해지는 것도 방법 중 하나인 것 같다.</p>
<p>실무 통합 프로젝트에서 따로 기획팀이 기획할 시간이 없이 곧바로 시작이라서 기획팀에서 병목이 생기는 경우가 많지만, 원래 프로젝트에서도 기획없이는 진행이 안 되는 게 맞다보니 운영측에서 해당 방식을 바꾸지 않는 이상 개발직군이 기획팀을 기다리며 기술적인 조사 위주로 하게 되는 느낌이 없지않아 있다.</p>
<p>운영진이나 강사진에게 문의해서 개발직군 중 몇명이 기획에 같이 하는 경우도 있는데 그러지 않아도 기획팀이 요청하는 내용에 따라 기획 안에서 기술적으로 가능한지 아닌지 정도는 이야기할 수 있다.</p>
<p>다만, 직접적으로 프로젝트에서 다루는 아이템을 결정하고 싶다면 강사&amp;운영측에게 문의해서 참여하는 방식으로 가능하다는 이야기도 들었다. 결국에 팀별로 다르다.</p>
<p>아직 기획을 기다리면서 로그인 화면을 만드는 정도이게 더 적을 수 없지만, kt cloud 현직자 멘토링도 거의 매주하기에 현업에서의 지식도 쉽게 접할 수 있고 조언도 금방 얻을 수 있다.</p>
<p>심지어 특강도 자주하기에 트렌드를 얻거나 현직자들의 관심사같은 걸 알아보기도 좋은 듯 하다.</p>
<h2 id="총평">총평</h2>
<p>팀프로젝트를 진짜 많이 하게 된다. 팀프로젝트가 없어서 고민이라면 나쁘지 않다.</p>
<p>무엇보다 온라인이라서 오프라인보다 시간적으로나 공간적으로나 여유롭다. 오프라인은 팀끼리 만나거나 가끔 과정 안에서 주도해서 열리기도 하니 너무 못 친해지는 것도 아니라서 내향형들에게는 본인이 원하는 만큼 사람과 상호작용할 수 있다는 장점이 있다.</p>
<p>무엇보다 실무통합 프로젝트까지 가면 kt cloud 실무자를 많이 만날 수 있어서 kt cloud 취업에 관심이 많다면 좋은 기회일 것 같다.</p>
<p>국비교육이라서 조건만 갖추면 무료에 지원도 많으니 첫 부트캠프로는 괜찮지 않을까 생각이 든다.</p>
<p>실제로 대규모 트래픽에 맞춰서 kt cloud스러운 특강이나 주제도 있으니 ssafy나 네이버 부캠같은 부캠이 지나갔거나 탈락했다면 도전하기 좋은 듯하다.</p>
<p>단점은 아무래도 온라인 부트캠프이다보니 부트캠프 특유의 단점은 있을 수 있지만, 그래도 그것도 팀 바이 팀이고 케이스 바이 케이스인 듯하다. 실제로 잘만 진행되는 팀이 훨씬 더 많다</p>
<p>AI 지원을 통해서 실제 현업에서 분리된 망 내부에 구축된 AI 사용 느낌? 같은 것도 체험할 수도 있다. 실제로 kt 자체에서 구축해둔 AI를 일부 쓰는 거라고 들었다.(아닐수도 있지만, OT때 그렇게 들었던 것 같다)</p>
<p>지원해서 손해보거나 붕 뜨는 부캠은 절대 아니고 하기 나름에 따라서 많은 경험을 얻을 수 있다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[경기 청년사다리 3기 합격 후기]]></title>
            <link>https://velog.io/@ksh-g001/%EA%B2%BD%EA%B8%B0-%EC%B2%AD%EB%85%84%EC%82%AC%EB%8B%A4%EB%A6%AC-3%EA%B8%B0-%ED%95%A9%EA%B2%A9-%ED%9B%84%EA%B8%B0</link>
            <guid>https://velog.io/@ksh-g001/%EA%B2%BD%EA%B8%B0-%EC%B2%AD%EB%85%84%EC%82%AC%EB%8B%A4%EB%A6%AC-3%EA%B8%B0-%ED%95%A9%EA%B2%A9-%ED%9B%84%EA%B8%B0</guid>
            <pubDate>Thu, 29 May 2025 13:46:39 GMT</pubDate>
            <description><![CDATA[<blockquote>
<p>취직도 안 되겠다 경기청년포털 사이트를 뒤적거리다가 해외연수 프로그램을 신청하게 됐다. 제목에 적힌대로 합격도 했다. 다만, 해당 프로그램의 유용성이나 프로그램에서 지원하는 어학연수 대학교가 세계 명문대인 것에 비해 경쟁률은 16.3:1정도 밖에 안 되는 거 같아 이 글을 작성하게 됐다. 경기도민 청년은 당장 경기청년포털에 가서 이익을 얻기 바라며 적는다.
<del>아 맞다, 하나투어도 채용연계형 인턴 뽑고 있다. 나는 7월</del>8월에 퀸즐랜드 대학교 가야해서 못한다~~</p>
</blockquote>
<h2 id="경기도-청년이면-일단-경기청년포털을-들어가라">경기도 청년이면 일단 경기청년포털을 들어가라</h2>
<p>오늘의 글은 딱 한가지 중요한 점이 있다. 일단, 이 모든 혜택은 <strong>경기도에 거주 중인 만 19세에서 39세 청년만 대상(가끔 예외 있음)</strong>이라는 것이다.
어쩌면, 경기도랑 같은 색의 지역에서는 비슷한 걸 할지도 모른다... 아마도...
경기청년포털은 무엇인가. 경기도에서 먹여주는 청년 대상 혜택 및 각종 교육,기회를 모아두는 포털이다.
그렇다. 경기도에서 보내준다는 해외 취직 인턴같은 것도 거기서 다 공고가 되어서 잡아바어플라이에서 신청하게 되어있다.
나는 이번 기회로 해외연수를 가는 거지만, 사실 이외에도 토익 접수비 지원, 면접 수당, 각종 교육(자격증, 자동차설비, 에어컨 점검, 포토샵 강의, 지자체 행정 인턴, 서포터즈, 경기청년기본소득, 운전면허 지원금 등등)을 공고한다.
사실 그런 거 다 귀찮다 생각한다면 잡아바 어플라이만으로도 충분하다.
나도 사실 SSAFY 불합하고서 잡아바 어플라이에서 뒤적거리다가 발견한 거다.</p>
<h2 id="경기-청년사다리는-무슨-사업인가">경기 청년사다리는 무슨 사업인가</h2>
<p>경기도는 왜 청년을 해외로 보낼려고 하는가.
간단하다.
인재양성이다. 다만, 저소득층에 존재하는 숨겨진 인재를 발굴하는 것에도 중점을 두고 있어서 대부분 소외계층에 대한 가산점이 존재한다.</p>
<p>여기서 말하는 소외계층은 대부분 아래와 같다.
1.기초생활수급자
2.차상위계층
3.양육시설 및 가정위탁 등의 보호를 받다가 보호가 종료된 자립준비청년
4.장애청년
5.고졸 이하 혹은 전문대 재학 및 졸업한 (상대적) 저학력청년
6.해외경험이 없는 청년(이건 해외에 나가는 프로그램만 존재하는 듯 하다)</p>
<p>경기도는 경기 청년사디리에 대해서 이렇게 평가했다.</p>
<blockquote>
<p><strong>해외대학 연수를 통해 청년들의 높은 꿈을 실현할 기회를 주고 다양한 진로 개척과 도전 의지를 심어주기 위한 프로그램</strong></p>
</blockquote>
<p>이번 년도에는 340명을 모집했고 모집 인원수는 매년 꾸준히 지원하는 대학교가 늘어나면서 늘어나고 있다.</p>
<p>면접과 여러 과정을 거치며 느낀 점이 있다면, 경기도는 해당 프로그램을 해외에 가서 놀 사람을 뽑기 보단 해당 프로그램으로 진로 탐색이나 진로 확장을 하는 이들을 선호한다는 것이다. 다만, 그게 외국어 실력은 아니다.
면접관도 &quot;우리가 여러분의 외국어 실력을 중시했다면 공인된 외국어 성적을 제출하게 했을 겁니다&quot;라고 말할 만큼, 어학연수가 중점인 이 프로그램에서 가장 중요한 건 뭐라도 할려는 의지와 가서 무엇을 배우고 올 것인가에 대한 계획성이다.
<img src="https://velog.velcdn.com/images/ksh-g001/post/c7f37156-4319-456c-8cd7-c01a81b4f169/image.png" alt=""></p>
<p>원래 1,2기에서는 합숙면접이라는 과정이 없어서 서류+면접이었다고 하지만, 3기부터 서류+면접+합숙면접이라는 형태로 바뀌었다.</p>
<p>다만, 합숙면접 전에 하는 합숙인 역량강화교육은 교육생들이 해당 해외 대학에 연수를 간다는 가정 아래에 이루어지기에 탈락하면 나는 분명 대학교 소개도 받았고 선배와도 만났는데 정작 해외연수에는 참여할 수 없는 그런 슬픈 일도 일어난다.</p>
<hr>
<h2 id="서류-전형">서류 전형</h2>
<p>서류 전형이 다 그렇다지만, 서류는 신청 사이트인 잡아바 어플라이에 내는 각종 증명서류 및 자기소개서이다.
만약 우대사항이 전혀 없는 일반 신청자로서 자기소개서를 작성하게 된다면,3기에서는 추가 항목의 자기소개서를 작성해야 했지만, 우대사항이면 적을 필요가 없다. 너무 소외계층만 뽑히지 않게 나름 경기도에서 일반 전형의 사람들에게도 보너스를 받을 기회를 만들어 준 거 같다.
자기소개서 문항은 노출해도 되는지 모르겠어서 안 적겠지만. 아마 딱히 숨겨져 있지 않으니 4기 모집이 시작된다면 잡아바 어플라이에 가서 신청하는 과정을 누르면 보일 거다. 이력서 제출하는 타 회사 사이트처럼 여기도 임시저장 기능과 제출 기능이 명확하게 구분되어 있어 자기소개서항목만 보고 오는 것도 가능하다.
하지만, 문항들을 총평하는 건 가능할 거라 믿는다!
자기소개서 문항들의 총평은 딱 한 마디로 <strong>&quot;네가 왜 여기에 지원했고 그 동안 네가 꿈꾸는 걸 위해서 뭘했느냐?&quot;</strong>이다.
느낌은 SSAFY 때처럼 <strong>&quot;나 잘났어 I&#39;m a super genius, god damn cool.&quot;</strong> 같은 답보단 <strong>난 노력했고 나름 이런 결과도 얻었는데 더 배우고 싶고 이런 걸 하고싶어&quot;</strong>를 원한다는 것이 자기소개서에서부터 팍팍 느껴졌다.</p>
<p>가장 좋은 건 하고 싶은 일과 목표로 삼은 대학 혹은 지역이 맞물리면 더 설명하기 쉽다는 거다. 해외 취직을 바라는데 영어 실력을 키우고 싶어서 지원한다 같은 동기는 꽤 생각보다 잘먹히는 거 같다. 다만, 그런 동기를 적기 위해서는 자기소개서에서 그걸 위해 무슨 노력을 했는지 같은 해당 국가 언어와 관련된 에피소드가 있다면 더 좋을 듯 하다.</p>
<h2 id="면접">면접</h2>
<p>면접은 수원시에 있는 경기도여성비전센터에서 했다. 먼저 도착하면 있는 대기실에서 면접번호와 대학교명이 적힌 목걸이를 받고 본인이 원하는 자리에 앉아서 자료를 볼 수 있다.
나는 노션에 면접 준비용 노트를 다 적어놔서 핸드폰을 봤지만, 노트나 정리한 내용을 프린트해서 오신 분도 많았다.</p>
<p>면접은 총 4분만 주어진다. 난 그걸 몰라서 너무 길게 답했고 고작 3<del>4문항만 대답할 수 있었다.
기본적으로 공통적으로 물어보는 질문 3</del>4개가 있고 그 뒤에 자기소개서를 물어보는 듯 하다. 다만, 자기소개서에 대해서 질문을 받은 사람은 거의 없었다. 적어도 경기 청년사다리 3기 퀸즐랜드 대학교 면접에서는.</p>
<p>합숙 때 은근슬쩍 타 대학 참가자분들께 물어보니 국가마다 조금 내용은 다른 듯했다.</p>
<p>그리고 정장.
나는 SSAFY때 교육받는 기회를 얻고자하는 자리에도 정장밖에 없다는 걸 깨닫고 이걸 위해 십만원짜리 정장+정장구두를 질렀다. 그런데, 이곳에서는 정장을 입은 이들이 그리 많진 않았다. 대기실에서는 정장이 반이고 편하지만 단정한 복장이 반이었다.
면접 직전에 대기하는 자리에서는 5명 중 나를 포함한 2명만 정장이었다. 뻘쭘할 수도 있지만, 오히려 정장의 수트핏이 내 근거없는 자신감을 증폭시켜준 덕분에 오히려 당당했다.
면접관이 긴장했냐고 묻는 말에 너무 정신놓고 주변을 구경하다가 못들어서 &quot;네?&quot;도 했다. 그리고 심호흡하라는 거에 하는 척만 하기도 했다.
이제 생각해보니 이거 안 떨어져서 다행인 듯하다.
면접 질문은 엠바고일테니 더는 말하지 않겠다.
다만, 4분이라는 짧은 시간인 만큼 인상을 남길 수 있는 캐치프라이즈 정도는 만들어둬서 첫인사말 때 쓰는 것은 좋았던 거 같다.</p>
<h2 id="합숙">합숙</h2>
<p>여기까지오니 원래라면 합격인데~라고 농담삼아 말하는 참가자들이 많아진다. 나도 그랬다.
3기부터 생긴 전형으로 2번에 걸쳐 3일씩 총 6일 정도 합숙하는 과정있다.
합숙 마지막 날은 합숙 면접을 보고 나머지 활동을 하게 된다.</p>
<p>연수는 YBM 연수원에서 진행됐고 병점역 근처에서 셔틀 버스가 운영됐다. 그렇지만, 1호선을 탈 수 있는 이들이 경기도 내에서도 한정되어 있어서 그런지 다들 셔틀을 퇴실할 때빼고 안 탄 거 같다.
<img src="https://velog.velcdn.com/images/ksh-g001/post/31f77600-f0e5-4635-864a-c8f7e9e91a20/image.JPG" alt="여기가 연수가 이루어지던 YBM 연수원의 YBM홀">
이 사진이 처음 연수에 참가한 날 찍은 사진으로 난 이 날 3시간 정도 잔 채 제정신이 아닌 채로 토크를 했다. 도대체 무슨 정신이었는지 모르겠다. 면접 때 옆자리에 있었던 분도 못 알아봐서 죄송할 일을 만들어버렸다.</p>
<p><img src="https://velog.velcdn.com/images/ksh-g001/post/91231f57-f132-4cbd-abd3-8b36228edfac/image.JPG" alt="">
합숙에서는 탈락자를 위해서 인생 디자인을 한다며 워크북도 나눠줬는데 내 생각에 중학교 진로시간에 했던 그 활동같은 느낌이 있다. 유쾌하고 유익하긴 했지만, 영어 자기소개 및 질의응답이 예고된 상황 속에서 합숙 면접이 다가올 때마다 워크북은 커녕 영어 교재 보느라 다들 바빠진다.</p>
<p>합숙 일정을 자세히 말할 순 없지만, 지난 기수에서 외국어만 가득한 타지에서 3~4주 살아야 하는 환경에 적응하지 못해 자꾸 아픈 사람들(?)이 속출했고 그렇기에 이번 합숙에서는 하루 정도 하루종일 원어민 강사와 함께 연수를 갈 현지 언어를 배우는 시간이 존재한다.</p>
<p>CNN과 공대 교수님들의 강행으로 단련된 나의 말랑말랑한 뇌에 영어자극이 팍팍 들어오게 된다. 그래도 언론사에서 해외 뉴스 모아오라고 일을 받아서 CNN을 일주일에 한 두번 정도 시청한 덕인지 CNN이 키워준 내 영어세포 덕분에 팀의 번역기 정도로 활약한 거 같다.
정적이 흐를 때 내가 뭔가 번역해서 말하면 다들 그거 듣고 따라가는 느낌. 다만, 원어민 강사분과 디렉터분께서 잘 조절해주신 거 같다. 가끔 내가 틀리게 번역해도 그거에 맞춰주신 거 아닐까. 아직도 그런 의심이 든다.</p>
<p><img src="https://velog.velcdn.com/images/ksh-g001/post/dde65079-aed5-4053-bfbe-79df570daf74/image.JPG" alt="">
그리고 YBM 연수원 밥은 생각보다 맛있다. 연수원에서 밥으로 불평한 적은 없는 거 같다...
<del>공지가 자꾸 번복되는 일이 있었지만, 이번 합숙에서 꽤 변수가 많아서 일어났다고 생각한다. 변수가 많으면 원래 지시체계가 있어도 어느정도 혼선은 있을거다... 아마도?</del></p>
<h2 id="합숙면접">합숙면접</h2>
<p>대망의 합숙면접. 3기에서 도입된 이 절차는 간단히 말해서 지금까지 합숙 동안의 태도 및 평가를 비롯해 마지막 날에 1분 연수가는 국가 외국어로 자기소개+1개의 질의응답으로 &#39;합숙 하루에 배운 영어를 얼마나 노력해서 숙지하고 있는가&#39;와 몰라도 막혀도 얼마나 외국어로 말할려고 노력하는가를 파악하는 단체 면접이 있다. 그리고 그 다음에는 특정 주제에 대한 참가자들끼리의 토론 면접이 있다.
기본적으로 M대N로 진행된다.
늘 그렇듯 평가 기준은 경기청년포털의 공지사항으로 꼬박꼬박 올라가있다.
토론 면접에서는 각자 발언권 2개씩은 채울 수 있게 추가 시간을 제공하는 듯하다. 적어도 내가 지원한 퀸즐랜드 대학교에서는 그랬다.</p>
<p>사실, 합숙 면접도 면접이지만, 더 큰 평가는 합숙 동안의 태도와 각종 평가이다. 평가라고 해도 시험을 보는 게 아니라 참가자의 합숙 과정을 지켜보는 스태프 분들, 강사분들, 디렉터 분들의 평가다. 그러니까, 감시당하면서 평가당한다.</p>
<p>그러다보니 합숙할 때 설명회에서 &quot;여러분이 실시간으로 평가를 받는다고 하셔도 즐기는 게 중요하다&quot;라고 몇번을 강조한다. 의외로 합숙하면서 자진 탈락을 하는 이들도 타 대학교에서는 있었고, 도중에 탈주하면 그대로 탈락이고 추후에 중도포기자로 분류되어 다시는 신청을 못하게 될지도 모르니 합숙은 힘들어도 웃는 게 일류가 맞는 거 같다.</p>
<h2 id="총평">총평</h2>
<p>3<del>4주 갔다오는 거 치고 너무 길게 인원을 선별하는 거 아닌가하는 의문이 들었다면 맞다고 해주고 싶지만, 의외로 교육하는 곳에서 뽑는 건 조금 오랫동안 선별하는 느낌이 있다.
아무래도 교육이다보니 &quot;시험받는다&quot;같은 느낌보다는 &quot;이왕 하는 거 신청해야지</del>&quot;하는 이들이 꽤 많다. 진짜 생각보다 많다! 그래서, 거르는데 시간이 필요한 듯하다.
심지어, 이 프로그램은 싫든 좋든 뽑힌 인원과 3~4주를 붙어다녀야 한다. 현지에서 2인 1조가 원칙이기에 무조건 개인 시간이라는 건 숙소 방에 홀로 남았을 때를 제외하면 없다.
그러니, 지원할 때 참고하면 좋겠다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[SSAFY 도전]]></title>
            <link>https://velog.io/@ksh-g001/SSAFY-%EB%8F%84%EC%A0%84</link>
            <guid>https://velog.io/@ksh-g001/SSAFY-%EB%8F%84%EC%A0%84</guid>
            <pubDate>Mon, 06 Jan 2025 12:14:16 GMT</pubDate>
            <description><![CDATA[<blockquote>
<p>SSAFY를 도전했다. 솔직히 C, JAVA Python 모두 본격적으로 코딩테스트를 준비해본 적도 없어서 기대도 안 했다. 최대한 연습했지만, 나에겐 졸업을 건 모의토익 시험도 겹쳐있었다. 어찌저찌 코테를 합격했지만, Kotlin이었으면 금방 풀었을 문제들을 Python으로 꽤 헤매서 개인적으로 이런 일을 대비할 땐 Python을 더 공부해야 겠다는 교훈을 얻을 수 있었다. 결국 둘 다 실패했지만, 그래도 졸업 문제는 다시 도전해서 해결했으니 다행이라고 생각한다.</p>
</blockquote>
<p>다른 글들을 보면 본격적이고 자세한 과정을 공유해서 SSAFY를 도전하는 이들에게 도움을 주지만, 나도 모르게 적으면 안 되는 정보를 나불거릴까봐 일부로 불합 소식이 나온지 꽤 된 뒤에 글을 작성하게되었다. 원래 근황 공유겸 적는 글이니까... 괜찮지 않을까 생각한다.</p>
<h3 id="자기소개서">자기소개서</h3>
<p>도전한 이들은 다들 알지만, 사실 이런 교육을 목표로 하는 곳은
<strong>나 개쩜 나 뽑지 않으면 네 손해</strong>라고 하는 사람보단 <strong>저 부족해서 더 배우고 싶습니다. 그래서 옛날에 이런 노력도 했는데 결과가 안 나와서 여기서 이러이러한 점을 보완해서 성공하고 싶습니다!!!</strong> 하는 사람을 뽑는다.</p>
<p>나도 그래서 이제껏 활동한 내용들을 적은 다음에 내가 어디서 실패해서 어느 부분을 <strong>어떤 캠퍼스의 어떤 교육과정</strong>으로 보완하고 싶은지 적었다. 안드로이드 네이티브 개발자 지망생이기에 당연히 모바일 과정인 구미 캠퍼스를 목표로 열심히 적었다.</p>
<p>기수에 따라서 다르겠지만, 현 기수를 기점으로는 아무래도 웹과 앱의 통합이 많은 시점에서 Vue를 통한 하이브리드앱 커리큘럼이니 난 안드로이드 네이티브를 뽑아도 우대사항에 꼭 React나 Flutter같은 웹도 구현할 수 있는 기술 스택을 선호하는 요즘 취업시장에서의 경험을 담았다.</p>
<p>Vue는 별로 본 적이 없긴 하지만, 나에게 지금 중요한건 웹과 앱을 둘 다 이해할 수 있고 협업이 가능한 네이티브 개발자가 되는것. 대충 이렇게 적었던 거 같다.<em>이제 생각해보니 이 파트는 인터뷰 때 한 거 같기도 하고.</em>
그리고 네이티브 앱도 솔직히 아직 전문가는 커녕 취뽀해본 적도 없기 때문에 졸업작품을 하면서 겪었던 이런 저런 이슈경험과 그 과정에서 어려웠던 점을 적었다. 
예를 들면, </p>
<ul>
<li>Activity와 Fragment 화면의 순서(이전 버튼을 누르면 뭐가 나와? 홈에서 네비게이션 버튼을 누르고서 돌아오면 이전 화면이 살아있나? 이 버튼을 누르면 어느 화면? 만약에 거기서 안드로이드 기본 네비게이션 이전 버튼 누르면 어떤 화면이 나오나?)</li>
<li>자원관리 (그대의 RecyclerView adapter는 지웠나? fragment의 binding를 fragment가 화면에서 사라져서 백그라운드에서 대기하는 중에도 살려둔다고?)</li>
<li>내 생각과 다른 UI와 UX (과연 13dp가 적절한 텍스트 크기라고 생각하나? 버튼이 18dp 사이즈에 8dp 정도 magin을 가진다고 잘 눌린다고 생각하나?)</li>
</ul>
<p>같은 문제들이 있다고 간략하게 적고 나머지 상세한 부분은 인터뷰 때 말한 거 같다.</p>
<h3 id="코테">코테</h3>
<p>간단하다. 다른 블로그에 나온대로 삼성 코테 준비하는 사이트 시스템 그대로이니 그거에 익숙해지는 수 밖에 없다. 난이도는 정말 나처럼 개떡같이 준비한 게 아니면 코테 조금 풀어본 전공자라면, 졸작도 능동적으로 해본 전공자라면 일단 2개 다 제출할 수 있다. 그 만큼 쉽다. 솔직히 나처럼 1개 제출하는 사람은 공부 안 한 게 아닌가 자체 반성을 해야 하는 정도다.</p>
<p>하지만, 코테에서도 유의해야 하는 점이 이건 취직을 위한 코테가 아니고 가르칠 사람을 고르는 코테라는 것이다. 심사 과정은 잘 모르지만,(알 수도 없지만,) 코드를 짜는 과정이 의미가 있어야 한다.</p>
<p>뇌피셜로 추측하자면, &#39;아, 이 사람은 문제를 제대로 푼 건 아니지만, 원리를 알고 있다. 조금 가르치면 금방 따라올 거 같다.&#39; or &#39;원래 쓰던 언어가 아니라서 헷갈려하는 거 같다.&#39;같은 느낌이면 어느정도 되는 거 아닐까 싶다.</p>
<p>&#39;원리를 알고 있지만, 공식을 몰라서 몸통박치기로 답을 구하다가 제 시간 내에 다 답을 도출 못했다.&#39;같은 유형은 통과되는 거 아닐까.</p>
<p><del>아니면 그냥 내가 구미 캠퍼스를 지원해서 그런 걸지도 모른다.</del></p>
<h3 id="인터뷰">인터뷰</h3>
<p>인터뷰 진짜 준비 안 했다! 애초에 면접이 처음이었다. 내 인생에 면접 경험이라곤 알바 면접이나 연합 동아리 면접 밖에 없었다.</p>
<p>일단 블로그에 나온 인터뷰 대비 질문을 싹 봤다. 다들 어디서 가져온 건지 대부분 비슷한 예상 질문이 있던데 예상 질문은 예상일 뿐이였다! 예상 질문에 맞춰서 여러 답을 만들어봤지만, 아무리 외워도 PT를 위한 아이디어 도출 시간에 다 잊어버렸다.</p>
<p>그리고 PT 면접은 생각보다 어렵고 비밀 유출일까봐 말하진 못하지만, 짧은 시간 안에 본인의 생각을 정리해서 말한다는 건 상당히 어려운 일이었다. 내 경험에서 얻을 수 있는 아이디어가 있어서 최대한 머리를 굴려봤지만, 솔직히 인터뷰 끝나자마자 내가 생각한 건 &#39;교통비 준 걸로 점심이나 먹어야 겠다&#39; 였다.</p>
<p>지하철 타면서 내가 발표한 걸 회상해도 &#39;아, 망했구나.&#39;를 벗어나지 못한다.</p>
<p>점심에 면접장 근처에서 먹은 타코는 맛있었다.</p>
<p>그나마 내가 인터넷에 남길 수 있는 정보는 이거 인 거 같다.</p>
<p><strong>나 빼고 다들 정장 입었다.</strong></p>
<h3 id="결과">결과</h3>
<p>한동안 갑자기 게임 만들고 싶다고 언리얼 만지작 거리며 윈도우 데스크탑에서 살아서 몰랐는데 SSAFY 홈페이지의 지원결과 조회 페이지는 리눅스 지원 안 해준다.</p>
<p>슬프다. 나중에 사진은 업데이트 해야 할 거 같다.
<img src="https://velog.velcdn.com/images/ksh-g001/post/abe57912-6ab6-4bfa-85e7-40d02d12453a/image.png" alt=""></p>
<p>그래도 SSAFY에 도전하면서 배운 것도 많다. PT 면접도 처음해보면서 내가 얼마나 초기 기획할 때 구멍이 쑹쑹 뚫린 기획을 하는지 돌아볼 수도 있고. IT 트렌드에 조금 얕은 거 아닐까 하는 생각과 함께 백엔드도 더 공부할 때가 되었다는 생각이 들었다.</p>
<p>또, 기본적인 면접 방식도 마찬가지이고 예의도 내 생각보다는 훨씬 많은 문제가 있다는 점도 알았다. 내가 너무 무덤덤이라서 몰랐던 거지 유튜브로 찾아보니 사람들은 참 많은 생각을 하는 구나라는 생각도 했고 막상 나도 영상을 보고나니 면접 예의범절이 엄청 신경쓰이기 시작했다.</p>
<p>사실 난 무덤덤한 게 아니라 디테일한 생각이 부족했던 거 아닐까. 너무 무대책으로 &#39;하면 되는 거지&#39; 마인드를 줄일 필요가 있을 거 같다. 옛날에 이것저것 고민하는 성격을 고칠려고 도입한 마인드셋이었는데 요즘 조금 과적용이었던 걸지도 모른다.</p>
<p>이제는 미뤄덨던 Compose에 대한 공부를 할 생각이다. 대략 1,2년 정도 숙성한 책이 있는데 늘 그렇듯 공식문서랑 함께 공부하게 될 거 같다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[한이음 ICT 멘토링&공모전, ACK 2024를 마치며]]></title>
            <link>https://velog.io/@ksh-g001/%ED%95%9C%EC%9D%B4%EC%9D%8C-ICT-%EB%A9%98%ED%86%A0%EB%A7%81%EA%B3%B5%EB%AA%A8%EC%A0%84-ACK-2024%EB%A5%BC-%EB%A7%88%EC%B9%98%EB%A9%B0</link>
            <guid>https://velog.io/@ksh-g001/%ED%95%9C%EC%9D%B4%EC%9D%8C-ICT-%EB%A9%98%ED%86%A0%EB%A7%81%EA%B3%B5%EB%AA%A8%EC%A0%84-ACK-2024%EB%A5%BC-%EB%A7%88%EC%B9%98%EB%A9%B0</guid>
            <pubDate>Thu, 07 Nov 2024 16:04:28 GMT</pubDate>
            <description><![CDATA[<blockquote>
<p>원래는 이곳에 경험담같은 건 안 적으려고 했다. 되도록 내가 신기해했던 것들을 위주로 정말 특별한 것들만 적을려고 했는데 그러다보니 이 블로그를 잊게 되는 거 같아서 이제는 경험담도 적기로 결심했다. 어차피 그러라고 있는 게 카테고리 아니겠는가.</p>
</blockquote>
<p>파란만장했던 졸업작품도 끝났고 한이음 ICT 공모전도 12월에 입선 상장(?)을 받는 걸로 끝날테니 슬슬 취직을 준비해야 했다. 이제보니 블로그에서 존댓말과 반말을 오가며 글을 적고 있었는데 이제는 그냥 반말을 찍찍 적고 있었다. 밤 12시가 넘어가서 그런지 지금 내가 정신이 없지만, 정말 지저분한 글을 적고 있지만, IT 기술 블로그란 원래 그런 것이 아닐까.</p>
<p>아마도 정신이 멀쩡해진 나는 이 글을 읽고 처참한 글솜씨에 절망할지도 모른다. 그도 그럴게 맞춤법 검사기도 안 돌렸다.</p>
<p><img src="https://file.notion.so/f/f/853b2bb9-d0d0-4058-854b-3febc65c4b57/46fbf06c-ea58-44fd-aa0a-3d1773da6e77/%ED%95%9C%EC%9D%B4%EC%9D%8C_%EC%9E%85%EC%84%A0.png?table=block&id=1069eea6-e09d-80f9-81c2-d0d94cc6d289&spaceId=853b2bb9-d0d0-4058-854b-3febc65c4b57&expirationTimestamp=1731081600000&signature=-_FrJ-zJHB33wftjVPSjJg7cblkCABxmH7hFQ4Aa3NM&downloadName=%ED%95%9C%EC%9D%B4%EC%9D%8C+%EC%9E%85%EC%84%A0.png" alt="아쉽지만, 어쩌겠는가"></p>
<p>요즘 트랜드가 서론을 빼버린 본론 위주의 글쓰기라고 한다. 바쁜 현대 사회는 이해하지만, 난 그런 글쓰기가 싫다. 서론만 주구장창말하는 건 대화에서 거슬리는 좋은 평가를 받기 어려운 습관이라고 하지만, 작문은 원래 그런 매력이 있기 마련인데 효율이 낭만을 굶겨 죽였다. 요즘 인터넷 소설은 사이다니 뭐니 해서 암시도 빼버리고 이야기 빌딩 과정도 고구마라며 좋아하질 않는다. 슬프다.</p>
<p>그래서 결론만 말하고자 한다. 한이음 ICT 공모전은 입선했다. 멘토님께서는 동상도 가능했을 거 같았다고 말씀하셨지만, 난 간밤마다 괜시리 공모전 상금 리스트를 보며 금상, 수상, 은상은 아니어도 동상을 남몰래 원했기에 소식을 듣고 분했던 기억이 남아있다.</p>
<p>물론, 지금은 결과가 이해가 간다. 여행 앱이라는 기획은 좋았으나, 여행 앱 특성상, 데이터베이스에 쌓인 데이터가 승부를 가르는 시장의 API 값은 대학생이 감당하기에 너무나도 컸다. 수많은 외부 API, 덕지덕지 붙은 지적재산권, 구글 광고에 의존하는 수입. 앱을 출시했다면 금방 적자에 시달릴 운명이었다.</p>
<p>생각해보면 우리는 여행앱을 계획했을 때 수익에 대한 고려를 하지 못했다. 아무리 여행이 그 당시 좋은 주제라고 해도 수익 구조가 없는 앱을 시장성이 있다고 생각할 수 있는가.</p>
<p>난 이제 이 질문의 답이 입선이라는 결과로 나타났다고 생각한다.</p>
<p>그래도 한이음 ICT 멘토링을 통해서 배운 점도 많았다. 예를 들면 이번에 만난 멘토님의 조언이었다.</p>
<p>Retrofit2은 call을 어댑터로 관리할 수 있다는 것이다.</p>
<p>안드로이드 공식 문서로 독학 해온 거나 마찬가지인 나는 그 소식을 듣자마자 약간 멍했다. 정말로 모든 응답마다 코드를 작성 중이었고 그로인해 통신 부분에서 불필요한 코드가 굉장히 많았다.</p>
<p>한 번에 응답을 다루는 방법이 있으면 좋겠다고 생각했지만, 사실 난 이미 Interceptor의 존재도 겨우 알고 있었다. 그러니까, 방법을 몰랐다. 사실 그렇게 쉬운 방법이 이미 만들어져있는지도 몰랐다. 괜히 어댑터를 만들 시도를 구상하고 있었던 거 같다.</p>
<p>내 스타일이 volley 스타일이라고 하는데 옛날 스타일이라고 한다. volley는 이름만 들어보고 Retrofit2도 간단한 조작만 읽고 곧바로 투입되었던 기억은 있다. 그리고서 따로 공부를 하지 않았으니 이런 독특한 스타일이 완성된 게 아닌가 싶다.</p>
<p>사실 좋게 말하면 독특한 것이고 나쁘게 말하면 게으르게 공부했다는 것이다.</p>
<p>안드로이드 하기 전에 맨날 소켓 통신만 보고 하던 내게 Retrofit2의 기본 형태는 엄청난 편리함이었기에 그런 불편함을 덜 느꼈던 거 같기도 하다.</p>
<p>솔직히 너무 편리했다. 그래서 더 편리한 방법이 있다는 걸 인지 못 했던 걸지도 모른다. 결국에 공부를 게으르게한 핑계를 말하고 있는 건데. 아무튼 너무 편리했다.</p>
<p>그것도 그렇고 멘토님께서는 굉장히 친절하셨다. 취직에 대한 조언도 꾸준히 해주셨고 (하지만, 내가 그걸 따랐던 적은 별로 없는 거 같다. 게으름 +1) 디자인 시에 앱의 흐름을 고려해야 한다는 말씀도 해주셨다. (하지만, 그 흐름을 내가 잘 구현 못 했던 거 같다.)</p>
<p>안드로이드 앱과 앱 디자인을 혼자서 맡고 서버 담당자가 두 명이었는데 시작할 때는 내가 그 일을 해내는 게 가능할 거 같았다. 보고서도 내가 쓰고 디자인도 내가 하고 앱도 만들고 테스트하고. 하지만, 현실은 인간은 그렇게까지 멀티를 못한다는 씁쓸한 결과였다.</p>
<p>솔직히 마감일 직전에 서버에서 기능이 몰려오면 난 잠을 잘 수가 없었다. 4시간 자면 행운. 그렇지만, 누구에게 탓할 수도 없는 게 내가 하겠다고 했으니 책임질 수 밖에 없었다. 결정적으로 졸업작품 최종 심사에 내가 늦잠을 자고 데모 앱을 못 가져올 뻔한 일도 발생했었다. 팀원분께서 깨워주셔서 정말 다행이라고 생각한다. 다음부터는 일의 진행을 위해서라도 적당히 적당한 일만 맡기로 결심했다.</p>
<p>그리고, 이번 일로 Figma와 굉장히 많이 친해졌다고 생각한다. ACK 2024에 수상은 아니어도 논문이 게재되고 당연하게도 논문 포스터를 준비했는데.</p>
<p>Figma는 여기서도 활약했다. 논문 포스터를 직접 디자인하면서 처음 구상한 파랑 베이스에 보라색과 노란색을 섞은 몽환적 이미지의 배경색은 CMYK 이슈로 쓰지 못했지만, 단단한 느낌의 파랑 베이스 디자인으로 급하게 선회할 수는 있었다.</p>
<p>Figma와 친해진 이후로는 포토샵과는 멀어진 거 같지만, 뭐 어떤가. 난 디자이너가 아니다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[Android] RecyclerView 드래그 앤 드랍 구현과 화면 스크롤]]></title>
            <link>https://velog.io/@ksh-g001/Android-RecyclerView-%EB%93%9C%EB%9E%98%EA%B7%B8-%EC%95%A4-%EB%93%9C%EB%9E%8D-%EA%B5%AC%ED%98%84%EA%B3%BC-%ED%99%94%EB%A9%B4-%EC%8A%A4%ED%81%AC%EB%A1%A4</link>
            <guid>https://velog.io/@ksh-g001/Android-RecyclerView-%EB%93%9C%EB%9E%98%EA%B7%B8-%EC%95%A4-%EB%93%9C%EB%9E%8D-%EA%B5%AC%ED%98%84%EA%B3%BC-%ED%99%94%EB%A9%B4-%EC%8A%A4%ED%81%AC%EB%A1%A4</guid>
            <pubDate>Thu, 07 Nov 2024 15:17:16 GMT</pubDate>
            <description><![CDATA[<blockquote>
<p>졸업작품을 만들던 어느날 나는 드래그 드랍과 아이템이 드래그되는 도중에 화면이 스크롤되는 것을 구현해달라는 요청을 들었다. 해본적은 없지만, 재밌어 보였다.
<strong>내가 직접하기 전까지...</strong></p>
</blockquote>
<p>그나마 다행인 점은 많이들 구현하는 내용이기에 자료는 많았다. 구현은 끝났지만, 공부할 겸 다시 코드를  들여다보며 복습하기로 결심했다.</p>
<p>사실 복습하면서 새롭게 알게 된 내용도 있어서 블로그 글로 탄생하게 되었다. 신기하게 여기는 것만 적는 습관을 버려야 하는데 쉽지가 않다.</p>
<h2 id="itemtouchhelper">ItemTouchHelper</h2>
<p>ItemTouchHelper 클래스를 한마디로 정의하자면 RecyclerView 내의 Item의 단순 클릭을 넘어서는 액션(드래그 앤 드랍, 스와이프 등)을 구현할 수 있도록 제공되는 클래스이다.</p>
<p>구체적으로 예시를 들자면, 가끔 햄버그 아이콘을 꾹 누르면 아이템이 떼어져서 움직일 수 있게 되거나 리스트 아이템 중 하나를 옆으로 슬라이드했더니 삭제 아이콘이 생기거나 삭제되었다 같은 이벤트를 구현하는데에 사용된다.</p>
<p>심지어는 드래그 앤 드랍 도중에 아이템을 화면 상단에 가져다대면 자연스럽게 위로 스크롤이 되는 것도 이것으로 구현할 수 있다.</p>
<pre><code class="language-kotlin">import androidx.recyclerview.widget.ItemTouchHelper
import androidx.recyclerview.widget.RecyclerView


class TestItemTouchHelperCallback () :  ItemTouchHelper.Callback(){
    override fun getMovementFlags(
        recyclerView: RecyclerView,
        viewHolder: RecyclerView.ViewHolder
    ): Int {
        TODO(&quot;Not yet implemented&quot;)
    }

    override fun onMove(
        recyclerView: RecyclerView,
        viewHolder: RecyclerView.ViewHolder,
        target: RecyclerView.ViewHolder
    ): Boolean {
        TODO(&quot;Not yet implemented&quot;)
    }

    override fun onSwiped(viewHolder: RecyclerView.ViewHolder, direction: Int) {
        TODO(&quot;Not yet implemented&quot;)
    }
}</code></pre>
<p>ItemTouchHelper는 Callback이라는 추상 하위 클래스를 만들어서 상속되어야 하는데 내 추측은 이러하다.</p>
<p>ItemTouchHelper 클래스를 안드로이드 스튜디오에서 살펴보면,onTouchEvent부터 사용자가 아이템을 누를 때의 아이템의 view의 움직임 등이 구현되어 있다. 드래그 앤 드랍이나 스와이프로 작동해야 하는 기능은 앱마다 다를 것이다. 스와이프가 아이템 삭제인 곳이 있고 스와이프가 더보기 메뉴 호출인 경우도 있다.</p>
<p>하지만, 스와이프가 되어 일어나는 뷰의 변화, 즉 옆으로 밀리는 효과는 어느 때나 똑같이 작동해야 할 것이다. 즉, 그러한 기본적인 기능을 ItemTouchHelper에 모아두고 Callback에서는 개발자가 커스텀할 수 있는 부분을 선별하여 둔 것이 아닐까.</p>
<p>이러한 분리는 불필요한 요소까지 확장에 있어서 고려하지 않도록 배려한 구조가 아닐까 싶다.</p>
<p>아직도 ItemTouchHelper 코드를 자세히 본 것은 아니지만, 지금은 그렇게 생각할려고 한다.</p>
<h3 id="getmovementflags">getMovementFlags</h3>
<p>대충 감을 잡았으니 ItemTouchHelper.Callback()를 상속했을 때 반드시 구현해야 하는 세 가지 메소드에 대해서 알아야 한다.</p>
<p>getMovementFlages의 메소드를 Callback 클래스 내부에서 찾아내면 이런 글을 발견할 수 있다.</p>
<pre><code class="language-java">/*
Should return a composite flag which defines the enabled move directions in each state (idle, swiping, dragging).
Instead of composing this flag manually, you can use makeMovementFlags(int, int) or makeFlag(int, int).
This flag is composed of 3 sets of 8 bits, where first 8 bits are for IDLE state, next 8 bits are for SWIPE state and third 8 bits are for DRAG state. Each 8 bit sections can be constructed by simply OR&#39;ing direction flags defined in ItemTouchHelper.
For example, if you want it to allow swiping LEFT and RIGHT but only allow starting to swipe by swiping RIGHT, you can return:
       makeFlag(ACTION_STATE_IDLE, RIGHT) | makeFlag(ACTION_STATE_SWIPE, LEFT | RIGHT);

This means, allow right movement while IDLE and allow right and left movement while swiping.
Params:
recyclerView – The RecyclerView to which ItemTouchHelper is attached. viewHolder – The ViewHolder for which the movement information is necessary.
Returns:
flags specifying which movements are allowed on this ViewHolder.
See Also:
makeMovementFlags(int, int), makeFlag(int, int)
*/

public abstract int getMovementFlags(@NonNull RecyclerView recyclerView,
                @NonNull ViewHolder viewHolder);

</code></pre>
<p>한마디로 정의하자면, 이 ItemTouchHelper.Callback()를 상속하는 TouchHelper에서 어떤 방향의 움직임을 어떤 상태에서 허용할 것인가에 대한 정보를 설정해야 한다는 뜻이다.</p>
<p>방향은 </p>
<ol>
<li>UP</li>
<li>DOWN</li>
<li>LEFT</li>
<li>RIGHT
가 존재한다.</li>
</ol>
<p>상태로는 </p>
<ol>
<li>IDLE : 대기 상태</li>
<li>SWIPE : 스와이프 상태</li>
<li>DRAG : 드래그 상태
가 존재한다.</li>
</ol>
<p>체감상 IDLE은 항상 허용 같은 느낌이고 SWIPE는 내가 아이템을 하나 선택해서 드래그나 스와이프 할때에만 활성화되는 기능을 SWIPEsk DRAG 플래그를 이용해서 구현하는 듯 싶었다.</p>
<p>만약, 위의 코드 속 주석을 잘 읽어보았다면, 이러한 의문이 들 수 있다.</p>
<p><strong>makeMovementFlags랑 makeFlag는 뭐지</strong></p>
<p>그것에 대한 정답은 위의 코드의 바로 위에 존재한다.</p>
<pre><code class="language-java">/* Convenience method to create movement flags.
For instance, if you want to let your items be drag &amp; dropped vertically and swiped left to be dismissed, you can call this method with: makeMovementFlags(UP | DOWN, LEFT);
Params:
dragFlags – The directions in which the item can be dragged. swipeFlags – The directions in which the item can be swiped.
Returns:
Returns an integer composed of the given drag and swipe flags. */

public static int makeMovementFlags(int dragFlags, int swipeFlags) {
            return makeFlag(ACTION_STATE_IDLE, swipeFlags | dragFlags)
                    | makeFlag(ACTION_STATE_SWIPE, swipeFlags)
                    | makeFlag(ACTION_STATE_DRAG, dragFlags);
        }

/* Shifts the given direction flags to the offset of the given action state.
Params:
actionState – The action state you want to get flags in. Should be one of ACTION_STATE_IDLE, ACTION_STATE_SWIPE or ACTION_STATE_DRAG. directions – The direction flags. Can be composed from UP, DOWN, RIGHT, LEFT START and END.
Returns:
And integer that represents the given directions in the provided actionState. */

@SuppressWarnings(&quot;WeakerAccess&quot;)
        public static int makeFlag(int actionState, int directions) {
            return directions &lt;&lt; (actionState * DIRECTION_FLAG_COUNT);
        }</code></pre>
<p>한마디로 정의하자면 모든 상태에서 구현하고 싶다면, makeMovementFlags를 사용하게 되고 하나의 상태에서 적용되는 이벤트를 구현하게 될때는 makeFlag를 사용하게 된다.</p>
<p>그렇다. makeMovementFlags는 or 연산자를 개발자가 직접 하지 않도록 하기 위해 존재하는 메소드이다.</p>
<p>다시 getMovementFlags로 돌아오면,</p>
<pre><code class="language-kotlin">override fun getMovementFlags(recyclerView: RecyclerView, viewHolder: RecyclerView.ViewHolder): Int {
        val dragFlags = ItemTouchHelper.UP | ItemTouchHelper.DOWN
        return makeMovementFlags(dragFlags, ItemTouchHelper.ACTION_STATE_IDLE)
    }</code></pre>
<p>혹은</p>
<pre><code class="language-kotlin">override fun getMovementFlags(recyclerView: RecyclerView, viewHolder: RecyclerView.ViewHolder): Int {
        return makeMovementFlags(ItemTouchHelper.UP | ItemTouchHelper.DOWN, ItemTouchHelper.LEFT)
    }</code></pre>
<p>이라는 방식으로 작성하면 될 것 같다.</p>
<h3 id="onmove">onMove</h3>
<p>그렇다면 onMove 메소드는 도대체 정체가 무엇일까. 답은 언제나 늘 그렇듯 코드에 담겨있다.</p>
<pre><code class="language-java">/**
         * Called when ItemTouchHelper wants to move the dragged item from its old position to
         * the new position.
         * &lt;p&gt;
         * If this method returns true, ItemTouchHelper assumes {@code viewHolder} has been moved
         * to the adapter position of {@code target} ViewHolder
         * ({@link ViewHolder#getAdapterPosition()
         * ViewHolder#getAdapterPosition()}).
         * &lt;p&gt;
         * If you don&#39;t support drag &amp; drop, this method will never be called.
         *
         * @param recyclerView The RecyclerView to which ItemTouchHelper is attached to.
         * @param viewHolder   The ViewHolder which is being dragged by the user.
         * @param target       The ViewHolder over which the currently active item is being
         *                     dragged.
         * @return True if the {@code viewHolder} has been moved to the adapter position of
         * {@code target}.
         * @see #onMoved(RecyclerView, ViewHolder, int, ViewHolder, int, int, int)
         */
        public abstract boolean onMove(@NonNull RecyclerView recyclerView,
                @NonNull ViewHolder viewHolder, @NonNull ViewHolder target);</code></pre>
<p>코드에 적힌 주석을 읽어보면 해당 메소드는 아이템이 어떤 위치에서 특정 위치로 이동되었을 때 해당 메소드가 실행된다(called)고 적혀있다. 또한, 해당 함수는 Boolean 형태의 return 값이 필요한데 주석에서는 해당 메소드의 return 값이 true일 때 onMoved라는 메소드가 실행된다고 말한다.</p>
<p>참고로 onMoved의 메소드는 아래와 같다.</p>
<pre><code class="language-java">        /**
         * Called when {@link #onMove(RecyclerView, ViewHolder, ViewHolder)} returns true.
         * &lt;p&gt;
         * ItemTouchHelper does not create an extra Bitmap or View while dragging, instead, it
         * modifies the existing View. Because of this reason, it is important that the View is
         * still part of the layout after it is moved. This may not work as intended when swapped
         * Views are close to RecyclerView bounds or there are gaps between them (e.g. other Views
         * which were not eligible for dropping over).
         * &lt;p&gt;
         * This method is responsible to give necessary hint to the LayoutManager so that it will
         * keep the View in visible area. For example, for LinearLayoutManager, this is as simple
         * as calling {@link LinearLayoutManager#scrollToPositionWithOffset(int, int)}.
         *
         * Default implementation calls {@link RecyclerView#scrollToPosition(int)} if the View&#39;s
         * new position is likely to be out of bounds.
         * &lt;p&gt;
         * It is important to ensure the ViewHolder will stay visible as otherwise, it might be
         * removed by the LayoutManager if the move causes the View to go out of bounds. In that
         * case, drag will end prematurely.
         *
         * @param recyclerView The RecyclerView controlled by the ItemTouchHelper.
         * @param viewHolder   The ViewHolder under user&#39;s control.
         * @param fromPos      The previous adapter position of the dragged item (before it was
         *                     moved).
         * @param target       The ViewHolder on which the currently active item has been dropped.
         * @param toPos        The new adapter position of the dragged item.
         * @param x            The updated left value of the dragged View after drag translations
         *                     are applied. This value does not include margins added by
         *                     {@link RecyclerView.ItemDecoration}s.
         * @param y            The updated top value of the dragged View after drag translations
         *                     are applied. This value does not include margins added by
         *                     {@link RecyclerView.ItemDecoration}s.
         */
        public void onMoved(@NonNull final RecyclerView recyclerView,
                @NonNull final ViewHolder viewHolder, int fromPos, @NonNull final ViewHolder target,
                int toPos, int x, int y) {
            final RecyclerView.LayoutManager layoutManager = recyclerView.getLayoutManager();
            if (layoutManager instanceof ViewDropHandler) {
                ((ViewDropHandler) layoutManager).prepareForDrop(viewHolder.itemView,
                        target.itemView, x, y);
                return;
            }

            // if layout manager cannot handle it, do some guesswork
            if (layoutManager.canScrollHorizontally()) {
                final int minLeft = layoutManager.getDecoratedLeft(target.itemView);
                if (minLeft &lt;= recyclerView.getPaddingLeft()) {
                    recyclerView.scrollToPosition(toPos);
                }
                final int maxRight = layoutManager.getDecoratedRight(target.itemView);
                if (maxRight &gt;= recyclerView.getWidth() - recyclerView.getPaddingRight()) {
                    recyclerView.scrollToPosition(toPos);
                }
            }

            if (layoutManager.canScrollVertically()) {
                final int minTop = layoutManager.getDecoratedTop(target.itemView);
                if (minTop &lt;= recyclerView.getPaddingTop()) {
                    recyclerView.scrollToPosition(toPos);
                }
                final int maxBottom = layoutManager.getDecoratedBottom(target.itemView);
                if (maxBottom &gt;= recyclerView.getHeight() - recyclerView.getPaddingBottom()) {
                    recyclerView.scrollToPosition(toPos);
                }
            }
        }</code></pre>
<p>onMoved 메소드를 정확하게 이해하진 못했지만, 아이템을 드랍할 때에 대한 기본적인 움직임을 구현한 코드같다. </p>
<p>한마디로 onMove 메소드는 해당 아이템의 이동이 허락되는 조건을 설정하는 메소드인 것이다. 아주 희귀한 경우이겠지만, 위치가 고정되어야 하는 아이템과 위치를 바꿀 수 있는 아이템이 있을 때 onMove 메소드에서 조건문으로 위치를 고정시켜야 하는 아이템을 구분하게 할 수 있을 거 같다.</p>
<h3 id="onswiped">onSwiped</h3>
<p>이쯤 되면 onMove와 onMoved의 사례로 눈치챘을거라고 생각한다. onSwiped 메소드는 드래그앤드랍으로 스와이프가 이뤄졌을 때에 호출되는 메소드이다.</p>
<pre><code class="language-java">        /**
         * Called when a ViewHolder is swiped by the user.
         * &lt;p&gt;
         * If you are returning relative directions ({@link #START} , {@link #END}) from the
         * {@link #getMovementFlags(RecyclerView, ViewHolder)} method, this method
         * will also use relative directions. Otherwise, it will use absolute directions.
         * &lt;p&gt;
         * If you don&#39;t support swiping, this method will never be called.
         * &lt;p&gt;
         * ItemTouchHelper will keep a reference to the View until it is detached from
         * RecyclerView.
         * As soon as it is detached, ItemTouchHelper will call
         * {@link #clearView(RecyclerView, ViewHolder)}.
         *
         * @param viewHolder The ViewHolder which has been swiped by the user.
         * @param direction  The direction to which the ViewHolder is swiped. It is one of
         *                   {@link #UP}, {@link #DOWN},
         *                   {@link #LEFT} or {@link #RIGHT}. If your
         *                   {@link #getMovementFlags(RecyclerView, ViewHolder)}
         *                   method
         *                   returned relative flags instead of {@link #LEFT} / {@link #RIGHT};
         *                   `direction` will be relative as well. ({@link #START} or {@link
         *                   #END}).
         */
        public abstract void onSwiped(@NonNull ViewHolder viewHolder, int direction);</code></pre>
<p>부가적으로 해당 코드를 다시 읽다가 발견한 내용이 있다. 해당 코드를 작성한 개발자들의 전체적인 설명이 담긴 주석은 ItemTouchHelper 클래스의 Callback이라는 내부 추상 클래스의 주석으로 적혀있었다.</p>
<blockquote>
<p> /**
     * This class is the contract between ItemTouchHelper and your application. It lets you control
     * which touch behaviors are enabled per each ViewHolder and also receive callbacks when user
     * performs these actions.
     * <p>
     * To control which actions user can take on each view, you should override
     * {@link #getMovementFlags(RecyclerView, ViewHolder)} and return appropriate set
     * of direction flags. ({@link #LEFT}, {@link #RIGHT}, {@link #START}, {@link #END},
     * {@link #UP}, {@link #DOWN}). You can use
     * {@link #makeMovementFlags(int, int)} to easily construct it. Alternatively, you can use
     * {@link SimpleCallback}.
     * <p>
     * If user drags an item, ItemTouchHelper will call
     * {@link Callback#onMove(RecyclerView, ViewHolder, ViewHolder)
     * onMove(recyclerView, dragged, target)}.
     * Upon receiving this callback, you should move the item from the old position
     * ({@code dragged.getAdapterPosition()}) to new position ({@code target.getAdapterPosition()})
     * in your adapter and also call {@link RecyclerView.Adapter#notifyItemMoved(int, int)}.
     * To control where a View can be dropped, you can override
     * {@link #canDropOver(RecyclerView, ViewHolder, ViewHolder)}. When a
     * dragging View overlaps multiple other views, Callback chooses the closest View with which
     * dragged View might have changed positions. Although this approach works for many use cases,
     * if you have a custom LayoutManager, you can override
     * {@link #chooseDropTarget(ViewHolder, java.util.List, int, int)} to select a
     * custom drop target.
     * <p>
     * When a View is swiped, ItemTouchHelper animates it until it goes out of bounds, then calls
     * {@link #onSwiped(ViewHolder, int)}. At this point, you should update your
     * adapter (e.g. remove the item) and call related Adapter#notify event.
     */</p>
</blockquote>
<p>   이 본문 내용을 번역하면 내용은 다음과 같다.</p>
<blockquote>
<pre><code>   /**</code></pre></blockquote>
<ul>
<li><p>이 클래스는 ItemTouchHelper와 애플리케이션 간의 계약입니다. 이를 통해 각 ViewHolder에서 활성화되는 터치 동작을 제어할 수 있으며, 사용자가 이러한 작업을 수행할 때 콜백을 수신할 수 있습니다.</p>
</li>
<li><p></li>
<li><p>각 뷰에서 사용자가 수행할 수 있는 작업을 제어하려면</p>
</li>
<li><p>{@link #getMovementFlags(RecyclerView, ViewHolder)}를 재정의하고 적절한 방향 플래그 집합을 반환해야 합니다.</p>
</li>
<li><p>({@link #LEFT}, {@link #RIGHT}, {@link #START}, {@link #END},</p>
</li>
<li><p>{@link #UP}, {@link #DOWN}).</p>
</li>
<li><p>{@link #makeMovementFlags(int, int)}를 사용하여 쉽게 구성할 수 있습니다. 또는</p>
</li>
<li><p>{@link SimpleCallback}을 사용할 수 있습니다.</p>
</li>
<li><p>사용자가 항목을 끌면 ItemTouchHelper가</p>
</li>
<li><p>{@link Callback#onMove(RecyclerView, ViewHolder, ViewHolder)</p>
</li>
<li><p>onMove(recyclerView, dragged, target)}를 호출합니다.</p>
</li>
<li><p>이 콜백을 받으면 어댑터에서 이전 위치({@code dragged.getAdapterPosition()})에서 새 위치({@code target.getAdapterPosition()})로 항목을 이동해야 하며</p>
</li>
<li><p>{@link RecyclerView.Adapter#notifyItemMoved(int, int)}도 호출해야 합니다.</p>
</li>
<li><p>View를 놓을 수 있는 위치를 제어하려면</p>
</li>
<li><p>{@link #canDropOver(RecyclerView, ViewHolder, ViewHolder)}를 재정의할 수 있습니다.</p>
</li>
<li><p>드래그하는 View가 여러 다른 View와 겹치는 경우 Callback은</p>
</li>
<li><p>드래그한 View가 위치를 변경했을 수 있는 가장 가까운 View를 선택합니다. 이 접근 방식은 많은 사용 사례에 효과적이지만,</p>
</li>
<li><p>사용자 지정 LayoutManager가 있는 경우</p>
</li>
<li><p>{@link #chooseDropTarget(ViewHolder, java.util.List, int, int)}를 재정의하여</p>
</li>
<li><p>사용자 지정 놓기 대상을 선택할 수 있습니다.</p>
</li>
<li><p>View가 스와이프되면 ItemTouchHelper는 범위를 벗어날 때까지 애니메이션을 적용한 다음</p>
</li>
<li><p>{@link #onSwiped(ViewHolder, int)}를 호출합니다. 이 시점에서 어댑터를 업데이트해야 합니다.</p>
</li>
<li><p>(예: 항목 제거) 및 관련 Adapter#notify 이벤트를 호출합니다.</p>
</li>
<li><p>/</p>
<p> 첫문장만 읽어도 감이 올거라 생각한다. 모든 내용은 결국 이곳에 간략하게 요약되어 있었다.
 onMove에서 예측한 아이템의 놓을 수 있는 위치 제어는 canDropOver라는 메소드를 오버라이드해서 지정하는 것이 좋다고 말하고 있다.</p>
<p> 또한 onSwaped에서는 Adapter에서 실행해야 하는 과정을 처리하면 된다는 말이 적혀있다. 예를 들면, 오른쪽으로 아이템을 스와이프했을 때 <strong>아이템이 삭제되는 과정</strong>을 onSwaped 안에서 실행하면 되는 거 같다. 혹은 스와이프로 아이템이 추가된다거나, 아이템이 회전을 한다거나 아무튼 단순한 드래그앤 드랍, 스와이프 같은 <strong>이미 구현된 움직임을 제외한 추가적인 구현이 필요할 때 구체화 시켜야 하는 메소드</strong>가 onSwap인 듯하다.</p>
</li>
</ul>
<p>사실 이게 이 글을 내가 블로그에 작성하게 된 이유다. 졸업 작품을 만들 당시에는 onSwaped가 아니라 onSelectedChanged에서 adapter에서 해줘야하는 아이템 순서 바꾸기를 처리했다.</p>
<p>onSelectedChange는 드래그 앤 드랍 그러니까, 드래그이든 스와이프이든 모든 과정이 끝났을 때 마무리 과정으로 불려지는 메소드이다. 순서상 onSwaped보다 앞인 거 같다. (왠지 틀릴 거 같지만, 일단 지금은 그렇다고 이해하고 있다.)</p>
<h3 id="그래서-어떻게-해결했는가">그래서 어떻게 해결했는가.</h3>
<p>지금까지 ItemTouchHelper에 대한 기본적인 내용을 살펴봤다. 드래그와 스와이프 이후의 동작에 대한 구현은 대략적인 감은 오지만, 드래그 도중에 동작은 어떻게 구현해야 할까. 그것에 대한 답은 아직 등장하지 않았다.</p>
<p>코드를 바라보던 마감일 1주 정도 남은 나도 그런 심정이었다.</p>
<p>ItemTouchHelper의 코드는 몇줄인 줄 아는가. 2480줄이다. 이전에 살펴봤던 AppCompatActivity보다 줄이 더 길었다. 다 읽고 이해하다간 졸업작품 데모 발표 시기에 못 맞출 듯 싶었다. 앱과 앱 디자인은 나 혼자서 맡았고 개발해야 하는 핵심 기능은 아직도 3개 정도가 남았고 졸업작품 전시회는 3개월이 남았는데 테스트나 할 시간이 남을까.</p>
<p>그래서 나는 익숙함에 기대기로 했다.</p>
<h3 id="onchilddraw">onChildDraw</h3>
<p>내가 생각하는 안드로이드에서 뭐든 구현할 수 있다는 것을 암시하는 키워드는 Draw같다. 개인적으로 안드로이드의 메소드들의 코드를 뜯어본 적이 많았다. 그리고 Draw라는 키워드가 적힌 메소드는 대체로 화면에 뷰를 만들때, 화면에서 뷰에 변화가 일어나서 다시 그릴 때, 혹은 있던 뷰를 원하는 모습으로 다시 만들 때 등등 화면에 변화가 일어나면 무조건적으로 호출되는 메소드였다. 이런 메소드의 특징으로는 늘 Canvar 클래스가 함께한다는 것. (물론 틀릴 수 있다.)</p>
<p>그러니까 시간적 한계에 압박을 느낀 내가 한 선택은 <strong>뷰가 그려질 때, 재활용 될 때를 노려서 현재 아이템의 위치를 확인하고 위치의 y좌표가 0에 너무 가까울 때 혹은 휴대폰 화면의 길이에 가까운 y좌표를 아이템이 갖고 있다면 커스텀 ItemTouchHelper의 매개변수로 있는 ScrollView를 해당 방향으로 스크롤되도록 하는 것</strong>이었다.</p>
<p>이 무슨 성능 저하라고 생각한다. 하지만, 이 생각으로 나는 해당 기능을 구현해냈고 백엔드 팀에서 마감일을 앞두고 몰려서 완성되는 기능들을 앱에 연결할 수 있었다. <strong>내가 하고 싶었던 건 됐으니 괜찮지 않을까!</strong> 그렇게 넘긴 것이다.</p>
<pre><code class="language-kotlin">override fun onChildDraw(
        c: Canvas,
        recyclerView: RecyclerView,
        viewHolder: RecyclerView.ViewHolder,
        dX: Float,
        dY: Float,
        actionState: Int,
        isCurrentlyActive: Boolean
    ) {
        super.onChildDraw(c, recyclerView, viewHolder, dX, dY, actionState, isCurrentlyActive)

          //아이템의 위치 정보 가져오기
        val location = IntArray(2)
        val itemView = viewHolder.itemView
        itemView.getLocationOnScreen(location)
        val itemY = location[1]

          //화면의 길이 정보 가져오기
        val displayMetrics = recyclerView.context.resources.displayMetrics
        val screenHeight = displayMetrics.heightPixels

        // NestedScrollView의 스크롤 처리
        if (itemY &lt; 300) {
            // 상단에 도달했을 때 위로 스크롤
            nestedScrollView.smoothScrollBy(0, -20)
        } else if (itemY &gt; screenHeight - 300) {
            // 하단에 도달했을 때 아래로 스크롤
            nestedScrollView.smoothScrollBy(0, 20)
        }
    }</code></pre>
<p>당시에 내가 완성한 코드다. 코드를 겨우 완성하고 작동했을 때 되게 기뻐했던 걸로 기억하는데 여기에 로그 생성하면 드래그시에 그 로그가 화면을 점령하는 걸 볼 수 있었다.</p>
<h3 id="더-나은-방법은-없을까">더 나은 방법은 없을까</h3>
<p>이게 바로 이 글을 작성하게 된 두 번째 이유다. 비록 그 졸업 작품 데모 준비와 전시회 끝까지 기회 동안 리눅스 버전 안드로이드 스튜디오는 더 이상하게 변했지만 해볼 생각이다.</p>
<p>  <del>체감상 자잘한 오류가 두 배다. 이제 리눅스 2년차인 거 같은데 다시 윈도우로 돌아가야 하나 심각하고 고민 중이다. 업데이트 하자마자 구글에게 보내고 싶은 메일 내용을 정리했던 거 같은데 어디로 갔는지 모르겠다.</del></p>
<p>일단 내가 한 일은 다시 ItemTouchHelper를 읽는 것이었다. 그리고 진짜로 스크롤링만을 위한 메소드가 존재했었다.</p>
<h3 id="interpolateoutofboundsscroll">interpolateOutOfBoundsScroll</h3>
<p>  뷰가 attach된 RecyclerView를 기준으로 경계에서 벗어난 정도에 따라서 RecyclerView를 스크롤할 수 있는 메소드라는 설명이 적혀있다.</p>
<pre><code class="language-java">  /**
         * Called by the ItemTouchHelper when user is dragging a view out of bounds.
         * &lt;p&gt;
         * You can override this method to decide how much RecyclerView should scroll in response
         * to this action. Default implementation calculates a value based on the amount of View
         * out of bounds and the time it spent there. The longer user keeps the View out of bounds,
         * the faster the list will scroll. Similarly, the larger portion of the View is out of
         * bounds, the faster the RecyclerView will scroll.
         *
         * @param recyclerView        The RecyclerView instance to which ItemTouchHelper is
         *                            attached to.
         * @param viewSize            The total size of the View in scroll direction, excluding
         *                            item decorations.
         * @param viewSizeOutOfBounds The total size of the View that is out of bounds. This value
         *                            is negative if the View is dragged towards left or top edge.
         * @param totalSize           The total size of RecyclerView in the scroll direction.
         * @param msSinceStartScroll  The time passed since View is kept out of bounds.
         * @return The amount that RecyclerView should scroll. Keep in mind that this value will
         * be passed to {@link RecyclerView#scrollBy(int, int)} method.
         */
        @SuppressWarnings(&quot;WeakerAccess&quot;)
        public int interpolateOutOfBoundsScroll(@NonNull RecyclerView recyclerView,
                int viewSize, int viewSizeOutOfBounds,
                int totalSize, long msSinceStartScroll) {
            final int maxScroll = getMaxDragScroll(recyclerView);
            final int absOutOfBounds = Math.abs(viewSizeOutOfBounds);
            final int direction = (int) Math.signum(viewSizeOutOfBounds);
            // might be negative if other direction
            float outOfBoundsRatio = Math.min(1f, 1f * absOutOfBounds / viewSize);
            final int cappedScroll = (int) (direction * maxScroll
                    * sDragViewScrollCapInterpolator.getInterpolation(outOfBoundsRatio));
            final float timeRatio;
            if (msSinceStartScroll &gt; DRAG_SCROLL_ACCELERATION_LIMIT_TIME_MS) {
                timeRatio = 1f;
            } else {
                timeRatio = (float) msSinceStartScroll / DRAG_SCROLL_ACCELERATION_LIMIT_TIME_MS;
            }
            final int value = (int) (cappedScroll * sDragScrollInterpolator
                    .getInterpolation(timeRatio));
            if (value == 0) {
                return viewSizeOutOfBounds &gt; 0 ? 1 : -1;
            }
            return value;
        }
    }</code></pre>
<p>  즉, recyclerView의 스크롤을 다룰 수 있다는 건데. 사실인지 확인할 필요가 있었다.</p>
<p>  일단 테스트를 위해서 나는 간단하게 뷰를 만들어냈다. 사실 졸업작품에서 사용했던 리사이클러뷰를 복붙한 결과이지만, 그렇게 어려운 형태는 아니다. 사실 급하게 복붙하느랴 해당 뷰와 짝꿍이던 가이드라인은 전부 빼버렸다. padding으로 수평 간격을 설정하기에는 뷰가 살짝 짤리는 형상이 꽤 심하게 일어난다. 이유는 모르지만, 이것도 나중에 알아볼 생각이다.</p>
<p>  <img src="https://velog.velcdn.com/images/ksh-g001/post/63bbf512-5dfc-4a26-bf61-1d133c3731ae/image.png" alt=""></p>
<p>  일단, 첫 결과는 장렬하게 실패했다. 이유는 간단했다. 오버라이드한 메소드에 대한 로그부터 볼려고 했지만, 로그조차도 안 나타났던 것이다.</p>
<pre><code class="language-kotlin">  override fun interpolateOutOfBoundsScroll(
        recyclerView: RecyclerView,
        viewSize: Int,
        viewSizeOutOfBounds: Int,
        totalSize: Int,
        msSinceStartScroll: Long
    ): Int {
        Log.d(&quot;ItemTouchHelper&quot;, &quot;interpolateOutOfBoundsScroll: $fromPosition, $toPosition\nviewSizeOutOfBounds: $viewSizeOutOfBounds&quot;)

        return super.interpolateOutOfBoundsScroll(
            recyclerView,
            viewSize,
            viewSizeOutOfBounds,
            totalSize,
            msSinceStartScroll
        )
    }</code></pre>
<p>  그래서 확인을 위해서 이전에 작성한 onChildDraw의 주석처리를 제외해봤더니 갑자기 로그가 찍히기 시작했다.</p>
<p>  <img src="https://velog.velcdn.com/images/ksh-g001/post/2ac82c3a-183d-4bc2-bf3c-9bf4dde427b5/image.png" alt="">
도대체 두 메소드에 무슨 상관관계가 있어서 작동하는 걸까. 답을 찾기 위해서 열심히 코드를 봤지만, 알 수 없었다. 하지만, 내 추측으로는 onChildDraw를 통해서만 interpolateOutBoundScroll이 작동하는 거 아닐까 싶다.</p>
<p>그렇지만, 저 로그를 끝으로 또 다시 interpolateOutBoundScroll은 응답이 없었다. 그리고 정답은 정말 쉬웠는데 interpolateOutBoundScroll은 attach된 recyclerView의 크기를 기준으로 경계를 구분한다는 것이었다. 그러니까, RecyclerView의 경계에만 반응하는 것이다. 화면이 절대 기준이 아니었다. 화면을 넘어가는 RecyclerView는 interpolateOutBoundScroll이 반응을 하지 않는다.</p>
<p>  그렇다. 내가 만든 nestedScrollView 안에 든 아주 긴 recyclerView는 이 메소드를 이용하기에 아주아주 부적합한 형태였던 것이다. 여기서 도달한 결론이 있었는데 어쩌면 졸업작품 때는 onChildDraw로 스크롤 기능을 구현했던 것이 맞았던 걸지도 모르겠다는 결론이었다. RecyclerView말고 NestedScroll이 스크롤 되게 하는 건 어떻게든 변경할 수 있겠지만, 졸업작품에서 사용한 RecyclerView는 내 기억상 computing 문제로 recyclerView가 아닌 nestedScroll이 움직이도록 만들었고 그 과정에서 RecyclerView는 스크롤 기능을 잠근채 아주 긴 형태를 가지고 있었다.</p>
<p>  그리고 이번 테스트에서 해당 Computing 문제가 발생해 일단 RecyclerView만 화면에 가득찬 형태로 다시 도전하게 되었다.</p>
<p>  <img src="https://velog.velcdn.com/images/ksh-g001/post/27aadf96-bafc-4a69-b5ad-d71f7dfb7cd5/image.png" alt=""></p>
<p>  그리고 드디어 스크롤에 성공했다. 아까 전과 달리 interpolateOutBoundScroll은 onChildDraw가 필요없었다. 아마도 아까는 부적합한 형태의 뷰였기 때문에 예상치 못한 오류가 발생했거나 우연히 그 때 RecyclerView의 경계에 뷰를 드래그했던 걸지도 모른다.</p>
<p>  설명대로 RecyclerView는 경계에 있는 시간에 따라서 스크롤 되는 속도가 가속된다. 내가 onChildDraw로 만든 스크롤보다 매끄럽다. 이미 있는 코드를 활용하고 싶으면 RecyclerView는 길게 만들지 말라는 교훈을 얻었다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[SceneView 2.0.2 업데이트 공부하기]]></title>
            <link>https://velog.io/@ksh-g001/SceneView-2.0.2-%EC%97%85%EB%8D%B0%EC%9D%B4%ED%8A%B8-%EA%B3%B5%EB%B6%80%ED%95%98%EA%B8%B0</link>
            <guid>https://velog.io/@ksh-g001/SceneView-2.0.2-%EC%97%85%EB%8D%B0%EC%9D%B4%ED%8A%B8-%EA%B3%B5%EB%B6%80%ED%95%98%EA%B8%B0</guid>
            <pubDate>Sat, 20 Jan 2024 15:56:46 GMT</pubDate>
            <description><![CDATA[<blockquote>
<p>오랜만에 돌아왔습니다... 사실 AR은 AR 프로젝트가 끝난 뒤로 공부하지 않았는데 이것과 관련해서 질문이 몇 개 들어왔고 Sceneview 라이브러리가 업데이트를 거치면서 완전히 사용법이 달라졌기에 다른 이들에게 알려주기 전에 정리하고자 이 글을 작성합니다. SceneView 0.10.0 버전에서 SceneView 2.0.2의 차이점은 굉장히 많지만, 그래도 정리해보고자 합니다. 오늘 살펴본 코드는 <a href="https://github.com/ksh-g001/Study/tree/master">이곳에 SceneView/sceneviewTest</a> 있습니다. ARCore의 API 사용 설정을 Google Could에서 진행하시길 바랍니다.(파일의 API키는 제 google colud에서 삭제되었습니다.)</p>
</blockquote>
<h2 id="sceneview의-업데이트">SceneView의 업데이트</h2>
<p><a href="https://github.com/SceneView/sceneview-android/releases/tag/v2.0.2">공식 GitHub</a>
<img src="https://velog.velcdn.com/images/ksh-g001/post/b22e128b-b3dd-4af0-a41a-3fd4d99b3d26/image.png" alt=""></p>
<p>잠시 안 본 사이에 정말 많은 업데이트가 이루어졌습니다. engine요소가 추가된 것을 제외하고도 많은 Node 클래스가 사라지고 대부분 AnchorNode, ARCameraNode, HitResultNode등 기존의 Node 생성과 anchor 생성을 한꺼번에 하는 느낌이 되어버렸다. 물론, 아직 자세하게 읽어보지 않아 두 개를 한꺼번에 한다는 건 틀릴 수도 있다.</p>
<p><img src="https://velog.velcdn.com/images/ksh-g001/post/bfd60c3d-58e0-47d8-a104-67299a3241b6/image.png" alt=""></p>
<p>대표적인 업데이트는 아래와 같고 생각한다.</p>
<h3 id="1-compose추가">1. Compose추가</h3>
<p>Compose가 추가됨에 따라 <a href="https://github.com/SceneView/sceneview-android/tree/main/samples">공식 GitHub의 예제</a>의 방식이 layout 방식과 compose 방식으로 나뉘어져 있다.</p>
<p>하지만, 현재 안드로이드 내에서는 Compose를 통한 안드로이드 코딩 과정의 대부분을 코틀린으로 작성하는 정형화? 통일화? 라고 하는 것을 하고 싶어하기 때문에 공식 깃허브의 README.md는 Compose를 기준으로 작성되었다.</p>
<h3 id="2-arcore의-session">2. ARCore의 Session</h3>
<p>ARCore의 Session 기능에 접근이 가능하게 되었습니다.
<a href="https://developers.google.com/ar/develop/java/session-config?hl=ko">ARCore Session 구성 문서</a>
<a href="https://developers.google.com/ar/reference/java/com/google/ar/core/Session">ARCore Session 문서</a>
ARCore의 Session이 무엇인가는 위의 공식 문서를 통해서 확인할 수 있습니다. 
쉽게 정리하자면, ARCore의 Session은 ARCore의 모든 활동을 관리하고 ARCore 작동에 필요한 생명주기를 관리하는 객체라고 합니다. </p>
<p>다음으로 layout 방식의 ArSceneView(2.0.2 버전) 공식 예시 중 ArSceneView를 설정하는 코드입니다. </p>
<pre><code class="language-kotlin">sceneView = findViewById&lt;ARSceneView?&gt;(R.id.sceneView).apply {
            planeRenderer.isEnabled = true
            //해당 ARSceneView의 Sesstion을 설정합니다. 해당 코드에서는 ARCore의 Depth API를 사용할 수 있는지 확인한 다음에 해당 API를 활성화하거나 비활성화하는 코드입니다. Depth API는 특정 센서가 존재하지 않으면 사용할 수 없습니다.
            configureSession { session, config -&gt;
            //해당 기기에서 Depth API를 사용할 수 있는 확인하는 부분
                config.depthMode = when (session.isDepthModeSupported(Config.DepthMode.AUTOMATIC)) {
                    true -&gt; Config.DepthMode.AUTOMATIC
                    else -&gt; Config.DepthMode.DISABLED
                }
                config.instantPlacementMode = Config.InstantPlacementMode.DISABLED
                config.lightEstimationMode = Config.LightEstimationMode.ENVIRONMENTAL_HDR
            }
            // 해당 ARSceneView의 1 프레임당의 시스템 업데이트 방식을 정하는 코드입니다.
            onSessionUpdated = { _, frame -&gt;
                if (anchorNode == null) {
                    frame.getUpdatedPlanes()
                        .firstOrNull { it.type == Plane.Type.HORIZONTAL_UPWARD_FACING }
                        ?.let { plane -&gt;
                            addAnchorNode(plane.createAnchor(plane.centerPose))
                        }
                }
            }</code></pre>
<p>각각 Session이 들어간 두 개의 configureSession 메소드와 onSessionUpdated 변수를 코드에서 발견할 수 있습니다.</p>
<p>각각의 공식 설명은 AndroidStudio의 디컴파일(Ctrl + 클릭)을 통해 볼 수 있습니다.
먼저 configureSession 메소드에 대한 공식의 설명과 코드는 이러합니다.</p>
<pre><code class="language-kotlin">    /**
     * Define the session config used by ARCore.
     *
     * Prefer calling this method before the global (Activity or Fragment) onResume() cause the
     * session base configuration in made there.
     * Any later calls (after onSessionResumed()) to this function are not completely sure be taken
     * in account by ARCore (even if most of them will work)
     *
     * Please check that all your Session Config parameters are taken in account by ARCore at
     * runtime.
     *
     * @param applyConfig the apply block for the new config
     */
    fun configureSession(applyConfig: (Session, Config) -&gt; Unit) {
        _onSessionCreated += object : (Session) -&gt; Unit {
            override fun invoke(session: Session) {
                _onSessionCreated -= this
                session.configure { config -&gt;
                    applyConfig.invoke(session, config)
                }
            }
        }
    }</code></pre>
<p>위의 내용은 ARCore의 Session 관리에 대해서 배경 지식이 없으면 이해하기 어려울 수도 있습니다. 먼저, ARCore의 모든 활동을 접근할 수 있는 Session의 경우 onResume() 때 설정이 완료된다는 설명을 발견할 수 있습니다. 따라서, 해당 메소드를 onResume() 전에 사용할 것을 권장하고 있습니다.
또한, 해당 메소드의 config 매개 변수에는 ARCore에서 지원하는 기능에 대한 설정을 할 수 있도록 매개변수가 존재합니다.
만약, 프로젝트에서 ARCore에서 AR만 띄우는 게 아니라 CloudAnchor나, streetscapeGeometry, geospatial 같은 기능을 사용하고 싶다면 아래와 같이 설정해야 한다.</p>
<pre><code class="language-kotlin">configureSession { session, config -&gt;
                //추가된 부분
                config.streetscapeGeometryMode  = Config.StreetscapeGeometryMode.ENABLED
                config.geospatialMode = Config.GeospatialMode.ENABLED
                //추가된 부분

                config.depthMode = when (session.isDepthModeSupported(Config.DepthMode.AUTOMATIC)) {
                    true -&gt; Config.DepthMode.AUTOMATIC
                    else -&gt; Config.DepthMode.DISABLED
                }
                config.instantPlacementMode = Config.InstantPlacementMode.DISABLED
                config.lightEstimationMode = Config.LightEstimationMode.ENVIRONMENTAL_HDR
            }</code></pre>
<p>아직 직접적으로 해당 기능을 활용한 것은 아니고 해당 기능을 사용할 것이며 해당 내용을 매 프레임마다 반영할 필요가 있다고 ARCore에게 알리기만 하였다.</p>
<p>이제는 sessionUpdated 필드에 대한 공식 설명이다.</p>
<pre><code>Updates of the state of the ARCore system.
This includes: receiving a new camera frame, updating the location of the device, updating the location of tracking anchors, updating detected planes, etc.
This call may update the pose of all created anchors and detected planes. The set of updated objects is accessible through Frame.getUpdatedTrackables.
Invoked once per Frame immediately before the Scene is updated.</code></pre><p>왜 코드가 없는가는 직접 확인해보시면 압니다. 코드가 엄청 깁니다. 매 프레임마다 해당 무엇을 업데이트해야 하는가를 명시하는 코드라고 합니다.</p>
<h3 id="node의-생성-방식">Node의 생성 방식</h3>
<p>이전의 방식은 Node를 생성할 때 해당 Node를 보여줄 sceneview 나 arsceneview의 엔진만 설정해주는 등의 방식으로 연결되고 공식 예제에서 보여주는 방식도 매우 간단한 코드처럼 보였다.
하지만, 지금의 node를 생성하는 공식 예제 코드는 이러하다.</p>
<pre><code class="language-kotlin">    fun addAnchorNode(anchor: Anchor) {
        sceneView.addChildNode(
            AnchorNode(sceneView.engine, anchor)
                .apply {
                    isEditable = true
                    lifecycleScope.launch {
                        isLoading = true
                        sceneView.modelLoader.loadModelInstance(
                            &quot;https://sceneview.github.io/assets/models/DamagedHelmet.glb&quot;
                        )?.let { modelInstance -&gt;
                            addChildNode(
                                ModelNode(
                                    modelInstance = modelInstance,
                                    // Scale to fit in a 0.5 meters cube
                                    scaleToUnits = 0.5f,
                                    // Bottom origin instead of center so the model base is on floor
                                    centerOrigin = Position(y = -0.5f)
                                ).apply {
                                    isEditable = true
                                }
                            )
                        }
                        isLoading = false
                    }
                    anchorNode = this
                }
        )
    }</code></pre>
<p>이전 포스트에서 많이 본 형태들보다 복잡하다. 그러나, 생각보다 쉽다. 잘 살펴보면 apply, let, 코루틴 때문에 복잡해졌을 뿐 사실 이전의 그 코드들과 이름만 다르다는 걸 알 수 있다.
이 코드는 sceneView라는 ARSceneView에 자식 노드를 추가하는 코드이다.
여기서 AnchorNode는 현실 공간에서의 위치가 고정된 Node이다. anchor이라는 단어가 들어간 것들은 대부분 위치가 고정되었다는 것을 내포한다.</p>
<p>차례대로 apply의 내용은 이러하다.</p>
<ul>
<li>isEditable : 해당 Node는 나중에 수정될 수 있는가에 대한 변수</li>
<li>isLoading : 이 예제에서 만든 변수로 glb 파일을 로딩 중이라면 로딩 화면을 띄우는 상태를 결정하는 변수</li>
<li>sceneView.modelLoader.loadModelInstance : glb, gltf 파일을 가져와서 모델 인스턴스 생성, url, uri, file 위치등등 여러가지 형태로 glb, gltf 파일을 가져올 수 있으며 잠시 실행을 중지시킬 수 있는 suspend 함수이다.</li>
<li>anchorNode : 이 예제에서 만든 변수로 ARCore가 평면이나 빈 공간 인식에 실패했을 때 천천히 빈공간에 옮기라고 하는 등의 안내를 하기 위해 만든 변수</li>
</ul>
<p>다음으로 let의 내용은 이러하다.
일단, 이 내용은 loadModelInstance로 만들어진 모델 인스턴스가 null이 아니면 실행된다.
먼저 AnchorNode의 자식 node를 생성하는데 여기서는 ModelNode를 생성하여 모델 인스턴스를 볼 수있도록 한다.</p>
<ul>
<li>modelInstance: loadModelInstance에서 생성된 모델 인스턴스를 설정하는 변수. 3D로 렌더링된 모델을 주로 사용한다고 설명되어 있다.</li>
<li>scaleToUnits : 모델의 사이즈를 설정하는 변수. Float 타입이다.</li>
<li>centerOrigin : 옛날의 screenPosition과 동일하게 모델의 핸드폰 카메라에 대한 상대적인 위치를 결정하는 변수이다. 다만, 모델의 중심을 기준으로 설정되는 것같다.</li>
</ul>
<p>한 마디로 sceneView 아래에 AnchorNode를 생성하고, AnchorNode 아래에 ModelNode를 생성한다. 옛날에 node를 만들고 해당 node 안에서 anchor를 설정 했다면 이번에는 AnchorNode를 만들어 그곳에 ModelNode를 매달리게 만들었다는 느낌이 든다. (아직 다 안 읽어봐서 아닐 수도 있습니다.)</p>
<h2 id="여담-sceneview-110">여담 SceneView 1.1.0</h2>
<p>왜 이런 대격변을 겪었는가에 대한 이제부터는 추측글이다. 릴리즈 노트를 읽던 중 이런 글을 보고 말았다.
<img src="https://velog.velcdn.com/images/ksh-g001/post/2c186ca6-64e3-42f1-b556-5033516264db/image.png" alt="">
아무래도 구현 과정에서 SceneView -&gt; ARCore -&gt; Filament 라는 흐름에서 ARCore를 빼고 SceneView -&gt; Filament 방식으로 대체된 부분이 많은 거 같다.
실제로 많은 부분을 그러한 방식으로 바꾼 것인지 아예 SceneView 이전 버전과 현재 버전을 구분한 폴더로 GitHub에 올라와있었다.
이미 1.1.0 버전부터 많은 Node가 삭제된 흔적을 발견했지만, 문제는 내부의 코드가 거의 다르다는 거다. 이러니, 옛날 예제들이 전부 안 됐던 거 같다.
자세한 건 커뮤니티를 좀 더 들여다 봐야 알 수 있겠지만, 2.0.0 버전이 시작되고서 꽤 많은 issue와 dicuss가 있는 것처럼 보인다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[우분투 22.04에서 CLion 설치 및 바로가기(.desktop)생성하기 (with 학생 인증 라이센스)]]></title>
            <link>https://velog.io/@ksh-g001/%EC%9A%B0%EB%B6%84%ED%88%AC-22.04%EC%97%90%EC%84%9C-CLion-%EC%84%A4%EC%B9%98-%EB%B0%8F-%EB%B0%94%EB%A1%9C%EA%B0%80%EA%B8%B0.desktop%EC%83%9D%EC%84%B1%ED%95%98%EA%B8%B0-with-%ED%95%99%EC%83%9D-%EC%9D%B8%EC%A6%9D-%EB%9D%BC%EC%9D%B4%EC%84%BC%EC%8A%A4</link>
            <guid>https://velog.io/@ksh-g001/%EC%9A%B0%EB%B6%84%ED%88%AC-22.04%EC%97%90%EC%84%9C-CLion-%EC%84%A4%EC%B9%98-%EB%B0%8F-%EB%B0%94%EB%A1%9C%EA%B0%80%EA%B8%B0.desktop%EC%83%9D%EC%84%B1%ED%95%98%EA%B8%B0-with-%ED%95%99%EC%83%9D-%EC%9D%B8%EC%A6%9D-%EB%9D%BC%EC%9D%B4%EC%84%BC%EC%8A%A4</guid>
            <pubDate>Tue, 07 Nov 2023 15:28:10 GMT</pubDate>
            <description><![CDATA[<blockquote>
<p>창모드로 접속해서 tar.gz 파일만 보였는데 이제보니 CLion을 설치하는 방법이 두 가지가 있었습니다. apt 말고 snap에 등록되어 있어서 놓친 듯합니다. 그래도 snap에서 dependency 문제로 설치가 안 된다거나 하는 일이 발생할 것 같아 글을 작성하게 되었습니다.
<a href="https://www.jetbrains.com/ko-kr/clion/download/#section=linux">CLion 공식 다운로드 주소</a></p>
</blockquote>
<h3 id="목차">목차</h3>
<ol>
<li><p>snap을 통한 설치 방법</p>
</li>
<li><p>tar.gz 파일 압축 해제 후 설치 방법</p>
</li>
<li><p>바탕화면 바로가기 아이콘 생성 (.desktop)</p>
</li>
<li><p>학생 인증 라이센스</p>
<br/>
## snap을 통한 설치 방법
snap를 통해서 안전하게 package를 받아오는 방법입니다.
tar.gz 파일을 압축해체해서 실행 파일을 실행하는 방법보다 쉽게 할 수 있다는 장점과 함께 설치 파일의 위치를 자신이 정할 수 없고 가끔 바로가기 아이콘을 생성해주지 않는 프로그램의 경우 그 경로를 찾을 때 곤란할 수 있다는 단점과 간혹 느리다는 단점이 있습니다.
</li>
<li><p>터미널을 Ctrl+Alt+T나 바탕화면에서 우클릭을 하여 터미널을 실행해줍니다.</p>
</li>
<li><p>공식 다운로드 사이트에 올라온 명령어를 입력해줍니다.</p>
<pre><code>$ sudo snap install clion --classic</code></pre><p>반드시 --classic 옵션을 적어주셔야 합니다. 그렇지 않으면 오류가 뜨는 오류의 내용은 다음과 같습니다.</p>
<pre><code>오류: This revision of snap &quot;clion&quot; was published using classic confinement
     and thus may perform arbitrary system changes outside of the security
     sandbox that snaps are usually confined to, which may put your system
     at risk.

     If you understand and want to proceed repeat the command including
     --classic.
</code></pre></li>
</ol>
<pre><code>해당 프로그램은 시스템에 접근할 수 있고 이로 인해 보안적인 문제나 시스템에 문제가 있을 수 있다는 경고입니다. 마지막 문단은 위의 경고를 이해했다고 설치를 원한다면 --classic 옵션을 추가하여 명령어를 실행하라는 뜻입니다.

![](https://velog.velcdn.com/images/ksh-g001/post/e6232c88-d5fb-41c7-9dcc-e3427afd591a/image.png)

터미널에서 아래와 같은 문장이 나타나면 설치가 완료된 겁니다.</code></pre><p>clion2023.2.2 from jetbrains✓ installed</p>
<pre><code>
설치를 완료하면 오른쪽 밑의 프로그램 표시에서 위의 사진처럼 설치된 모습(CLion)을 확인할 수 있습니다. clion은 tar.gz 파일 압축 해제 후 아이콘 생성을 하여 설치한 것과 snap를 통해 설치한 것과 비교할 수 있게 일부로 공식명을 무시하고 소문자로만 작성한 아이콘이니 무시하셔도 됩니다.

설치해본 결과 바탕화면에 바로가기 아이콘은 생성해주지 않습니다.

## tar.gz 파일 압축 해제 후 설치 방법
**1. 다운로드 주소로 가서 tar.gz 파일(CLion-2023.2.2.tar.gz  **_파일명의 연도 부분은 조금 달라질 수 있습니다._**)을 다운로드 받고 해당 프로그램을 설치하고 싶은 장소로 이동시켜 줍니다.**

파일을 GUI 환경에서 드래그로 옮겨주어도 되지만, 터미널에서 mv 명령어를 통해 이동시켜도 됩니다. 다운로드한 파일이 위치한 곳에서 터미널을 작동시킵니다.
    cd 명령어를 사용해도 되지만, 해당 파일이 존재하는 폴더에서 우클릭을 하여 터미널을 실행시켜도 됩니다.</code></pre><p>$ cd [이동하고 싶은 파일의 위치]
ex) cd /home/사용자명/download(혹은 한국어 설정을 완료하신 경우 다운로드)</p>
<pre><code>ls 명령어를 사용하시면 해당 위치의 폴더와 파일들을 볼 수 있습니다. ls -a는 숨겨진 폴더와 이전 폴더, 현재 폴더 등등 볼 수 없게 처리된 여러 폴더와 파일을 볼 수 있습니다.
없는 폴더나 파일이라고 뜰 때 ls 명령어를 활용하여 파일명을 확인해주면 좋습니다.

**2. 다운로드한 tar.gz 파일을 압축해제 해주어야 합니다.**</code></pre><p>$ gzip -d CLion-2023.2.2.tar.gz</p>
<pre><code>gzip은 .gz 확장자를 대상으로 압축 해제와 압축을 해주는 명령어이기에 앞으로 만날 .gz 확장자 압축 파일 해제에 대해서 위의 명령어는 유효할 겁니다.
CLion-2023.2.2.tar.gz 부분이 파일명을 작성하는 부분이기에 Postman.tar.gz 와 같이 다른 파일명을 넣어 압축해제가 가능합니다.

**3. 압축해제로 생성된 tar 파일을 실행해야 합니다.**</code></pre><p>$ tar xvf CLion-2023.2.2.tar</p>
<pre><code>tar는 여러 파일을 tar 아카이브라는 하나의 파일로 묶어놓거나 tar 아카이브를 실행해주어 원본 파일로 풀어주는 명령어입니다. 
사실 .gz 압축해제와 tar 아카이브 실행을 동시에 하는 zxvf 옵션이 존재하여 아래와 같이 작성할 수 있지만, 혹여나 나중에 .gz과 .tar를 하나로 생각하시게 될까봐 분리해서 적어놓았습니다. </code></pre><p>$ tar zxvf CLion-2023.2.2.tar.gz</p>
<pre><code>
**4. CLion 공식 설치 안내서 읽기**
3번까지 진행했다면 실행 파일이 담긴 폴더가 생성되었을 겁니다. 해당 폴더로 이동해 **Install-Linux-tar.txt** 라는 안내 파일을 찾아 열어줍니다.</code></pre><h1 id="installation-instructions">INSTALLATION INSTRUCTIONS</h1>
<ol>
<li><p>Unpack the CLion distribution archive that you downloaded
where you wish to install the program. We will refer to this
location as your {installation home}.</p>
</li>
<li><p>To start the application, open a console, cd into &quot;{installation home}/bin&quot; and type:</p>
<p>  ./clion.sh</p>
<p>This will initialize various configuration files in the configuration directory:
~/.config/JetBrains/CLion2023.2.</p>
</li>
<li><p>[OPTIONAL] Add &quot;{installation home}/bin&quot; to your PATH environment
variable so that you can start CLion from any directory.</p>
</li>
<li><p>[OPTIONAL] To adjust the value of the JVM heap size, create a file clion.vmoptions
(or clion64.vmoptions if using a 64-bit JDK) in the configuration directory
and set the -Xms and -Xmx parameters. To see how to do this,
you can reference the vmoptions file under &quot;{installation home}/bin&quot; as a model
but do not modify it, add your options to the new file.</p>
<p>[OPTIONAL] Change the location of the &quot;config&quot; and &quot;system&quot; directories</p>
</li>
</ol>
<hr>
<p>  By default, CLion stores all your settings in the
  ~/.config/JetBrains/CLion2023.2 directory
  and uses ~/.local/share/JetBrains/CLion2023.2 as a data cache.
  To change the location of these directories:</p>
<ol>
<li><p>Open a console and cd into ~/.config/JetBrains/CLion2023.2</p>
</li>
<li><p>Create a file idea.properties and set the idea.system.path and idea.config.path variables, for example:</p>
<p>idea.system.path=<del>/custom/system
idea.config.path=</del>/custom/config</p>
<p>NOTE: Store the data cache (&quot;system&quot; directory) on a disk with at least 1 GB of free space.</p>
</li>
</ol>
<p>Enjoy!</p>
<p>-CLion Development Team</p>
<pre><code>정리하자면, 원하는 곳에 tar 파일을 풀고 생성된 폴더에 bin 파일에서 터미널로 ./clion을 실행해주면 프로그램이 실행된다는 안내사항을 발견할 수 있습니다.
밑에 config나 system 디렉토리 위치를 바꿀 수 있는 방법도 있는데 당장은 무시해도 상관없습니다.

## 바탕화면 바로가기 아이콘 생성 (.desktop)
tar.gz 파일을 통해서 프로그램을 설치했다면 매번 설치한 폴더의 bin 폴더로 가서 ./clion을 실행해야 한다는 단점과 snap에서 설치한 CLion도 바탕화면에 아이콘을 추가해주는 건 아니라는 단점이 있습니다. 
snap으로 설치한 경우, 즐겨찾기에 추가하기로 왼쪽 밑 아이콘을 클릭해도 되지만 누군가는 즐겨찾기가 이미 꽉찼거나 불편할 수도 있기에 바탕화면에 바로가기 아이콘을 생성하는 방법을 적어보고자 합니다.

.desktop 파일은 리눅스에서 바로가기 아이콘을 만들기 위해 존재하는 파일로 프로그램에 대한 정보를 담고 있는 간단한 텍스트 파일이라고 이해하면 됩니다. 
로컬 계정에서만 접근이 허용되는 경우와 공용으로 사용할 수 있는 프로그램에 따라서 파일 위치가 다르지만, 공용으로 사용할 수 있는 프로그램의 .desktop 파일을 생성한다는 가정 아래에 방법을 알려드리고자 합니다.

1. 터미널에서 /usr/share/applications로 이동하기</code></pre><p>$ cd /usr/share/applications</p>
<pre><code>
2. vi으로 .desktop 파일 생성하기</code></pre><p>$ sudo vi clion.desktop</p>
<pre><code>clion / CLion 둘 중 마음에 드시는 걸로 하셔도 새로운 이름으로 하셔도 상관없습니다.
위의 과정에서 사용할 vi 간단 사용 방법은 아래와 같습니다.
- i : 파일에 텍스트를 작성할 수 있습니다.
- ESC키 : 편집 빠져나오기
- :q : 나가기
- :w : 저장하기
- :wq :저장하고 나가기
- :!q : 변경사항 무시하고 나가기

3. vi으로 .desktop 파일 작성하기
![](https://velog.velcdn.com/images/ksh-g001/post/7ae38990-932a-45a5-88a8-c001c911c316/image.png)
처음 vi를 작동시키면 이런 화면이 나타납니다. i를 눌러 편집을 시작합니다. 그리고 아래와 같은 내용을 작성해줍니다.</code></pre><p>[Desktop Entry]
Version=1.0
Type=Application
Terminal=false
Exec=[CLion을 설치한 위치]/bin/clion.sh
Name=[바로가기 이름]
Comment=[바로가기 설명으로 마우스 커서를 두면 뜨는 텍스트입니다.]
Icon=[CLion을 설치한 위치]/bin/clion.png</p>
<pre><code>작성 예시는 아래와 같습니다.</code></pre><p>[Desktop Entry]
Version=1.0
Type=Application
Terminal=false
Exec=/home/CLion/bin/clion.sh
Name=CLion
Comment=CLion
Icon=/home/CLion/bin/clion.png</p>
<pre><code>[자세한 설명은 이곳에서 확인하셔도 됩니다.](https://crasy.tistory.com/143)

++ 새벽에 작성했더니 까먹고 마지막 단계를 적지 않아 수정했습니다.

4. 생성한 desktop 파일을 바탕화면에 복사하기
GUI 환경에서 /usr/share/applications로 이동하시거나 터미널에서 이동하셔도 됩니다.
nautilus 명령어는 터미널에서 특정 위치에 있는 폴더에서 파일 관리자를 실행시킬 수 있는 명령어입니다.</code></pre><p>$ cd /usr/share/applications</p>
<pre><code></code></pre><p>$ nautilus /usr/share/applications/</p>
<pre><code>마찬가지로 복사를 할 때 cp 명령어를 사용하거나 파일관리자가 열린 GUI 환경에서 파일을 복사해도 상관없습니다.</code></pre><p>$ cp clion.desktop /home/사용자명/바탕화면/clion.desktop</p>
<pre><code>만약에 우분투에 한글이 적용되지 않았다면 바탕화면은 영어로 되어 있을 겁니다. 

5. 바탕화면에 복사한 desktop 파일 실행 권환 부여하기
++ 하기 전에 또 까먹고 권한 설정 명령어를 까먹었습니다. 바탕화면에 복사한 desktop을 대상으로 권한 설정을 해주는 명령어를 실행해줍니다.</code></pre><p>sudo chmod +x clion.desktop</p>
<p>```
<img src="https://velog.velcdn.com/images/ksh-g001/post/60666dce-4616-4011-adc9-43386b34196a/image.png" alt="">
위의 사진에서와 같이 실행 허용을 클릭해주시면 바로가기 아이콘이 활성화됩니다.</p>
<h2 id="학생-인증-라이센스">학생 인증 라이센스</h2>
<p>CLion은 설치시 무료 30일 체험 라이센스를 줍니다. 30일 체험이 끝나고 나서는 유료로 돈을 지불해야 하지만, 여타 프로그램들 처럼 학생들을 대상으로 모든 JetBrains 프로그램을 무료로 쓸 수 있는 1년 짜리 education 라이센스를 제공해주고 있습니다.</p>
<p><a href="https://www.jetbrains.com/ko-kr/community/education/#students">무료 교육용 라이센스 안내사항</a>
<img src="https://velog.velcdn.com/images/ksh-g001/post/e31aa172-eb5e-492a-aaf5-e6576d9df844/image.png" alt=""></p>
<p>이미 GitHub의 학생인증을 끝내신 분들은 비교적 아주 쉽게 GitHub 계정으로 바로 학생 인증을 할 수 있습니다. 저 같은 경우에도 GitHub 학생 인증을 해놓았기에 10분만에 education라이센스가 주어졌습니다.</p>
<p><a href="https://www.jetbrains.com/shop/eform/students">education 라이센스 신청</a>
<img src="https://velog.velcdn.com/images/ksh-g001/post/27f1c925-7140-4edd-8a44-e4a6f2483408/image.png" alt="">
신청 과정이 전부 한국어이고 GitHub처럼 영문 재학증명서를 요구하지 않기에 쉽게 될거라 생각됩니다. 다만, 학교 이메일이 거부당하면 JetBrains DB에 해당 도메인이 존재하지 않아 일어나는 일일 수도 있기에 JetBrains가 전 세계 학생 및 교직원에게 무료 라이선스를 제공하기 위해 사용하는 <a href="https://github.com/JetBrains/swot">swot 저장소에 학교 이메일 도메인을 제출할 수 있다고 합니다.</a></p>
<p>더불어 국제학생증(ISIC/ITIC)이 있다면 그걸 통해서 증명할 수도 있고 아니면 느리지만 확실하게 라이센스를 받을 수 있게 사람이 학교 공식 문서를 검토하는 방식도 있습니다.</p>
<p>공식 문서는 아래와 같은 사항이 포함되어야 한다고 합니다.</p>
<ul>
<li>본인의 이름 포함</li>
<li>재학 중인 학교 이름 포함</li>
<li>유효 기간 포함</li>
<li>Google 번역을 통해 읽을 수 있거나 쉽게 번역됨(영어 외 언어의 경우)</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[SceneView Node 종류]]></title>
            <link>https://velog.io/@ksh-g001/SceneView-Node-%EC%A2%85%EB%A5%98</link>
            <guid>https://velog.io/@ksh-g001/SceneView-Node-%EC%A2%85%EB%A5%98</guid>
            <pubDate>Mon, 17 Jul 2023 04:07:04 GMT</pubDate>
            <description><![CDATA[<blockquote>
<p>AR에 대해 공부하다보니 SceneView에 다양한 기능을 제공하는 Node가 많다고 생각했다. 슬슬 머리로만 기억해서 꺼내는 방식은 불가능할 듯해서 나름대로 정리하기로 했다. infoNode에 대해서 README에 언급되지만, 해당 Node는 어디에 있는지 찾지 못해 쓰지 못했다. +2023.07.17 공식 Github을 뒤적거리다가 <a href="https://sceneview.github.io/">라이브러리에 대한 문서</a>를 찾았다. 이 문서에 더 많은 Node에 대한 정보가 존재한다.
+2024.01.14(일) 기존의 주소에서 세부 주소가 바뀌어 404가 걸리길래 공식 홈페이지 메인 주소를 올려두었습니다. 위의 링크로 가셔서 API 항목으로 가시면 해당 라이브러리의 클래스와 메소드를 확인하실 수 있습니다.</p>
</blockquote>
<h3 id="node란">Node란?</h3>
<p>AR에서 Node란 Scene에 적용될 물체, 즉 화면에 보여지는 물체를 뜻한다. 따라서 Node는 하드웨어 카메라가 보여주는 현실세계 위에 증강하여 올라가는 가상 물체들을 통틀어 일컫는 말이다.</p>
<h3 id="node">Node</h3>
<p>아주아주 기본적인 Node로 사실상 사용되는 Node의 구현을 위한 클래스이다.</p>
<p>Node의 가장 기본적인 기능이 구현되어있으며 화면 속 좌표에서의 Node의 변환을 다루며 렌더링 엔진이 렌더링이 가능한 항목을 추가할 수 있게 해준다. 또한 Node의 부모 Node 설정, 자식 Node 설정이 가능하도록 되어있다.</p>
<p>Node의 위치(position), Node의 회전(rotation), Node의 크기(scale)이 params으로 존재한다.</p>
<h3 id="arnode">ArNode</h3>
<p>AR 환경에서 사용되는 Node로 Node를 검색하다가 나왔다.
ArModelNode가 상속받는 클래스로 Ar에서 사용되는 Node의 기본적인 내용이 정의되어 있는 듯 하다.</p>
<p>현재로는 ArModelNode와의 차이는 잘 모르겠다.</p>
<h3 id="modelnode">ModelNode</h3>
<p>일반적인 그래픽 렌더링 환경에서 사용되는 Node로 Filament만 포함된 &#39;io.github.sceneview:sceneview:1.0.10&#39; 라이브러리에서 사용된다.</p>
<p>README에도 사용방법이 나와있는 기본적인 Node이다.</p>
<h3 id="armodelnode">ArModelNode</h3>
<p>AR 환경에서 사용되는 Node로 Filament와 ARCore가 포함된 &#39;io.github.sceneview:arsceneview:0.10.0&#39; 라이브러리에서 사용된다.</p>
<p>README에도 사용방법이 나와있고 0.10.0 버전 이후로 달라진 사용법은 아직 업데이트 되지 않았다. 이 시리즈를 다루며 주로 다루어진 Node이다.</p>
<h3 id="viewnode">ViewNode</h3>
<p>최근에 ArModelNode 위에 이름표를 달며 사용한 Node이다. View를 렌더링하는 ViewRenderable로 설정된 렌더링 옵션들을 받는 Node 같다.
<del>원본 소스코드의 설명도 Node에서 중간 문단만 다르게 적혀 있어 뭔지 모르겠지만, 경험상 그런 거 같다.</del> </p>
<h3 id="augmentedimagenode">AugmentedImageNode</h3>
<p><a href="https://www.freecodecamp.org/news/how-to-build-an-augmented-images-application-with-arcore-93e417b8579d/">내가 제대로 이해한 게 맞다면</a> 지정된 이미지 위에서만 렌더링되는 Node다. 한동안 아동 교육용으로 유행했던 책+AR앱에 사용된 기능이라 생각하면 된다.</p>
<p>CloudAnchor와 다른 점은 CloudAnchor는 위치 기반으로 특정 위치에 존재하여 AR로 볼 수 있다면 AugmentedImageNode는 위치가 아닌 카메라에 포착된 이미지가 설정된 이미지인가 아닌가를 판단하여 렌더링이 되는 Node이다.</p>
<h3 id="renderablenode">RenderableNode</h3>
<p>소스코드 내의 설명으로는 Node와 똑같다. ModelNode가 상속받는 클래스로 직접적으로 쓰는 예시를 본 적 없다.
직접적으로 Renderable을 생성하고 관리하기 보단 Renderable의 관리와 Factory로 기능하는 RenderableManager 클래스로 생성한다. RenderableManager 클래스의 기본적인 사용법은 아래와 같다.</p>
<pre><code class="language-kotlin">val entity = EntityManager.get().create()

  RenderableManager.Builder(1)
          .boundingBox(Box(0.0f, 0.0f, 0.0f, 9000.0f, 9000.0f, 9000.0f))
          .geometry(0, RenderableManager.PrimitiveType.TRIANGLES, vb, ib)
          .material(0, material)
          .build(engine, entity)

  scene.addEntity(renderable)</code></pre>
]]></description>
        </item>
        <item>
            <title><![CDATA[SceneView ViewRenderable, AR 객체 위에 이름표 띄우기]]></title>
            <link>https://velog.io/@ksh-g001/SceneView-ViewRenderable-AR-%EA%B0%9D%EC%B2%B4-%EC%9C%84%EC%97%90-%EC%9D%B4%EB%A6%84%ED%91%9C-%EB%9D%84%EC%9A%B0%EA%B8%B0</link>
            <guid>https://velog.io/@ksh-g001/SceneView-ViewRenderable-AR-%EA%B0%9D%EC%B2%B4-%EC%9C%84%EC%97%90-%EC%9D%B4%EB%A6%84%ED%91%9C-%EB%9D%84%EC%9A%B0%EA%B8%B0</guid>
            <pubDate>Sat, 15 Jul 2023 13:08:29 GMT</pubDate>
            <description><![CDATA[<blockquote>
<p>해당 글은 SceneView 0.9.10를 사용했습니다. SceneView 0.10.0 버전에 대한 테스트는 이루어지지 않았으며 이전 글에서 밝힌 position이 제대로 먹히지 않는 문제에 대해서는 아직도 해결방법을 찾지 못했습니다.
+2023.08.07 확인 결과 SceneView 0.10.0 버전에서 정상작동합니다.</p>
</blockquote>
<p>이전까지 글에서는 그래픽 파일을 구해 화면에 띄우는 것까지 다루었다. 그도 그럴게 나도 거기까지만 알아냈고 해당 라이브러리의 README에 적힌 커스터마이징 안내를 봤지만 Renderable에 TextView나 ImageView를 사용하라는 말만 있고 정확한 코드가 나와있지는 않았다.</p>
<p>당장 AR 위에 이름표처럼 정보를 표시해야 하는데 이전 버전이나 마찬가지인 Sceneform에서 사용한 코드만 나오고 막상 적용하면 에러가 넘쳐났다. 밑져야 본전이라는 마음가짐으로 Chat GPT4에게도 물어봤지만, 마찬가지였다.</p>
<p>안 그래도 생으로 배우고 있는데 더 생으로 배우자는 의미로 제발 Renderable에 대해서 버그가 발생했고 누군가 깃허브의 issue나 discuss에 올려뒀으면 좋겠다는 생각으로 깃허브 다이빙을 했다.</p>
<p>그리고 정말로 <a href="https://github.com/SceneView/sceneview-android/issues/227">Renderable에 버그가 있었던 기록</a>이 있었고 거기서 코드를 가져오는데 성공했다. 그렇게 온갖 뻘짓이 공식 깃허브 탐방으로 해결되었다.</p>
<h2 id="ar-객체-위에-이름표-띄우기">AR 객체 위에 이름표 띄우기</h2>
<h3 id="이름표-띄우기를-시작하기-전에-주의사항">이름표 띄우기를 시작하기 전에 주의사항</h3>
<p>이전 글을 통해 ArModelNode를 화면에 띄우는데에 성공했다고 가정하고 과정 설명이 진행된다.</p>
<p>또 정확하게 말하자면 이건 이름표가 아니라 커스텀 렌더링 객체를 만드는 과정이다.</p>
<p>내 편의로 이렇게 이름을 붙였을 뿐 명확한 명칭은 <strong>ViewNode</strong>이다. 커스텀 렌더링 객체, 즉 ViewNode라는 건 말그래도 사용자가 만든 레이아웃을 렌더링하여 보여주는 객체라고 생각하면 된다.</p>
<p><strong>그러니 굳이 이름표가 아니라 다른 xml 레이아웃 파일(View)을 AR과 함께 그래픽으로 보여주고 싶을 때도 충분히 사용 가능한 방법이라는 것이다.</strong></p>
<h3 id="간략한-과정">간략한 과정</h3>
<p>본격적인 과정에 들어가기 앞서서 간단하게 과정을 설명하자면 이렇다.</p>
<ol>
<li>AR로 띄울 뷰의 xml 파일을 작성한다. (layout 작성)</li>
<li>ViewRenderable를 builder를 통해 사용할 View를 지정해주고 build한다. (ViewRenderable 작성)</li>
<li>build된 ViewRenderable에 thenAccept를 통해 ViewNode를 생성한다. (ViewNode 작성)</li>
<li>ViewNode의 Renderable, parent, position를 설정한다. (ViewNode 설정)</li>
</ol>
<h3 id="1-layout-작성">1. layout 작성</h3>
<pre><code class="language-xml">&lt;?xml version=&quot;1.0&quot; encoding=&quot;utf-8&quot;?&gt;
&lt;androidx.constraintlayout.widget.ConstraintLayout xmlns:android=&quot;http://schemas.android.com/apk/res/android&quot;
    xmlns:app=&quot;http://schemas.android.com/apk/res-auto&quot;
    android:layout_width=&quot;wrap_content&quot;
    android:layout_height=&quot;wrap_content&quot;&gt;

    &lt;TextView
        android:id=&quot;@+id/info&quot;
        android:layout_width=&quot;wrap_content&quot;
        android:layout_height=&quot;wrap_content&quot;
        android:background=&quot;@drawable/monster_info&quot;
        android:paddingHorizontal=&quot;30dp&quot;
        android:paddingVertical=&quot;10dp&quot;
        android:text=&quot;test monster info xml&quot;
        android:textColor=&quot;@color/white&quot;
        android:textSize=&quot;30dp&quot;
        app:layout_constraintBottom_toBottomOf=&quot;parent&quot;
        app:layout_constraintEnd_toEndOf=&quot;parent&quot;
        app:layout_constraintStart_toStartOf=&quot;parent&quot;
        app:layout_constraintTop_toTopOf=&quot;parent&quot; /&gt;

&lt;/androidx.constraintlayout.widget.ConstraintLayout&gt;</code></pre>
<p>특별한 건 없다. ViewNode가 생각보다 잘 안 보일 가능성이 있어서 크기가 커도 된다.</p>
<h3 id="2-viewrenderable-작성">2. ViewRenderable 작성</h3>
<pre><code class="language-kotlin">    ViewRenderable.builder()
            .setView(this, arTextViewBinding.root)
            .build()
            .thenAccept { 
                //build가 이루어지고나서 적용되는 것들을 작성하는 구간이다.
                //3, 4번의 과정이 여기서 이루어진다.
            }</code></pre>
<p>Android Studio에서 ViewRenderable에 대한 설명을 보면 View의 크기는 250dp마다 1m라는 설명을 볼 수 있다. 즉 250dp가 실제 카메라에서 1m로 보인다. </p>
<p>또한, ViewRenderable은 2D Android View를 렌더링한다는 설명도 확인할 수 있다.</p>
<p>자세히 확인하면 글이 끝도 없이 길어지기에 중요하다 생각되는 부분만 언급하자면 옛날 RelativeLayout처럼 연결된 노드를 중심으로 ViewRenderable의 위치를 정해줄 수 있다.</p>
<p><strong>setVerticalAlignment()는 수직에서의 ViewRenderable의 위치를 정하는 메소드</strong>로 기본 값은 BOTTOM이다. 이 메소드에 사용되는 값은 ViewRenderable 내부에 존재하는 열거형 VerticalAlignment에 존재한다.</p>
<p><strong>setHorizontalAlignment()도 존재</strong>한다. Vertical과 다르게 수평상에서의 ViewRenderable의 위치를 정하며 기본값은 CENTER이다. 이 또한 ViewRenderable의 내부에 존재하는 열거형 HorizontalAlignment에 메소드에 사용되는 값이 존재한다.</p>
<p>마지막으로 ViewRenderable는 3D 공간에서 ModelNode의 setModelInstance를 이용해 Node로 연결하여 렌더링을 하는 기본 클래스인 Renderable을 상속받은 자식 클래스이다.</p>
<h3 id="3-viewnode-작성--설정">3. ViewNode 작성 &amp; 설정</h3>
<pre><code class="language-kotlin">    ViewRenderable.builder()
            .setView(this, 커스텀한_뷰노드의viewBinding.root)
            .setVerticalAlignment(ViewRenderable.VerticalAlignment.TOP)
            .build()
            .thenAccept { renderable: ViewRenderable -&gt;
                val viewNode = ViewNode()
                viewNode?.parent = modelNode            //부모 노드를 설정한다.
                viewNode?.setRenderable(renderable)        //사용할 Renderable을 설정한다.
                viewNode?.position = Position(x = 0.0f, y = 5.0f, z = 0.0f)        //위치는 부모 노드를 기준으로 설정된다.
            }</code></pre>
<p>위 코드가 거의 최종 코드이다. </p>
<p>viewBinding을 적용했기에 커스텀한 ViewNode의 viewBinding 내용을 가진 변수를 만들어 작성해주었다. viewBinding을 사용하지 않고 R.layout.커스텀한_뷰노드_id로 setView의 View 설정도 가능하다.</p>
<p>아직 viewBinding으로 설정한 커스텀 뷰의 내용 변경이 순서에 따라 어디까지 반영되는지는 확인하지 않았지만, 렌더링 작업 이전에 변경한 내용이 무사히 반영되는 것은 확인을 마쳤다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[SceneView 0.10.0 업데이트에 대하여]]></title>
            <link>https://velog.io/@ksh-g001/SceneView-0.10.0-%EC%97%85%EB%8D%B0%EC%9D%B4%ED%8A%B8%EC%97%90-%EB%8C%80%ED%95%98%EC%97%AC</link>
            <guid>https://velog.io/@ksh-g001/SceneView-0.10.0-%EC%97%85%EB%8D%B0%EC%9D%B4%ED%8A%B8%EC%97%90-%EB%8C%80%ED%95%98%EC%97%AC</guid>
            <pubDate>Mon, 03 Jul 2023 11:48:24 GMT</pubDate>
            <description><![CDATA[<blockquote>
<p>sceneView가 업데이트를 하면서 AR 관련해서 성능이 향상되고 기존 구글의 렌더링 엔진 오픈소스인 filament의 Engine가 ArModelNode의 매개변수로 추가되었다. 따라서 SceneView의 README.md에 적힌 것과 아주 살짝 사용법이 달라졌는데 README.md에 해당 변경사항이 반영되지 않는 것 같아 혹여나 헤매고 있을 이들을 위해 이 사실을 적기로 하였다.</p>
</blockquote>
<h2 id="시작하기-전에">시작하기 전에</h2>
<p>기존의 SceneView 라이브러리도 filament 라이브러리를 사용하고 있었다. 다만, 업데이트 전까지 SceneView 라이브러리를 사용하는 프로그래머가 filament를 신경쓰지 않아도 자동으로 해주어 문제가 없었다면 이제는 우리가 Engine 클래스의 변수를 생성해 ArModelNode의 매개변수로 넘겨주어야 하는 것이다.</p>
<h2 id="filament-라이브러리">filament 라이브러리</h2>
<p><a href="https://github.com/google/filament">filament 라이브러리 GitHub</a></p>
<p>우선 SceneView를 깊게 파지 않고 그냥 사용하는데에 해당 라이브러리에 대한 상세한 지식은 필요없다. ArModelNode 메소드를 살펴보면 Engine에 대한 내용도 볼 수 있어서 해당 코드에서 설명하는대로 따라하기만 하면 된다.</p>
<p>Android Studio에서는 클래스나 메소드를 Ctrl+클릭하면 해당 클래스나 메소드의 상세한 코드와 함께 개발자가 남겨둔 주석을 읽을 수 있다.</p>
<p>그러나 자세한 내용을 알고 싶은 이들을 위해서 GitHub 주소를 가져왔다. 해당 라이브러리는 다양한 운영체제 혹은 환경에서 사용될 수 있는 렌더링 엔진 라이브러리이지만, 안드로이드에서 특히나 적합한 형태를 취하고 있다고 공식적으로 밝힌 라이브러리이기도 하다.</p>
<p>Engine은 filament의 주 진입점으로 사용자가 생성한 모든 리소스를 추적하고 렌더링 스레드와 하드웨어 렌더러를 관리한다고 한다.</p>
<p>혹여나 filament와 SceneView가 무슨 차이가 있나 싶은 분들을 위해서 짧막하게 설명하자면 먼저 사용된 언어가 다르고 SceneView가 좀 더 간편한 렌더링을 위해서 만들어진 라이브러리라면 filament는 렌더링을 위해서 만들어진 라이브러리이다.</p>
<h2 id="변경점">변경점</h2>
<p>그래서 뭔데 싶을 것이다. 자고로 초보란 이론과 실습을 병행해야 겨우 이해하는 자들을 뜻하고 나 또한 엊그제부터 새로운 문제의 실마리를 얻고자 오늘 GitHub을 방문했다가 업데이트 공지를 봤을 때 filament가 뭔지도 몰라서 머리를 쥐어뜯으며 이해했기에 천천히 풀어나가고자 한다.</p>
<p>먼저 우리는 업데이트 공지를 읽을 필요가 있다. 내가 깨달은 게 있다면 괜히 남이 공부할 때까지 기다리지 말고 공식이 멀쩡하게 잘 업데이트하고 있으면 그거라도 따라가는 게 좋다는 것이다. 공식이 부지런하게 상세한 공지사항을 올릴 때 감지덕지하며 공식을 따라가야 한다.</p>
<p><a href="https://github.com/SceneView/sceneview-android/releases">공식 GitHub의 릴리즈 페이지</a>에 가면 이런 화면이 우리를 기다린다.
<img src="https://velog.velcdn.com/images/ksh-g001/post/5a632c77-48c6-40b4-80ff-269cf21f92be/image.png" alt="">
참고로 최근 들어서 업데이트 주기는 1~2주인 듯 하다. 영어를 못하니 번역기에 의지해 영어와 한글을 왔다갔다하며 읽으면 여러 사실을 알 수 있다.</p>
<ol>
<li>렌더링과 그에 관한 소비결과(?)는 이번 업데이트와 거의 상관이 없다. -&gt; AR 모델이나 AR Point Cloud에서의 변경점이 있다.</li>
<li>기존의 SceneView에서는 fliament를 제대로 활용하지 못하는 문제가 있었다. -&gt; 개선하여 성능을 향상시켰다. (덕분에 게임 엔진을 사용하는 것처럼 가벼워졌다고 합니다.)</li>
<li>Lifecycle과 연결된 Activity 관련 문제에 대해 리펙토링을 통한 개선이 이루어졌다. -&gt; Flutter에서 사용이 더 쉬워졌다.</li>
<li>3번의 영향으로 Node를 생성할 때 Engine을 매개변수로 사용해야 한다. -&gt; 기존의 방식과 다르게 내부에 대한 계산같은 것(The whole transforms and parenting calculations)이 filament 라이브러리에게 위임됐기 때문이라고 합니다.</li>
<li>SceneView/ARSceneView를 렌더링하기 전에 3D 항목을 미리 로드하고 View 범위 외부(ViewModel 내부 또는 원하는 위치)에 연결된 항목에 연결된 Filament 엔진을 관리할 수 있습니다. -&gt; 이건 자세히 모르겠지만, 3D 모델 데이터를 렌더링 때 로딩하는 게 아니라 미리 준비시켜서 렌더링을 더 빨리 진행할 수 있다는 건가 싶습니다.</li>
</ol>
<p>글 더미가 지나가면 변경사항에 대해 나열한 목록이 보이는데 이 글을 쓰는 이유인 두 번째 이유 ArModelNode hitPosition -&gt; screenPosition라는 변경사항이 보인다.</p>
<p>대충 공지사항을 읽었으니 바로 READMD.md로 달려가보자 README.md에서는 ArModelNode 생성에 대해 이렇게하라고 나와있다.</p>
<pre><code class="language-kotlin">ArModelNode(
    placementMode = PlacementMode.BEST_AVAILABLE, 
    hitPosition = Position(0.0f, 0.0f, -2.0f),
    followHitPosition = true,
    instantAnchor = false
)</code></pre>
<p>하지만, 최신 버전에서 해당 코드로 실행하면 빨간 줄이 신나게 그어질 것이다. 먼저 필수인 Engine 매개변수가 없고 hitPosition은 screenPosition으로 바뀌었기 때문이다.</p>
<p>이전 포스팅을 보고 15분 간단 AR앱을 따라했다면 저 부분이 아마도 아래와 같은 형식 일 것이다.</p>
<pre><code class="language-kotlin"> modelNode = ArModelNode().apply {
            placementMode = PlacementMode.BEST_AVAILABLE
            hitPosition = Position(0.0f, 0.0f, -10.0f)

            loadModelGlbAsync(
                glbFileLocation = &quot;models/bird_orange.glb&quot;,
            ){
                binding.sceneView.planeRenderer.isVisible = true
             }
             onAnchorChanged = {
                binding.place.isGone
            }
        }</code></pre>
<p>일단 실행에는 성공한 이번 업데이트로 변경된 사항이 적용된 코드는 아래와 같다.</p>
<p>++ 2023.07.31
제대로 position도 먹히고 viewModel도 생성되는 코드를 알아냈다.</p>
<pre><code class="language-kotlin">
class MainActivity : AppCompatActivity() {
...
...
...
    //sceneview 0.10.0에서 추가. 기존 3D 렌더링 구글 오픈소스인 filament의 활용을 극대화시키기 위해 해당 라이브러리와의 endpoint인 Engine 사용을 도입
    //lifecycle과 관련된 문제 해결과 동시에 어느정도 성능 개선이 이루어짐. -&gt; 렌더링이 부드러워졌고 기존의 문제(어느 View가 중복생성되거나 중복삭제되는 등의 문제)로 생긴 자원소모가 줄었다고 합니다.
    //filament는 그래픽을 렌더링해주는 엔진이라고 합니다.
    //Engine을 따로 만들지 않는다. SceneView 클래스 내부에 engine이 존재하는데 이 engine을 매개변수로 사용한다.

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        binding = ActivityMainBinding.inflate(layoutInflater)
        setContentView(binding.root)

     modelNode = ArModelNode(binding.sceneView.engine).apply {
                placementMode = PlacementMode.BEST_AVAILABLE
                screenPosition = Position(0.0f, 0.0f, -30.0f)

                lifecycleScope.launchWhenCreated {
                    loadModelGlbAsync(
                        glbFileLocation = &quot;models/bird_orange.glb&quot;,
                    ){
                        binding.sceneView.planeRenderer.isVisible = true
                       }    
                    onAnchorChanged = {
                        binding.place.isGone
                    }
                }
            }

...
...
...
}</code></pre>
<p>위의 방식으로 ViewModel도 가능하다.</p>
<pre><code>  ViewRenderable.builder()
                .setView(this@MainActivity, tapBinding.root)
                .setVerticalAlignment(ViewRenderable.VerticalAlignment.TOP)
                .setHorizontalAlignment(ViewRenderable.HorizontalAlignment.RIGHT)
                .build()
                .thenAccept {renderable: ViewRenderable -&gt;
                    val viewNode = ViewNode(modelNode.engine)
                    viewNode.parent = modelNode
                    viewNode.setRenderable(renderable)
                    viewNode.position = Position(0.0f, 10.0f, 0.0f)
                }
</code></pre><p>요점은 생성하고자 하는 모델링의 상위 부모의 engine을 매개변수로 설정하는 것이라고 생각한다. (확신은 못 하고 아직은 더 살펴봐야 한다.)</p>
<p>+++ 매개변수로 주는 engine은 자신들의 내부 필드의 engine을 정의하기 위해 받아오는 값인 듯 하다. 딱히 상위 부모일 필요없고 sceneView의 engine을 가져와도 상관없을 것 같다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[Android AR, ARCore, SceneView에 대하여]]></title>
            <link>https://velog.io/@ksh-g001/Android-AR-ARCore-SceneView</link>
            <guid>https://velog.io/@ksh-g001/Android-AR-ARCore-SceneView</guid>
            <pubDate>Tue, 27 Jun 2023 07:28:59 GMT</pubDate>
            <description><![CDATA[<blockquote>
<p>해당 글은 ARCore에 대해서 공부하면서 노션에서 작성한 것을 그대로 복사하여 가져온 것입니다. AndroidStudio 내에서 AR를 구현하는 것에 대한 한글 최신 자료가 없어서 이곳저곳에 떠돌아다니며 나름대로 정리한 내용으로 저처럼 이미 deprecated된 2020년 쯤의 sceneform자료와 Android AR를 검색하면 나오는 seceneform 1.5.0 이하 버전 구글 공식문서에 속아 힘들어 할 미래의 AR 초보분들을 위해서 최신정보(2023.06.27 기준)가 검색이 되도록 기록을 남깁니다.</p>
</blockquote>
<h2 id="1-obj--fbx--glb--gltf">1. obj &amp; fbx &amp; glb &amp; gltf</h2>
<h3 id="📍개요">📍개요</h3>
<p>네 가지의 파일 확장자는 3D 모델 파일의 확장자이다. 더 자세한 3D 모델 확장자가 존재하지만, ARCore에서 자주 언급되는 네 가지만을 소개한다. <a href="https://all3dp.com/2/most-common-3d-file-formats-model/">(확장자들에 대한 상세 정보)</a></p>
<h3 id="📍설명">📍설명</h3>
<ol>
<li><p>obj : 그래픽의 기본적인 정보인 UV 데이터, 버텍스, 노말 등만 저장</p>
<ol>
<li>가장 처음으로 만들어진 확장자</li>
<li>애니메이션 동작 저장 불가능</li>
<li>복잡하고 잡스러운 정보를 저장하지 않아 fbx보다 가볍고 깔끔함</li>
</ol>
</li>
<li><p>fbx : obj에 비해 다양한 정보를 함께 저장</p>
<ol>
<li>애니메이션 동작 저장 가능</li>
</ol>
</li>
<li><p>glb : 3D 모델 정보를 바이너리의 형태로 저장</p>
<ol>
<li><p>하나의 파일로 출력</p>
</li>
<li><p>기타 API를 사용하여 응용 프로그램의 런타임 처리를 최소화</p>
<p> ex) WebGL</p>
</li>
<li><p>최적화가 이루어져 가벼움</p>
<ol>
<li>웹 분야에서 자주 사용</li>
</ol>
</li>
</ol>
</li>
<li><p>gltf : 3D 모델 정보를 JSON 형식으로 저장</p>
<ol>
<li><p>여러 개의 파일 출력 → 결과물 = 파일 그룹</p>
</li>
<li><p>기타 API를 사용하여 응용 프로그램의 런타임 처리를 최소화</p>
<p> ex) WebGL</p>
</li>
<li><p>최적화가 이루어져 가벼움</p>
<ol>
<li>웹 분야에서 자주 사용</li>
</ol>
</li>
</ol>
</li>
</ol>
<h3 id="📍arcore-접근-방식에-따른-주-사용-확장자-분류">📍ARCore 접근 방식에 따른 주 사용 확장자 분류</h3>
<h3 id="arcore">ARCore</h3>
<hr>
<ul>
<li>obj</li>
<li>fbx</li>
</ul>
<h3 id="sceneview-라이브러리">SceneView 라이브러리</h3>
<hr>
<ul>
<li>glb</li>
<li>gltf</li>
</ul>
<hr>
<h2 id="2-arcore">2. ARCore</h2>
<h3 id="📍개요-1">📍개요</h3>
<p>AR용 Google Play Service으로 구글에서 개발한 AR 소프트웨어 개발 키트이다. AR과 관련된 API를 제공하며 최근에 지리 정보 API(Geospatial API)도 추가되었다. 다른 여타 구글 API와 마찬가지로 Google Cloud를 통해 API를 관리할 수 있다.</p>
<p><a href="https://developers.google.com/ar/develop?hl=ko">ARCore 및 지원되는 개발 환경 개요      |  Google for Developers</a></p>
<h3 id="📍지원기기">📍지원기기</h3>
<p>ARCore를 이용하기 위해선 OpenGL ES가 필요하고 Android7.0(에뮬레이터 8.1이상)이상이 필요하다. 따라서 API 수준은 필수로 ARCore를 사용한다면 24이상, 선택적인 ARCore라면 19이상으로 제한을 둬야 한다.</p>
<p><strong>반드시 테스트하기 전에 공식 문서로 가서 테스트 기기가 지원하는지 확인해야 한다.</strong> 안 그러면 테스트 자체가 불가능할 수 있다. ARCore도 비교적 고사양 스마트폰을 필요로 해서 지원하는 기기가 일반적인 API보다 훨씬 적다. ARCore를 지원해도 AR 관련 API 중 특정 하드웨어 장치가 필요한 (특히 depth API) API가 존재한다.</p>
<h3 id="📍예제">📍예제</h3>
<pre><code class="language-groovy">//ARCore 라이브러리
implementation &#39;com.google.ar:core:1.37.0&#39;
//obj 파일을 읽기 위한 라이브러리
implementation &#39;de.javagl:obj:0.2.1&#39;</code></pre>
<pre><code class="language-xml">//카메라 권한
&lt;uses-permission android:name=&quot;android.permission.CAMERA&quot;/&gt;
  &lt;!-- Limits app visibility in the Google Play Store to ARCore supported devices
       (https://developers.google.com/ar/devices). --&gt;
  &lt;uses-feature android:name=&quot;android.hardware.camera.ar&quot; android:required=&quot;true&quot;/&gt;
  &lt;uses-feature android:glEsVersion=&quot;0x00020000&quot; android:required=&quot;true&quot; /&gt;
  &lt;uses-feature
      android:name=&quot;android.hardware.camera&quot;
      android:required=&quot;true&quot; /&gt;

&lt;application
...
...
...&gt;
    &lt;activity
        ...
        .../&gt;
    //ARCore 사용 명시 및 ARCore 사용이 필수(required)인지 선택(optional)인지 명시 
    &lt;meta-data android:name=&quot;com.google.ar.core&quot; android:value=&quot;required&quot; /&gt;
&lt;/application&gt;</code></pre>
<p>ARCore를 기본 제공 라이브러리로 다루는 예제로 Open GL를 다루는 함수를 통해 그래픽 렌더링과 ARCore를 다루면서 발생하는 세션의 라이프사이클을 다룬다. 예제의 코드는 복잡하고 관련 지식이 없다면 파악하기 어렵다는 특징을 가지고 있다.</p>
<p>개요에 북마크되어 있는 공식문서에서 예제와 지원기기에 대한 정보를 알아볼 수 있다.</p>
<hr>
<h2 id="3-sceneview-라이브러리">3. SceneView 라이브러리</h2>
<h3 id="📍개요-2">📍개요</h3>
<p><a href="https://github.com/SceneView/sceneview-android">https://github.com/SceneView/sceneview-android</a></p>
<p>Open GL없이 ARCore 앱을 쉽게 빌드할 수 있도록 만들어진 3D 프레임워크로 구글이 관리한다. 기본적인 상황에서 ARCore를 다루는 것보다 편의성이 높고 AR를 다루는데 필수적으로 필요한 요소들을 자동으로 import해주며 권한 설정을 해준다는 특징이 있다.</p>
<p>GitHub의 README가 공식 문서라고 봐도 됩니다.</p>
<p><del><a href="https://sceneview.github.io/api/sceneview-android/arsceneview/io.github.sceneview.ar.node/-ar-camera-node/index.html">+추가 문서를 찾았습니다.</a></del></p>
<ul>
<li>2024.01.14(일) <a href="https://sceneview.github.io/">큰 업데이트 이후로 세부 주소가 바뀌었길래 그냥 메인 주소를 올립니다.</a></li>
</ul>
<h3 id="📍역사">📍역사</h3>
<ul>
<li>Google Sceneform Tools(Beta) : 지원종료<ul>
<li>안드로이드 스튜디오 3.1~ 3.4에서 사용하던 플러그인으로 현재는 안드로이드 스튜디오로 해당 플러그인을 설치하면 IDE 버전이 안 맞는다는 오류가 뜨며 다운그레이드를 시켜 플러그인을 적용하면 약간 운영체제 기반(리눅스)의 경우 심각한 에러를 초래할 수 있으므로 조심할 것.</li>
<li>우분투에서 혹여나 제대로 작동시킬 수 없을까하고 시도했다가 제대로 꼬여서 Shell 탐험을 두 시간 했습니다. 혹여나 리눅스를 사용하시는 분이라면 시도하지 마시길 바랍니다.<ul>
<li>만약에 혹여나 경고를 무시하고 시도했다가 이전 버전 안드로이드 스튜디오가 실행되지 않는 리눅서를 위한 해결방법(우분투 22.04.2 LTS 기준)<ol>
<li>터미널을 실행시키고 home/사용자명으로 이동합니다.</li>
<li>.AndroidStudioN.N으로 된 옛 버전 안드로이드 스튜디오 디렉토리를 찾아서 이동합니다. (N.N는 버전을 뜻하는 숫자, 3.4버전이면 .AndroidStudio3.4이십니다.)</li>
<li>config 디렉토리안의 plugins 디렉토리로 이동합니다.</li>
<li>google-sceneform-tools(정확한 이름은 기억 안 납니다.)라고 적힌 걸 삭제합니다.</li>
<li>AndroidStudio를 실행하면서 쌓인 에러를 처리합니다. (플러그인을 삭제하기 전에 ./studio.sh를 하고서 나오는 에러문에 해결법이 있습니다.)</li>
</ol>
<ul>
<li>아무리 리눅스여도 최신 버전에서는 그럴 일은 거의 없을 거라 생각하지만, 이 부분을 지나서 컴퓨터 운영 자체에 문제가 생길 정도로 꼬였다면 어느 부분이 꼬였냐에 따라서 대처가 달라질 겁니다. 일단 눈 앞의 오류문을 읽고 대처하시되 이것저것해도 안 된다면 중요 파일만 뽑아서 포맷하는 것도 하나의 방법일지도 모릅니다.</li>
</ul>
</li>
<li>운영체제가 다른 이들을 위한 해결법 요약&amp;안드로이드 스튜디오 내에서의 해결법<ul>
<li>혹여나 리눅스가 아닌데 시도해서 이전 버전 안드로이드 스튜디오가 막힌 다른 운영체제 분들을 위한 요약<ul>
<li>이전 버전 안드로이드 스튜디오가 저장된 디렉토리로 가서 플러그인이 저장된 디렉토리로 이동해 google-sceneform-tools(정확한 이름은 기억 안 납니다.)라고 적힌 걸 삭제</li>
<li>안드로이드 스튜디오가 저장된 위치는 리눅스랑 다를 수 있지만, 대체로 그 안의 디렉토리의 위치는 같습니다.</li>
</ul>
</li>
<li>안드로이드 스튜디오가 막힌 건 아닌데 매번 실행할 때마다 플러그인 에러문이 뜨시는 분들을 위한 해결법<ul>
<li>안드로이드 스튜디오 설정에 가셔서 플러그인으로 이동하시고 Google Sceneform Tools(Beta) 삭제</li>
</ul>
</li>
</ul>
</li>
</ul>
</li>
</ul>
</li>
<li>Google Sceneform SDK 1.0.0-1.15.0 : 지원종료<ul>
<li>오픈 소스 X</li>
<li>fbx, obj를 sfa, sfb라는 Sceneform 전용 형식으로 변환 가능</li>
</ul>
</li>
<li>Google Sceneform SDK 1.16.0 : 지원종료<ul>
<li>이 버전 이후로 오픈소스화</li>
<li>기존의 Sceneform전용 형식말고 gltf를 지원</li>
</ul>
</li>
<li>Google Sceneform SDK 1.17.0 : 지원종료<ul>
<li>릴리즈가 안 됨 → 구글에서 사용을 금지함</li>
</ul>
</li>
<li>Google Sceneform SDK 1.17.1 : 1.15.0 버전과 동일, 지원 종료<ul>
<li>Sceneform 자체의 업데이트 종료를 선언</li>
</ul>
</li>
<li>Google SceneView 1.0.0~1.0.9 : 코틀린에서의 Sceneform replacement<ul>
<li>단순 3D 렌더링 버전과 AR 지원 버전으로 나뉨</li>
<li>Jetpack Compose를 지원하며 xml 레이아웃(View Activity)에서도 사용 가능</li>
<li>3D Model Viewer인 ModelNode라는 특수 클래스가 존재하여 이를 이용해 3D 모델을 다룰 수 있음</li>
<li>AR Model Viewer는 ArModelNode라는 특수 클래스로 다룸</li>
<li>Model Viewr 클래스의 loadModelAsync()메소드를 통해 glb, gltf 확장자로 변환된 모델을 불러올 수 있음</li>
</ul>
</li>
</ul>
<h3 id="📍예제-1">📍예제</h3>
<p><a href="https://github.com/Gebort/FESTU.Navigator">GitHub - Gebort/FESTU.Navigator: Kotlin AR app for indoor navigation</a></p>
<p>라이브러리 제작자 예시</p>
<p><a href="https://www.youtube.com/watch?v=DaWrqUqjrE8&amp;list=PLJLBm00K_Pl_GRRWLltNS74uAImGwULtr&amp;index=2">Create AR app in Android Studio under 15 minutes | Beginner&#39;s Tutorial</a></p>
<p>AR앱 튜토리얼 (초보자용)</p>
<hr>
<h2 id="4-assets-디렉토리-추가">4. assets 디렉토리 추가</h2>
<p><a href="https://dxkor2.tistory.com/196">안드로이드 스튜디오 코틀린 에셋 파일 (기초 개념)</a></p>
<h3 id="📍이유">📍이유</h3>
<ul>
<li><p>assets에 저장된 파일은 읽기만 가능하므로 보안에서 일정 부분 유리하다. (외부나 내부에서 쓰기를 당하지 않음)</p>
</li>
<li><p>기본적으로 파일을 압축해 저장하기 때문에 데이터를 읽을 때 압축하는 과정이 없다.</p>
<ul>
<li>읽기 속도가 빠르고 CPU 부하가 적음</li>
</ul>
</li>
<li><p>압축 제외 설정</p>
<pre><code class="language-groovy">  android{
  ...
  ...
  aaptOptions{
      noCompress &#39;확장자명&#39;
      }

  }</code></pre>
<h3 id="📍추가하는-법">📍추가하는 법</h3>
<ol>
<li>안드로이드 스튜디오의 Project에서 app을 우클릭하여 new→Folder→Assets Folder 선택</li>
<li>저장하는 데이터를 분류하기 위한 패키지 생성 후 데이터 분류하여 저장</li>
</ol>
</li>
</ul>
<hr>
<h2 id="5-3d-모델-다운로드">5. 3D 모델 다운로드</h2>
<p>⭐ <strong>저작권 위반을 피하기 위해선 모든 사이트에서 저작물에 대한 라이센스를 주의해야 한다.</strong></p>
<p>⭐ <strong>라이센스별로 제한된 사항을 확인하기 힘들 경우 CC0 라이센스(저작권 없는 무료 저작물)를 가진 저작물만 사용하세요.</strong></p>
<ol>
<li><p><a href="https://sketchfab.com/feed">sketchfab</a></p>
<ol>
<li>다양성 : 높음</li>
<li>3D 모델, AR, VR등에 쓰이는 모델을 얻을 수 있으며 유료 마켓도 있지만, 무료 마켓도 존재</li>
<li>단, 각 제품마다 저작권 라이센스가 존재하므로 반드시 라이센스를 읽고 사용할 것<ol>
<li>작품의 다운로드 버튼을 누르면 제작자에 대한 Credit을 복사할 수 있는 버튼이 존재하므로 안전하게 모든 저작물에 대한 Credit을 가져와 저장해놓고 저작권 표시가 필요한 저작물에 표시해줍시다.</li>
</ol>
</li>
</ol>
</li>
<li><p><a href="https://www.turbosquid.com/ko/3d-models/3d-garage-2039673#">Turbosquid</a></p>
<ol>
<li>다양성 : 낮음 (건축물 조경 위주)</li>
<li>유료, 무료 마켓 존재</li>
<li>라이센스 주의</li>
</ol>
</li>
<li><p><a href="https://www.mixamo.com/#/">Mixamo</a></p>
<ol>
<li>다양성 : 낮음 (사람 및 애니메이션 위주)</li>
<li>회원가입만 하면 무료</li>
<li>주로 유니티에서 많이 사용함<ol>
<li>대다수가 fbx 형식만 존재하므로 fbx를 glb나 gltf로 <a href="https://3dbank.xyz/help/convert.jsp">변환해주는 프로그램</a>이 필요할 수 있음</li>
<li>캐릭터를 직접 업로드해서 애니메이션을 덮는 것이 가능하다고 한다.</li>
</ol>
</li>
<li>라이센스 주의</li>
</ol>
</li>
<li><p><a href="https://www.renderhub.com/">renderhub</a></p>
<ol>
<li><p>다양성 : 높음</p>
</li>
<li><p>무료자료 수  &lt;&lt;&lt;&lt;&lt;&lt; 유료자료 수</p>
</li>
<li><p>라이센스 주의 및 패러디, 2차 창작 작품 혹은 기존 저작물의 저작권을 무시한 저작물인지 확인 필요</p>
<p> <img src="https://velog.velcdn.com/images/ksh-g001/post/d3d1c828-9740-4032-b24e-40b5ebf14405/image.png" alt="들어갔다가 놀랐음">  </p>
</li>
</ol>
</li>
<li><p><a href="https://free3d.com/ko/">Free 3D</a></p>
<ol>
<li>다양성 : 측정 및 체감 실패(특유의 분류 때문에 돌아다니다가 도중 포기)</li>
<li>어떤 형식으로 만들어진 모델인지 혹은 어디서 만들었는지(어떤 그래픽 프로그램 사용)에 따라 분류한다.</li>
<li>무료 작품 수  &gt;&gt;&gt;&gt;&gt;&gt; 유료 작품 수</li>
</ol>
</li>
<li><p><a href="https://www.cgtrader.com/">CGTrader</a></p>
<ol>
<li>다양성 : 높음</li>
<li>무료 마켓과 유료 마켓이 공존한다.</li>
<li>로얄티 프리 : 로열티를 지불하지 않고 사용할 수 있는 저작물</li>
</ol>
</li>
</ol>
]]></description>
        </item>
    </channel>
</rss>