<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0">
  <channel>
    <title>잉</title>
    <link>https://ing-min.tistory.com/</link>
    <description></description>
    <language>ko</language>
    <pubDate>Mon, 13 Apr 2026 08:01:32 +0900</pubDate>
    <generator>TISTORY</generator>
    <ttl>100</ttl>
    <managingEditor>잉_민</managingEditor>
    <item>
      <title>아두이노(seeed Xiao) + 자석감지 센서 MLX90393 + 디스플레이 ST7789 TFT (2.0&amp;quot;, 240&amp;times;320) : 뮤토스코프 + 숨 프로젝트</title>
      <link>https://ing-min.tistory.com/324</link>
      <description>&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h1&gt;뮤토스코프 인터랙티브 설치 &amp;mdash; 프로젝트 정리&lt;/h1&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;1. 주제&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;사람의 &lt;b&gt;숨&lt;/b&gt;이 바람개비(이 안에 자석들어있음)를 돌리면, 자석 센서가 회전을 감지하고&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그 주기에 따라 영상 재생 속도와 모터 속도가 실시간으로 반응하는 인터랙티브 설치 작품.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;숨 &amp;rarr; 바람개비(자석 회전) &amp;rarr; MLX90393 감지 &amp;rarr; 속도 계산 &amp;rarr; 모터 PWM(뮤토스코프 회전) + TFT 스크린 영상 프레임 동기화&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2026-03-01 03.51.27.png&quot; data-origin-width=&quot;866&quot; data-origin-height=&quot;984&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/lyU9Q/dJMcaflo2iO/wDS01mARJouwOQUxtSbgck/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/lyU9Q/dJMcaflo2iO/wDS01mARJouwOQUxtSbgck/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/lyU9Q/dJMcaflo2iO/wDS01mARJouwOQUxtSbgck/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FlyU9Q%2FdJMcaflo2iO%2FwDS01mARJouwOQUxtSbgck%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;866&quot; height=&quot;984&quot; data-filename=&quot;스크린샷 2026-03-01 03.51.27.png&quot; data-origin-width=&quot;866&quot; data-origin-height=&quot;984&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;2. 방법론&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;전체 흐름&lt;/h3&gt;
&lt;pre class=&quot;yaml&quot;&gt;&lt;code&gt;[숨 &amp;rarr; 바람개비 회전]
    &amp;rarr; 자석 센서: 회전 1회 감지
    &amp;rarr; period(ms) 계산 (회전 주기)
    &amp;rarr; 영상 속도: frameDelay = period / 21
    &amp;rarr; 모터 속도: PWM = map(period, 500, 10000, 250, 190)
    &amp;rarr; TFT: 21프레임 루프 재생
&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;영상 재생 기준&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;원래 속도: 1초에 2장 (500ms/frame)&lt;/li&gt;
&lt;li&gt;period = 10500ms일 때 frameDelay = 500ms &amp;rarr; 2fps&lt;/li&gt;
&lt;li&gt;빠른 숨 &amp;rarr; period 짧음 &amp;rarr; 빠른 영상 + 높은 PWM&lt;/li&gt;
&lt;li&gt;느린 숨 &amp;rarr; period 김 &amp;rarr; 느린 영상 + 낮은 PWM&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;정지 감지&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;자석이 2초 이상 센서에 고정 &amp;rarr; 정지 판정&lt;/li&gt;
&lt;li&gt;정지 후 자석이 실제로 벗어날 때까지 재감지 차단 (waitingForRelease)&lt;/li&gt;
&lt;li&gt;정지 시 lastTrigger = 0 리셋 &amp;rarr; 다음 회전 감지 가능&lt;/li&gt;
&lt;/ul&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;3. 하드웨어&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;메인 보드&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;XIAO ESP32C6&lt;/b&gt; (싱글코어, 160MHz)&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;센서: MLX90393 자석 센서&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;프로토콜: I2C&lt;/li&gt;
&lt;li&gt;SDA: GPIO16, SCL: GPIO17&lt;/li&gt;
&lt;li&gt;설정: GAIN_1X, RES_19(XY), RES_16(Z)&lt;/li&gt;
&lt;li&gt;감지값 범위: magnitude 110~210 (자석 없을 때), 임계값 threshold = 160.0&lt;/li&gt;
&lt;li&gt;폴링: 16ms (약 60Hz)&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;디스플레이: ST7789 TFT (2.0&quot;, 240&amp;times;320)&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;프로토콜: 하드웨어 SPI (SPIClass(SPI))&lt;/li&gt;
&lt;li&gt;CS: GPIO21, DC: GPIO22, RST: GPIO23, MOSI: GPIO18, SCK: GPIO19&lt;/li&gt;
&lt;li&gt;SPI 속도: 40MHz&lt;/li&gt;
&lt;li&gt;설정: rotation(1), invertDisplay(true)&lt;/li&gt;
&lt;li&gt;이미지 위치: setAddrWindow(60, 20, 200, 200) &amp;rarr; 320&amp;times;240 화면 중앙&lt;/li&gt;
&lt;li&gt;파일읽기: 27ms, 화면출력: 24ms &amp;rarr; 총 51ms&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;모터: N20 소형 DC 모터&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;핀: GPIO0 (D0)&lt;/li&gt;
&lt;li&gt;트랜지스터: 2N2222A (TO-18 금속캔)&lt;/li&gt;
&lt;li&gt;PWM 범위: 190~250 (period 기반 자동 조정)&lt;/li&gt;
&lt;li&gt;기동 최소 PWM: 200 이상&lt;/li&gt;
&lt;/ul&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;4. 회로도&lt;/h2&gt;
&lt;pre class=&quot;properties&quot;&gt;&lt;code&gt;[모터 회로]
ESP32C6 D0 ──[1k&amp;Omega;]── 2N2222A 베이스
                      2N2222A 이미터 &amp;rarr; GND
                      2N2222A 컬렉터 &amp;rarr; 모터(-)
3.3V ──────────────── 모터(+)
모터 양단에 다이오드 (스트라이프&amp;rarr;모터+)

[2N2222A TO-18 핀아웃]
탭 기준:
  탭 옆 = 이미터 &amp;rarr; GND
  가운데 = 베이스 &amp;rarr; 1k&amp;Omega; &amp;rarr; D0
  반대 = 컬렉터 &amp;rarr; 모터(-)

[자석 센서 회로]
MLX90393 SDA &amp;rarr; GPIO16
MLX90393 SCL &amp;rarr; GPIO17
MLX90393 VCC &amp;rarr; 3.3V
MLX90393 GND &amp;rarr; GND

[TFT 회로]
TFT CS   &amp;rarr; GPIO21
TFT DC   &amp;rarr; GPIO22
TFT RST  &amp;rarr; GPIO23
TFT MOSI &amp;rarr; GPIO18
TFT SCK  &amp;rarr; GPIO19
TFT VCC  &amp;rarr; 3.3V
TFT GND  &amp;rarr; GND
TFT BLK  &amp;rarr; 3.3V (백라이트 ON)
&lt;/code&gt;&lt;/pre&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;5. 초기화 순서 (중요)&lt;/h2&gt;
&lt;pre class=&quot;reasonml&quot;&gt;&lt;code&gt;1. Wire.begin(16, 17)      // I2C 먼저
2. sensor.begin_I2C()
3. pinMode(MOTOR_PIN) + analogWrite  // 모터
4. spi-&amp;gt;begin()            // SPI
5. LittleFS.begin()
6. tft.init()              // TFT 마지막
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;순서 틀리면 &quot;센서 못찾음&quot; 발생.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;6. 영상 파일&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;경로: LittleFS /img_00.raw ~ /img_20.raw&lt;/li&gt;
&lt;li&gt;포맷: RGB565 raw, 200&amp;times;200px = 80,000 bytes&lt;/li&gt;
&lt;li&gt;총 21프레임&lt;/li&gt;
&lt;li&gt;바이트스왑 필수: frameBuf[i] = (val &amp;gt;&amp;gt; 8) | (val &amp;lt;&amp;lt; 8)&lt;/li&gt;
&lt;/ul&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;최종 코드&amp;nbsp;&lt;/h2&gt;
&lt;pre id=&quot;code_1772332999166&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;#include &amp;lt;Adafruit_GFX.h&amp;gt;
#include &amp;lt;Adafruit_ST7789.h&amp;gt;
#include &amp;lt;LittleFS.h&amp;gt;
#include &amp;lt;SPI.h&amp;gt;
#include &amp;lt;Wire.h&amp;gt;
#include &amp;lt;Adafruit_MLX90393.h&amp;gt;

#define TFT_CS   21
#define TFT_DC   22
#define TFT_RST  23
#define TFT_MOSI 18
#define TFT_SCK  19
#define MOTOR_PIN 0

#define IMG_WIDTH  200
#define IMG_HEIGHT 200
#define TOTAL_IMGS 21

SPIClass* spi = new SPIClass(SPI);
Adafruit_ST7789 tft = Adafruit_ST7789(spi, TFT_CS, TFT_DC, TFT_RST);
Adafruit_MLX90393 sensor = Adafruit_MLX90393();

uint16_t frameBuf[200 * 200];

float threshold = 360.0;
bool magnetPresent = false;
bool isStopped = false;
unsigned long lastTrigger = 0;
unsigned long frameDelay = 500;
int currentFrame = 0;
unsigned long lastFrame = 0;

void setup() {
  Serial.begin(115200);
  delay(3000);
  Serial.println(&quot;시작&quot;);

  Wire.begin(16, 17);
  if (!sensor.begin_I2C()) {
    Serial.println(&quot;센서 못찾음&quot;);
    while(1);
  }
  Serial.println(&quot;센서 OK&quot;);
  sensor.setGain(MLX90393_GAIN_1X);
  sensor.setResolution(MLX90393_X, MLX90393_RES_19);
  sensor.setResolution(MLX90393_Y, MLX90393_RES_19);
  sensor.setResolution(MLX90393_Z, MLX90393_RES_16);

  ledcAttach(MOTOR_PIN, 5000, 8);
  ledcWrite(MOTOR_PIN, 200);

  spi-&amp;gt;begin(TFT_SCK, -1, TFT_MOSI, TFT_CS);
  LittleFS.begin(true);
  tft.init(240, 320, SPI_MODE0);
  tft.setSPISpeed(40000000);
  tft.setRotation(1);
  tft.invertDisplay(true);
  tft.fillScreen(ST77XX_BLACK);
  Serial.println(&quot;TFT OK&quot;);
}

void loop() {
  float x, y, z;
  if (sensor.readData(&amp;amp;x, &amp;amp;y, &amp;amp;z)) {
    float magnitude = sqrt(x*x + y*y + z*z);

    Serial.print(&quot;M:&quot;); Serial.print(magnitude);
    Serial.print(&quot; stopped:&quot;); Serial.print(isStopped);
    Serial.print(&quot; magnet:&quot;); Serial.println(magnetPresent);

    // [1] 상승 엣지
    if (magnitude &amp;gt; threshold &amp;amp;&amp;amp; !magnetPresent) {
      magnetPresent = true;
      unsigned long now = millis();
      Serial.println(&quot;---1---&quot;);

      if (isStopped) {
        isStopped = false;
        ledcWrite(MOTOR_PIN, 200);
        frameDelay = 500;
        Serial.println(&quot;&amp;gt;&amp;gt;&amp;gt; Restarting 200&quot;);
      } else if (lastTrigger &amp;gt; 0) {
        unsigned long period = now - lastTrigger;
        if (period &amp;gt; 500 &amp;amp;&amp;amp; period &amp;lt; 10000) {
          frameDelay = period / 21;
          int pwm = map(period, 500, 10000, 250, 190);
          pwm = constrain(pwm, 190, 250);
          ledcWrite(MOTOR_PIN, pwm);
          Serial.print(&quot;&amp;gt;&amp;gt;&amp;gt; period: &quot;); Serial.print(period);
          Serial.print(&quot; PWM: &quot;); Serial.println(pwm);
        } else {
          ledcWrite(MOTOR_PIN, 200);
          Serial.println(&quot;&amp;gt;&amp;gt;&amp;gt; period 범위 밖 200&quot;);
        }
      }
      lastTrigger = now;
    }

    // 자석 벗어남
    if (magnitude &amp;lt; threshold * 0.9) {
      magnetPresent = false;
    }
  }

  // [2] 마지막 회전 후 2초 = 정지
  if (!isStopped &amp;amp;&amp;amp; lastTrigger &amp;gt; 0 &amp;amp;&amp;amp; millis() - lastTrigger &amp;gt; 2000) {
    isStopped = true;
    lastTrigger = 0;
    magnetPresent = false;  // 여기
    ledcWrite(MOTOR_PIN, 0);
    Serial.println(&quot;---0--- PWM:0&quot;);
  }

  unsigned long now = millis();
  if (now - lastFrame &amp;gt; frameDelay) {
    char path[20];
    sprintf(path, &quot;/img_%02d.raw&quot;, currentFrame);
    File f = LittleFS.open(path, &quot;r&quot;);
    if (f) {
      f.read((uint8_t*)frameBuf, IMG_WIDTH * IMG_HEIGHT * 2);
      f.close();
      for (int i = 0; i &amp;lt; IMG_WIDTH * IMG_HEIGHT; i++) {
        uint16_t val = frameBuf[i];
        frameBuf[i] = (val &amp;gt;&amp;gt; 8) | (val &amp;lt;&amp;lt; 8);
      }
      tft.startWrite();
      tft.setAddrWindow(60, 20, IMG_WIDTH, IMG_HEIGHT);
      tft.writePixels(frameBuf, IMG_WIDTH * IMG_HEIGHT);
      tft.endWrite();
    }
    currentFrame = (currentFrame + 1) % TOTAL_IMGS;
    lastFrame = now;
  }

  delay(16);
}&lt;/code&gt;&lt;/pre&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;7. 시리얼 출력 형식&lt;/h2&gt;
&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;---1---   &amp;rarr; 자석 감지 (한 바퀴)
---0---   &amp;rarr; 정지 (2초 이상 고정)
period: 2000 frameDelay: 95 PWM: 235
&lt;/code&gt;&lt;/pre&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;8. 주요 해결 이슈 이력&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;문제 원인 해결&lt;/p&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;센서 못찾음&lt;/td&gt;
&lt;td&gt;초기화 순서 잘못됨&lt;/td&gt;
&lt;td&gt;I2C를 SPI보다 먼저&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;TFT 위에서 아래 렌더링&lt;/td&gt;
&lt;td&gt;drawRGBBitmap 줄마다 호출&lt;/td&gt;
&lt;td&gt;frameBuf + writePixels 한번에&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;TFT 화면출력 2169ms&lt;/td&gt;
&lt;td&gt;소프트웨어 SPI&lt;/td&gt;
&lt;td&gt;SPIClass(SPI) 하드웨어 SPI&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;정지 후 재감지 안됨&lt;/td&gt;
&lt;td&gt;lastTrigger 리셋 안됨 + waitingForRelease 누락&lt;/td&gt;
&lt;td&gt;정지 시 lastTrigger=0, waitingForRelease 플래그&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;모터 안돌음&lt;/td&gt;
&lt;td&gt;PWM 너무 낮음&lt;/td&gt;
&lt;td&gt;최소 200 이상&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;9. 정지 후 모터 재시작 문제 &amp;mdash; 시행착오 전체 기록&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;목표&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;숨을 멈추면 모터 정지 &amp;rarr; 다시 숨을 불면 모터 재시작&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;시행착오 목록&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;시도 1: 2초 고정 감지&lt;/b&gt; 자석이 센서 위에 2초 이상 머물면 정지 판정. 문제: 숨을 천천히 불면 정상 회전인데도 정지 판정남. magnetSinceTime 방식 자체가 틀림.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;시도 2: analogWrite(MOTOR_PIN, 0)&lt;/b&gt; 모터 끄기. 문제: ESP32C6에서 analogWrite(pin, 0)은 LEDC PWM 채널 자체를 해제(detach)해버림. 이후 analogWrite(pin, 200) 호출해도 채널이 없어서 무반응.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;시도 3: digitalWrite(MOTOR_PIN, LOW)&lt;/b&gt; PWM 채널 유지하면서 끄기 시도. 문제: LEDC PWM이 켜진 상태에서 digitalWrite는 무시됨. PWM이 핀을 장악하고 있어서 효과 없음.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;시도 4: analogWrite(MOTOR_PIN, 10)&lt;/b&gt; 완전히 끄지 않고 최소값 유지. 문제: PWM 10은 모터가 돌기엔 너무 낮고 꺼지지도 않아서 모터가 버둥대며 전류를 계속 끌어당김 &amp;rarr; 삐 소리 + ESP32C6 전력 불안정 &amp;rarr; 렉.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;시도 5: pinMode 재초기화 + delay 킥스타트&lt;/b&gt;&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;cpp&lt;/div&gt;
&lt;div&gt;
&lt;pre class=&quot;lisp&quot; style=&quot;color: #14181f;&quot;&gt;&lt;code&gt;pinMode(MOTOR_PIN, OUTPUT);
analogWrite(MOTOR_PIN, 255);
delay(50);
analogWrite(MOTOR_PIN, 200);&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;문제: 채널이 이미 해제된 상태라 효과 없음.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;시도 6: 정지 감지 방식 변경&lt;/b&gt; &quot;자석 2초 고정&quot; &amp;rarr; &quot;마지막 회전 후 2초 동안 새 회전 없음&quot;으로 변경. 개선됨. 하지만 재시작 여전히 안됨.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;시도 7: ledcAttach + ledcWrite (최종 해결)&lt;/b&gt; analogWrite 완전히 제거하고 LEDC API 직접 사용. ledcWrite(pin, 0)은 채널 유지하면서 0 출력 &amp;rarr; 채널 해제 안됨.&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;cpp&lt;/div&gt;
&lt;div&gt;
&lt;pre class=&quot;angelscript&quot; style=&quot;color: #14181f;&quot;&gt;&lt;code&gt;ledcAttach(MOTOR_PIN, 5000, 8);  // setup에서 한번만
ledcWrite(MOTOR_PIN, 200);       // 켜기
ledcWrite(MOTOR_PIN, 0);         // 끄기 (채널 유지)&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;문제: 재시작 여전히 안됨.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;시도 8: magnetPresent 리셋 누락 발견 (최종 해결)&lt;/b&gt; ---0--- 후 magnetPresent = true 상태로 남아있어서 상승 엣지 조건 !magnetPresent가 영원히 안 걸림. 정지 시 magnetPresent = false 추가로 완전 해결.&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;cpp&lt;/div&gt;
&lt;div&gt;
&lt;pre class=&quot;yaml&quot; style=&quot;color: #14181f;&quot;&gt;&lt;code&gt;if (!isStopped &amp;amp;&amp;amp; lastTrigger &amp;gt; 0 &amp;amp;&amp;amp; millis() - lastTrigger &amp;gt; 2000) {
  isStopped = true;
  lastTrigger = 0;
  magnetPresent = false;  // 핵심
  ledcWrite(MOTOR_PIN, 0);
  Serial.println(&quot;---0---&quot;);
}&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;결론&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;두 가지가 같이 해결되어야 했음:&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;&lt;span style=&quot;color: #ee2323;&quot;&gt;&lt;b&gt;analogWrite &amp;rarr; ledcWrite 교체 (PWM 채널 유지)&lt;/b&gt;&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;color: #ee2323;&quot;&gt;&lt;b&gt;정지 시 magnetPresent = false 리셋 (상승 엣지 재감지 가능하게)&lt;/b&gt;&lt;/span&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;시행착오들..&amp;nbsp;&lt;/p&gt;
&lt;h3 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;디스플레이: ST7789 TFT (2.0&quot;, 240&amp;times;320)&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;GND &amp;rarr; GND&lt;/li&gt;
&lt;li&gt;VCC &amp;rarr; 3.3V&lt;/li&gt;
&lt;li&gt;SCL &amp;rarr; D8&lt;/li&gt;
&lt;li&gt;SDA &amp;rarr; D10&lt;/li&gt;
&lt;li&gt;RES &amp;rarr; D5&lt;/li&gt;
&lt;li&gt;DC &amp;rarr; D4&lt;/li&gt;
&lt;li&gt;CS &amp;rarr; D3&lt;/li&gt;
&lt;li&gt;BLK &amp;rarr; 3.3V&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;MOSI 핀 번호를 17이라고 계속 우겼는데 18이 맞았어&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;바꾼 tft 되는거 빨강.&lt;/p&gt;
&lt;pre id=&quot;code_1772333586494&quot; class=&quot;cpp&quot; style=&quot;background-color: #f8f8f8; color: #383a42;&quot; data-ke-type=&quot;codeblock&quot; data-ke-language=&quot;bash&quot;&gt;&lt;code&gt;#include &amp;lt;Adafruit_GFX.h&amp;gt;
#include &amp;lt;Adafruit_ST7789.h&amp;gt;
#include &amp;lt;SPI.h&amp;gt;

#define TFT_CS   21
#define TFT_DC   22
#define TFT_RST  23
#define TFT_MOSI 18   // D10 = GPIO18 (맞는 번호)
#define TFT_SCK  19   // D8 = GPIO19

Adafruit_ST7789 tft = Adafruit_ST7789(TFT_CS, TFT_DC, TFT_MOSI, TFT_SCK, TFT_RST);

void setup() {
  tft.init(240, 280);
  tft.invertDisplay(true);
  tft.fillScreen(ST77XX_RED);
}

void loop() {}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;영상출력&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;참고 : &lt;a href=&quot;https://ing-min.tistory.com/323&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://ing-min.tistory.com/323&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1772333586495&quot; class=&quot;arduino&quot; style=&quot;background-color: #f8f8f8; color: #383a42;&quot; data-ke-type=&quot;codeblock&quot; data-ke-language=&quot;bash&quot;&gt;&lt;code&gt;#include &amp;lt;Adafruit_GFX.h&amp;gt;
#include &amp;lt;Adafruit_ST7789.h&amp;gt;
#include &amp;lt;LittleFS.h&amp;gt;
#include &amp;lt;SPI.h&amp;gt;

#define TFT_CS   21
#define TFT_DC   22
#define TFT_RST  23
#define TFT_MOSI 18
#define TFT_SCK  19

#define IMG_WIDTH  200
#define IMG_HEIGHT 200
#define TOTAL_IMGS 21

Adafruit_ST7789 tft = Adafruit_ST7789(TFT_CS, TFT_DC, TFT_MOSI, TFT_SCK, TFT_RST);
uint16_t lineBuf[200];

