TypeScript에서 프로그래밍 방식으로 3D 메시를 구축하는 방법
Aspose.3D FOSS for TypeScript는 파일을 로드하지 않고도 코드를 통해 3D 기하학을 완전히 구축할 수 있게 해줍니다. 정점 위치를 제어점으로 정의하고, 인덱스로 폴리곤 면을 지정하며, 노멀, UV 또는 정점 색상과 같은 선택적 정점 요소를 첨부할 수 있습니다. 결과는 glTF, GLB, STL, FBX 또는 COLLADA와 같은 모든 쓰기 가능한 형식으로 저장할 수 있습니다.
전제 조건
- Node.js 18 이상
- TypeScript 5.0 이상
@aspose/3d설치됨 (Step 1을 참조)
단계별 가이드
1단계: @aspose/3d 설치
npm install @aspose/3d네이티브 애드온이나 시스템 라이브러리가 필요하지 않습니다. 이 패키지에는 TypeScript 타입 정의가 포함되어 있습니다.
최소 tsconfig.json:
{
"compilerOptions": {
"target": "ES2020",
"module": "commonjs",
"moduleResolution": "node",
"esModuleInterop": true,
"strict": true
}
}2단계: 씬 및 노드 만들기
A Scene는 최상위 컨테이너입니다. 모든 기하학은 씬 트리 내부의 Node에 연결되어야 합니다:
import { Scene } from '@aspose/3d';
const scene = new Scene();
const node = scene.rootNode.createChildNode('triangle');createChildNode(name)은(는) 명명된 노드를 생성하고 현재 노드의 자식으로 연결합니다. 반환된 Node 객체는 단계 7에서 메쉬를 연결할 위치입니다.
3단계: 메시 객체 만들기
Mesh은 정점 위치와 폴리곤 정의를 보유합니다. 선택적 이름으로 하나를 구성하십시오:
import { Mesh } from '@aspose/3d/entities';
const mesh = new Mesh('triangle');메시는 비어 있는 상태로 시작합니다: 정점이 없고 면도 없습니다. 다음 단계에서 이를 추가합니다.
4단계: 제어점(정점) 추가
제어점은 로컬 공간의 정점 위치입니다. Vector4 값을 mesh.controlPoints에 푸시합니다. 네 번째 구성 요소(w)는 위치에 대해 1입니다.
import { Vector4 } from '@aspose/3d/utilities';
mesh.controlPoints.push(new Vector4(0.0, 0.0, 0.0, 1.0)); // index 0
mesh.controlPoints.push(new Vector4(1.0, 0.0, 0.0, 1.0)); // index 1
mesh.controlPoints.push(new Vector4(0.5, 1.0, 0.0, 1.0)); // index 2
다각형 면을 정의할 때 이러한 위치를 0부터 시작하는 인덱스로 참조합니다.
5단계: 폴리곤 면 만들기
createPolygon()은 정점 인덱스를 순서대로 나열하여 면을 정의합니다. 세 개의 인덱스가 삼각형을 만듭니다:
mesh.createPolygon(0, 1, 2);지원하는 형식에 대해 사각형(네 개 인덱스)이나 임의의 다각형을 정의할 수도 있습니다. glTF의 경우, 라이브러리가 내보낼 때 사각형과 n-각형을 자동으로 삼각형으로 변환합니다.
6단계: 정점 법선 추가
노멀은 렌더링 품질을 향상시킵니다. mesh.createElement()를 사용하여 VertexElementNormal를 만들고, 노멀 벡터를 배열에 수집한 다음 setData()를 호출하여 저장합니다. data getter는 방어적 복사본을 반환하므로, 여기에 푸시해도 효과가 없습니다. 노멀 데이터에는 FVector3(단정밀 부동소수점)를 사용하고, Vector4는 사용하지 마세요.
import { VertexElementNormal } from '@aspose/3d/entities';
import { VertexElementType, MappingMode, ReferenceMode } from '@aspose/3d/entities';
import { FVector3 } from '@aspose/3d/utilities';
const normals = mesh.createElement(
VertexElementType.NORMAL,
MappingMode.CONTROL_POINT,
ReferenceMode.DIRECT
) as VertexElementNormal;
// Build the normal array, then call setData() — do NOT push to normals.data
normals.setData([
new FVector3(0, 0, 1), // normal for vertex 0 (pointing +Z)
new FVector3(0, 0, 1), // normal for vertex 1
new FVector3(0, 0, 1), // normal for vertex 2
]);MappingMode.CONTROL_POINT 은 정점당 하나의 노멀을 의미합니다. ReferenceMode.DIRECT 은 데이터 배열이 폴리곤 정점 인덱스로 직접 인덱싱된다는 것을 의미합니다.
7단계: 메쉬를 연결하고 glTF로 저장
node.entity를 통해 메시를 노드에 할당한 다음, 씬을 저장하십시오:
import { GltfSaveOptions, GltfFormat } from '@aspose/3d/formats/gltf';
node.entity = mesh;
const saveOpts = new GltfSaveOptions();
scene.save('triangle.gltf', GltfFormat.getInstance(), saveOpts);
console.log('Triangle mesh saved to triangle.gltf');대신 단일 자체 포함 .glb 파일을 생성하려면, saveOpts.binaryMode = true를 설정하고 출력 파일 확장자를 .glb로 변경하십시오.
전체 예제
다음은 위의 모든 단계를 결합한 전체 스크립트입니다:
import { Scene } from '@aspose/3d';
import { Mesh, VertexElementNormal } from '@aspose/3d/entities';
import { VertexElementType, MappingMode, ReferenceMode } from '@aspose/3d/entities';
import { Vector4, FVector3 } from '@aspose/3d/utilities';
import { GltfSaveOptions, GltfFormat } from '@aspose/3d/formats/gltf';
const scene = new Scene();
const node = scene.rootNode.createChildNode('triangle');
const mesh = new Mesh('triangle');
mesh.controlPoints.push(new Vector4(0.0, 0.0, 0.0, 1.0));
mesh.controlPoints.push(new Vector4(1.0, 0.0, 0.0, 1.0));
mesh.controlPoints.push(new Vector4(0.5, 1.0, 0.0, 1.0));
mesh.createPolygon(0, 1, 2);
const normals = mesh.createElement(
VertexElementType.NORMAL,
MappingMode.CONTROL_POINT,
ReferenceMode.DIRECT
) as VertexElementNormal;
// setData() is the correct API — normals.data returns a defensive copy; pushing to it has no effect
normals.setData([
new FVector3(0, 0, 1),
new FVector3(0, 0, 1),
new FVector3(0, 0, 1),
]);
node.entity = mesh;
const saveOpts = new GltfSaveOptions();
scene.save('triangle.gltf', GltfFormat.getInstance(), saveOpts);
console.log('Triangle mesh saved to triangle.gltf');ts-node와 함께 실행:
npx ts-node triangle.ts일반적인 문제
| Issue | Cause | Fix |
|---|---|---|
mesh.controlPoints.length이 푸시 후 0 | 메시가 어떤 노드에도 참조되지 않음 | node.entity을 할당하기 전에 푸시하십시오; 순서는 중요하지 않지만, 참조를 확인하십시오 |
| 내보내기 시 빈 지오메트리가 생성됩니다 | node.entity이 할당되지 않음 | scene.save()을 호출하기 전에 node.entity = mesh을 확인하십시오 |
| 노멀 개수 불일치 | setData()에 전달된 배열이 controlPoints보다 짧음 | MappingMode.CONTROL_POINT를 사용할 때 제어점당 FVector3 항목을 하나 추가하십시오 |
| glTF 뷰어에서 검은색 메시가 표시됩니다 | 노멀 벡터가 안쪽을 향함 | createPolygon에서 와인딩 순서를 반전시키세요(예: 0, 2, 1) 또는 노멀 벡터를 부정하십시오 |
| TypeScript: ’normals.data’ 속성을 찾을 수 없음 | 잘못된 import 경로 | VertexElementNormal을 @aspose/3d 루트가 아니라 @aspose/3d/entities에서 import 하세요 |
자주 묻는 질문
삼각형 대신 사각형을 만들 수 있나요?
예. createPolygon(0, 1, 2, 3)에 네 개의 인덱스를 전달하십시오. 라이브러리는 삼각형이 필요한 형식(glTF, STL)으로 내보낼 때 사각형을 삼각형으로 삼각분할합니다.
MappingMode.CONTROL_POINT와 MappingMode.POLYGON_VERTEX의 차이점은 무엇입니까?CONTROL_POINT는 고유 정점당 하나의 값을 저장합니다. POLYGON_VERTEX는 폴리곤‑정점 쌍당 하나의 값을 저장하며, 이는 동일한 정점이 여러 폴리곤에 속할 때(하드 에지) 서로 다른 법선을 허용합니다.
STL로 저장하기 전에 메쉬를 삼각분할 해야 하나요? 아니요. 라이브러리는 삼각형이 필요한 형식으로 내보낼 때 자동으로 삼각분할을 처리합니다. 메쉬에서 사각형 및 n-각형을 정의하고 바로 STL로 저장할 수 있습니다.
UV 좌표를 어떻게 추가합니까?
Use mesh.createElementUV(TextureMapping.Diffuse, MappingMode.CONTROL_POINT, ReferenceMode.DIRECT)을 사용하여 VertexElementUV을 생성하고, setData([...])을 호출하여 FVector2 또는 FVector3 값의 배열을 전달하십시오 — 제어점당 하나씩. data getter는 복사본을 반환합니다; 직접 푸시하지 마십시오.
한 씬에 여러 메쉬를 만들 수 있나요?
예. scene.rootNode 아래에 여러 노드를 만들고 각 노드의 entity 속성에 별도의 Mesh을 할당하십시오.