groupBy API

JS에서 어떤 데이터를 표현하는 객체의 형태를 변경하는 일은 매우 자주 있고 이에 대한 코드를 작성하는 것은 한번 정도는 도전 정신이 발휘 되기도 하지만 그 다음부터는 짜증스럽기도 합니다. 아래와 같은 데이터가 있다고 가정해 봅시다.

[
  { name: "Jim", age: 48, sex: "F" },
  { name: "Kyle", age: 28, sex: "M" },
  { name: "Sally", age: 28, sex: "F" },
  { name: "Jane", age: 32, sex: "M" },
  { name: "Tom", age: 48, sex: "F" }
]

이 데이터의 필드 중 sex는 성별을 나타내는 것으로 이를 기준으로 데이터를 다음처럼 구성하려고 합니다.

{
  "F": [
    {"name":"Jim","age":48,"sex":"F"},
    {"name":"Sally","age":28,"sex":"F"},
    {"name":"Tom","age":48,"sex":"F"}
  ],
  "M": [
    {"name":"Kyle","age":28,"sex":"M"},
    {"name":"Jane","age":32,"sex":"M"}
  ]
}

데이터의 표현을 F, M으로 구분해 다시 구성한 형태입니다. 이를 위한 목적을 이루기 위해 JS는 하나의 API 제공하고 있는데 다음과 같습니다.

const people = [
  { name: "Jim", age: 48, sex: "F" },
  { name: "Kyle", age: 28, sex: "M" },
  { name: "Sally", age: 28, sex: "F" },
  { name: "Jane", age: 32, sex: "M" },
  { name: "Tom", age: 48, sex: "F" }
]

const groupBySex = Object.groupBy(people, person => {
  return person.sex
})

Object.groupBy가 핵심인데 이 API는 입력 데이터는 변경하지 않고 새로운 데이터를 만들어 반환합니다.

Mac에 OpenJDK 설치하기

Mac에서 OpenJDK 설치 순서인데, 설치라고 하기에 너무 간단합니다. 그냥 압축 풀고 복사하는 정도입니다.

일단 OpenJDK 공식 사이트에서 OpenJDK를 다운로드 받습니다. 확장자가 tar.gz인 압축 파일을 다운로드 받아 압축을 풉니다. 아래와 같습니다. 압축이 풀린 것이 jdk-21.0.1 디렉토리입니다. 이해를 돕기 위해 아래의 이미지를 참조하세요.

jdk-21.0.1 디렉토리 전체를 그대로 /Library/Java/JavaVirtualMachines 디렉토리로 이동시킵니다. 암호를 입력 하라고 하면 암호를 입력하세요.

끝.

확인을 위해 터미널을 열고 아래처럼 javac -version을 입력해 봅니다.

사실 Windows도 같습니다. 차이점은 내가 직접 Path를 지정해줘야 한다는 것인데.. 맥은 이미 Path로 지정된 디렉토리를 가지고 있다는 점이네요. 맥의 Java 사랑이 희한하면서도 남다르네요.

추가로 VS.Code에서 자바 프로젝트를 생성하기 위해 [1]필요한 확장 기능(Extension Pack for Java)을 설치 후 [2]명령 팔레트(Shift+Command+P)에서 Java:Create Java Project로 명령을 선택해 실행한 뒤 [3]No Build Tools을 선택(바닐라 프로젝트)하고 [4]프로젝트의 디렉토리가 만들어질 부모 디렉토리를 선택하고 마지막으로 [5]프로젝트의 이름(디렉토리 이름)을 입력하면 됩니다.

three.js의 애니메이션 시스템

개요

three.js의 애니메이션 시스템을 통해 모델의 다양한 속성을 애니메이트 할 수 있습니다 : 스킨드 앤 리지드 모델(Skinned & Rigged Model)의 뼈대, 모프 타겟(Morph Targets), 재질의 속성(색, 불투명도, boolean 값), 보이고 않보이기, 변환(위치, 크기, 회전). 애니메이트 된 속성은 점진적으로 서서히 변경될 수 있습니다. 다른 객체 뿐만 아니라 동일한 객체의 서로 다른 연속된 애니메이션의 가중치(Weight)와 타임 스케일(Time Scale)은 독립적으로 변경될 수 있습니다. 동시에 다양한 애니메이션을 다른 물체에 동기화할 수 있습니다.