void drawRaw(int index) {
  char path[20];
  sprintf(path, &quot;/img_%02d.raw&quot;, index);
  File f = LittleFS.open(path, &quot;r&quot;);
  if (!f) return;
  for (int y = 0; y &amp;lt; IMG_HEIGHT; y++) {
    int bytesRead = 0;
    uint8_t* buf = (uint8_t*)lineBuf;
    while (bytesRead &amp;lt; IMG_WIDTH * 2) {
      int r = f.read(buf + bytesRead, IMG_WIDTH * 2 - bytesRead);
      if (r &amp;lt;= 0) break;
      bytesRead += r;
    }
    for (int x = 0; x &amp;lt; IMG_WIDTH; x++) {
      uint16_t val = lineBuf[x];
      lineBuf[x] = (val &amp;gt;&amp;gt; 8) | (val &amp;lt;&amp;lt; 8);
    }
    tft.drawRGBBitmap(20, 40 + y, lineBuf, IMG_WIDTH, 1);
  }
  f.close();
}

void setup() {
  Serial.begin(115200);
  LittleFS.begin(true);
  tft.init(240, 280);
  tft.setRotation(0);
  tft.invertDisplay(true);
  tft.fillScreen(ST77XX_BLACK);
}

void loop() {
  for (int i = 0; i &amp;lt; TOTAL_IMGS; i++) {
    drawRaw(i);
    delay(80);
  }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;영상이 위에서 아래로 선을 그리며 렌더링 되는문제&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;해결&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;ms (밀리초)&lt;/b&gt;&amp;nbsp;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;파일읽기:&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;b&gt;27ms&lt;/b&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&amp;rarr; LittleFS에서 80KB 읽는 시간&lt;/li&gt;
&lt;li&gt;화면출력:&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;b&gt;24ms&lt;/b&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&amp;rarr; SPI로 TFT에 전송하는 시간&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;--디버깅 코드&lt;/p&gt;
&lt;pre id=&quot;code_1772333586496&quot; class=&quot;arduino&quot; style=&quot;background-color: #f8f8f8; color: #383a42;&quot; data-ke-type=&quot;codeblock&quot; data-ke-language=&quot;bash&quot;&gt;&lt;code&gt;#include &amp;lt;Adafruit_GFX.h&amp;gt;
#include &amp;lt;Adafruit_ST7789.h&amp;gt;
#include &amp;lt;LittleFS.h&amp;gt;
#include &amp;lt;SPI.h&amp;gt;

#define TFT_CS   21
#define TFT_DC   22
#define TFT_RST  23
#define TFT_MOSI 18
#define TFT_SCK  19

#define IMG_WIDTH  200
#define IMG_HEIGHT 200
#define TOTAL_IMGS 21

SPIClass* spi = new SPIClass(SPI);
Adafruit_ST7789 tft = Adafruit_ST7789(spi, TFT_CS, TFT_DC, TFT_RST);
uint16_t frameBuf[200 * 200];

int currentFrame = 0;
unsigned long lastFrame = 0;

void setup() {
  Serial.begin(115200);
  delay(1000);

  spi-&amp;gt;begin(TFT_SCK, -1, TFT_MOSI, TFT_CS);
  LittleFS.begin(true);
  tft.init(240, 320, SPI_MODE0);
  tft.setSPISpeed(40000000);
  tft.setRotation(0);
  tft.invertDisplay(true);
  tft.fillScreen(ST77XX_BLACK);
  Serial.println(&quot;TFT OK&quot;);
}

void loop() {
  unsigned long now = millis();
  if (now - lastFrame &amp;gt; 50) {
    char path[20];
    sprintf(path, &quot;/img_%02d.raw&quot;, currentFrame);
    File f = LittleFS.open(path, &quot;r&quot;);
    if (f) {
      f.read((uint8_t*)frameBuf, IMG_WIDTH * IMG_HEIGHT * 2);
      f.close();
      for (int i = 0; i &amp;lt; IMG_WIDTH * IMG_HEIGHT; i++) {
        uint16_t val = frameBuf[i];
        frameBuf[i] = (val &amp;gt;&amp;gt; 8) | (val &amp;lt;&amp;lt; 8);
      }
      tft.startWrite();
      tft.setAddrWindow(20, 60, IMG_WIDTH, IMG_HEIGHT);
      tft.writePixels(frameBuf, IMG_WIDTH * IMG_HEIGHT);
      tft.endWrite();
    }
    currentFrame = (currentFrame + 1) % TOTAL_IMGS;
    lastFrame = now;
  }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;되는 TFT 센서 코드&lt;/p&gt;
&lt;pre id=&quot;code_1772333586497&quot; class=&quot;arduino&quot; style=&quot;background-color: #f8f8f8; color: #383a42; text-align: start;&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;#include &amp;lt;Adafruit_GFX.h&amp;gt;
#include &amp;lt;Adafruit_ST7789.h&amp;gt;
#include &amp;lt;LittleFS.h&amp;gt;
#include &amp;lt;SPI.h&amp;gt;

#define TFT_CS   21
#define TFT_DC   22
#define TFT_RST  23
#define TFT_MOSI 18
#define TFT_SCK  19

#define IMG_WIDTH  200
#define IMG_HEIGHT 200
#define TOTAL_IMGS 21

SPIClass* spi = new SPIClass(SPI);
Adafruit_ST7789 tft = Adafruit_ST7789(spi, TFT_CS, TFT_DC, TFT_RST);
uint16_t frameBuf[200 * 200];

int currentFrame = 0;
unsigned long lastFrame = 0;

void setup() {
  Serial.begin(115200);
  delay(1000);

  spi-&amp;gt;begin(TFT_SCK, -1, TFT_MOSI, TFT_CS);
  LittleFS.begin(true);
  tft.init(240, 320, SPI_MODE0);
  tft.setSPISpeed(40000000);
  tft.setRotation(1);
  tft.invertDisplay(true);
  tft.fillScreen(ST77XX_BLACK);
  Serial.println(&quot;TFT OK&quot;);
}

void loop() {
  unsigned long now = millis();
  if (now - lastFrame &amp;gt; 50) {
    char path[20];
    sprintf(path, &quot;/img_%02d.raw&quot;, currentFrame);
    File f = LittleFS.open(path, &quot;r&quot;);
    if (f) {
      f.read((uint8_t*)frameBuf, IMG_WIDTH * IMG_HEIGHT * 2);
      f.close();
      for (int i = 0; i &amp;lt; IMG_WIDTH * IMG_HEIGHT; i++) {
        uint16_t val = frameBuf[i];
        frameBuf[i] = (val &amp;gt;&amp;gt; 8) | (val &amp;lt;&amp;lt; 8);
      }
      tft.startWrite();
      tft.setAddrWindow(60, 20, IMG_WIDTH, IMG_HEIGHT);
      tft.writePixels(frameBuf, IMG_WIDTH * IMG_HEIGHT);
      tft.endWrite();
    }
    currentFrame = (currentFrame + 1) % TOTAL_IMGS;
    lastFrame = now;
  }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;자석감지 센서&lt;/p&gt;
&lt;h3 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;센서: MLX90393 자석 센서&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;프로토콜: I2C&lt;/li&gt;
&lt;li&gt;SDA: GPIO16, SCL: GPIO17&lt;/li&gt;
&lt;li&gt;설정: GAIN_1X, RES_19(XY), RES_16(Z)&lt;/li&gt;
&lt;li&gt;감지값 범위: magnitude 110~210 (자석 없을 때), 임계값 threshold = 160.0&lt;/li&gt;
&lt;li&gt;폴링: 16ms (약 60Hz)&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;MLX90393&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;XIAO&lt;br /&gt;3V3&amp;nbsp;&amp;nbsp;&amp;rarr;&amp;nbsp;&amp;nbsp;3.3V&lt;br /&gt;GND&amp;nbsp;&amp;nbsp;&amp;rarr;&amp;nbsp;&amp;nbsp;GND&lt;br /&gt;SDA&amp;nbsp;&amp;nbsp;&amp;rarr;&amp;nbsp;&amp;nbsp;D6&amp;nbsp;(GPIO16)&lt;br /&gt;SCL&amp;nbsp;&amp;nbsp;&amp;rarr;&amp;nbsp;&amp;nbsp;D7&amp;nbsp;(GPIO17)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;두개 통합코드 (자석감지 , tft)&lt;/p&gt;
&lt;pre id=&quot;code_1772333586498&quot; class=&quot;arduino&quot; style=&quot;background-color: #f8f8f8; color: #383a42;&quot; data-ke-type=&quot;codeblock&quot; data-ke-language=&quot;bash&quot;&gt;&lt;code&gt;#include &amp;lt;Adafruit_GFX.h&amp;gt;
#include &amp;lt;Adafruit_ST7789.h&amp;gt;
#include &amp;lt;LittleFS.h&amp;gt;
#include &amp;lt;SPI.h&amp;gt;
#include &amp;lt;Wire.h&amp;gt;
#include &amp;lt;Adafruit_MLX90393.h&amp;gt;

#define TFT_CS   21
#define TFT_DC   22
#define TFT_RST  23
#define TFT_MOSI 18
#define TFT_SCK  19

#define IMG_WIDTH  200
#define IMG_HEIGHT 200
#define TOTAL_IMGS 21

Adafruit_ST7789 tft = Adafruit_ST7789(TFT_CS, TFT_DC, TFT_MOSI, TFT_SCK, TFT_RST);
Adafruit_MLX90393 sensor = Adafruit_MLX90393();

uint16_t lineBuf[200];
float threshold = 200.0;
bool magnetPresent = false;
unsigned long lastTrigger = 0;
float currentSpeed = 0;
int currentFrame = 0;
unsigned long lastFrame = 0;

void drawRaw(int index) {
  char path[20];
  sprintf(path, &quot;/img_%02d.raw&quot;, index);
  File f = LittleFS.open(path, &quot;r&quot;);
  if (!f) return;
  for (int y = 0; y &amp;lt; IMG_HEIGHT; y++) {
    int bytesRead = 0;
    uint8_t* buf = (uint8_t*)lineBuf;
    while (bytesRead &amp;lt; IMG_WIDTH * 2) {
      int r = f.read(buf + bytesRead, IMG_WIDTH * 2 - bytesRead);
      if (r &amp;lt;= 0) break;
      bytesRead += r;
    }
    for (int x = 0; x &amp;lt; IMG_WIDTH; x++) {
      uint16_t val = lineBuf[x];
      lineBuf[x] = (val &amp;gt;&amp;gt; 8) | (val &amp;lt;&amp;lt; 8);
    }
    tft.drawRGBBitmap(20, 40 + y, lineBuf, IMG_WIDTH, 1);
  }
  f.close();
}

void setup() {
  Serial.begin(115200);
  Wire.begin(16, 17);  // SDA=D6, SCL=D7

  LittleFS.begin(true);

  tft.init(240, 280);
  tft.setRotation(0);
  tft.invertDisplay(true);
  tft.fillScreen(ST77XX_BLACK);

  if (!sensor.begin_I2C()) {
    Serial.println(&quot;MLX90393 못찾음&quot;);
  } else {
    Serial.println(&quot;MLX90393 OK&quot;);
    sensor.setGain(MLX90393_GAIN_1X);
    sensor.setResolution(MLX90393_X, MLX90393_RES_19);
    sensor.setResolution(MLX90393_Y, MLX90393_RES_19);
    sensor.setResolution(MLX90393_Z, MLX90393_RES_16);
  }
}

void loop() {
  float x, y, z;
  if (sensor.readData(&amp;amp;x, &amp;amp;y, &amp;amp;z)) {
    float magnitude = sqrt(x*x + y*y + z*z);

    if (magnitude &amp;gt; threshold &amp;amp;&amp;amp; !magnetPresent) {
      magnetPresent = true;
      unsigned long now = millis();
      if (lastTrigger &amp;gt; 0) {
        float period = (now - lastTrigger) / 1000.0;
        currentSpeed = 1.0 / period;
        Serial.print(&quot;속도: &quot;);
        Serial.println(currentSpeed);
      }
      lastTrigger = millis();
    }
    if (magnitude &amp;lt; threshold * 0.7) magnetPresent = false;
  }

  // 속도에 따라 재생 속도 조절
  float frameDelay = 80;
  if (currentSpeed &amp;gt; 0) {
    frameDelay = max(20.0, 150.0 / (currentSpeed * TOTAL_IMGS));
  }

  unsigned long now = millis();
  if (now - lastFrame &amp;gt; frameDelay) {
    drawRaw(currentFrame);
    currentFrame = (currentFrame + 1) % TOTAL_IMGS;
    lastFrame = now;
  }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;자석&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;span style=&quot;background-color: #f0eee6; color: #141413; text-align: start;&quot;&gt;110- 310 값 범위.&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2026-03-01 02.42.32.png&quot; data-origin-width=&quot;646&quot; data-origin-height=&quot;430&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/rIqpo/dJMcab4mGB5/RrrRHHhbuYHD6q5YYoEthk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/rIqpo/dJMcab4mGB5/RrrRHHhbuYHD6q5YYoEthk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/rIqpo/dJMcab4mGB5/RrrRHHhbuYHD6q5YYoEthk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FrIqpo%2FdJMcab4mGB5%2FRrrRHHhbuYHD6q5YYoEthk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;646&quot; height=&quot;430&quot; data-filename=&quot;스크린샷 2026-03-01 02.42.32.png&quot; data-origin-width=&quot;646&quot; data-origin-height=&quot;430&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>Iot/Arduino</category>
      <author>잉_민</author>
      <guid isPermaLink="true">https://ing-min.tistory.com/324</guid>
      <comments>https://ing-min.tistory.com/324#entry324comment</comments>
      <pubDate>Sun, 1 Mar 2026 11:54:25 +0900</pubDate>
    </item>
    <item>
      <title>Arduino IDE : xiao seeed esp32-c6  + TFT ST7789</title>
      <link>https://ing-min.tistory.com/323</link>
      <description>&lt;h1&gt;XIAO ESP32C6 + ST7789 TFT 영상 재생 &amp;mdash; 셋업 전체 기록&lt;/h1&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;숨으로 바람개비를 돌리면 자석 센서가 회전을 감지하고, 그 주기에 맞춰 영상과 모터가 반응하는 인터랙티브 설치 작품을 만들면서 겪은 과정을 정리했다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;하드웨어&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;XIAO ESP32C6&lt;/b&gt; (Seeed Studio)&lt;/li&gt;
&lt;li&gt;&lt;b&gt;ST7789 TFT 디스플레이&lt;/b&gt; (2.0&quot;, 240&amp;times;320)&lt;/li&gt;
&lt;li&gt;&lt;b&gt;MLX90393 자석 센서&lt;/b&gt;&lt;/li&gt;
&lt;li&gt;&lt;b&gt;N20 DC 모터&lt;/b&gt; + 2N2222A 트랜지스터&lt;/li&gt;
&lt;/ul&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;1. 보드 설정&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Arduino IDE에서 보드를 처음 잡을 때 ESP32 Family Device로 잡히는 경우가 있다. 이건 틀렸다.&lt;/p&gt;
&lt;pre class=&quot;properties&quot;&gt;&lt;code&gt;ESP32 Family Device  &amp;larr; 너무 넓은 분류, 핀 번호 등 세부 설정이 안 맞음
XIAO_ESP32C6         &amp;larr; 정확한 모델 지정 필요
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;보드를 정확히 지정해야 GPIO 번호, 메모리 주소, 플래시 크기가 맞게 들어간다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;2. TFT 라이브러리 선택&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;처음에 TFT_eSPI 라이브러리를 시도했다. User_Setup.h에서 핀 번호를 직접 수정해야 하고 설정이 복잡하다. 결국 &lt;b&gt;Adafruit_ST7789&lt;/b&gt;로 교체했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;최종 사용 라이브러리: Adafruit_ST7789&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이유:&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;설정이 단순하고 코드에서 직접 핀을 지정할 수 있음&lt;/li&gt;
&lt;li&gt;하드웨어 SPI를 명시적으로 지정 가능&lt;/li&gt;
&lt;li&gt;setSPISpeed() 지원&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;TFT 핀 연결&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;TFT XIAO ESP32C6&lt;/p&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;MOSI&lt;/td&gt;
&lt;td&gt;D10 (GPIO18)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;SCK&lt;/td&gt;
&lt;td&gt;D8 (GPIO19)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;CS&lt;/td&gt;
&lt;td&gt;D3 (GPIO21)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;DC&lt;/td&gt;
&lt;td&gt;D4 (GPIO22)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;RST&lt;/td&gt;
&lt;td&gt;D5 (GPIO23)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;BLK&lt;/td&gt;
&lt;td&gt;3.3V&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;3. 소프트웨어 SPI vs 하드웨어 SPI&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;처음 Adafruit_ST7789 tft = Adafruit_ST7789(TFT_CS, TFT_DC, TFT_MOSI, TFT_SCK, TFT_RST) 방식으로 초기화하면 &lt;b&gt;소프트웨어 SPI&lt;/b&gt;로 동작한다. 화면 출력 시간이 &lt;b&gt;2169ms&lt;/b&gt;가 나왔다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하드웨어 SPI로 바꾸면 &lt;b&gt;24ms&lt;/b&gt;로 줄어든다.&lt;/p&gt;
&lt;pre class=&quot;reasonml&quot;&gt;&lt;code&gt;// ❌ 소프트웨어 SPI (느림, 2169ms)
Adafruit_ST7789 tft = Adafruit_ST7789(TFT_CS, TFT_DC, TFT_MOSI, TFT_SCK, TFT_RST);

// ✅ 하드웨어 SPI (빠름, 24ms)
SPIClass* spi = new SPIClass(SPI);
Adafruit_ST7789 tft = Adafruit_ST7789(spi, TFT_CS, TFT_DC, TFT_RST);

// setup()에서
spi-&amp;gt;begin(TFT_SCK, -1, TFT_MOSI, TFT_CS);
tft.init(240, 320, SPI_MODE0);
tft.setSPISpeed(40000000);
&lt;/code&gt;&lt;/pre&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;4. 영상 &amp;rarr; TFT 출력 전체 파이프라인&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;전체 흐름&lt;/h3&gt;
&lt;pre class=&quot;gcode&quot;&gt;&lt;code&gt;영상(mp4) &amp;rarr; 프레임 추출(ffmpeg) &amp;rarr; RGB565 변환(Python) &amp;rarr; LittleFS 패키징 &amp;rarr; Flash 업로드(esptool) &amp;rarr; TFT 출력
&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Step 1. 영상 &amp;rarr; 이미지 프레임 (ffmpeg)&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;5초 24fps 영상에서 6프레임마다 1장씩 총 21장 추출.&lt;/p&gt;
&lt;pre class=&quot;lua&quot;&gt;&lt;code&gt;import subprocess, os, glob

videos = sorted(glob.glob(&quot;*.mp4&quot;))

for video in videos:
    name = os.path.splitext(video)[0]
    output_dir = f&quot;frames_{name}&quot;
    os.makedirs(output_dir, exist_ok=True)
    
    cmd = [
        &quot;ffmpeg&quot;, &quot;-i&quot;, video,
        &quot;-vf&quot;, &quot;scale=200:200,select='not(mod(n\\,6))'&quot;,
        &quot;-vsync&quot;, &quot;vfr&quot;,
        &quot;-q:v&quot;, &quot;2&quot;,
        os.path.join(output_dir, &quot;frame_%04d.jpg&quot;)
    ]
    subprocess.run(cmd)
&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Step 2. 이미지 &amp;rarr; RGB565 변환 (Python)&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;TFT는 JPG를 직접 못 읽는다. 픽셀을 16bit 숫자로 변환한 .raw 파일로 저장해야 한다.&lt;/p&gt;
&lt;pre class=&quot;vim&quot;&gt;&lt;code&gt;from PIL import Image
import os, glob, struct

input_dir = &quot;frames&quot;
output_dir = &quot;data&quot;
os.makedirs(output_dir, exist_ok=True)

files = sorted(glob.glob(os.path.join(input_dir, &quot;*.jpg&quot;)))[:21]

for i, f in enumerate(files):
    img = Image.open(f).convert(&quot;RGB&quot;).resize((200, 200))
    pixels = []
    for r, g, b in img.getdata():
        rgb565 = ((b &amp;amp; 0xF8) &amp;lt;&amp;lt; 8) | ((g &amp;amp; 0xFC) &amp;lt;&amp;lt; 3) | (r &amp;gt;&amp;gt; 3)
        pixels.append(struct.pack(&quot;&amp;lt;H&quot;, rgb565))  # little-endian
    
    out_path = os.path.join(output_dir, f&quot;img_{i:02d}.raw&quot;)
    with open(out_path, &quot;wb&quot;) as out:
        out.write(b&quot;&quot;.join(pixels))
&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Step 3. raw 파일 &amp;rarr; LittleFS 이미지 (mklittlefs)&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;21개 .raw 파일을 하나의 littlefs.bin으로 패키징.&lt;/p&gt;
&lt;pre class=&quot;livescript&quot;&gt;&lt;code&gt;[Arduino 설치 경로]/packages/esp32/tools/mklittlefs/[버전]/mklittlefs \
  -c [data 폴더 경로] \
  -s 1966080 \
  -b 4096 \
  -p 256 \
  littlefs.bin
&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Step 4. littlefs.bin &amp;rarr; XIAO Flash (esptool)&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Flash의 0x210000 주소(SPIFFS/LittleFS 영역)에 직접 굽는다.&lt;/p&gt;
&lt;pre class=&quot;jboss-cli&quot;&gt;&lt;code&gt;[Arduino 설치 경로]/packages/esp32/tools/esptool_py/[버전]/esptool \
  --chip esp32c6 \
  --port /dev/cu.usbmodem[포트번호] \
  --baud 921600 \
  write_flash 0x210000 littlefs.bin
&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Flash 메모리 구조&lt;/h3&gt;
&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;XIAO Flash 4MB
├── 0x000000  부트로더
├── 0x010000  스케치 (Arduino IDE로 업로드)
└── 0x210000  LittleFS (esptool로 업로드)
                  img_00.raw ~ img_20.raw
&lt;/code&gt;&lt;/pre&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;5. 이미지 출력 코드&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;❌ 잘못된 방법 (위에서 아래로 렌더링 보임)&lt;/h3&gt;
&lt;pre class=&quot;gml&quot;&gt;&lt;code&gt;// 줄마다 drawRGBBitmap 호출 &amp;rarr; 스캔라인이 눈에 보임
for (int y = 0; y &amp;lt; IMG_HEIGHT; y++) {
    tft.drawRGBBitmap(x, y, lineBuf, IMG_WIDTH, 1);
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;✅ 올바른 방법 (한번에 전송)&lt;/h3&gt;
&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;uint16_t frameBuf[200 * 200];

// 전체 읽기
f.read((uint8_t*)frameBuf, IMG_WIDTH * IMG_HEIGHT * 2);

// 바이트 스왑 (색상 순서 보정)
for (int i = 0; i &amp;lt; IMG_WIDTH * IMG_HEIGHT; i++) {
    uint16_t val = frameBuf[i];
    frameBuf[i] = (val &amp;gt;&amp;gt; 8) | (val &amp;lt;&amp;lt; 8);
}

// 한번에 전송
tft.startWrite();
tft.setAddrWindow(60, 20, IMG_WIDTH, IMG_HEIGHT);  // 중앙 배치
tft.writePixels(frameBuf, IMG_WIDTH * IMG_HEIGHT);
tft.endWrite();
&lt;/code&gt;&lt;/pre&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;6. 다른 장치들과 연결시 초기화 순서&amp;nbsp;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;TFT가 먼저 시작되면, 뒤에 통신들을 못받는 에러가 난다.&lt;/p&gt;
&lt;pre class=&quot;reasonml&quot;&gt;&lt;code&gt;1. Wire.begin(SDA, SCL)       // I2C 먼저
2. sensor.begin_I2C()
3. ledcAttach(MOTOR_PIN, ...)  // 모터
4. spi-&amp;gt;begin(...)             // SPI
5. LittleFS.begin()
6. tft.init()                  // TFT 마지막
&lt;/code&gt;&lt;/pre&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&amp;nbsp;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>Iot/Arduino</category>
      <category>Arduino</category>
      <category>Display</category>
      <category>ESP32</category>
      <category>st7789</category>
      <category>tft</category>
      <author>잉_민</author>
      <guid isPermaLink="true">https://ing-min.tistory.com/323</guid>
      <comments>https://ing-min.tistory.com/323#entry323comment</comments>
      <pubDate>Thu, 26 Feb 2026 11:13:02 +0900</pubDate>
    </item>
    <item>
      <title>AI Openclaw 오픈클로) 소형컴퓨터 버전 : zclaw +Seeed ESP32-C6/ S3</title>
      <link>https://ing-min.tistory.com/322</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;color: #1f1f1f;&quot;&gt;1. 프로젝트 공식 링크&lt;/span&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #1f1f1f;&quot;&gt;zclaw는 아래의 깃허브(GitHub) 페이지에서 관리됩니다.&lt;/span&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li style=&quot;list-style-type: disc; color: #1f1f1f;&quot;&gt;&lt;span style=&quot;color: #1f1f1f;&quot;&gt;공식 깃허브:&lt;/span&gt;&lt;span style=&quot;color: #1f1f1f;&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #0b57d0;&quot;&gt;&lt;/span&gt;&lt;a href=&quot;https://github.com/tnm/zclaw&quot;&gt;https://github.com/tnm/zclaw&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;figure id=&quot;og_1772064054851&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;object&quot; data-og-title=&quot;GitHub - tnm/zclaw: Your personal AI assistant at all-in 888KiB (~25KB in app code). Running on an ESP32. GPIO, cron, custom too&quot; data-og-description=&quot;Your personal AI assistant at all-in 888KiB (~25KB in app code). Running on an ESP32. GPIO, cron, custom tools, memory, and more. - tnm/zclaw&quot; data-og-host=&quot;github.com&quot; data-og-source-url=&quot;https://github.com/tnm/zclaw&quot; data-og-url=&quot;https://github.com/tnm/zclaw&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/1eEjn/dJMb8PGtjaX/u7kkpbzagZwtcnsPc5jtJ0/img.png?width=1200&amp;amp;height=600&amp;amp;face=928_148_1018_247,https://scrap.kakaocdn.net/dn/bcprTj/dJMb8PGtjaY/gNWNkAcvHZPbD1FFdtN0i1/img.png?width=1200&amp;amp;height=600&amp;amp;face=928_148_1018_247&quot;&gt;&lt;a href=&quot;https://github.com/tnm/zclaw&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://github.com/tnm/zclaw&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/1eEjn/dJMb8PGtjaX/u7kkpbzagZwtcnsPc5jtJ0/img.png?width=1200&amp;amp;height=600&amp;amp;face=928_148_1018_247,https://scrap.kakaocdn.net/dn/bcprTj/dJMb8PGtjaY/gNWNkAcvHZPbD1FFdtN0i1/img.png?width=1200&amp;amp;height=600&amp;amp;face=928_148_1018_247');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;GitHub - tnm/zclaw: Your personal AI assistant at all-in 888KiB (~25KB in app code). Running on an ESP32. GPIO, cron, custom too&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;Your personal AI assistant at all-in 888KiB (~25KB in app code). Running on an ESP32. GPIO, cron, custom tools, memory, and more. - tnm/zclaw&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;github.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #1f1f1f;&quot;&gt;이곳에서 소스 코드를 확인하고, 이슈(질문/오류)를 제보하거나 업데이트 사항을 볼 수 있습니다.&lt;/span&gt;&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;color: #1f1f1f;&quot;&gt;2. 설치 및 빌드 방법 (C6 보드 타겟)&lt;/span&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #1f1f1f;&quot;&gt;보유하신 &lt;/span&gt;&lt;span style=&quot;color: #1f1f1f;&quot;&gt;ESP32-C6&lt;/span&gt;&lt;span style=&quot;color: #1f1f1f;&quot;&gt; 보드를 기준으로, 가장 표준적인 설치 흐름입니다.&lt;/span&gt;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;span style=&quot;color: #1f1f1f;&quot;&gt;1단계: 환경 준비&lt;/span&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #1f1f1f;&quot;&gt;컴퓨터에 &lt;/span&gt;&lt;span style=&quot;color: #1f1f1f;&quot;&gt;ESP-IDF&lt;/span&gt;&lt;span style=&quot;color: #1f1f1f;&quot;&gt; 환경이 구축되어 있어야 합니다. (VS Code에서 &lt;/span&gt;&lt;span style=&quot;color: #1f1f1f;&quot;&gt;ESP-IDF Extension&lt;/span&gt;&lt;span style=&quot;color: #1f1f1f;&quot;&gt;을 설치하면 가장 편리합니다.)&lt;/span&gt;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;span style=&quot;color: #1f1f1f;&quot;&gt;2단계: 코드 가져오기&lt;/span&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #1f1f1f;&quot;&gt;터미널을 열고 다음 명령어를 입력합니다:&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;background-color: #f0f4f9;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #444746;&quot;&gt;Bash&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #444746;&quot;&gt;git &lt;/span&gt;&lt;span style=&quot;color: #1967d2;&quot;&gt;clone&lt;/span&gt;&lt;span style=&quot;color: #444746;&quot;&gt; &lt;a href=&quot;https://github.com/tnm/zclaw.git&quot;&gt;https://github.com/tnm/zclaw.git&lt;/a&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #1967d2;&quot;&gt;cd&lt;/span&gt;&lt;span style=&quot;color: #444746;&quot;&gt; zclaw&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;background-color: #f0f4f9;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;span style=&quot;color: #1f1f1f;&quot;&gt;3단계: 보드 설정 (Target 설정)&lt;/span&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #1f1f1f;&quot;&gt;C6 모델을 사용한다고 환경에 알려주어야 합니다.&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;background-color: #f0f4f9;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #444746;&quot;&gt;Bash&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #444746;&quot;&gt;idf.py set-target esp32c6&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;background-color: #f0f4f9;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;span style=&quot;color: #1f1f1f;&quot;&gt;4단계: 빌드 및 플래싱&lt;/span&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #1f1f1f;&quot;&gt;다음 명령어를 순서대로 실행하여 코드를 보드에 올립니다:&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;background-color: #f0f4f9;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #444746;&quot;&gt;Bash&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #5f6368;&quot;&gt;# 코드 빌드 (컴파일)&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #444746;&quot;&gt;idf.py build&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&amp;nbsp;&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #5f6368;&quot;&gt;# 보드에 업로드 (플래싱)&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #444746;&quot;&gt;idf.py -p [컴포트번호] flash monitor&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;background-color: #f0f4f9;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li style=&quot;list-style-type: disc; color: #1f1f1f;&quot;&gt;&lt;span style=&quot;background-color: #e9eef6; color: #444746;&quot;&gt;[컴포트번호]&lt;/span&gt;&lt;span style=&quot;color: #1f1f1f;&quot;&gt;는 연결된 보드의 포트(예: &lt;/span&gt;&lt;span style=&quot;background-color: #e9eef6; color: #444746;&quot;&gt;COM3&lt;/span&gt;&lt;span style=&quot;color: #1f1f1f;&quot;&gt; 또는 &lt;/span&gt;&lt;span style=&quot;background-color: #e9eef6; color: #444746;&quot;&gt;/dev/ttyUSB0&lt;/span&gt;&lt;span style=&quot;color: #1f1f1f;&quot;&gt;)로 바꿔주세요.&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;color: #1f1f1f;&quot;&gt;3. 이해를 돕는 구조도&lt;/span&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #1f1f1f;&quot;&gt;이 구조를 이해하시면 나중에 C6 보드에서 문제가 생겼을 때 어디가 잘못되었는지 찾기 훨씬 수월합니다.&lt;/span&gt;&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;color: #1f1f1f;&quot;&gt;⚠️ 주의사항 및 팁&lt;/span&gt;&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li style=&quot;list-style-type: disc; color: #1f1f1f;&quot;&gt;&lt;span style=&quot;color: #1f1f1f;&quot;&gt;메모리 최적화:&lt;/span&gt;&lt;span style=&quot;color: #1f1f1f;&quot;&gt; 앞서 말씀드렸듯, C6는 PSRAM이 없기 때문에 &lt;/span&gt;&lt;span style=&quot;background-color: #e9eef6; color: #444746;&quot;&gt;menuconfig&lt;/span&gt;&lt;span style=&quot;color: #1f1f1f;&quot;&gt;에서 메모리 사용 옵션을 최소화해야 할 수 있습니다. 빌드 시 오류가 발생하면 &lt;/span&gt;&lt;span style=&quot;background-color: #e9eef6; color: #444746;&quot;&gt;idf.py menuconfig&lt;/span&gt;&lt;span style=&quot;color: #1f1f1f;&quot;&gt;를 실행하여 불필요한 기능(예: Wi-Fi 고도화 설정 등)을 끄고 다시 시도해 보세요.&lt;/span&gt;&lt;/li&gt;
&lt;li style=&quot;list-style-type: disc; color: #1f1f1f;&quot;&gt;&lt;span style=&quot;color: #1f1f1f;&quot;&gt;로그 확인:&lt;/span&gt;&lt;span style=&quot;color: #1f1f1f;&quot;&gt; &lt;/span&gt;&lt;span style=&quot;background-color: #e9eef6; color: #444746;&quot;&gt;idf.py monitor&lt;/span&gt;&lt;span style=&quot;color: #1f1f1f;&quot;&gt; 명령어를 사용하면 보드가 어떤 오류를 내뱉는지 실시간으로 볼 수 있습니다. &quot;Memory allocation failed&quot; 같은 메시지가 뜬다면 메모리 부족이 원인이니, 더 가벼운 코드로 수정해야 합니다.&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>AI</category>
      <category>AI</category>
      <category>ESP32</category>
      <category>OpenClaw</category>
      <category>zclaw</category>
      <author>잉_민</author>
      <guid isPermaLink="true">https://ing-min.tistory.com/322</guid>
      <comments>https://ing-min.tistory.com/322#entry322comment</comments>
      <pubDate>Thu, 26 Feb 2026 09:29:07 +0900</pubDate>
    </item>
    <item>
      <title>AI)OpenClaw 설치 &amp;amp; Gemini CLI + Telegram 연동 가이드 (mac)</title>
      <link>https://ing-min.tistory.com/319</link>
      <description>&lt;h1&gt;&amp;nbsp;&lt;/h1&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://youtu.be/ch4EsgfHOJc&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://youtu.be/ch4EsgfHOJc&lt;/a&gt;&lt;/p&gt;
&lt;figure data-ke-type=&quot;video&quot; data-ke-style=&quot;alignCenter&quot; data-video-host=&quot;youtube&quot; data-video-url=&quot;https://www.youtube.com/watch?v=ch4EsgfHOJc&quot; data-video-thumbnail=&quot;https://scrap.kakaocdn.net/dn/dM65xL/dJMb8VNrLd0/Lg6Lz3hcIyxIPSwlxF33h1/img.jpg?width=1280&amp;amp;height=720&amp;amp;face=986_268_1114_408,https://scrap.kakaocdn.net/dn/c6Ak6O/dJMb8VNrLd1/4TJV7sHGFqcPbGiqRlCLYK/img.jpg?width=1280&amp;amp;height=720&amp;amp;face=986_268_1114_408&quot; data-video-width=&quot;860&quot; data-video-height=&quot;484&quot; data-video-origin-width=&quot;860&quot; data-video-origin-height=&quot;484&quot; data-ke-mobilestyle=&quot;widthContent&quot; data-video-title=&quot;24시간 무급으로 대신 일해주는 AI 비서 등장;; 오픈클로 드디어 써봤습니다..&quot; data-original-url=&quot;&quot;&gt;&lt;iframe src=&quot;https://www.youtube.com/embed/ch4EsgfHOJc&quot; width=&quot;860&quot; height=&quot;484&quot; frameborder=&quot;&quot; allowfullscreen=&quot;true&quot;&gt;&lt;/iframe&gt;
&lt;figcaption style=&quot;display: none;&quot;&gt;&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;1. OpenClaw 설치&lt;/h2&gt;
&lt;pre class=&quot;dsconfig&quot;&gt;&lt;code&gt;curl -fsSL --proto '=https' --tlsv1.2 https://openclaw.ai/install.sh | bash
&lt;/code&gt;&lt;/pre&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;2. Gemini API 키 발급 (무료)&lt;/h2&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;&lt;a href=&quot;https://aistudio.google.com/app/apikey&quot;&gt;https://aistudio.google.com/app/apikey&lt;/a&gt; 접속&lt;/li&gt;
&lt;li&gt;Google 계정 로그인&lt;/li&gt;
&lt;li&gt;&lt;b&gt;Create API key&lt;/b&gt; 클릭 &amp;rarr; 키 복사&lt;/li&gt;
&lt;/ol&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Claude API는 유료(pay-as-you-go)이므로 Gemini 무료 티어 사용 권장&lt;/p&gt;
&lt;/blockquote&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;3. OpenClaw 설정 (Gemini 연동)&lt;/h2&gt;
&lt;pre class=&quot;ebnf&quot;&gt;&lt;code&gt;openclaw configure
&lt;/code&gt;&lt;/pre&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Model provider: &lt;b&gt;Google CLI/&amp;nbsp; Gemini&lt;/b&gt; 선택&lt;/li&gt;
&lt;/ul&gt;
&lt;pre id=&quot;code_1771373061398&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;sudo npm install -g @google/gemini-cli&lt;/code&gt;&lt;/pre&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;이거 설치해야함&lt;/li&gt;
&lt;li&gt;발급받은 Gemini API 키 입력&lt;/li&gt;
&lt;li&gt;기본 모델 추천: google/gemini-2.5-flash
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;gemini-2.5-pro-preview-06-05는 무료 API에서 404 오류 발생하므로 사용 불가&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;4. Telegram 봇 생성&lt;/h2&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;Telegram에서 &lt;b&gt;@BotFather&lt;/b&gt; 검색&lt;/li&gt;
&lt;li&gt;/newbot 입력 &amp;rarr; 봇 이름 설정 (** 여기 봇으로 들어가야함: 검색으로 자신이 생성한 봇으로 채팅입장)&lt;/li&gt;
&lt;li&gt;발급된 &lt;b&gt;Bot Token&lt;/b&gt; 복사 &amp;rarr; OpenClaw 설정에 입력&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;5. 게이트웨이 실행&lt;/h2&gt;
&lt;pre class=&quot;ebnf&quot;&gt;&lt;code&gt;openclaw gateway
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이미 실행 중인 경우:&lt;/p&gt;
&lt;pre class=&quot;crmsh&quot;&gt;&lt;code&gt;openclaw gateway stop   # 먼저 중지
openclaw gateway        # 재시작
&lt;/code&gt;&lt;/pre&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;6. Telegram 봇 페어링&lt;/h2&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;Telegram에서 &lt;b&gt;본인이 만든 봇&lt;/b&gt; 검색 (BotFather 아님!)&lt;/li&gt;
&lt;li&gt;봇에게 /start 전송&lt;/li&gt;
&lt;li&gt;봇이 &lt;b&gt;페어링 코드&lt;/b&gt; 전송&lt;/li&gt;
&lt;li&gt;터미널에서 아래 명령어로 승인:&lt;/li&gt;
&lt;/ol&gt;
&lt;pre class=&quot;nginx&quot;&gt;&lt;code&gt;openclaw pairing approve telegram 페어링코드
&lt;/code&gt;&lt;/pre&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;완료&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이제 Telegram에서 본인 봇에게 메시지를 보내면 , 엄청기다리면, Gemini가 응답해요.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;주요 명령어 정리&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;명령어 설명&lt;/p&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;openclaw gateway&lt;/td&gt;
&lt;td&gt;게이트웨이 실행&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;openclaw gateway stop&lt;/td&gt;
&lt;td&gt;게이트웨이 중지&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;openclaw status&lt;/td&gt;
&lt;td&gt;게이트웨이 상태 확인&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;openclaw pairing list&lt;/td&gt;
&lt;td&gt;대기 중인 페어링 요청 확인&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;openclaw pairing approve telegram &amp;lt;코드&amp;gt;&lt;/td&gt;
&lt;td&gt;텔레그램 페어링 승인&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;openclaw models&lt;/td&gt;
&lt;td&gt;모델 설정 변경&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;openclaw doctor&lt;/td&gt;
&lt;td&gt;설정 오류 진단&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;openclaw dashboard&lt;/td&gt;
&lt;td&gt;브라우저 대시보드 열기&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;</description>
      <category>AI</category>
      <category>AI</category>
      <category>OpenClaw</category>
      <author>잉_민</author>
      <guid isPermaLink="true">https://ing-min.tistory.com/319</guid>
      <comments>https://ing-min.tistory.com/319#entry319comment</comments>
      <pubDate>Wed, 18 Feb 2026 09:06:52 +0900</pubDate>
    </item>
    <item>
      <title>C4D to Unity_ colth animation .abc 천 시뮬레이션 유니티로 보내기</title>
      <link>https://ing-min.tistory.com/318</link>
      <description>&lt;h3 data-end=&quot;604&quot; data-start=&quot;569&quot; data-ke-size=&quot;size23&quot;&gt;shift +c&amp;nbsp; 검색 : bake or alembic&amp;nbsp;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&lt;span style=&quot;background-color: #f3c000;&quot;&gt;선택 bake as alembic&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&lt;span style=&quot;background-color: #f3c000;&quot;&gt;경로지정하고 가서 보면 있음. &lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2025-12-08 19.20.38.png&quot; data-origin-width=&quot;734&quot; data-origin-height=&quot;530&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bLH4Xd/dJMcad1xIEn/xv4OW9HKzXMdR11WeTbxM0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bLH4Xd/dJMcad1xIEn/xv4OW9HKzXMdR11WeTbxM0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bLH4Xd/dJMcad1xIEn/xv4OW9HKzXMdR11WeTbxM0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbLH4Xd%2FdJMcad1xIEn%2Fxv4OW9HKzXMdR11WeTbxM0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;734&quot; height=&quot;530&quot; data-filename=&quot;스크린샷 2025-12-08 19.20.38.png&quot; data-origin-width=&quot;734&quot; data-origin-height=&quot;530&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;h3 data-end=&quot;604&quot; data-start=&quot;569&quot; data-ke-size=&quot;size23&quot;&gt;&amp;nbsp;&lt;/h3&gt;
&lt;h3 data-end=&quot;604&quot; data-start=&quot;569&quot; data-ke-size=&quot;size23&quot;&gt;윈도우 -패키니 매니져 -unity registry : 인스톨 alembic&amp;nbsp;&lt;/h3&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2025-12-08 19.18.58.png&quot; data-origin-width=&quot;2098&quot; data-origin-height=&quot;1056&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/tmGgo/dJMcacBCOZ2/zXI5KNcpDPT1CbaWaqI930/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/tmGgo/dJMcacBCOZ2/zXI5KNcpDPT1CbaWaqI930/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/tmGgo/dJMcacBCOZ2/zXI5KNcpDPT1CbaWaqI930/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FtmGgo%2FdJMcacBCOZ2%2FzXI5KNcpDPT1CbaWaqI930%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;2098&quot; height=&quot;1056&quot; data-filename=&quot;스크린샷 2025-12-08 19.18.58.png&quot; data-origin-width=&quot;2098&quot; data-origin-height=&quot;1056&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;h3 data-end=&quot;604&quot; data-start=&quot;569&quot; data-ke-size=&quot;size23&quot;&gt;&amp;nbsp;&lt;/h3&gt;
&lt;pre id=&quot;code_1765218146398&quot; class=&quot;csharp&quot; data-ke-language=&quot;csharp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;using UnityEngine;
using UnityEngine.Formats.Alembic.Importer;

public class AlembicLoop : MonoBehaviour
{
    AlembicStreamPlayer player;
    
    void Start()
    {
        player = GetComponent&amp;lt;AlembicStreamPlayer&amp;gt;();
    }
    
    void Update()
    {
        // CurrentTime은 0 ~ Duration 사이 값
        player.CurrentTime += Time.deltaTime;
        
        // Duration 넘으면 0으로 리셋 (루프)
        if (player.CurrentTime &amp;gt;= player.Duration)
        {
            player.CurrentTime = 0;
        }
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2025-12-08 19.22.35.png&quot; data-origin-width=&quot;644&quot; data-origin-height=&quot;1366&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/kyCjl/dJMcabbDNB0/ZAQLnKwnS6nWb2dh4GXyYK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/kyCjl/dJMcabbDNB0/ZAQLnKwnS6nWb2dh4GXyYK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/kyCjl/dJMcabbDNB0/ZAQLnKwnS6nWb2dh4GXyYK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FkyCjl%2FdJMcabbDNB0%2FZAQLnKwnS6nWb2dh4GXyYK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;644&quot; height=&quot;1366&quot; data-filename=&quot;스크린샷 2025-12-08 19.22.35.png&quot; data-origin-width=&quot;644&quot; data-origin-height=&quot;1366&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-end=&quot;604&quot; data-start=&quot;569&quot; data-ke-size=&quot;size23&quot;&gt;아래는 gpt가 가르쳐준 아주 쓸 때 없고 안되는 방법.&amp;nbsp;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-end=&quot;604&quot; data-start=&quot;569&quot; data-ke-size=&quot;size23&quot;&gt;&amp;nbsp;&lt;/h3&gt;
&lt;h3 data-end=&quot;604&quot; data-start=&quot;569&quot; data-ke-size=&quot;size23&quot;&gt;1) 시뮬레이션이 들어 있는 Cloth 오브젝트 선택&lt;/h3&gt;
&lt;p data-end=&quot;651&quot; data-start=&quot;605&quot; data-ke-size=&quot;size16&quot;&gt;&amp;rarr; 오른쪽 Attribute Manager에서 &lt;b&gt;Simulation 탭&lt;/b&gt; 열어.&lt;/p&gt;
&lt;h3 data-end=&quot;669&quot; data-start=&quot;653&quot; data-ke-size=&quot;size23&quot;&gt;2) 아래 항목 찾기:&lt;/h3&gt;
&lt;p data-end=&quot;700&quot; data-start=&quot;670&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Simulation &amp;rarr; Scene &amp;rarr; Cache&lt;/b&gt;&lt;/p&gt;
&lt;p data-end=&quot;718&quot; data-start=&quot;702&quot; data-ke-size=&quot;size16&quot;&gt;여기에서 반드시 다음 순서로:&lt;/p&gt;
&lt;h3 data-end=&quot;746&quot; data-start=&quot;720&quot; data-ke-size=&quot;size23&quot;&gt;3) Cache &amp;rarr; Bake 클릭&lt;/h3&gt;
&lt;p data-end=&quot;765&quot; data-start=&quot;747&quot; data-ke-size=&quot;size16&quot;&gt;&amp;rarr; 시뮬레이션이 메모리에 저장됨.&lt;/p&gt;
&lt;h3 data-end=&quot;819&quot; data-start=&quot;767&quot; data-ke-size=&quot;size23&quot;&gt;4) Cache &amp;rarr; Convert to Mesh 또는 Export Cache&lt;/h3&gt;
&lt;p data-end=&quot;866&quot; data-start=&quot;820&quot; data-ke-size=&quot;size16&quot;&gt;(C4D 2024 버전에서는 &amp;ldquo;Bake as Mesh&amp;rdquo; 같은 이름으로 나오기도 해)&lt;/p&gt;
&lt;p data-end=&quot;927&quot; data-start=&quot;868&quot; data-ke-size=&quot;size16&quot;&gt;이 기능이 시뮬레이션 결과를&lt;br /&gt;&lt;b&gt;프레임 단위 Mesh 캐시(Geometry Cache)&lt;/b&gt; 로 바꿔줘.&lt;/p&gt;
&lt;h3 data-end=&quot;959&quot; data-start=&quot;929&quot; data-ke-size=&quot;size23&quot;&gt;5) 이제 생성된 Mesh Cache를 선택&lt;/h3&gt;
&lt;p data-end=&quot;1040&quot; data-start=&quot;960&quot; data-ke-size=&quot;size16&quot;&gt;&amp;rarr; 이건 더 이상 Dynamic Simulation 오브젝트가 아니기 때문에&lt;br /&gt;&lt;b&gt;Animation &amp;rarr; Bake Objects&amp;hellip;가 활성화됨&lt;/b&gt;&lt;/p&gt;
&lt;h3 data-end=&quot;1066&quot; data-start=&quot;1042&quot; data-ke-size=&quot;size23&quot;&gt;6) Bake Objects 실행&lt;/h3&gt;
&lt;p data-end=&quot;1070&quot; data-start=&quot;1067&quot; data-ke-size=&quot;size16&quot;&gt;옵션:&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-end=&quot;1150&quot; data-start=&quot;1072&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;1109&quot; data-start=&quot;1072&quot;&gt;✔ &lt;b&gt;PLA (Point Level Animation)&lt;/b&gt;&lt;/li&gt;
&lt;li data-end=&quot;1131&quot; data-start=&quot;1110&quot;&gt;✔ &lt;b&gt;Create Copy&lt;/b&gt;&lt;/li&gt;
&lt;li data-end=&quot;1150&quot; data-start=&quot;1132&quot;&gt;✔ &lt;b&gt;PSR&lt;/b&gt; 꺼도 됨&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-end=&quot;1184&quot; data-start=&quot;1152&quot; data-ke-size=&quot;size16&quot;&gt;&amp;rarr; 이렇게 하면 시뮬 데이터가 PLA 애니메이션으로 굳음.&lt;/p&gt;
&lt;h3 data-end=&quot;1211&quot; data-start=&quot;1186&quot; data-ke-size=&quot;size23&quot;&gt;7) 마지막으로 FBX Export&lt;/h3&gt;
&lt;p data-end=&quot;1221&quot; data-start=&quot;1212&quot; data-ke-size=&quot;size16&quot;&gt;FBX 옵션에서:&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-end=&quot;1277&quot; data-start=&quot;1223&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;1250&quot; data-start=&quot;1223&quot;&gt;✔ &lt;b&gt;Bake Animation ON&lt;/b&gt;&lt;/li&gt;
&lt;li data-end=&quot;1277&quot; data-start=&quot;1251&quot;&gt;✔ &lt;b&gt;Deformed Models ON&lt;/b&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-end=&quot;1304&quot; data-start=&quot;1279&quot; data-ke-size=&quot;size16&quot;&gt;&amp;rarr; Unity에서 메시 애니메이션으로 재생됨.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2025-12-08 18.19.15.png&quot; data-origin-width=&quot;870&quot; data-origin-height=&quot;1406&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/boqdXO/dJMcabCHU5u/vcjFm6AecDpCH56ykdgpqk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/boqdXO/dJMcabCHU5u/vcjFm6AecDpCH56ykdgpqk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/boqdXO/dJMcabCHU5u/vcjFm6AecDpCH56ykdgpqk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FboqdXO%2FdJMcabCHU5u%2FvcjFm6AecDpCH56ykdgpqk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;870&quot; height=&quot;1406&quot; data-filename=&quot;스크린샷 2025-12-08 18.19.15.png&quot; data-origin-width=&quot;870&quot; data-origin-height=&quot;1406&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2025-12-08 18.19.25.png&quot; data-origin-width=&quot;842&quot; data-origin-height=&quot;1218&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/S9twU/dJMcaiBMO2Q/IY6CGPeAg25yIKAqWz3av0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/S9twU/dJMcaiBMO2Q/IY6CGPeAg25yIKAqWz3av0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/S9twU/dJMcaiBMO2Q/IY6CGPeAg25yIKAqWz3av0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FS9twU%2FdJMcaiBMO2Q%2FIY6CGPeAg25yIKAqWz3av0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;842&quot; height=&quot;1218&quot; data-filename=&quot;스크린샷 2025-12-08 18.19.25.png&quot; data-origin-width=&quot;842&quot; data-origin-height=&quot;1218&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2025-12-08 18.19.31.png&quot; data-origin-width=&quot;1006&quot; data-origin-height=&quot;240&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/c7FsXc/dJMcabCHU5s/KKxRY7xkH8Y0OGL385n50K/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/c7FsXc/dJMcabCHU5s/KKxRY7xkH8Y0OGL385n50K/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/c7FsXc/dJMcabCHU5s/KKxRY7xkH8Y0OGL385n50K/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fc7FsXc%2FdJMcabCHU5s%2FKKxRY7xkH8Y0OGL385n50K%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1006&quot; height=&quot;240&quot; data-filename=&quot;스크린샷 2025-12-08 18.19.31.png&quot; data-origin-width=&quot;1006&quot; data-origin-height=&quot;240&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2025-12-08 18.20.27.png&quot; data-origin-width=&quot;1222&quot; data-origin-height=&quot;1480&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bV2n5y/dJMcaioh8yX/sONJBfRieg70ZIBU7qmMk0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bV2n5y/dJMcaioh8yX/sONJBfRieg70ZIBU7qmMk0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bV2n5y/dJMcaioh8yX/sONJBfRieg70ZIBU7qmMk0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbV2n5y%2FdJMcaioh8yX%2FsONJBfRieg70ZIBU7qmMk0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1222&quot; height=&quot;1480&quot; data-filename=&quot;스크린샷 2025-12-08 18.20.27.png&quot; data-origin-width=&quot;1222&quot; data-origin-height=&quot;1480&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-end=&quot;1304&quot; data-start=&quot;1279&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-end=&quot;1304&quot; data-start=&quot;1279&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-end=&quot;1304&quot; data-start=&quot;1279&quot; data-ke-size=&quot;size16&quot;&gt;in 유니티&lt;/p&gt;
&lt;p data-end=&quot;1304&quot; data-start=&quot;1279&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2025-12-08 18.25.00.png&quot; data-origin-width=&quot;596&quot; data-origin-height=&quot;388&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/lXW57/dJMcafSElJX/0R5UJy39AfMuwhkaBrWAH0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/lXW57/dJMcafSElJX/0R5UJy39AfMuwhkaBrWAH0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/lXW57/dJMcafSElJX/0R5UJy39AfMuwhkaBrWAH0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FlXW57%2FdJMcafSElJX%2F0R5UJy39AfMuwhkaBrWAH0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;596&quot; height=&quot;388&quot; data-filename=&quot;스크린샷 2025-12-08 18.25.00.png&quot; data-origin-width=&quot;596&quot; data-origin-height=&quot;388&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;점 애니메이션 &lt;span style=&quot;background-color: #ee2323;&quot;&gt;안됨.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #ee2323;&quot;&gt;알렘빅으로 가져오면 바로됨.&lt;/span&gt;&lt;/p&gt;</description>
      <category>Unity</category>
      <author>잉_민</author>
      <guid isPermaLink="true">https://ing-min.tistory.com/318</guid>
      <comments>https://ing-min.tistory.com/318#entry318comment</comments>
      <pubDate>Tue, 9 Dec 2025 03:22:44 +0900</pubDate>
    </item>
    <item>
      <title>VFX 파티클 시스템 : 용천수 영향권scalar map 시각화 + 하이트맵height map vfx적용</title>
      <link>https://ing-min.tistory.com/317</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h1&gt;VFX 파티클 시스템 구현 완료&lt;/h1&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;목표 1: 용천수 영향권 시각화&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;파티클이 용천수 근처를 지날 때 색상 변화&lt;/b&gt;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;준비물&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;spring_distance.exr (Houdini 1025개 용천수 distance field)&lt;/li&gt;
