티스토리 뷰

후디니

houdini to unity 좌표값 보내기

잉_민 2025. 12. 1. 20:41
728x90
반응형

 

 

**curve_u = 선을 따라 진행된 비율 (0~1)** ``` 출생지 ─────────────────────> 사망지 0.0 0.25 0.5 0.75 1.0

CSV 파일 구조 확인

 
 
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 (사망날짜)

Primitive 속성 (선에 저장):

- path_id (string): 고유 ID ⭐ - person_name (string): 이름 ⭐ - death_date (int): 사망날짜 ⭐

- `time`: 선을 따라 진행된 비율 (0.01.0) - 애니메이션용 - curve_u와 같은 값

 

- `death_date`: 실제 사망날짜 (: 19500315) - 그룹핑/필터링용 - Unity에서 중요!

값매피

               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])      # 사망날짜

프리미티브에저장

poly.setAttribValue("path_id", path_id)              # 고유 ID
                poly.setAttribValue("person_name", person_name)      # 이름
                poly.setAttribValue("death_date", death_date)        # 사망날짜
                
                # 좌표 데이터
                poly.setAttribValue("birth_lat", birth_lat)
                poly.setAttribValue("birth_lon", birth_lon)
                poly.setAttribValue("death_lat", death_lat)
                poly.setAttribValue("death_lon", death_lon)
                
                # 그룹핑용 ID
                poly.setAttribValue("death_grid_id", death_grid)
                

1. 속도값과 pscale의 의미

pscale (Point Scale)

  • 의미: 포인트의 크기를 제어하는 속성
  • 현재 코드: uncertainty * PSCALE_MULTIPLIER로 설정
  • 목적: 위치 데이터의 불확실성을 시각적으로 표현 (불확실성이 클수록 포인트가 큼)
  • 출생지/사망지 각각의 불확실성을 구분하여 표시
import hou
import csv
import os
import math

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

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

# 위경도 → 거리 변환
LAT_KM_PER_DEGREE = 111.0  # 위도 1도 ≈ 111km
LON_KM_PER_DEGREE = 111.0 * math.cos(math.radians(CENTER_LAT))  # ≈ 92.4km

# Houdini 공간 스케일 (제주도 동서 73km → 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, "pscale", 0.0)           # 점 크기 (시각화)
geo.addAttrib(hou.attribType.Point, "curve_u", 0.0)          # 선 진행 비율 (0→1)
geo.addAttrib(hou.attribType.Point, "uncertainty", 0.0)      # 불확실성 원본값

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

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

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

# -------------------------------------------------------------------------
# 좌표 변환 함수
# -------------------------------------------------------------------------
def latlon_to_xy(lat, lon):
    """
    위경도를 Houdini XZ 좌표로 변환
    - 제주도 중심을 (0, 0)으로
    - 실제 거리 비율 유지
    """
    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):
    """
    사망지를 그리드로 분할해서 ID 생성
    grid_size=0.01 → 약 1km 단위 그리드
    grid_size=0.1 → 약 10km 단위 그리드
    """
    precision = int(1.0 / grid_size)
    grid_lat = int(lat * precision)
    grid_lon = int(lon * precision)
    return f"{grid_lat}_{grid_lon}"

# -------------------------------------------------------------------------
# CSV 파일 읽기 및 경로 생성
# -------------------------------------------------------------------------
if not os.path.exists(FILE_PATH):
    print(f"❌ 파일을 찾을 수 없습니다: {FILE_PATH}")
    print("   경로를 확인해주세요!")
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) < 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("curve_u", 0.0)
                pt_birth.setAttribValue("pscale", uncert_birth * PSCALE_MULTIPLIER)
                pt_birth.setAttribValue("uncertainty", uncert_birth)
                poly.addVertex(pt_birth)

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

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

        # 완료 메시지
        print("=" * 60)
        print(f"✅ 경로 생성 완료!")
        print(f"   생성: {count}개")
        print(f"   스킵: {skipped}개")
        print(f"   총계: {count + skipped}개")
        print("=" * 60)
        print(f"📏 스케일: 1km = {SCALE_FACTOR:.3f} Houdini units")
        print(f"📐 제주도 크기: 동서 {73 * SCALE_FACTOR:.1f} units, 남북 {31 * SCALE_FACTOR:.1f} units")
        print("=" * 60)

 

 

============================================================
Houdini 노드 체인 - 완전한 워크플로우
============================================================

1. Python 노드 (위의 코드)
   ↓
   [출력] 400개 선 (Polygons)
   Primitive 속성: path_id, person_name, death_date, death_grid_id, 좌표들
   Point 속성: P, pscale, curve_u, uncertainty

============================================================

2. Resample SOP
   ↓
   설정:
   - Resample: Length
   - Length: 0.2 (또는 0.1 - 점을 촘촘하게)
   - Treat Polygons As: Subdivision Curves
   
   [출력] 선마다 여러 점이 생김
   - Point 속성 자동 보간 (pscale, curve_u, uncertainty)
   - Primitive 속성 유지 (path_id, person_name, death_date)

============================================================

3. PolyFrame SOP
   ↓
   설정:
   - Entity: Points
   - Style: First Edge
   - Tangent Name: v
   
   [출력] 각 점에 속도(v) 추가됨
   Point 속성 추가: v (vector)

============================================================

4. Attribute Wrangle SOP (Point 모드) ⭐ 중요!
   ↓
   VEX 코드:

// Primitive 속성 → Point 속성으로 복사
s@path_id = prim(0, "path_id", @primnum);        // 사람 고유 ID
s@person_name = prim(0, "person_name", @primnum); // 이름
i@death_date = prim(0, "death_date", @primnum);   // 사망날짜 (정수)

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

// 그리드 ID (사망 위치 클러스터링용)
s@death_grid_id = prim(0, "death_grid_id", @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("date_color", normalized_date);  // Ramp 파라미터 필요




   [출력] 모든 속성이 Point에 있음!
   Point 속성: P, v, path_id, person_name, death_date, death_grid_id, 
               time, curve_u, uncertainty, pscale, 좌표들

============================================================

5. Delete SOP
   ↓
   설정:
   - Entity: Primitives

delete non select
   - Keep Points: ✅ 체크!
   
   [출력] Point만 남음 (선은 삭제됨)
   - 모든 속성은 Point에 보존됨

============================================================

6. Labs Niagara ROP (또는 ROP Alembic Output)
   ↓
   Labs Niagara ROP 설정:
   
   Output Path:
   $HOME/Desktop/migration_paths.hcsv
   
   Keep Attributes:
   P v path_id person_name death_date death_grid_id time pscale uncertainty
   
   Cast Attributes:
   0 (필요없음)

   ─────────────────────────────
   
여기서 실패

Labs Niagara ROP 대신:

  1. File SOP 추가 (Delete 뒤에)
  2. File → $HIP/test.bgeo
  3. File Mode → Write
  4. Save to Disk 클릭

.bgeo 파일이 생성되고 데이터가 있나요? ㅇㅇ

있으면 → Labs Niagara ROP 버그 없으면 → 노드 체인 문제

Labs Niagara ROP 버그 

1. bgeo를 JSON으로 변환

delete 다음 - File 연결  뒤에 Python SOP 추가:

 

import json

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

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

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

print(f"✅ JSON 저장 완료: {output_path}")
print(f"📊 포인트 개수: {len(data)}")


============================================================

최종 출력 데이터 구조:
============================================================

각 Point마다:
- P (vector): 위치
- v (vector): 속도/방향
- path_id (string): 고유 ID
- person_name (string): 이름
- death_date (int): 사망날짜 (YYYYMMDD)
- death_grid_id (string): 사망지 그리드
- time (float): 선 진행 비율 (0~1)
- pscale (float): 점 크기
- uncertainty (float): 불확실성

============================================================
Unity에서 사용:
============================================================

VFX Graph에서 사용하는 방법:

1️⃣ JSON → Texture로 변환 필요

VFX Graph는 JSON을 직접 못 읽습니다. Texture2D 또는 GraphicsBuffer로 변환해야 합니다.

2️⃣ Unity C# 스크립트로 데이터 로드

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<ParticleData> particles;
    
    void Start()
    {
        // JSON 파싱
        particles = JsonUtility.FromJson<List<ParticleData>>(
            "{\"items\":" + jsonFile.text + "}"
        ).items;
        
        Debug.Log($"로드 완료: {particles.Count}개 파티클");
    }
}

3️⃣ VFX Graph 사용 방법 

 Graphics Buffer 사용

  • Compute Shader로 데이터 전달
  • 더 복잡하지만 성능 좋음
  •  
728x90
반응형
250x250
공지사항
최근에 올라온 글
최근에 달린 댓글
Total
Today
Yesterday
링크
«   2025/12   »
1 2 3 4 5 6
7 8 9 10 11 12 13
14 15 16 17 18 19 20
21 22 23 24 25 26 27
28 29 30 31
글 보관함
반응형