하나의 동일한 차원의 시스템에서 애니메이션에 대한 이 모든 것들을 이루기 위해서 three.js의 애니메이션 시스템은 2015년에 완전히 변경되었으며, 유니티와 언리얼 엔진4와 유사한 구조를 갖게 되었습니다. 이 문서는 애니메이션 시스템의 주요 요소에 대한 개략적인 개요와 어떻게 함께 작동 되는지를 설명합니다.

애니메이션 클립(Animation Clips)

블렌더 등과 같은 3차원 모델링 툴로부터 glTF 형식으로 저장하고 이를 three.js의 GLTFLoader 코드를 통해 모델을 불러왔다면(이 모델이 Bones이나 Morph Targets 또는 둘다 없어도 상관없음) 이 모델 갹체에는 AnimationClips 타입의 객체를 가지는 배열인 “animations”라는 이름의 필드를 가지고 있습니다.

각각의 AnimationClip 객체는 객체의 특정 행위에 대한 데이터를 가지고 있습니다. 예를들어 만약 매시가 케릭터라면 첫번째는 걷기, 두번째는 점프, 세번째는 옆으로 걷기에 대한 데이터가 될 수 있습니다.

키프레임 트랙(Keyframe Tracks)

이런 AnimationClip 내부에는 개별적인 KeyframeTrack 객체에 저장된 애니메이트 속성 데이터가 저장되어 있습니다. 케릭터 객체가 스켈레턴(Skeleton)을 가지고 있다고 한다면, 하나의 키프레임 트랙은 시간에 대한 팔의 위치 변화에 대한 데이터를 저장하고 있습니다. 다른 키프레임 트랙은 회전 변화나 크기 변화 등이 저장되어 있구요. 하나의 AnimationClip은 여러개의 키프레임 트랙으로 구성될 수 있다는 것입니다.

애니메이션 믹서(Animation Mixer)

저장된 데이터는 오직 애니메이션을 위한 기반만으로 구성됩니다. 실제 재생은 AnimationMixer를 통해 제어됩니다. 이 AnimationMixer가 단순히 애니메이션을 재생하는 객체일 뿐이라고 생각할 수 있지만 이 객체는 여러개의 애니메이션을 연속적으로 자연스럽게 처리될 수 있도록 블렌딩하고 합칠 수 있는 실제 믹서(Mixer)와 같은 기능을 수행합니다.

애니메이션 액션(Animation Action)

AnimationMixer 자체는 몇개 안되는 (일반적인) 속성과 매서드를 가지고 있는데, AnimationMixer가 AnimationAction에 의해 제어되기 때문입니다. AnimationAction을 구성함으로써 어떤 AnimationClip이 재생되어지고, 잠시 멈추거나 완전히 멈추거나 할때를 결정할 수 있고 애니메이션을 얼마나 반복될 것인지, 다른 애니메이션 액선으로의 전환(Fade)이나 타임 스케일링(Time Scaling) 등을 수행할 수 있습니다.

애니메이션 객체 그룹(Animation Object Groups)

공유된 애니메이션 상태를 받기 위해 객체들의 그룹이 필요하다면 AnimationObjectGroup를 사용할 수 있습니다.

지원되는 형식과 로더(Loader)

모든 모델 형식이 애니메이션을 포함할 수 있는 것은 아닙니다. three.js 로더 들중 몇개만이 AnimationClip을 지원합니다 : THREE.ObjectLoader, THREE.BVHLoader, THREE.ColladaLoader, THREE.FBXLoader, THREE.GLTFLoader, THREE.GLTFLoader, THREE.MMDLoader