&lt;li&gt;해상도: 원본 비율 유지&lt;/li&gt;
&lt;li&gt;값 범위: 0~20m&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Unity 설정&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;SM_VictimPathVFX.cs:&lt;/b&gt;&lt;/p&gt;
&lt;pre class=&quot;reasonml&quot;&gt;&lt;code&gt;// Spring map 파라미터
Vector3 springCenter = new Vector3(-2278, -4058.18091f, -2890) 
                     + new Vector3(-2644, -1356.30273f, 13395);
vfxGraph.SetVector3(&quot;MapCenter&quot;, springCenter);
vfxGraph.SetVector2(&quot;MapSize&quot;, new Vector2(43200, 58320));
&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;VFX Graph - Blackboard&lt;/h3&gt;
&lt;pre class=&quot;lisp&quot;&gt;&lt;code&gt;SpringDistanceMap (Texture2D)
MapCenter (Vector3)
MapSize (Vector2)
SpringThreshold (float) = 20.0
&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;VFX Graph - Update Particle&lt;/h3&gt;
&lt;pre class=&quot;yaml&quot;&gt;&lt;code&gt;1. UV 계산:
   Get Attribute: position &amp;rarr; currentPos
   Subtract: currentPos - MapCenter &amp;rarr; localPos
   Swizzle.xz: localPos &amp;rarr; localPos2D (Vector2)
   Divide: localPos2D / MapSize &amp;rarr; normalized
   Add: normalized + (0.5, 0.5) &amp;rarr; springUV

2. Distance 샘플링:
   Sample Texture2D(SpringDistanceMap, springUV) &amp;rarr; s
   Swizzle.x: s &amp;rarr; distance

3. 색상 적용:
   Compare: distance &amp;lt; SpringThreshold &amp;rarr; isNear
   Branch:
   ├─ True: (1, 0, 0, 1) &amp;larr; 빨강
   └─ False: (0, 0, 0, 1) &amp;larr; 검정
   &amp;rarr; color
   
   Set Attribute: color
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;결과:&lt;/b&gt; 파티클이 용천수 20m 반경 지나갈 때만 빨간색 표시&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;목표 2: 지형 높이 따라가기&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;파티클이 제주도 지형을 따라 이동&lt;/b&gt;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;준비물&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;jeju_height.tif (국토정보플랫폼 DEM)&lt;/li&gt;
&lt;li&gt;해상도: QGIS 처리&lt;/li&gt;
&lt;li&gt;높이 범위: 0~58m&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Unity 임포트 설정&lt;/h3&gt;
&lt;pre class=&quot;routeros&quot;&gt;&lt;code&gt;Texture Type: Default
sRGB: OFF
Compression: None
Format: R16
Wrap Mode: Clamp
&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Unity 설정&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;SM_VictimPathVFX.cs:&lt;/b&gt;&lt;/p&gt;
&lt;pre class=&quot;reasonml&quot;&gt;&lt;code&gt;// Height map 파라미터
Vector3 heightCenter = new Vector3(-2278, -4058.18091f, -2890) 
                     + new Vector3(-9668, 0, 694);
vfxGraph.SetVector3(&quot;HeightMapCenter&quot;, heightCenter);
vfxGraph.SetVector2(&quot;HeightMapSize&quot;, new Vector2(28511.48f, 20509.97f));
vfxGraph.SetFloat(&quot;HeightMin&quot;, 0f);
vfxGraph.SetFloat(&quot;HeightMax&quot;, 58f);
&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;VFX Graph - Blackboard&lt;/h3&gt;
&lt;pre class=&quot;lisp&quot;&gt;&lt;code&gt;HeightMap (Texture2D)
HeightMapCenter (Vector3)
HeightMapSize (Vector2)
HeightMapRotation (float) = 180
HeightMin (float) = 0
HeightMax (float) = 58
HeightOffset (float) = 10
&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;VFX Graph - Update Particle&lt;/h3&gt;
&lt;pre class=&quot;reasonml&quot;&gt;&lt;code&gt;1. 기본 경로 계산:
   age / lifetime &amp;rarr; t
   Lerp(StartPosition, EndPosition, t) &amp;rarr; pathPos

2. HeightMap UV 계산 (180도 회전):
   pathPos - HeightMapCenter &amp;rarr; localPos
   Compose Vector2(-localPos.x, -localPos.z) &amp;rarr; rotated
   rotated / HeightMapSize &amp;rarr; normalized
   normalized + (0.5, 0.5) &amp;rarr; heightUV

3. 지형 높이 샘플링:
   Sample Texture2D(HeightMap, heightUV) &amp;rarr; heightSample
   Swizzle.x: heightSample &amp;rarr; normalizedHeight
   Lerp(HeightMin, HeightMax, normalizedHeight) &amp;rarr; terrainHeight
   Add: terrainHeight + HeightOffset &amp;rarr; finalHeight

4. 최종 위치:
   Compose Vector3(pathPos.x, finalHeight, pathPos.z) &amp;rarr; finalPos
   Set Position: finalPos
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;결과:&lt;/b&gt; 파티클이 XZ 경로는 유지하면서 Y값만 지형 높이를 따라감&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;핵심 포인트&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;좌표 변환 주의사항&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;Spring map:&lt;/b&gt; -90도 회전 = Swizzle(x, z)&lt;/li&gt;
&lt;li&gt;&lt;b&gt;Height map:&lt;/b&gt; 180도 회전 = (-x, -z)&lt;/li&gt;
&lt;li&gt;&lt;b&gt;Y 제거:&lt;/b&gt; Vector3 &amp;rarr; Vector2 변환 시 Swizzle.xz&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;UV 계산 공식&lt;/h3&gt;
&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;1. Position - Center &amp;rarr; localPos
2. 회전 적용 &amp;rarr; rotated
3. / Size &amp;rarr; normalized (월드 좌표 &amp;rarr; 0~1)
4. + 0.5 &amp;rarr; UV (중심 맞추기)
&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;파티클 생성 방식&lt;/h3&gt;
&lt;pre class=&quot;yaml&quot;&gt;&lt;code&gt;Periodic Burst:
- Count: VictimPathCount (9653)
- Delay: 2초
- Loop: Infinite
- Capacity: VictimPathCount &amp;times; 5
&lt;/code&gt;&lt;/pre&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;완성!  &lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;1 height map (0-1범위)&lt;/b&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2025-12-08 11.18.50.png&quot; data-origin-width=&quot;1688&quot; data-origin-height=&quot;1208&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/YIqh5/dJMcacBCHd1/LRJZAeBQjUQhYbta3QF5K0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/YIqh5/dJMcacBCHd1/LRJZAeBQjUQhYbta3QF5K0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/YIqh5/dJMcacBCHd1/LRJZAeBQjUQhYbta3QF5K0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FYIqh5%2FdJMcacBCHd1%2FLRJZAeBQjUQhYbta3QF5K0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1688&quot; height=&quot;1208&quot; data-filename=&quot;스크린샷 2025-12-08 11.18.50.png&quot; data-origin-width=&quot;1688&quot; data-origin-height=&quot;1208&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;figure class=&quot;imagegridblock&quot;&gt;
  &lt;div class=&quot;image-container&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bqBS5F/dJMcafSEe6q/Xb0D7rwr1kpivL8Tyi3dr0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bqBS5F/dJMcafSEe6q/Xb0D7rwr1kpivL8Tyi3dr0/img.png&quot; data-is-animation=&quot;false&quot; data-origin-width=&quot;1380&quot; data-origin-height=&quot;578&quot; data-filename=&quot;스크린샷 2025-12-08 11.15.47.png&quot; style=&quot;width: 58.0673%; margin-right: 10px;&quot; data-widthpercent=&quot;58.75&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bqBS5F/dJMcafSEe6q/Xb0D7rwr1kpivL8Tyi3dr0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbqBS5F%2FdJMcafSEe6q%2FXb0D7rwr1kpivL8Tyi3dr0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1380&quot; height=&quot;578&quot;/&gt;&lt;/span&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/by7kJj/dJMcahXeOxn/uESTi4lK8GwpNRGiL3dEXK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/by7kJj/dJMcahXeOxn/uESTi4lK8GwpNRGiL3dEXK/img.png&quot; data-is-animation=&quot;false&quot; data-origin-width=&quot;694&quot; data-origin-height=&quot;414&quot; data-filename=&quot;스크린샷 2025-12-08 11.16.27.png&quot; style=&quot;width: 40.7699%;&quot; data-widthpercent=&quot;41.25&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/by7kJj/dJMcahXeOxn/uESTi4lK8GwpNRGiL3dEXK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fby7kJj%2FdJMcahXeOxn%2FuESTi4lK8GwpNRGiL3dEXK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;694&quot; height=&quot;414&quot;/&gt;&lt;/span&gt;&lt;/div&gt;
&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2025-12-08 11.16.46.png&quot; data-origin-width=&quot;678&quot; data-origin-height=&quot;220&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/uhpfO/dJMcaiaLmfa/azsa9ZAIfn0hWV4rGO9ifK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/uhpfO/dJMcaiaLmfa/azsa9ZAIfn0hWV4rGO9ifK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/uhpfO/dJMcaiaLmfa/azsa9ZAIfn0hWV4rGO9ifK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FuhpfO%2FdJMcaiaLmfa%2Fazsa9ZAIfn0hWV4rGO9ifK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;678&quot; height=&quot;220&quot; data-filename=&quot;스크린샷 2025-12-08 11.16.46.png&quot; data-origin-width=&quot;678&quot; data-origin-height=&quot;220&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2.용천수 , 스칼라 맵 set color&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2025-12-08 11.18.39.png&quot; data-origin-width=&quot;688&quot; data-origin-height=&quot;1022&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/c6Nuxg/dJMcaajuN16/SX5fMOkuyOzATYJyONLJpk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/c6Nuxg/dJMcaajuN16/SX5fMOkuyOzATYJyONLJpk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/c6Nuxg/dJMcaajuN16/SX5fMOkuyOzATYJyONLJpk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fc6Nuxg%2FdJMcaajuN16%2FSX5fMOkuyOzATYJyONLJpk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;688&quot; height=&quot;1022&quot; data-filename=&quot;스크린샷 2025-12-08 11.18.39.png&quot; data-origin-width=&quot;688&quot; data-origin-height=&quot;1022&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;figure class=&quot;imagegridblock&quot;&gt;
  &lt;div class=&quot;image-container&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/byowe8/dJMcabW0xtm/PBkpK4qRWFEi8P5LgjiyCK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/byowe8/dJMcabW0xtm/PBkpK4qRWFEi8P5LgjiyCK/img.png&quot; data-is-animation=&quot;false&quot; data-origin-width=&quot;1770&quot; data-origin-height=&quot;566&quot; data-filename=&quot;스크린샷 2025-12-08 11.17.05.png&quot; style=&quot;width: 48.8186%; margin-right: 10px;&quot; data-widthpercent=&quot;49.39&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/byowe8/dJMcabW0xtm/PBkpK4qRWFEi8P5LgjiyCK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fbyowe8%2FdJMcabW0xtm%2FPBkpK4qRWFEi8P5LgjiyCK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1770&quot; height=&quot;566&quot;/&gt;&lt;/span&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/ROBGX/dJMcagcXmcQ/HjrtAtiVN3I7zDR3angC50/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/ROBGX/dJMcagcXmcQ/HjrtAtiVN3I7zDR3angC50/img.png&quot; data-is-animation=&quot;false&quot; data-origin-width=&quot;1570&quot; data-origin-height=&quot;490&quot; data-filename=&quot;스크린샷 2025-12-08 11.17.37.png&quot; style=&quot;width: 50.0186%;&quot; data-widthpercent=&quot;50.61&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/ROBGX/dJMcagcXmcQ/HjrtAtiVN3I7zDR3angC50/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FROBGX%2FdJMcagcXmcQ%2FHjrtAtiVN3I7zDR3angC50%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1570&quot; height=&quot;490&quot;/&gt;&lt;/span&gt;&lt;/div&gt;