3dx max와 Maya는 단일 파일에 여러개의 애니메이션을 내보낼 수 없습니다.

예제

let mesh;

// Create an AnimationMixer, and get the list of AnimationClip instances
const mixer = new THREE.AnimationMixer( mesh );
const clips = mesh.animations;

// Update the mixer on each frame
function update () {
	mixer.update( deltaSeconds );
}

// Play a specific animation
const clip = THREE.AnimationClip.findByName( clips, 'dance' );
const action = mixer.clipAction( clip );
action.play();

// Play all animations
clips.forEach( function ( clip ) {
	mixer.clipAction( clip ).play();
} );

CSS의 @property 사용예

자주 사용하지는 않지만 CSS에도 변수를 정의해 재활용할 수 있는 문법을 제공하는데 :root를 정의하고 이 안에 원하는 값을 넣어 정의하곤 했습니다. CSS에 대한 더 높은 수준의 경험 많은 개발자는 이런 CSS에서의 변수 정의를 반드시 사용해야 하는 상황을 만나게 됩니다. 이런 CSS에서의 변수를 정의하는 좀더 표준화된 문법이 있는데 그것은 @property입니다. 표준화의 의미는 엄격함이라는 조건을 달아 실수를 줄여 견고한 코드를 작성하도록 하는 장치와 같습니다.

여튼 저는 –a라는 이름의 변수(프로퍼티)를 다음처럼 정의했습니다.

@property --a {
  syntax: "<angle>"; /* https://developer.mozilla.org/en-US/docs/Web/CSS/@property/syntax */
  inherits: false;  
  initial-value: 0turn;
}

프로퍼티가 가지는 타입(syntax)와 초기값을 지정하고 있습니다. 그럼 이 프로퍼티를 사용하는 코드를 보면 다음과 같습니다.

.box::before {
  content: '';
  position: absolute;
  inset: 0;
  background: repeating-conic-gradient(from var(--a), #f00, #ff0, #fff, #0ff, #f0f, #f00);
  border-radius: 25px;
  animation: rotating 4s linear infinite;
}

@keyframes rotating {
  0% {
    --a: 0turn;
  }
  100% {
    --a: -4turn;
  }
}

명확하고 직관적입니다.

실제 위의 코드가 적용한 예제 코드는 다음과 같습니다.

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Document</title>
  <style>
    * {
      margin: 0;
      padding: 0;
      box-sizing: border-box;
    }

    body {
      display: flex;
      justify-content: center;
      align-items: center;
      min-height: 100vh;
      background-color: #222;
    }

    .box {
      position: relative;
      width: 400px;
      height: 300px;
    }

    .box span {
      color: white;
      display: flex;
      align-items: center;
      justify-content: center;
      font-size: 2.5rem;
      font-weight: bold;
    }

    .box::before {
      content: '';
      position: absolute;
      inset: 0;
      background: repeating-conic-gradient(from var(--a), #f00, #ff0, #fff, #0ff, #f0f, #f00);
      border-radius: 25px;
      animation: rotating 4s linear infinite;
    }

    .box::after {
      content: '';
      position: absolute;
      inset: 0;
      background: repeating-conic-gradient(from var(--a), #f00, #ff0, #fff, #0ff, #f0f, #f00);
      border-radius: 25px;
      animation: rotating 4s linear infinite;
      filter: blur(40px);
      opacity: 0.75;
    }

    .box span {
      position: absolute;
      inset: 4px;
      background: #222;
      border-radius: 22px;
      z-index: 1;
    }

    @property --a {
      syntax: '<angle>'; /* https://developer.mozilla.org/en-US/docs/Web/CSS/@property/syntax */
      inherits: false;  
      initial-value: 0turn;
    }

    @keyframes rotating {
      0% {
        --a: 0turn;
      }
      100% {
        --a: -4turn;
      }
    }
  </style>
</head>
<body>
  <div class="box">
    <span>GIS DEVELOPER</span>
  </div>
</body>
</html>

결과는 다음과 같습니다.