&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2025-12-08 11.17.58.png&quot; data-origin-width=&quot;718&quot; data-origin-height=&quot;738&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/zTz5M/dJMcadAtg3u/UzHtKLN6vEsafhcNYMfL9k/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/zTz5M/dJMcadAtg3u/UzHtKLN6vEsafhcNYMfL9k/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/zTz5M/dJMcadAtg3u/UzHtKLN6vEsafhcNYMfL9k/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FzTz5M%2FdJMcadAtg3u%2FUzHtKLN6vEsafhcNYMfL9k%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;718&quot; height=&quot;738&quot; data-filename=&quot;스크린샷 2025-12-08 11.17.58.png&quot; data-origin-width=&quot;718&quot; data-origin-height=&quot;738&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2025-12-08 11.29.51.png&quot; data-origin-width=&quot;658&quot; data-origin-height=&quot;1234&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/blUffb/dJMcabvWq43/fKxSVzHaifXFsmOuOaKEwK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/blUffb/dJMcabvWq43/fKxSVzHaifXFsmOuOaKEwK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/blUffb/dJMcabvWq43/fKxSVzHaifXFsmOuOaKEwK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FblUffb%2FdJMcabvWq43%2FfKxSVzHaifXFsmOuOaKEwK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;658&quot; height=&quot;1234&quot; data-filename=&quot;스크린샷 2025-12-08 11.29.51.png&quot; data-origin-width=&quot;658&quot; data-origin-height=&quot;1234&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;</description>
      <category>Unity/VFX Graph</category>
      <author>잉_민</author>
      <guid isPermaLink="true">https://ing-min.tistory.com/317</guid>
      <comments>https://ing-min.tistory.com/317#entry317comment</comments>
      <pubDate>Mon, 8 Dec 2025 19:19:45 +0900</pubDate>
    </item>
    <item>
      <title>Houdini _ geo json 위도경도, 특정 지점 주변으로 Scalar Field (스칼라 필드)생성</title>
      <link>https://ing-min.tistory.com/316</link>
      <description>&lt;h1&gt;제주 4&amp;middot;3 용천수 &lt;b&gt;Scalar Field&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/b&gt; 생성 문서&lt;/h1&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;Scalar Field (스칼라 필드)&lt;/b&gt;&lt;/li&gt;
&lt;li&gt;각 복셀에 &lt;b&gt;숫자 하나&lt;/b&gt; (0~1)&lt;/li&gt;
&lt;li&gt;용천수까지 거리/영향도&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2025-12-07 19.21.13.png&quot; data-origin-width=&quot;1256&quot; data-origin-height=&quot;1040&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/xzuLz/dJMb99ZbAUR/Mq9neibwwvv3dsNMx1SLkk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/xzuLz/dJMb99ZbAUR/Mq9neibwwvv3dsNMx1SLkk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/xzuLz/dJMb99ZbAUR/Mq9neibwwvv3dsNMx1SLkk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FxzuLz%2FdJMb99ZbAUR%2FMq9neibwwvv3dsNMx1SLkk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1256&quot; height=&quot;1040&quot; data-filename=&quot;스크린샷 2025-12-07 19.21.13.png&quot; data-origin-width=&quot;1256&quot; data-origin-height=&quot;1040&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;목적&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;제주 지형에서 각 지점으로부터 가장 가까운 용천수까지의 거리를 계산하여 텍스처로 출력. Unity VFX Graph에서 파티클이 용천수로 끌려가는 효과에 사용.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;데이터&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;제주 지형&lt;/b&gt;: HeightField (1080 x 1800, 높이 ~50)&lt;/li&gt;
&lt;li&gt;&lt;b&gt;용천수 포인트&lt;/b&gt;: 1025개, GeoJSON에서 로드하여 제주 좌표계로 변환됨&lt;/li&gt;
&lt;/ul&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;Houdini 노드 체인&lt;/h2&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2025-12-07 17.51.17.png&quot; data-origin-width=&quot;634&quot; data-origin-height=&quot;686&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/breunc/dJMcafZo1pk/Rsd7taDrdFjNWYpKoaZYj0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/breunc/dJMcafZo1pk/Rsd7taDrdFjNWYpKoaZYj0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/breunc/dJMcafZo1pk/Rsd7taDrdFjNWYpKoaZYj0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fbreunc%2FdJMcafZo1pk%2FRsd7taDrdFjNWYpKoaZYj0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;634&quot; height=&quot;686&quot; data-filename=&quot;스크린샷 2025-12-07 17.51.17.png&quot; data-origin-width=&quot;634&quot; data-origin-height=&quot;686&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;pre class=&quot;yaml&quot;&gt;&lt;code&gt;1. heightfield_file1 (제주 지형 로드)
2. python (용천수 GeoJSON 로드)
3. transform (좌표 변환)
4. pointwrangle1 (용천수를 지형 높이로 올림)
   └─ 코드: pos.y = volumesample(1, &quot;height&quot;, pos);

5. HeightField Copy Layer
   └─ Source: height
   └─ Destination: spring_dist

6. Volume Wrangle (거리 계산)
   └─ 입력 1: HeightField (height, mask, spring_dist)
   └─ 입력 2: 용천수 포인트

7. HeightField Remap (0-1 정규화)
   └─ Input Min: 578.666
   └─ Input Max: 1796.660
   └─ Output Min: 0
   └─ Output Max: 1

8. HeightField Output (EXR 저장)
&lt;/code&gt;&lt;/pre&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;Volume Wrangle 코드&lt;/h2&gt;
&lt;pre class=&quot;csharp&quot; data-ke-language=&quot;csharp&quot;&gt;&lt;code&gt;if (@primnum == 2) {
    float total = 0.0;
    float radius = 20.0;  // 20m
    
    for(int i = 0; i &amp;lt; 1025; i++) {
        vector sp = point(1, &quot;P&quot;, i);
        
        float dx = @P.x - sp.x;
        float dz = @P.z - sp.z;
        float dist = sqrt(dx*dx + dz*dz);
        
        if (dist &amp;lt; radius) {
            total += 1.0 - (dist / radius);
        }
    }
    
    f@spring_dist = clamp(total, 0.0, 1.0);
}&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;코드 설명&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;@primnum == 2: spring_dist 레이어만 처리 (primitive 2번)&lt;/li&gt;
&lt;li&gt;volumeindextopos(): 복셀 인덱스를 월드 좌표로 변환&lt;/li&gt;
&lt;li&gt;npoints(1): 두 번째 입력(용천수 포인트) 개수&lt;/li&gt;
&lt;li&gt;point(1, &quot;P&quot;, i): 용천수 포인트 위치 가져오기&lt;/li&gt;
&lt;li&gt;XZ 평면 거리만 계산: 높이 차이 무시&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2025-12-07 17.51.33.png&quot; data-origin-width=&quot;1522&quot; data-origin-height=&quot;504&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/doFzbR/dJMcacO8TPF/fAbqQE2qpGKwEb7KSvJehK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/doFzbR/dJMcacO8TPF/fAbqQE2qpGKwEb7KSvJehK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/doFzbR/dJMcacO8TPF/fAbqQE2qpGKwEb7KSvJehK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdoFzbR%2FdJMcacO8TPF%2FfAbqQE2qpGKwEb7KSvJehK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1522&quot; height=&quot;504&quot; data-filename=&quot;스크린샷 2025-12-07 17.51.33.png&quot; data-origin-width=&quot;1522&quot; data-origin-height=&quot;504&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2025-12-07 17.53.12.png&quot; data-origin-width=&quot;1510&quot; data-origin-height=&quot;950&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/biNIHY/dJMcah3ZgtU/p4CjFIqW9UMCiGz9xf9Ihk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/biNIHY/dJMcah3ZgtU/p4CjFIqW9UMCiGz9xf9Ihk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/biNIHY/dJMcah3ZgtU/p4CjFIqW9UMCiGz9xf9Ihk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbiNIHY%2FdJMcah3ZgtU%2Fp4CjFIqW9UMCiGz9xf9Ihk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1510&quot; height=&quot;950&quot; data-filename=&quot;스크린샷 2025-12-07 17.53.12.png&quot; data-origin-width=&quot;1510&quot; data-origin-height=&quot;950&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;주요 문제와 해결&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;문제 1: s@name 작동 안 함&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;증상&lt;/b&gt;: if (s@name == &quot;spring_dist&quot;) 조건이 실행 안 됨&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;원인&lt;/b&gt;: HeightField의 volume primitive는 name 속성이 비어있음&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;해결&lt;/b&gt;: @primnum으로 직접 지정&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;primitive 0 = height&lt;/li&gt;
&lt;li&gt;primitive 1 = mask&lt;/li&gt;
&lt;li&gt;primitive 2 = spring_dist&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;HeightField Output 설정&lt;/h2&gt;
&lt;pre class=&quot;groovy&quot;&gt;&lt;code&gt;Output Type: Deep Raster
Type: 32b Floating Point
Filename: $HIP/spring_distance.exr
Layers: spring_dist
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;내보내기&lt;/b&gt;:&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;HeightField Output 노드 선택&lt;/li&gt;
&lt;li&gt;&quot;Save to Disk&quot; 버튼 클릭&lt;/li&gt;
&lt;li&gt;$HIP/spring_distance.exr 파일 생성됨&lt;/li&gt;
&lt;/ol&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;Unity 사용법&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;1. 텍스처 Import&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;spring_distance.exr를 Unity 프로젝트로 드래그&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;2. VFX Graph에서 사용&lt;/h3&gt;
&lt;pre class=&quot;yaml&quot;&gt;&lt;code&gt;Sample Texture2D
├─ Texture: spring_distance
├─ UV: Particle Position (XZ를 0-1로 정규화)
└─ Output: distance (float)

Force
└─ Direction: 용천수 방향으로 계산
└─ Strength: 1.0 - distance (가까울수록 강함)
&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;3. 값 해석&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;0 (검은색)&lt;/b&gt;: 용천수 바로 근처 &amp;rarr; 최대 끌림&lt;/li&gt;
&lt;li&gt;&lt;b&gt;1 (흰색)&lt;/b&gt;: 용천수에서 가장 멀리 &amp;rarr; 최소 끌림&lt;/li&gt;
&lt;/ul&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;결과물&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;파일&lt;/b&gt;: spring_distance.exr (32-bit floating point, single channel)&lt;/li&gt;
&lt;li&gt;&lt;b&gt;해상도&lt;/b&gt;: 540 x 900 (HeightField 해상도와 동일)&lt;/li&gt;
&lt;li&gt;&lt;b&gt;값 범위&lt;/b&gt;: 0.0 ~ 1.0 (정규화됨)&lt;/li&gt;
&lt;li&gt;&lt;b&gt;의미&lt;/b&gt;: 각 복셀에서 가장 가까운 용천수까지의 2D 거리&lt;/li&gt;
&lt;/ul&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;참고사항&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;2D 수평 거리만 계산 (Y축 높이 무시)&lt;/li&gt;
&lt;li&gt;용천수 포인트 개수: 1025개&lt;/li&gt;
&lt;li&gt;거리 원본 범위: 578 ~ 1796 (미터 단위)&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;원인&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Volume Wrangle에서 npoints(), nprimitives() 같은 geometry 쿼리 함수가 두 번째 입력에 대해 제대로 작동하지 않음.&lt;/b&gt;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;해결&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;직접 loop으로 point 개수 확인하거나, 알고 있는 개수 하드코딩:&lt;/b&gt;&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;c&lt;/div&gt;
&lt;div&gt;
&lt;pre class=&quot;yaml&quot; style=&quot;color: #383a42; text-align: left;&quot;&gt;&lt;code&gt;// ✅ 해결책 1: 알고 있는 개수 사용
for(int i = 0; i &amp;lt; 1025; i++) {
    vector sp = point(1, &quot;P&quot;, i);
    // ...
}

// ✅ 해결책 2: loop으로 카운트
int count = 0;
for(int i = 0; i &amp;lt; 10000; i++) {
    vector testP = point(1, &quot;P&quot;, i);
    if(testP.x == 0 &amp;amp;&amp;amp; testP.y == 0 &amp;amp;&amp;amp; testP.z == 0) break;
    count = i + 1;
}
```

---

## 최종 작동 코드

### 노드 구조
```
heightfield_file1 (540&amp;times;900&amp;times;1 HeightField)
  &amp;darr;
heightfield_copylayer1
  - Source: height
  - Destination: spring_dist
  - Copy Source Data: ✓
  &amp;darr;
volumewrangle1
  - Input 0: heightfield_copylayer1
  - Input 1: pointwrangle1 (용천수 1025개)
  &amp;darr;
heightfield_visualize1 (확인용)
  &amp;darr;
heightfield_output1 (spring_distance.exr)&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Volume Wrangle 코드&lt;/h3&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;c&lt;/div&gt;
&lt;div&gt;
&lt;pre class=&quot;angelscript&quot; style=&quot;color: #383a42; text-align: left;&quot;&gt;&lt;code&gt;if (@primnum == 2) {
    float total = 0.0;
    float radius = 20.0;  // 영향 반경 (m)
    
    // npoints(1) 대신 직접 1025 사용
    for(int i = 0; i &amp;lt; 1025; i++) {
        vector sp = point(1, &quot;P&quot;, i);
        
        // XZ 평면 거리만 계산 (Y 무시)
        float dx = @P.x - sp.x;
        float dz = @P.z - sp.z;
        float dist = sqrt(dx*dx + dz*dz);
        
        if (dist &amp;lt; radius) {
            float influence = 1.0 - (dist / radius);
            total += influence;
        }
    }
    
    f@spring_dist = clamp(total, 0.0, 1.0);
}&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;디버깅 과정에서 발견한 것들&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;1. HeightField에서 @P는 이미 월드 좌표&lt;/h3&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;c&lt;/div&gt;
&lt;div&gt;
&lt;pre class=&quot;mel&quot; style=&quot;color: #383a42; text-align: left;&quot;&gt;&lt;code&gt;// ❌ 불필요
vector worldPos = volumeindextopos(0, @primnum, @P);

// ✅ 직접 사용
float dx = @P.x - sp.x;&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;2. @primnum으로 레이어 구분&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;@primnum == 0: height&lt;/li&gt;
&lt;li&gt;@primnum == 1: mask&lt;/li&gt;
&lt;li&gt;@primnum == 2: spring_dist&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;3. Y축 차이 문제&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;복셀 Y: -1078 (volumeindextopos 사용 시)&lt;/li&gt;
&lt;li&gt;용천수 Y: 0.03&lt;/li&gt;
&lt;li&gt;&lt;b&gt;해결&lt;/b&gt;: XZ 평면 거리만 계산 (2D)&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;4. 해상도 확인 중요&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;정상: 540 &amp;times; 900 &amp;times; 1&lt;/li&gt;
&lt;li&gt;비정상: 540 &amp;times; 1 (망가진 상태)&lt;/li&gt;
&lt;li&gt;i@resx, i@resz로 확인&lt;/li&gt;
&lt;/ul&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;주요 실수들&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;❌ 틀린 접근&lt;/h3&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;c&lt;/div&gt;
&lt;div&gt;
&lt;pre class=&quot;angelscript&quot; style=&quot;color: #383a42; text-align: left;&quot;&gt;&lt;code&gt;// pcfind는 Y값도 고려해서 실패
int nearSprings[] = pcfind(1, &quot;P&quot;, worldPos, 100.0, 10);

// npoints()가 작동 안 함
int npts = npoints(1);

// volumeindextopos()가 이상한 Y 반환
vector worldPos = volumeindextopos(0, @primnum, @P);&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;✅ 올바른 접근&lt;/h3&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;c&lt;/div&gt;
&lt;div&gt;
&lt;pre class=&quot;x86asm&quot; style=&quot;color: #383a42; text-align: left;&quot;&gt;&lt;code&gt;// 직접 loop
for(int i = 0; i &amp;lt; 1025; i++)

// @P 직접 사용
float dx = @P.x - sp.x;

// XZ만 계산
float dist = sqrt(dx*dx + dz*dz);&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;결과&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;Min/Max&lt;/b&gt;: 0.0 ~ 1.0&lt;/li&gt;
&lt;li&gt;&lt;b&gt;빨강&lt;/b&gt;: 용천수 20m 근처 (영향 강함)&lt;/li&gt;
&lt;li&gt;&lt;b&gt;흰색&lt;/b&gt;: 용천수 없는 지역 (한라산 중심부)&lt;/li&gt;
&lt;li&gt;&lt;b&gt;바다&lt;/b&gt;: mask로 제외됨&lt;/li&gt;
&lt;/ul&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;교훈&lt;/h2&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;&lt;b&gt;Volume Wrangle의 geometry 함수는 제한적&lt;/b&gt; - 특히 두 번째 입력&lt;/li&gt;
&lt;li&gt;&lt;b&gt;디버깅 = 작은 단계로&lt;/b&gt; - setdetailattrib으로 중간값 확인&lt;/li&gt;
&lt;li&gt;&lt;b&gt;HeightField @P는 월드 좌표&lt;/b&gt; - 변환 불필요&lt;/li&gt;
&lt;li&gt;&lt;b&gt;2D 거리로 충분&lt;/b&gt; - Y축 무시&lt;/li&gt;
&lt;li&gt;&lt;b&gt;함수 안 되면 직접 구현&lt;/b&gt; - npoints() 대신 loop&lt;/li&gt;
&lt;/ol&gt;</description>
      <category>후디니</category>
      <author>잉_민</author>
      <guid isPermaLink="true">https://ing-min.tistory.com/316</guid>
      <comments>https://ing-min.tistory.com/316#entry316comment</comments>
      <pubDate>Mon, 8 Dec 2025 03:22:56 +0900</pubDate>
    </item>
    <item>
      <title>Unity VFX _ 2022.3 : 시작점에서 끝점 이동 + 인터렉션(플레이어)</title>
      <link>https://ing-min.tistory.com/313</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2025-12-06 22.01.57.png&quot; data-origin-width=&quot;1652&quot; data-origin-height=&quot;1022&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bIhPcC/dJMcaa4Q7yW/BGe5AquwKcwjwVP8LcDtp0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bIhPcC/dJMcaa4Q7yW/BGe5AquwKcwjwVP8LcDtp0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bIhPcC/dJMcaa4Q7yW/BGe5AquwKcwjwVP8LcDtp0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbIhPcC%2FdJMcaa4Q7yW%2FBGe5AquwKcwjwVP8LcDtp0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1652&quot; height=&quot;1022&quot; data-filename=&quot;스크린샷 2025-12-06 22.01.57.png&quot; data-origin-width=&quot;1652&quot; data-origin-height=&quot;1022&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;준비 1. visual Effects ( 유니티 프리퍼런스, 셋팅에 있음)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;커스텀 노트 만들 수 있어야함. ( 선택, 꼭 필요하지 않을수도..)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;준비 2. json 파일. 시작점 끝점 정보를 가진 데이터파일&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2025-12-06 22.05.51.png&quot; data-origin-width=&quot;1634&quot; data-origin-height=&quot;1100&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bdupUa/dJMcaiu0Xw0/fEGtGkNhTQJxhkOHVhjF7K/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bdupUa/dJMcaiu0Xw0/fEGtGkNhTQJxhkOHVhjF7K/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bdupUa/dJMcaiu0Xw0/fEGtGkNhTQJxhkOHVhjF7K/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbdupUa%2FdJMcaiu0Xw0%2FfEGtGkNhTQJxhkOHVhjF7K%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1634&quot; height=&quot;1100&quot; data-filename=&quot;스크린샷 2025-12-06 22.05.51.png&quot; data-origin-width=&quot;1634&quot; data-origin-height=&quot;1100&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이부분이 중요함. vfx 그래프에서 이부분을 가져다 씀. simplepathdate*&lt;/p&gt;
&lt;pre id=&quot;code_1765055026797&quot; class=&quot;csharp&quot; data-ke-language=&quot;csharp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;{
  &quot;paths&quot;: [
    {
      &quot;startPos&quot;: { &quot;x&quot;: -205.804, &quot;y&quot;: 6.231, &quot;z&quot;: 120.434 },
      &quot;endPos&quot;: { &quot;x&quot;: -268.549, &quot;y&quot;: 5.314, &quot;z&quot;: 148.032 },
      &quot;uncertainty&quot;: 0.0,
      &quot;victimAge&quot;: 28
    },
    {
      &quot;startPos&quot;: { &quot;x&quot;: -305.804, &quot;y&quot;: 6.231, &quot;z&quot;: 120.434 },
      &quot;endPos&quot;: { &quot;x&quot;: -28.017, &quot;y&quot;: 6.467, &quot;z&quot;: 129.938 },
      &quot;uncertainty&quot;: 0.0,
      &quot;victimAge&quot;: 0
    }
  ]
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;준비 3. 스크립트 :&amp;nbsp; json을 불러와서 파싱하고 &amp;gt;&amp;gt;&amp;gt; vfx 로 값 보낼수있음.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;**여기서 중요한건 데이터 구조를 만드는건데&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1765055003553&quot; class=&quot;csharp&quot; data-ke-language=&quot;csharp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;using UnityEngine;
using UnityEngine.VFX;
using System.Collections.Generic;

// ===== VFX Graph용 구조체 =====
[System.Serializable]
[VFXType(VFXTypeAttribute.Usage.GraphicsBuffer)]  // &amp;larr; 이것도
public struct SimplePathData
{
    public Vector3 startPos;
    public Vector3 endPos;
    public float uncertainty;
    public uint victimAge;
    //public float sizeMultiplier;
}

// ===== JSON 파싱용 클래스들 =====
[System.Serializable]
public class Vector3Data
{
    public float x, y, z;
    public Vector3 ToVector3() =&amp;gt; new Vector3(x, y, z);
}

[System.Serializable]
public class PathEntry
{
    public Vector3Data startPos;
    public Vector3Data endPos;
    public float uncertainty;
    public uint victimAge;
}

[System.Serializable]
public class PathDataWrapper
{
    public List&amp;lt;PathEntry&amp;gt; paths;
}

// ===== 메인 스크립트 =====
public class SimplePathTest : MonoBehaviour
{
    [Header(&quot;VFX&quot;)]
    public VisualEffect vfxGraph;
    
    [Header(&quot;Test Data&quot;)]
    public TextAsset jsonFile;
    
    [Header(&quot;Player&quot;)]
    public Transform player;
    public float detectionRadius = 50f;
    public float enlargeSize = 5f;
    
    private GraphicsBuffer pathBuffer;
    private int pathCount = 0;
    private SimplePathData[] pathDataArray;

    void Start()
    {
        LoadTestData();
    }

    void LoadTestData()
    {
        // JSON 파싱
        PathDataWrapper wrapper = JsonUtility.FromJson&amp;lt;PathDataWrapper&amp;gt;(jsonFile.text);
        pathCount = wrapper.paths.Count;
        
        Debug.Log($&quot;[TEST] {pathCount}개 경로 로드됨&quot;);
        
        // GraphicsBuffer 생성
        int stride = System.Runtime.InteropServices.Marshal.SizeOf(typeof(SimplePathData));
        pathBuffer = new GraphicsBuffer(
            GraphicsBuffer.Target.Structured,
            pathCount,
            stride
        );
        
        // 데이터 변환 및 저장
        pathDataArray = new SimplePathData[pathCount];
        for (int i = 0; i &amp;lt; pathCount; i++)
        {
            pathDataArray[i] = new SimplePathData
            {
                startPos = wrapper.paths[i].startPos.ToVector3(),
                endPos = wrapper.paths[i].endPos.ToVector3(),
                uncertainty = wrapper.paths[i].uncertainty,
                victimAge = wrapper.paths[i].victimAge,
                //sizeMultiplier = 1.0f
            };
            
            Debug.Log($&quot;[TEST] Path {i}: {pathDataArray[i].startPos} &amp;rarr; {pathDataArray[i].endPos}&quot;);
        }
        
        // VFX에 전송
        pathBuffer.SetData(pathDataArray);
        vfxGraph.SetGraphicsBuffer(&quot;SimplePathBuffer&quot;, pathBuffer);
        vfxGraph.SetInt(&quot;SimplePathCount&quot;, pathCount);
        
        vfxGraph.Reinit();
        vfxGraph.Play();
        
        StartCoroutine(CheckParticles());
    }
    
    void Update()
    {
        // Player 위치만 VFX에 전송
        if (player != null)
        {
            vfxGraph.SetVector3(&quot;PlayerPosition&quot;, player.position);
            vfxGraph.SetFloat(&quot;DetectionRadius&quot;, detectionRadius);
            vfxGraph.SetFloat(&quot;EnlargeSize&quot;, enlargeSize);
        }
        
        if (Input.GetKeyDown(KeyCode.Space))
        {
            Debug.Log($&quot;Alive: {vfxGraph.aliveParticleCount}&quot;);
        }
    }
    
    
    System.Collections.IEnumerator CheckParticles()
    {
        yield return new WaitForSeconds(1f);
        Debug.Log($&quot;[TEST] Alive Particles: {vfxGraph.aliveParticleCount}&quot;);
    }

    void OnDestroy()
    {
        if (pathBuffer != null)
        {
            pathBuffer.Release();
            pathBuffer = null;
        }
    }
    
    void OnDrawGizmos()
    {
        if (pathDataArray != null &amp;amp;&amp;amp; pathDataArray.Length &amp;gt; 0)
        {
            for (int i = 0; i &amp;lt; pathDataArray.Length; i++)
            {
                // 시작점 (빨강)
                Gizmos.color = Color.red;
                Gizmos.DrawSphere(pathDataArray[i].startPos, 5f);
                
                // 끝점 (파랑)
                Gizmos.color = Color.blue;
                Gizmos.DrawSphere(pathDataArray[i].endPos, 5f);
                
                // 연결선
                Gizmos.color = Color.yellow;
                Gizmos.DrawLine(pathDataArray[i].startPos, pathDataArray[i].endPos);
                
                // 감지 범위
                if (player != null)
                {
                    float distance = Vector3.Distance(player.position, pathDataArray[i].startPos);
                    if (distance &amp;lt; detectionRadius)
                    {
                        Gizmos.color = new Color(1, 1, 0, 0.3f);
                        Gizmos.DrawSphere(pathDataArray[i].startPos, 10f);
                    }
                }
            }
        }
        
        // 플레이어 감지 범위
        if (player != null)
        {
            Gizmos.color = new Color(0, 1, 0, 0.2f);
            Gizmos.DrawWireSphere(player.position, detectionRadius);
        }
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;준비 4 : VFX&amp;nbsp; 그래프에서 ***grapicsbuffer 이거를 만들어줘야함.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이름에 주의하자 ! 값을 받아올때 이름이 같아야 받아와짐.&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2025-12-06 22.01.48.png&quot; data-origin-width=&quot;482&quot; data-origin-height=&quot;422&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cOAqnf/dJMcaaX5VPS/z96lyG1BrhblQEk4GphFc0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cOAqnf/dJMcaaX5VPS/z96lyG1BrhblQEk4GphFc0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cOAqnf/dJMcaaX5VPS/z96lyG1BrhblQEk4GphFc0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcOAqnf%2FdJMcaaX5VPS%2Fz96lyG1BrhblQEk4GphFc0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;482&quot; height=&quot;422&quot; data-filename=&quot;스크린샷 2025-12-06 22.01.48.png&quot; data-origin-width=&quot;482&quot; data-origin-height=&quot;422&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2025-12-06 22.07.09.png&quot; data-origin-width=&quot;772&quot; data-origin-height=&quot;1412&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/byuh3Y/dJMcaaw1CrA/xtT5vBiNX9421dGi5H5jZ0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/byuh3Y/dJMcaaw1CrA/xtT5vBiNX9421dGi5H5jZ0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/byuh3Y/dJMcaaw1CrA/xtT5vBiNX9421dGi5H5jZ0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fbyuh3Y%2FdJMcaaw1CrA%2FxtT5vBiNX9421dGi5H5jZ0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;772&quot; height=&quot;1412&quot; data-filename=&quot;스크린샷 2025-12-06 22.07.09.png&quot; data-origin-width=&quot;772&quot; data-origin-height=&quot;1412&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;준비 5. VFX Property Binder&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이름 설정할수 있음 SSSPosition 이라고 티나게 바까봄.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아까 위에있는 변수랑 이름일치가 중요.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;준비 6.&amp;nbsp; VFX 그래프 만들자&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1. 여러번 spawn 되게하기&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;peridic Burst&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2025-12-06 22.08.54.png&quot; data-origin-width=&quot;1068&quot; data-origin-height=&quot;548&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/zk40m/dJMcaiaKNVo/K58H9max1uoVyYjc3qmiC1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/zk40m/dJMcaiaKNVo/K58H9max1uoVyYjc3qmiC1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/zk40m/dJMcaiaKNVo/K58H9max1uoVyYjc3qmiC1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fzk40m%2FdJMcaiaKNVo%2FK58H9max1uoVyYjc3qmiC1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1068&quot; height=&quot;548&quot; data-filename=&quot;스크린샷 2025-12-06 22.08.54.png&quot; data-origin-width=&quot;1068&quot; data-origin-height=&quot;548&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2. Count는 선(path) 두개.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;delay는 0.2초간격으로.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;3. 가끔 안보이거나 중심점이상하면 Bounds 박스 확인. capacity는 파티클 총개수.&amp;nbsp; 센터랑 size 크게 잘맞춰야 보임.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2025-12-06 22.10.25.png&quot; data-origin-width=&quot;1132&quot; data-origin-height=&quot;462&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cRqL0Z/dJMcacaxaad/EISqvJqIvvs9lkTlM22QA1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cRqL0Z/dJMcacaxaad/EISqvJqIvvs9lkTlM22QA1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cRqL0Z/dJMcacaxaad/EISqvJqIvvs9lkTlM22QA1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcRqL0Z%2FdJMcacaxaad%2FEISqvJqIvvs9lkTlM22QA1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1132&quot; height=&quot;462&quot; data-filename=&quot;스크린샷 2025-12-06 22.10.25.png&quot; data-origin-width=&quot;1132&quot; data-origin-height=&quot;462&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;4. 파티클 아이디 get&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이게 중요한데 발생하는 파티클에 번호가 붙어있다. 이거를 패스 개수로 나누면. *버퍼인덱스를 얻을 수 있다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;파티클을 계속 발생하니까, 2개의 선이 있으니까.. 몰라 그렇데&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;5. *bufferindex는 set custom 노드로 만들어야한다. 이름과 변수 성격 uint 를 잘 설정해줘야한다.( 인스펙터창에 있음)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;버퍼인덱스를 먼저 설정해주고&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;6.get bufferindex 할수있다. 왜냐면 set을 위에서 해줬으니까.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이것도 get custom node다. 변수명을 갖게 잘 설정하면, 데이터를 저장하고 다시 받아올수 있는 구조다. 편함.&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2025-12-06 22.13.33.png&quot; data-origin-width=&quot;832&quot; data-origin-height=&quot;492&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/crHJZw/dJMcachiLIm/8Fk3ZNVvwkWdKYbAVdVrd0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/crHJZw/dJMcachiLIm/8Fk3ZNVvwkWdKYbAVdVrd0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/crHJZw/dJMcachiLIm/8Fk3ZNVvwkWdKYbAVdVrd0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcrHJZw%2FdJMcachiLIm%2F8Fk3ZNVvwkWdKYbAVdVrd0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;832&quot; height=&quot;492&quot; data-filename=&quot;스크린샷 2025-12-06 22.13.33.png&quot; data-origin-width=&quot;832&quot; data-origin-height=&quot;492&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;7. SimplePathBuffer도 우리가 만든 변수다. 밖에서ㄱ 연결할수있게 블랙보드에 만들었던것. ( 코드랑 연결되어있어서 값이 자동으로 들어옴)&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2025-12-06 22.15.02.png&quot; data-origin-width=&quot;536&quot; data-origin-height=&quot;512&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/baqj7t/dJMcadtHIBr/kZuyKxTjbvkHhKlf25J5Rk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/baqj7t/dJMcadtHIBr/kZuyKxTjbvkHhKlf25J5Rk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/baqj7t/dJMcadtHIBr/kZuyKxTjbvkHhKlf25J5Rk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fbaqj7t%2FdJMcadtHIBr%2FkZuyKxTjbvkHhKlf25J5Rk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;536&quot; height=&quot;512&quot; data-filename=&quot;스크린샷 2025-12-06 22.15.02.png&quot; data-origin-width=&quot;536&quot; data-origin-height=&quot;512&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;8 그래픽 버퍼의 톱니바퀴누르면 우리가 아까 코드에 작성했던 데이터 구조 이름.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imagegridblock&quot;&gt;
  &lt;div class=&quot;image-container&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/GrLBv/dJMcabo9REk/kyxv0YvWHpZF0Spmg9LiCK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/GrLBv/dJMcabo9REk/kyxv0YvWHpZF0Spmg9LiCK/img.png&quot; data-is-animation=&quot;false&quot; data-origin-width=&quot;414&quot; data-origin-height=&quot;338&quot; data-filename=&quot;스크린샷 2025-12-06 22.16.06.png&quot; style=&quot;width: 48.3306%; margin-right: 10px;&quot; data-widthpercent=&quot;48.9&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/GrLBv/dJMcabo9REk/kyxv0YvWHpZF0Spmg9LiCK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FGrLBv%2FdJMcabo9REk%2Fkyxv0YvWHpZF0Spmg9LiCK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;414&quot; height=&quot;338&quot;/&gt;&lt;/span&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/esP3Gt/dJMcaiBL8dh/clyB6KRMm9fYQ0coYmdi2K/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/esP3Gt/dJMcaiBL8dh/clyB6KRMm9fYQ0coYmdi2K/img.png&quot; data-is-animation=&quot;false&quot; data-origin-width=&quot;384&quot; data-origin-height=&quot;300&quot; data-filename=&quot;스크린샷 2025-12-06 22.16.19.png&quot; style=&quot;width: 50.5066%;&quot; data-widthpercent=&quot;51.1&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/esP3Gt/dJMcaiBL8dh/clyB6KRMm9fYQ0coYmdi2K/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FesP3Gt%2FdJMcaiBL8dh%2FclyB6KRMm9fYQ0coYmdi2K%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;384&quot; height=&quot;300&quot;/&gt;&lt;/span&gt;&lt;/div&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;데이터 구조에 설정해놓은 시작점 끝점 가져올수있다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;9. 이거를 커스텀 노드에 저장한다. set customnode&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이름 설정 주의 .&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2025-12-06 22.17.04.png&quot; data-origin-width=&quot;1030&quot; data-origin-height=&quot;556&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cgjWD3/dJMcadf98Cj/2ZMixESgVumUIYHO9LZuLK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cgjWD3/dJMcadf98Cj/2ZMixESgVumUIYHO9LZuLK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cgjWD3/dJMcadf98Cj/2ZMixESgVumUIYHO9LZuLK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcgjWD3%2FdJMcadf98Cj%2F2ZMixESgVumUIYHO9LZuLK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1030&quot; height=&quot;556&quot; data-filename=&quot;스크린샷 2025-12-06 22.17.04.png&quot; data-origin-width=&quot;1030&quot; data-origin-height=&quot;556&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그러면 아까 말했듯 가져다 쓸수있다.&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2025-12-06 22.18.29.png&quot; data-origin-width=&quot;1338&quot; data-origin-height=&quot;1036&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cEXTSR/dJMcai2Qral/pogTe3yqIvdZtKjgnJUVeK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cEXTSR/dJMcai2Qral/pogTe3yqIvdZtKjgnJUVeK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cEXTSR/dJMcai2Qral/pogTe3yqIvdZtKjgnJUVeK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcEXTSR%2FdJMcai2Qral%2FpogTe3yqIvdZtKjgnJUVeK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1338&quot; height=&quot;1036&quot; data-filename=&quot;스크린샷 2025-12-06 22.18.29.png&quot; data-origin-width=&quot;1338&quot; data-origin-height=&quot;1036&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;set position : 초기 시작점&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;set target position : 이동할점.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;초기값 설정은 끝&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;10 . 업데이트에서 점을 이동시키자&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2025-12-06 22.19.46.png&quot; data-origin-width=&quot;1142&quot; data-origin-height=&quot;600&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/yb12T/dJMcaa4Q7JE/AfQeFe8LMYS0LbU4aVz29K/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/yb12T/dJMcaa4Q7JE/AfQeFe8LMYS0LbU4aVz29K/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/yb12T/dJMcaa4Q7JE/AfQeFe8LMYS0LbU4aVz29K/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fyb12T%2FdJMcaa4Q7JE%2FAfQeFe8LMYS0LbU4aVz29K%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1142&quot; height=&quot;600&quot; data-filename=&quot;스크린샷 2025-12-06 22.19.46.png&quot; data-origin-width=&quot;1142&quot; data-origin-height=&quot;600&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;러프를 쓰면됨. (a에서 b로 s동안 이동)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;라이프 타임동안 첫점과 끝점을 이동한다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;11. 인터렉션을 위한 장치.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;SSSPosition에서 플레이어 위치를 불러와서&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;각 선의 첫 점과 거리를 비교하여.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;특정 거리 내에 있으면 ( detectionRadius) 파티클의 크기를 키운다.&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2025-12-06 22.20.41.png&quot; data-origin-width=&quot;2048&quot; data-origin-height=&quot;334&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/lLfXS/dJMcacVUeYe/jMnOQLrkE967HJJtNhU8GK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/lLfXS/dJMcacVUeYe/jMnOQLrkE967HJJtNhU8GK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/lLfXS/dJMcacVUeYe/jMnOQLrkE967HJJtNhU8GK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FlLfXS%2FdJMcacVUeYe%2FjMnOQLrkE967HJJtNhU8GK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;2048&quot; height=&quot;334&quot; data-filename=&quot;스크린샷 2025-12-06 22.20.41.png&quot; data-origin-width=&quot;2048&quot; data-origin-height=&quot;334&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;(VFX binder에서 플레이어의 포지션과 연결해주고 변수명을 갖게 설정했기때문에 작동한다) : 위의 단계에서 했던것&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imagegridblock&quot;&gt;
  &lt;div class=&quot;image-container&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bdbNQH/dJMcafkNvyT/lQKukZJubUoB2pz6CorOD1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bdbNQH/dJMcafkNvyT/lQKukZJubUoB2pz6CorOD1/img.png&quot; data-is-animation=&quot;false&quot; data-origin-width=&quot;556&quot; data-origin-height=&quot;600&quot; data-filename=&quot;스크린샷 2025-12-06 22.22.00.png&quot; style=&quot;width: 35.1389%; margin-right: 10px;&quot; data-widthpercent=&quot;35.55&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bdbNQH/dJMcafkNvyT/lQKukZJubUoB2pz6CorOD1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbdbNQH%2FdJMcafkNvyT%2FlQKukZJubUoB2pz6CorOD1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;556&quot; height=&quot;600&quot;/&gt;&lt;/span&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cJ1yTg/dJMcadmVpbs/oYYSvpqVVz3Jq2xploKIBk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cJ1yTg/dJMcadmVpbs/oYYSvpqVVz3Jq2xploKIBk/img.png&quot; data-is-animation=&quot;false&quot; data-origin-width=&quot;766&quot; data-origin-height=&quot;456&quot; data-filename=&quot;스크린샷 2025-12-06 22.22.11.png&quot; style=&quot;width: 63.6983%;&quot; data-widthpercent=&quot;64.45&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cJ1yTg/dJMcadmVpbs/oYYSvpqVVz3Jq2xploKIBk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcJ1yTg%2FdJMcadmVpbs%2FoYYSvpqVVz3Jq2xploKIBk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;766&quot; height=&quot;456&quot;/&gt;&lt;/span&gt;&lt;/div&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;12. 마지막으로 아웃풋 어떻게 보일지 설정해주면 끝&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2025-12-06 22.22.51.png&quot; data-origin-width=&quot;1050&quot; data-origin-height=&quot;764&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dDAJqW/dJMcahQrT1I/vkwkT9Eg0MEzUognaFaSh1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dDAJqW/dJMcahQrT1I/vkwkT9Eg0MEzUognaFaSh1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dDAJqW/dJMcahQrT1I/vkwkT9Eg0MEzUognaFaSh1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdDAJqW%2FdJMcahQrT1I%2FvkwkT9Eg0MEzUognaFaSh1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1050&quot; height=&quot;764&quot; data-filename=&quot;스크린샷 2025-12-06 22.22.51.png&quot; data-origin-width=&quot;1050&quot; data-origin-height=&quot;764&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;----&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다음 단계에서는 포물선만들기. 혹은 후디니에서 효과추가하기&lt;/p&gt;</description>
      <author>잉_민</author>
      <guid isPermaLink="true">https://ing-min.tistory.com/313</guid>
      <comments>https://ing-min.tistory.com/313#entry313comment</comments>
      <pubDate>Sun, 7 Dec 2025 06:23:41 +0900</pubDate>
    </item>
    <item>
      <title>houdini to unity 좌표값 보내기</title>
      <link>https://ing-min.tistory.com/310</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;&lt;span style=&quot;color: #4078f2;&quot;&gt;**&lt;/span&gt;&lt;span&gt;curve_u &lt;/span&gt;&lt;span style=&quot;color: #4078f2;&quot;&gt;=&lt;/span&gt;&lt;span&gt; 선을 따라 진행된 비율 &lt;/span&gt;&lt;span style=&quot;color: #383a42;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #b76b01;&quot;&gt;0&lt;/span&gt;&lt;span style=&quot;color: #4078f2;&quot;&gt;~&lt;/span&gt;&lt;span style=&quot;color: #b76b01;&quot;&gt;1&lt;/span&gt;&lt;span style=&quot;color: #383a42;&quot;&gt;)&lt;/span&gt;&lt;span style=&quot;color: #4078f2;&quot;&gt;**&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;``` &lt;/span&gt;&lt;span&gt;&lt;span&gt;출생지 ─────────────────────&lt;/span&gt;&lt;span style=&quot;color: #4078f2;&quot;&gt;&amp;gt;&lt;/span&gt;&lt;span&gt; 사망지 &lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt; &lt;/span&gt;&lt;span style=&quot;color: #b76b01;&quot;&gt;0.0&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span style=&quot;color: #b76b01;&quot;&gt;0.25&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span style=&quot;color: #b76b01;&quot;&gt;0.5&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span style=&quot;color: #b76b01;&quot;&gt;0.75&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span style=&quot;color: #b76b01;&quot;&gt;1.0&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;CSV 파일 구조 확인&lt;/h2&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;
&lt;pre class=&quot;ini&quot; style=&quot;color: #383a42; text-align: left;&quot;&gt;&lt;code&gt;row[0] = ID (고유 ID)
row[1] = Name (이름)
row[2] = birth_lat (출생지 위도)
row[3] = birth_lon (출생지 경도)
row[4] = death_lat (사망지 위도)
row[5] = death_lon (사망지 경도)
row[6] = uncertainty_birth (출생지 불확실성)
row[7] = uncertainty_death (사망지 불확실성)
row[8] = Date_Int (사망날짜)&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;&lt;span&gt;Primitive 속성 &lt;/span&gt;&lt;span style=&quot;color: #383a42;&quot;&gt;(&lt;/span&gt;&lt;span&gt;선에 저장&lt;/span&gt;&lt;span style=&quot;color: #383a42;&quot;&gt;)&lt;/span&gt;&lt;span style=&quot;color: #383a42;&quot;&gt;:&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span style=&quot;color: #4078f2;&quot;&gt;-&lt;/span&gt;&lt;span&gt; path_id &lt;/span&gt;&lt;span style=&quot;color: #383a42;&quot;&gt;(&lt;/span&gt;&lt;span&gt;string&lt;/span&gt;&lt;span style=&quot;color: #383a42;&quot;&gt;)&lt;/span&gt;&lt;span style=&quot;color: #383a42;&quot;&gt;:&lt;/span&gt;&lt;span&gt; 고유 ID ⭐ &lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span style=&quot;color: #4078f2;&quot;&gt;-&lt;/span&gt;&lt;span&gt; person_name &lt;/span&gt;&lt;span style=&quot;color: #383a42;&quot;&gt;(&lt;/span&gt;&lt;span&gt;string&lt;/span&gt;&lt;span style=&quot;color: #383a42;&quot;&gt;)&lt;/span&gt;&lt;span style=&quot;color: #383a42;&quot;&gt;:&lt;/span&gt;&lt;span&gt; 이름 ⭐ &lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span style=&quot;color: #4078f2;&quot;&gt;-&lt;/span&gt;&lt;span&gt; death_date &lt;/span&gt;&lt;span style=&quot;color: #383a42;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #50a14f;&quot;&gt;int&lt;/span&gt;&lt;span style=&quot;color: #383a42;&quot;&gt;)&lt;/span&gt;&lt;span style=&quot;color: #383a42;&quot;&gt;:&lt;/span&gt;&lt;span&gt; 사망날짜 ⭐&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;&lt;span style=&quot;color: #4078f2;&quot;&gt;-&lt;/span&gt;&lt;span&gt; `time`&lt;/span&gt;&lt;span style=&quot;color: #383a42;&quot;&gt;:&lt;/span&gt;&lt;span&gt; 선을 따라 진행된 비율 &lt;/span&gt;&lt;span style=&quot;color: #383a42;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #b76b01;&quot;&gt;0.0&lt;/span&gt;&lt;span&gt; &amp;rarr; &lt;/span&gt;&lt;span style=&quot;color: #b76b01;&quot;&gt;1.0&lt;/span&gt;&lt;span style=&quot;color: #383a42;&quot;&gt;)&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt; &lt;/span&gt;&lt;span style=&quot;color: #4078f2;&quot;&gt;-&lt;/span&gt;&lt;span&gt; 애니메이션용 &lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt; &lt;/span&gt;&lt;span style=&quot;color: #4078f2;&quot;&gt;-&lt;/span&gt;&lt;span&gt; curve_u와 같은 값&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;&lt;span style=&quot;color: #4078f2;&quot;&gt;-&lt;/span&gt;&lt;span&gt; `death_date`&lt;/span&gt;&lt;span style=&quot;color: #383a42;&quot;&gt;:&lt;/span&gt;&lt;span&gt; 실제 사망날짜 &lt;/span&gt;&lt;span style=&quot;color: #383a42;&quot;&gt;(&lt;/span&gt;&lt;span&gt;예&lt;/span&gt;&lt;span style=&quot;color: #383a42;&quot;&gt;:&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span style=&quot;color: #b76b01;&quot;&gt;19500315&lt;/span&gt;&lt;span style=&quot;color: #383a42;&quot;&gt;)&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt; &lt;/span&gt;&lt;span style=&quot;color: #4078f2;&quot;&gt;-&lt;/span&gt;&lt;span&gt; 그룹핑&lt;/span&gt;&lt;span style=&quot;color: #4078f2;&quot;&gt;/&lt;/span&gt;&lt;span&gt;필터링용 &lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt; &lt;/span&gt;&lt;span style=&quot;color: #4078f2;&quot;&gt;-&lt;/span&gt;&lt;span&gt; Unity에서 중요! &lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;값매피&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp;path_id = row[0]&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;# 고유 ID&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;person_name&amp;nbsp;=&amp;nbsp;row[1]&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;#&amp;nbsp;이름&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;birth_lat&amp;nbsp;=&amp;nbsp;float(row[2])&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;#&amp;nbsp;출생지&amp;nbsp;위도&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;birth_lon&amp;nbsp;=&amp;nbsp;float(row[3])&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;#&amp;nbsp;출생지&amp;nbsp;경도&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;death_lat&amp;nbsp;=&amp;nbsp;float(row[4])&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;#&amp;nbsp;사망지&amp;nbsp;위도&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;death_lon&amp;nbsp;=&amp;nbsp;float(row[5])&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;#&amp;nbsp;사망지&amp;nbsp;경도&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;uncert_birth&amp;nbsp;=&amp;nbsp;float(row[6])&amp;nbsp;&amp;nbsp;#&amp;nbsp;출생지&amp;nbsp;불확실성&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;uncert_death&amp;nbsp;=&amp;nbsp;float(row[7])&amp;nbsp;&amp;nbsp;#&amp;nbsp;사망지&amp;nbsp;불확실성&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;death_date&amp;nbsp;=&amp;nbsp;int(row[8])&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;#&amp;nbsp;사망날짜&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;프리미티브에저장&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;poly.setAttribValue(&quot;path_id&quot;,&amp;nbsp;path_id)&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;#&amp;nbsp;고유&amp;nbsp;ID&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;poly.setAttribValue(&quot;person_name&quot;,&amp;nbsp;person_name)&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;#&amp;nbsp;이름&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;poly.setAttribValue(&quot;death_date&quot;,&amp;nbsp;death_date)&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;#&amp;nbsp;사망날짜&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;#&amp;nbsp;좌표&amp;nbsp;데이터&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;poly.setAttribValue(&quot;birth_lat&quot;,&amp;nbsp;birth_lat)&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;poly.setAttribValue(&quot;birth_lon&quot;,&amp;nbsp;birth_lon)&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;poly.setAttribValue(&quot;death_lat&quot;,&amp;nbsp;death_lat)&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;poly.setAttribValue(&quot;death_lon&quot;,&amp;nbsp;death_lon)&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;#&amp;nbsp;그룹핑용&amp;nbsp;ID&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;poly.setAttribValue(&quot;death_grid_id&quot;,&amp;nbsp;death_grid)&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;1. 속도값과 pscale의 의미&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;pscale (Point Scale)&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;의미&lt;/b&gt;: 포인트의 크기를 제어하는 속성&lt;/li&gt;
&lt;li&gt;&lt;b&gt;현재 코드&lt;/b&gt;: uncertainty * PSCALE_MULTIPLIER로 설정&lt;/li&gt;
&lt;li&gt;&lt;b&gt;목적&lt;/b&gt;: 위치 데이터의 불확실성을 시각적으로 표현 (불확실성이 클수록 포인트가 큼)&lt;/li&gt;
&lt;li&gt;출생지/사망지 각각의 불확실성을 구분하여 표시&lt;/li&gt;
&lt;/ul&gt;
&lt;pre id=&quot;code_1764582901173&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import hou
import csv
import os
import math

# =========================================================================
# 설정
# =========================================================================
# CSV 파일 경로
FILE_PATH = &quot;/Users/l.smin/Documents/4_3_jeju/pj_code/1129/processed_paths.csv&quot;

# 제주도 중심 좌표 (원점)
CENTER_LON = 126.53
CENTER_LAT = 33.50

# 위경도 &amp;rarr; 거리 변환
LAT_KM_PER_DEGREE = 111.0  # 위도 1도 &amp;asymp; 111km
LON_KM_PER_DEGREE = 111.0 * math.cos(math.radians(CENTER_LAT))  # &amp;asymp; 92.4km

# Houdini 공간 스케일 (제주도 동서 73km &amp;rarr; 10 units)
SCALE_FACTOR = 10.0 / 73.0  # 1km = 0.137 units

# pscale 배율 (시각화용)
PSCALE_MULTIPLIER = 0.01

# =========================================================================
# Houdini 초기화
# =========================================================================
node = hou.pwd()
geo = node.geometry()
geo.clear()

# -------------------------------------------------------------------------
# 속성 정의
# -------------------------------------------------------------------------
# Point 속성 (점에 저장)
geo.addAttrib(hou.attribType.Point, &quot;pscale&quot;, 0.0)           # 점 크기 (시각화)
geo.addAttrib(hou.attribType.Point, &quot;curve_u&quot;, 0.0)          # 선 진행 비율 (0&amp;rarr;1)
geo.addAttrib(hou.attribType.Point, &quot;uncertainty&quot;, 0.0)      # 불확실성 원본값

# Primitive 속성 (선에 저장 - 각 경로의 메타데이터)
geo.addAttrib(hou.attribType.Prim, &quot;path_id&quot;, &quot;&quot;)            # 고유 ID ⭐
geo.addAttrib(hou.attribType.Prim, &quot;person_name&quot;, &quot;&quot;)        # 이름 ⭐
geo.addAttrib(hou.attribType.Prim, &quot;death_date&quot;, 0)          # 사망날짜 ⭐

# 좌표 데이터 (그룹핑/필터링용)
geo.addAttrib(hou.attribType.Prim, &quot;birth_lat&quot;, 0.0)
geo.addAttrib(hou.attribType.Prim, &quot;birth_lon&quot;, 0.0)
geo.addAttrib(hou.attribType.Prim, &quot;death_lat&quot;, 0.0)
geo.addAttrib(hou.attribType.Prim, &quot;death_lon&quot;, 0.0)

# 사망지 그리드 ID (같은 지역 그룹핑용)
geo.addAttrib(hou.attribType.Prim, &quot;death_grid_id&quot;, &quot;&quot;)

# -------------------------------------------------------------------------
# 좌표 변환 함수
# -------------------------------------------------------------------------
def latlon_to_xy(lat, lon):
    &quot;&quot;&quot;
    위경도를 Houdini XZ 좌표로 변환
    - 제주도 중심을 (0, 0)으로
    - 실제 거리 비율 유지
    &quot;&quot;&quot;
    delta_lon = lon - CENTER_LON
    delta_lat = lat - CENTER_LAT
    
    # km 단위 거리
    x_km = delta_lon * LON_KM_PER_DEGREE
    z_km = delta_lat * LAT_KM_PER_DEGREE
    
    # Houdini 좌표 (Z축 반전으로 북쪽=위)
    x = x_km * SCALE_FACTOR
    z = -z_km * SCALE_FACTOR
    
    return (x, z)

def create_death_grid_id(lat, lon, grid_size=0.01):
    &quot;&quot;&quot;
    사망지를 그리드로 분할해서 ID 생성
    grid_size=0.01 &amp;rarr; 약 1km 단위 그리드
    grid_size=0.1 &amp;rarr; 약 10km 단위 그리드
    &quot;&quot;&quot;
    precision = int(1.0 / grid_size)
    grid_lat = int(lat * precision)
    grid_lon = int(lon * precision)
    return f&quot;{grid_lat}_{grid_lon}&quot;

# -------------------------------------------------------------------------
# CSV 파일 읽기 및 경로 생성
# -------------------------------------------------------------------------
if not os.path.exists(FILE_PATH):
    print(f&quot;❌ 파일을 찾을 수 없습니다: {FILE_PATH}&quot;)
    print(&quot;   경로를 확인해주세요!&quot;)
else:
    with open(FILE_PATH, 'r', encoding='utf-8-sig') as f:
        reader = csv.reader(f)
        header = next(reader, None)  # 헤더 스킵

        count = 0
        skipped = 0
        
        for row in reader:
            # 데이터 유효성 검사
            if not row or len(row) &amp;lt; 9:
                skipped += 1
                continue

            try:
                # CSV 데이터 파싱
                path_id = row[0]              # 고유 ID
                person_name = row[1]          # 이름
                birth_lat = float(row[2])     # 출생지 위도
                birth_lon = float(row[3])     # 출생지 경도
                death_lat = float(row[4])     # 사망지 위도
                death_lon = float(row[5])     # 사망지 경도
                uncert_birth = float(row[6])  # 출생지 불확실성
                uncert_death = float(row[7])  # 사망지 불확실성
                death_date = int(row[8])      # 사망날짜
                
                # 유효성 검사: 사망지 좌표가 없는 경우 스킵
                if death_lat == 0.0 and death_lon == 0.0:
                    skipped += 1
                    continue
                
                # 좌표 변환
                birth_x, birth_z = latlon_to_xy(birth_lat, birth_lon)
                death_x, death_z = latlon_to_xy(death_lat, death_lon)
                
                # 사망지 그리드 ID 생성
                death_grid = create_death_grid_id(death_lat, death_lon, grid_size=0.01)

                # =============================================================
                # 지오메트리 생성: 선 (Polygon)
                # =============================================================
                poly = geo.createPolygon()
                poly.setIsClosed(False)  # 열린 선

                # 시작점: 출생지
                pt_birth = geo.createPoint()
                pt_birth.setPosition((birth_x, 0.0, birth_z))
                pt_birth.setAttribValue(&quot;curve_u&quot;, 0.0)
                pt_birth.setAttribValue(&quot;pscale&quot;, uncert_birth * PSCALE_MULTIPLIER)
                pt_birth.setAttribValue(&quot;uncertainty&quot;, uncert_birth)
                poly.addVertex(pt_birth)

                # 끝점: 사망지
                pt_death = geo.createPoint()
                pt_death.setPosition((death_x, 0.0, death_z))
                pt_death.setAttribValue(&quot;curve_u&quot;, 1.0)
                pt_death.setAttribValue(&quot;pscale&quot;, uncert_death * PSCALE_MULTIPLIER)
                pt_death.setAttribValue(&quot;uncertainty&quot;, uncert_death)
                poly.addVertex(pt_death)

                # =============================================================
                # Primitive 속성 설정 (선의 메타데이터)
                # =============================================================
                poly.setAttribValue(&quot;path_id&quot;, path_id)              # 고유 ID
                poly.setAttribValue(&quot;person_name&quot;, person_name)      # 이름
                poly.setAttribValue(&quot;death_date&quot;, death_date)        # 사망날짜
                
                # 좌표 데이터
                poly.setAttribValue(&quot;birth_lat&quot;, birth_lat)
                poly.setAttribValue(&quot;birth_lon&quot;, birth_lon)
                poly.setAttribValue(&quot;death_lat&quot;, death_lat)
                poly.setAttribValue(&quot;death_lon&quot;, death_lon)
                
                # 그룹핑용 ID
                poly.setAttribValue(&quot;death_grid_id&quot;, death_grid)
                
                count += 1
                
            except (ValueError, IndexError) as e:
                print(f&quot;⚠️ 데이터 오류 - ID: {row[0] if row else '?'}, 이름: {row[1] if len(row) &amp;gt; 1 else '?'}&quot;)
                print(f&quot;   사유: {e}&quot;)
                skipped += 1
                continue

        # 완료 메시지
        print(&quot;=&quot; * 60)
        print(f&quot;✅ 경로 생성 완료!&quot;)
        print(f&quot;   생성: {count}개&quot;)
        print(f&quot;   스킵: {skipped}개&quot;)
        print(f&quot;   총계: {count + skipped}개&quot;)
        print(&quot;=&quot; * 60)
        print(f&quot;  스케일: 1km = {SCALE_FACTOR:.3f} Houdini units&quot;)
        print(f&quot;  제주도 크기: 동서 {73 * SCALE_FACTOR:.1f} units, 남북 {31 * SCALE_FACTOR:.1f} units&quot;)
        print(&quot;=&quot; * 60)&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;============================================================&lt;br /&gt;Houdini&amp;nbsp;노드&amp;nbsp;체인&amp;nbsp;-&amp;nbsp;완전한&amp;nbsp;워크플로우&lt;br /&gt;============================================================&lt;br /&gt;&lt;br /&gt;1.&amp;nbsp;Python&amp;nbsp;노드&amp;nbsp;(위의&amp;nbsp;코드)&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;darr;&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;[출력]&amp;nbsp;400개&amp;nbsp;선&amp;nbsp;(Polygons)&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;Primitive&amp;nbsp;속성:&amp;nbsp;path_id,&amp;nbsp;person_name,&amp;nbsp;death_date,&amp;nbsp;death_grid_id,&amp;nbsp;좌표들&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;Point&amp;nbsp;속성:&amp;nbsp;P,&amp;nbsp;pscale,&amp;nbsp;curve_u,&amp;nbsp;uncertainty&lt;br /&gt;&lt;br /&gt;============================================================&lt;br /&gt;&lt;br /&gt;2.&amp;nbsp;Resample&amp;nbsp;SOP&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;darr;&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;설정:&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;-&amp;nbsp;Resample:&amp;nbsp;Length&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;-&amp;nbsp;Length:&amp;nbsp;0.2&amp;nbsp;(또는&amp;nbsp;0.1&amp;nbsp;-&amp;nbsp;점을&amp;nbsp;촘촘하게)&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;-&amp;nbsp;Treat&amp;nbsp;Polygons&amp;nbsp;As:&amp;nbsp;Subdivision&amp;nbsp;Curves&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;[출력]&amp;nbsp;선마다&amp;nbsp;여러&amp;nbsp;점이&amp;nbsp;생김&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;-&amp;nbsp;Point&amp;nbsp;속성&amp;nbsp;자동&amp;nbsp;보간&amp;nbsp;(pscale,&amp;nbsp;curve_u,&amp;nbsp;uncertainty)&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;-&amp;nbsp;Primitive&amp;nbsp;속성&amp;nbsp;유지&amp;nbsp;(path_id,&amp;nbsp;person_name,&amp;nbsp;death_date)&lt;br /&gt;&lt;br /&gt;============================================================&lt;br /&gt;&lt;br /&gt;3.&amp;nbsp;PolyFrame&amp;nbsp;SOP&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;darr;&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;설정:&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;-&amp;nbsp;Entity:&amp;nbsp;Points&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;-&amp;nbsp;Style:&amp;nbsp;First&amp;nbsp;Edge&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;-&amp;nbsp;Tangent&amp;nbsp;Name:&amp;nbsp;v&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;[출력]&amp;nbsp;각&amp;nbsp;점에&amp;nbsp;속도(v)&amp;nbsp;추가됨&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;Point&amp;nbsp;속성&amp;nbsp;추가:&amp;nbsp;v&amp;nbsp;(vector)&lt;br /&gt;&lt;br /&gt;============================================================&lt;br /&gt;&lt;br /&gt;4.&amp;nbsp;Attribute&amp;nbsp;Wrangle&amp;nbsp;SOP&amp;nbsp;(Point&amp;nbsp;모드)&amp;nbsp;⭐&amp;nbsp;중요!&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;darr;&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;VEX&amp;nbsp;코드:&lt;/p&gt;
&lt;pre id=&quot;code_1764587350932&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// Primitive 속성 &amp;rarr; Point 속성으로 복사
s@path_id = prim(0, &quot;path_id&quot;, @primnum);        // 사람 고유 ID
s@person_name = prim(0, &quot;person_name&quot;, @primnum); // 이름
i@death_date = prim(0, &quot;death_date&quot;, @primnum);   // 사망날짜 (정수)

// 좌표 데이터 (원본 위경도 저장용)
f@birth_lat = prim(0, &quot;birth_lat&quot;, @primnum);
f@birth_lon = prim(0, &quot;birth_lon&quot;, @primnum);
f@death_lat = prim(0, &quot;death_lat&quot;, @primnum);
f@death_lon = prim(0, &quot;death_lon&quot;, @primnum);

// 그리드 ID (사망 위치 클러스터링용)
s@death_grid_id = prim(0, &quot;death_grid_id&quot;, @primnum);

// 애니메이션용 time 속성
f@time = f@curve_u;  // 0~1 범위

// 날짜 기반 색상 (1947~1958 범위를 0~1로 정규화)
float normalized_date = fit(i@death_date, 19470101, 19561201, 0, 1);
v@Cd = chramp(&quot;date_color&quot;, normalized_date);  // Ramp 파라미터 필요&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;[출력]&amp;nbsp;모든&amp;nbsp;속성이&amp;nbsp;Point에&amp;nbsp;있음!&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;Point&amp;nbsp;속성:&amp;nbsp;P,&amp;nbsp;v,&amp;nbsp;path_id,&amp;nbsp;person_name,&amp;nbsp;death_date,&amp;nbsp;death_grid_id,&amp;nbsp;&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;time,&amp;nbsp;curve_u,&amp;nbsp;uncertainty,&amp;nbsp;pscale,&amp;nbsp;좌표들&lt;br /&gt;&lt;br /&gt;============================================================&lt;br /&gt;&lt;br /&gt;5.&amp;nbsp;Delete&amp;nbsp;SOP&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;darr;&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;설정:&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;-&amp;nbsp;Entity:&amp;nbsp;Primitives&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;delete non select&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;-&amp;nbsp;Keep&amp;nbsp;Points:&amp;nbsp;✅&amp;nbsp;체크!&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;[출력]&amp;nbsp;Point만&amp;nbsp;남음&amp;nbsp;(선은&amp;nbsp;삭제됨)&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;-&amp;nbsp;모든&amp;nbsp;속성은&amp;nbsp;Point에&amp;nbsp;보존됨&lt;br /&gt;&lt;br /&gt;============================================================&lt;br /&gt;&lt;br /&gt;6.&amp;nbsp;Labs&amp;nbsp;Niagara&amp;nbsp;ROP&amp;nbsp;(또는&amp;nbsp;ROP&amp;nbsp;Alembic&amp;nbsp;Output)&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;darr;&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;Labs&amp;nbsp;Niagara&amp;nbsp;ROP&amp;nbsp;설정:&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;Output&amp;nbsp;Path:&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;$HOME/Desktop/migration_paths.hcsv&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;Keep&amp;nbsp;Attributes:&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;P&amp;nbsp;v&amp;nbsp;path_id&amp;nbsp;person_name&amp;nbsp;death_date&amp;nbsp;death_grid_id&amp;nbsp;time&amp;nbsp;pscale&amp;nbsp;uncertainty&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;Cast&amp;nbsp;Attributes:&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;0&amp;nbsp;(필요없음)&lt;br /&gt;&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;─────────────────────────────&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&lt;br /&gt;&lt;span style=&quot;color: #ee2323;&quot;&gt;여기서 실패&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Labs Niagara ROP 대신:&lt;/b&gt;&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;&lt;b&gt;File SOP&lt;/b&gt; 추가 (Delete 뒤에)&lt;/li&gt;
&lt;li&gt;&lt;b&gt;File&lt;/b&gt; &amp;rarr; $HIP/test.bgeo&lt;/li&gt;
&lt;li&gt;&lt;b&gt;File Mode&lt;/b&gt; &amp;rarr; Write&lt;/li&gt;
&lt;li&gt;&lt;b&gt;Save to Disk&lt;/b&gt; 클릭&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;.bgeo 파일이 생성되고 데이터가 있나요? ㅇㅇ&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;있으면&lt;/b&gt; &amp;rarr; Labs Niagara ROP 버그 &lt;b&gt;없으면&lt;/b&gt; &amp;rarr; 노드 체인 문제&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Labs Niagara ROP 버그&amp;nbsp;&lt;/b&gt;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;1. &lt;b&gt;bgeo를 JSON으로 변환&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;delete 다음 - File 연결&amp;nbsp;&lt;/b&gt; 뒤에 &lt;b&gt;Python SOP&lt;/b&gt; 추가:&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1764589901834&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import json

node = hou.pwd()
geo = node.geometry()

# Point 데이터 추출
data = []
for pt in geo.points():
    data.append({
        &quot;P&quot;: list(pt.position()),
        &quot;v&quot;: list(pt.attribValue(&quot;v&quot;)),
        &quot;path_id&quot;: pt.attribValue(&quot;path_id&quot;),
        &quot;person_name&quot;: pt.attribValue(&quot;person_name&quot;),
        &quot;death_date&quot;: pt.attribValue(&quot;death_date&quot;),
        &quot;death_grid_id&quot;: pt.attribValue(&quot;death_grid_id&quot;),
        &quot;time&quot;: pt.attribValue(&quot;time&quot;),
        &quot;pscale&quot;: pt.attribValue(&quot;pscale&quot;),
        &quot;uncertainty&quot;: pt.attribValue(&quot;uncertainty&quot;)
    })

# JSON 저장
output_path = hou.expandString(&quot;$HIP/migration_data.json&quot;)
with open(output_path, 'w', encoding='utf-8') as f:
    json.dump(data, f, indent=2)

print(f&quot;✅ JSON 저장 완료: {output_path}&quot;)
print(f&quot;  포인트 개수: {len(data)}&quot;)&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;============================================================&lt;br /&gt;&lt;br /&gt;최종&amp;nbsp;출력&amp;nbsp;데이터&amp;nbsp;구조:&lt;br /&gt;============================================================&lt;br /&gt;&lt;br /&gt;각&amp;nbsp;Point마다:&lt;br /&gt;-&amp;nbsp;P&amp;nbsp;(vector):&amp;nbsp;위치&lt;br /&gt;-&amp;nbsp;v&amp;nbsp;(vector):&amp;nbsp;속도/방향&lt;br /&gt;-&amp;nbsp;path_id&amp;nbsp;(string):&amp;nbsp;고유&amp;nbsp;ID&lt;br /&gt;-&amp;nbsp;person_name&amp;nbsp;(string):&amp;nbsp;이름&lt;br /&gt;-&amp;nbsp;death_date&amp;nbsp;(int):&amp;nbsp;사망날짜&amp;nbsp;(YYYYMMDD)&lt;br /&gt;-&amp;nbsp;death_grid_id&amp;nbsp;(string):&amp;nbsp;사망지&amp;nbsp;그리드&lt;br /&gt;-&amp;nbsp;time&amp;nbsp;(float):&amp;nbsp;선&amp;nbsp;진행&amp;nbsp;비율&amp;nbsp;(0~1)&lt;br /&gt;-&amp;nbsp;pscale&amp;nbsp;(float):&amp;nbsp;점&amp;nbsp;크기&lt;br /&gt;-&amp;nbsp;uncertainty&amp;nbsp;(float):&amp;nbsp;불확실성&lt;br /&gt;&lt;br /&gt;============================================================&lt;br /&gt;Unity에서&amp;nbsp;사용:&lt;br /&gt;============================================================&lt;br /&gt;&lt;br /&gt;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;VFX Graph에서 사용하는 방법:&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;1️⃣ &lt;b&gt;JSON &amp;rarr; Texture로 변환 필요&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;VFX Graph는 JSON을 직접 못 읽습니다. &lt;b&gt;Texture2D&lt;/b&gt; 또는 &lt;b&gt;GraphicsBuffer&lt;/b&gt;로 변환해야 합니다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;2️⃣ &lt;b&gt;Unity C# 스크립트로 데이터 로드&lt;/b&gt;&lt;/h3&gt;
&lt;pre class=&quot;cs&quot;&gt;&lt;code&gt;using UnityEngine;
using System.Collections.Generic;

[System.Serializable]
public class ParticleData
{
    public float[] P;      // Position [x,y,z]
    public float[] v;      // Velocity [x,y,z]
    public string path_id;
    public string person_name;
    public int death_date;
    public string death_grid_id;
    public float time;
    public float pscale;
    public float uncertainty;
}

public class MigrationDataLoader : MonoBehaviour
{
    public TextAsset jsonFile;
    public List&amp;lt;ParticleData&amp;gt; particles;
    
    void Start()
    {
        // JSON 파싱
        particles = JsonUtility.FromJson&amp;lt;List&amp;lt;ParticleData&amp;gt;&amp;gt;(
            &quot;{\&quot;items\&quot;:&quot; + jsonFile.text + &quot;}&quot;
        ).items;
        
        Debug.Log($&quot;로드 완료: {particles.Count}개 파티클&quot;);
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;3️⃣ &lt;b&gt;VFX Graph 사용 방법&amp;nbsp;&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&amp;nbsp;Graphics Buffer 사용&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Compute Shader로 데이터 전달&lt;/li&gt;
&lt;li&gt;더 복잡하지만 성능 좋음&lt;/li&gt;
&lt;/ul&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&amp;nbsp;&lt;/li&gt;
&lt;/ul&gt;</description>
      <category>후디니</category>
      <author>잉_민</author>
      <guid isPermaLink="true">https://ing-min.tistory.com/310</guid>
      <comments>https://ing-min.tistory.com/310#entry310comment</comments>
      <pubDate>Mon, 1 Dec 2025 20:41:21 +0900</pubDate>
    </item>
    <item>
      <title>Houdini to Unity : 벡터 필드(Vector Field)/SDF(Signed Distance Field)</title>
      <link>https://ing-min.tistory.com/309</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;div id=&quot;model-response-message-contentr_04327fb69f2d099e&quot;&gt;
&lt;p data-path-to-node=&quot;0&quot; data-ke-size=&quot;size16&quot;&gt;케이지로(Keijiro Takahashi) 방식 &lt;b&gt;&quot;벡터 필드(Vector Field)&quot;&lt;/b&gt; 또는 **&quot;SDF(Signed Distance Field) 기반의 흐름 제어&quot;**.&lt;/p&gt;
&lt;p data-path-to-node=&quot;1&quot; data-ke-size=&quot;size16&quot;&gt;**&quot;파티클의 위치(Position)를 굽는 것(Alembic)&quot;**이 아니라,&lt;/p&gt;
&lt;p data-path-to-node=&quot;1&quot; data-ke-size=&quot;size16&quot;&gt;&quot;공간에 흐르는 바람의 방향(Velocity/Force)을 굽는 것&quot;**&lt;/p&gt;
&lt;p data-path-to-node=&quot;2&quot; data-ke-size=&quot;size16&quot;&gt;이 방식이 유니티 VFX Graph에서 훨씬 더 &lt;b&gt;가볍고&lt;/b&gt;, &lt;b&gt;수백만 개의 파티클&lt;/b&gt;을 돌려도 렉이 안 걸리며, &lt;b&gt;무한 루프&lt;/b&gt;를 만들기에 가장 좋다.&lt;/p&gt;
&lt;hr data-path-to-node=&quot;3&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;  케이지로 스타일: [Vector Field] vs [SDF] 차이점&lt;/h3&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-path-to-node=&quot;5&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;&lt;b&gt;Vector Field (벡터 필드):&lt;/b&gt; 공간에 **&quot;화살표(방향)&quot;**를 채워 넣는 겁니다. 파티클이 이 공간에 들어오면 화살표 방향대로 떠내려갑니다. (강물, 바람)&lt;/li&gt;
&lt;li&gt;&lt;b&gt;SDF (거리 필드):&lt;/b&gt; 공간에 **&quot;가장 가까운 표면까지의 거리&quot;**를 저장합니다. 파티클을 선이나 벽에 &lt;b&gt;달라붙게(Attraction)&lt;/b&gt; 하거나 &lt;b&gt;밀어낼 때(Collision)&lt;/b&gt; 씁니다.&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-path-to-node=&quot;6&quot; data-ke-size=&quot;size16&quot;&gt;  &lt;b&gt;&amp;nbsp;&quot;선을 따라 흐르는 빛의 꼬리&quot;는 보통 이 두 가지를 섞어서 씁니다.&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-path-to-node=&quot;7&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;SDF:&lt;/b&gt; 파티클이 선 밖으로 도망가지 못하게 잡아당김 (Conform to SDF).&lt;/li&gt;
&lt;li&gt;&lt;b&gt;Vector Field:&lt;/b&gt; 파티클을 선 방향으로 계속 이동시킴 (Add Force).&lt;/li&gt;
&lt;/ul&gt;
&lt;hr data-path-to-node=&quot;8&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;  후디니에서 [벡터 필드] 만들어서 유니티로 보내는 법&lt;/h3&gt;
&lt;p data-path-to-node=&quot;10&quot; data-ke-size=&quot;size16&quot;&gt;Alembic 대신 **&lt;b&gt;Volume Texture (3D Texture)&lt;/b&gt;**를 구워서 유니티로 보내야 합니다.&lt;/p&gt;
&lt;p data-path-to-node=&quot;10&quot; data-ke-size=&quot;size16&quot;&gt;이 과정은 &lt;b&gt;SideFX Labs&lt;/b&gt; 툴을 쓰면 아주 쉽습니다.&lt;/p&gt;
&lt;p data-path-to-node=&quot;10&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;1단계: 선(Curve)에 흐름(Velocity) 만들기&lt;/h4&gt;
&lt;p data-path-to-node=&quot;12&quot; data-ke-size=&quot;size16&quot;&gt;기존에 만든 선(GUIDE_PATH)을 그대로 씁니다.&lt;/p&gt;
&lt;p data-path-to-node=&quot;12&quot; data-ke-size=&quot;size16&quot;&gt;선만들기&lt;/p&gt;
&lt;pre id=&quot;code_1764509770988&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import hou
import csv
import os
import math

# =========================================================================
# [설정 1] CSV 파일 경로
file_path = &quot;/Users/l.smin/Documents/4_3_jeju/pj_code/1129/processed_paths.csv&quot;

# [설정 2] 제주도 기준값 (원점 정렬용)
CENTER_LON = 126.53
CENTER_LAT = 33.50

# [설정 3] 실제 거리 변환 계산
# 제주도 위도(33.5도)에서의 1도당 거리 (km)
LAT_KM_PER_DEGREE = 111.0  # 위도 1도 &amp;asymp; 111km (지구 어디서나 거의 일정)
LON_KM_PER_DEGREE = 111.0 * math.cos(math.radians(CENTER_LAT))  # &amp;asymp; 92.4km

# [설정 4] 후디니 공간 스케일 (km -&amp;gt; Houdini units)
# 제주도 동서 약 73km, 남북 약 31km
# 후디니에서 10 units 정도 크기로 맞추려면:
SCALE_FACTOR = 10.0 / 73.0  # 1km = 0.137 units 정도

# [설정 5] pscale 조정값 (불확실성 시각화)
PSCALE_MULTIPLIER = 0.01  # 필요시 조정
# =========================================================================

node = hou.pwd()
geo = node.geometry()
geo.clear() 

# -------------------------------------------------------------------------
# 1. 속성 생성
# -------------------------------------------------------------------------
geo.addAttrib(hou.attribType.Point, &quot;pscale&quot;, 0.0)       
geo.addAttrib(hou.attribType.Point, &quot;curve_u&quot;, 0.0)      
geo.addAttrib(hou.attribType.Prim, &quot;ID&quot;, &quot;&quot;)             
geo.addAttrib(hou.attribType.Prim, &quot;Name&quot;, &quot;&quot;)           
geo.addAttrib(hou.attribType.Prim, &quot;Date_Int&quot;, 0)        
geo.addAttrib(hou.attribType.Prim, &quot;place_of_death&quot;, &quot;&quot;)
geo.addAttrib(hou.attribType.Point, &quot;uncertainty&quot;, 0.0)  # 원본 불확실성 저장

# -------------------------------------------------------------------------
# 2. 좌표 변환 함수
# -------------------------------------------------------------------------
def latlon_to_xy(lat, lon):
    &quot;&quot;&quot;
    위경도를 후디니 XZ 좌표로 변환
    - 실제 거리 비율을 유지
    - 제주도 중심을 (0, 0)으로
    &quot;&quot;&quot;
    # 1. 위경도 차이 계산
    delta_lon = lon - CENTER_LON
    delta_lat = lat - CENTER_LAT
    
    # 2. 실제 거리(km)로 변환
    x_km = delta_lon * LON_KM_PER_DEGREE
    z_km = delta_lat * LAT_KM_PER_DEGREE
    
    # 3. 후디니 좌표로 스케일 (Z축 반전으로 북쪽=위 맞춤)
    x = x_km * SCALE_FACTOR
    z = -z_km * SCALE_FACTOR  # 음수로 지도 방향 맞춤
    
    return (x, z)

# -------------------------------------------------------------------------
# 3. 파일 읽기 및 생성
# -------------------------------------------------------------------------
if not os.path.exists(file_path):
    print(&quot;❌ 파일 경로 에러: 파일을 찾을 수 없습니다 -&amp;gt; &quot; + file_path)
else:
    with open(file_path, 'r', encoding='utf-8-sig') as f:
        reader = csv.reader(f)
        header = next(reader, None) 

        count = 0
        skipped = 0
        
        for row in reader:
            if not row or len(row) &amp;lt; 9: 
                continue

            try:
                # 데이터 파싱
                s_lat = float(row[2])
                s_lon = float(row[3]) 
                e_lat = float(row[4])
                e_lon = float(row[5])
                
                # 유효성 검사
                if e_lat == 0.0 and e_lon == 0.0: 
                    skipped += 1
                    continue
                
                uncert_origin = float(row[6])
                uncert_death = float(row[7])
                date_val = int(row[8])

                # -------------------------------------------------
                # [좌표 변환] 실제 거리 비율 유지
                # -------------------------------------------------
                x1, z1 = latlon_to_xy(s_lat, s_lon)
                x2, z2 = latlon_to_xy(e_lat, e_lon)

                # -------------------------------------------------
                # [지오메트리 생성]
                # -------------------------------------------------
                poly = geo.createPolygon()
                poly.setIsClosed(False)

                # 시작점 (출생지)
                pt1 = geo.createPoint()
                pt1.setPosition((x1, 0.0, z1))  # Y=0으로 지면에 배치
                pt1.setAttribValue(&quot;curve_u&quot;, 0.0)
                pt1.setAttribValue(&quot;pscale&quot;, uncert_origin * PSCALE_MULTIPLIER)
                pt1.setAttribValue(&quot;uncertainty&quot;, uncert_origin)
                poly.addVertex(pt1)

                # 도착점 (사망지)
                pt2 = geo.createPoint()
                pt2.setPosition((x2, 0.0, z2))
                pt2.setAttribValue(&quot;curve_u&quot;, 1.0)
                pt2.setAttribValue(&quot;pscale&quot;, uncert_death * PSCALE_MULTIPLIER)
                pt2.setAttribValue(&quot;uncertainty&quot;, uncert_death)
                poly.addVertex(pt2)

                # 메타데이터 입력
                poly.setAttribValue(&quot;ID&quot;, row[0])
                poly.setAttribValue(&quot;Name&quot;, row[1])
                poly.setAttribValue(&quot;Date_Int&quot;, date_val)
                poly.setAttribValue(&quot;place_of_death&quot;, row[9] if len(row) &amp;gt; 9 else &quot;&quot;)
                
                count += 1
                
            except ValueError as e:
                print(f&quot;⚠️ 데이터 변환 오류: {row[:2]} / 사유: {e}&quot;)
                skipped += 1
                continue

        print(f&quot;✅ 성공: {count}개 경로 생성 완료&quot;)
        print(f&quot;  통계: 스킵됨 {skipped}개&quot;)
        print(f&quot;  스케일: 1km = {SCALE_FACTOR:.3f} units&quot;)
        print(f&quot;  비율: Lon={LON_KM_PER_DEGREE:.1f}km/deg, Lat={LAT_KM_PER_DEGREE:.1f}km/deg&quot;)&lt;/code&gt;&lt;/pre&gt;
&lt;h2 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size26&quot;&gt;  1단계: 선 만들기 (Python Node)&lt;/h2&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot; data-path-to-node=&quot;13&quot;&gt;
&lt;li&gt;&lt;b&gt;파이썬(위의 코드)으로 두점 연결하기 + Resample&lt;/b&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Resample 세팅:&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Resample: Length&lt;/li&gt;
&lt;li&gt;Length: 0.5 (선을 매끄럽게)&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size26&quot;&gt;  2단계: 제주도 지형에 붙이기 (Ray)&lt;/h2&gt;
&lt;pre class=&quot;cs&quot; style=&quot;background-color: #f8f8f8; color: #383a42; text-align: start;&quot;&gt;&lt;code&gt;[Resample] &amp;rarr; Ray
&lt;/code&gt;&lt;/pre&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Ray 노드:&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Ray from: First Input (선)&lt;/li&gt;
&lt;li&gt;Ray onto: Second Input (제주도 FBX 연결)&lt;/li&gt;
&lt;li&gt;Direction: Y축 (0, -1, 0)&lt;/li&gt;
&lt;li&gt;Maximum Ray Distance: 100&lt;/li&gt;
&lt;li&gt;Lift Amount: 0.1 (지면에서 살짝 띄우기)&lt;/li&gt;
&lt;/ul&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;제주 FBX 준비:&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;File SOP으로 불러오기&lt;/li&gt;
&lt;li&gt;Transform으로&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;b&gt;크기/위치&lt;/b&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;맞추기 (Python 노드 결과와 겹치게)&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size26&quot;&gt;  3단계: 속도(v) 만들기 (PolyFrame)&lt;/h2&gt;
&lt;pre class=&quot;cs&quot; style=&quot;background-color: #f8f8f8; color: #383a42; text-align: start;&quot;&gt;&lt;code&gt;[Ray] &amp;rarr; PolyFrame
&lt;/code&gt;&lt;/pre&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;PolyFrame 세팅:&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Entity: Points&lt;/li&gt;
&lt;li&gt;Style: First Edge&lt;/li&gt;
&lt;li&gt;Tangent Name: v&lt;/li&gt;
&lt;/ul&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;이제 선에 화살표 방향이 생깁니다!&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-path-to-node=&quot;13&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;&lt;b&gt;PolyFrame 노드&lt;/b&gt;: Resample 아래에 답니다.
&lt;ul style=&quot;list-style-type: disc;&quot; data-path-to-node=&quot;13,0,1&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;Style:&lt;/b&gt; First Edge&lt;/li&gt;
&lt;li&gt;&lt;b&gt;Tangent Name:&lt;/b&gt; v (접선 방향을 속도 v로 변환)&lt;/li&gt;
&lt;li&gt;&lt;i&gt;이러면 선을 따라가는 화살표가 생깁니다.&lt;/i&gt;&lt;/li&gt;
&lt;li&gt;&amp;nbsp;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;2단계: 볼륨(Volume)으로 변환&lt;/h4&gt;
&lt;p data-path-to-node=&quot;15&quot; data-ke-size=&quot;size16&quot;&gt;선은 얇아서 유니티가 감지를 못 합니다. 선 주변을 감싸는 두툼한 안개(Volume)로 만들어야 합니다.&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-path-to-node=&quot;16&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;&lt;b&gt;vdb from particles&lt;/b&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-path-to-node=&quot;19&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li style=&quot;list-style-type: none;&quot;&gt;&amp;nbsp;&lt;/li&gt;
&lt;/ol&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&amp;nbsp;&lt;/h2&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&amp;nbsp;&lt;/h2&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&amp;nbsp;&lt;/h2&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;  4단계: trail&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;result type : compute velocity&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;attribute match : v&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;  5단계: 볼륨으로 변환 (Vdb from oartucke&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;빨강/파랑 구름이 보이면 성공!&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;  6단계: 유니티용 3D 텍스처로 굽기&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;방법 A: Labs 툴 (쉬움)&lt;/h3&gt;
&lt;pre class=&quot;mathematica&quot;&gt;&lt;code&gt;[Volume] &amp;rarr; Labs Volume Texture 3D
&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;1. Mode&lt;/h3&gt;
&lt;pre class=&quot;coq&quot;&gt;&lt;code&gt;Mode: Density &amp;rarr; Vector Field 로 변경!
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;중요!&lt;/b&gt; 벡터 데이터를 굽는 거라 커스텀 Vector Field : v&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;2. Resolution&lt;/h3&gt;
&lt;pre class=&quot;yaml&quot;&gt;&lt;code&gt;Slices: 64 &amp;rarr; 128 (더 고품질)
Resolution per Slice: 256 &amp;rarr; 128

또는 테스트용:
Slices: 64
Resolution per Slice: 64
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;최종 해상도:&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;64 x 64 x 64 = 테스트용 (가벼움)&lt;/li&gt;
&lt;li&gt;128 x 128 x 128 = 추천 (균형)&lt;/li&gt;
&lt;li&gt;256 x 256 x 256 = 최고품질 (무거움)&lt;/li&gt;
&lt;/ul&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;3. 출력 파일&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;스크롤 내려서:&lt;/b&gt;&lt;/p&gt;
&lt;pre class=&quot;gams&quot;&gt;&lt;code&gt;Output Picture: 
$HIP/vectorfield.exr
&lt;/code&gt;&lt;/pre&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;최종 세팅:&lt;/h2&gt;
&lt;pre class=&quot;yaml&quot;&gt;&lt;code&gt;Mode: Vector Field &amp;larr; 필수!
Up Axis: Z
Slices: 128
Resolution per Slice: 128
Clipping Ratio: 1
Output: $HIP/vectorfield.exr
&lt;/code&gt;&lt;/pre&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Render 버튼 누르면 끝!&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;약 2-3분 걸립니다 (128&amp;sup3; 기준).&lt;/p&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2025-11-30 15.26.07.png&quot; data-origin-width=&quot;2308&quot; data-origin-height=&quot;1264&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cyLeIm/dJMcacO6lVQ/RXrzXA73ndkao61DodACik/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cyLeIm/dJMcacO6lVQ/RXrzXA73ndkao61DodACik/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cyLeIm/dJMcacO6lVQ/RXrzXA73ndkao61DodACik/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcyLeIm%2FdJMcacO6lVQ%2FRXrzXA73ndkao61DodACik%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;2308&quot; height=&quot;1264&quot; data-filename=&quot;스크린샷 2025-11-30 15.26.07.png&quot; data-origin-width=&quot;2308&quot; data-origin-height=&quot;1264&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;  유니티에서 쓰기&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;VFX Graph에서:&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;3D Texture 임포트&lt;/li&gt;
&lt;li&gt;Texture 3D 타입으로 설정&lt;/li&gt;
&lt;li&gt;Particle 노드에 Sample Vector Field 연결&lt;/li&gt;
&lt;li&gt;&amp;nbsp;&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;&amp;nbsp;&lt;/h3&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot; data-path-to-node=&quot;22&quot;&gt;이제 유니티로 와서 .exr 혹은 .asset으로 된&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;b&gt;3D Texture&lt;/b&gt;가 생겼을 겁니다.&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot; data-path-to-node=&quot;23&quot;&gt;
&lt;li&gt;&lt;b&gt;VFX Graph&lt;/b&gt;를 켭니다.&lt;/li&gt;
&lt;li&gt;Initialize 단계:
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot; data-path-to-node=&quot;23,1,1&quot;&gt;
&lt;li&gt;파티클을 선 근처 위치에 생성하거나, 박스 형태로 넉넉하게 뿌립니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;b&gt;Update 단계 (핵심)&lt;/b&gt;:
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot; data-path-to-node=&quot;23,2,1&quot;&gt;
&lt;li&gt;&lt;b&gt;Sample Texture 3D 노드&lt;/b&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;생성.&lt;/li&gt;
&lt;li&gt;여기에 후디니에서 가져온 텍스처를 넣습니다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;UVW 좌표:&lt;/b&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;Get Attribute: Position 노드를 연결해서, 파티클의 현재 위치가 텍스처의 어느 부분인지 알려줍니다. (좌표 스케일 조정 필요할 수 있음)&lt;/li&gt;
&lt;li&gt;&lt;b&gt;출력값(RGB)&lt;/b&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&amp;rarr;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;b&gt;Set Velocity&lt;/b&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;또는&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;b&gt;Add Force&lt;/b&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;노드에 연결.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;hr data-ke-style=&quot;style1&quot; data-path-to-node=&quot;24&quot; /&gt;&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&amp;nbsp;&lt;/h2&gt;
&lt;/div&gt;</description>
      <category>후디니</category>
      <author>잉_민</author>
      <guid isPermaLink="true">https://ing-min.tistory.com/309</guid>
      <comments>https://ing-min.tistory.com/309#entry309comment</comments>
      <pubDate>Mon, 1 Dec 2025 00:15:49 +0900</pubDate>
    </item>
  </channel>
</rss>