Praca z grafem sceny
Cała zawartość 3D w Aspose.3D FOSS dla TypeScript znajduje się wewnątrz Scene obiekt uporządkowany jako drzewo Node obiektów. Zrozumienie tej hierarchii jest podstawą do budowania, ładowania i przetwarzania dowolnego pliku 3D.
Instalcja
Zainstaluj pakiet z npm przed uruchomieniem jakiegokolwiek kodu w tym przewodniku:
npm install @aspose/3dUpewnij się, że Twój tsconfig.json zawiera "module": "commonjs" i "moduleResolution": "node" dla prawidłowego rozwiązywania importów podścieżek.
Koncepcje grafu sceny
Graf sceny ma trzy poziomy:
| Poziom | Klasa | Rola |
|---|---|---|
| Scena | Scene | Kontener najwyższego poziomu. Przechowuje rootNode, animationClips, oraz assetInfo. |
| Węzeł | Node | Nazwany węzeł drzewa. Może mieć węzły potomne, encję, transformację i materiały. |
| Encja | Mesh, Camera, Light, … | Zawartość dołączona do węzła. Węzeł może mieć maksymalnie jedną encję. |
Łańcuch dziedziczenia głównych elementów budulcowych jest następujący:
A3DObject
└─ SceneObject
├─ Node (tree structure)
└─ Entity
└─ Geometry
└─ Mesh (polygon geometry)scene.rootNode jest tworzony automatycznie. Nie tworzysz go ręcznie; tworzysz pod nim węzły potomne.
Step 1: Create a Scene
import { Scene } from '@aspose/3d';
const scene = new Scene();
console.log(scene.rootNode.name); // '' (empty string — the root node is created with no name)
Nowy Scene zaczyna się od pustego węzła głównego i braku klipów animacji. Tworzysz zawartość, dołączając węzły potomne.
Krok 2: Dodaj węzły potomne
Użyj createChildNode() aby rozbudować drzewo. Metoda zwraca nowy Node, więc możesz łańcuchowo wywoływać kolejne wywołania z dowolnego poziomu:
import { Scene } from '@aspose/3d';
const scene = new Scene();
const parent = scene.rootNode.createChildNode('parent');
const child = parent.createChildNode('child');
console.log(scene.rootNode.childNodes.length); // 1 (parent)
console.log(parent.childNodes.length); // 1 (child)
Nazwy węzłów są dowolnymi ciągami znaków. Nie muszą być unikalne, ale używanie znaczących nazw ułatwia debugowanie kodu przeglądającego strukturę.
Krok 3: Utwórz Mesh i ustaw wierzchołki
Mesh jest podstawową klasą geometrii. Dodaj pozycje wierzchołków, używając push Vector4 wartości do mesh.controlPoints, a następnie wywołaj createPolygon() aby zdefiniować faces przez indeksy wierzchołków:
import { Scene } from '@aspose/3d';
import { Mesh } from '@aspose/3d/entities';
import { Vector4 } from '@aspose/3d/utilities';
const scene = new Scene();
const child = scene.rootNode.createChildNode('parent').createChildNode('child');
const mesh = new Mesh('cube');
mesh.controlPoints.push(new Vector4(0, 0, 0, 1));
mesh.controlPoints.push(new Vector4(1, 0, 0, 1));
mesh.controlPoints.push(new Vector4(1, 1, 0, 1));
mesh.controlPoints.push(new Vector4(0, 1, 0, 1));
mesh.createPolygon(0, 1, 2, 3); // quad face using all four vertices
child.entity = mesh;
console.log(mesh.controlPoints.length); // 4
console.log(mesh.polygonCount); // 1
Vector4 używa współrzędnych jednorodnych: w komponent to 1 dla pozycji i 0 dla wektorów kierunkowych.
Step 4: Set Node Transforms
Każdy węzeł ma transform właściwość z translation, rotation, i scaling. Ustaw translation aby przesunąć węzeł względem jego rodzica:
import { Scene } from '@aspose/3d';
import { Mesh } from '@aspose/3d/entities';
import { Vector4, Vector3 } from '@aspose/3d/utilities';
const scene = new Scene();
const parent = scene.rootNode.createChildNode('parent');
const child = parent.createChildNode('child');
const mesh = new Mesh('cube');
mesh.controlPoints.push(new Vector4(0, 0, 0, 1));
mesh.controlPoints.push(new Vector4(1, 0, 0, 1));
mesh.controlPoints.push(new Vector4(1, 1, 0, 1));
mesh.controlPoints.push(new Vector4(0, 1, 0, 1));
mesh.createPolygon(0, 1, 2, 3);
child.entity = mesh;
child.transform.translation = new Vector3(2.0, 0.0, 0.0);globalTransform zapewnia macierz przekształcenia w przestrzeni światowej (tylko do odczytu), obliczaną przez konkatenację wszystkich przekształceń przodków.
Krok 5: Przeglądaj drzewo
Napisz funkcję rekurencyjną, aby odwiedzić każdy węzeł. Sprawdź node.entity i node.childNodes na każdym poziomie:
function traverse(node: any, depth = 0): void {
const indent = ' '.repeat(depth);
const entityType = node.entity ? node.entity.constructor.name : 'none';
console.log(`${indent}${node.name} [${entityType}]`);
for (const child of node.childNodes) {
traverse(child, depth + 1);
}
}
traverse(scene.rootNode);Dla powyżej utworzonej hierarchii, wynik będzie:
[none]
parent [none]
child [Mesh]Nazwa węzła głównego jest pustym ciągiem, ponieważ Scene tworzy go bez argumentu nazwy.
Zawsze zabezpieczaj dostęp do encji sprawdzając, czy nie jest null, przed rzutowaniem na konkretny typ. Nie każdy węzeł posiada encję.
Krok 6: Zapisz do glTF lub GLB
Użyj GltfSaveOptions aby kontrolować format wyjścia. Ustaw binaryMode = true aby wygenerować pojedynczy, samodzielny .glb plik; pozostaw go false dla JSON .gltf + .bin para sidecar:
import { Scene } from '@aspose/3d';
import { Mesh } from '@aspose/3d/entities';
import { Vector4, Vector3 } from '@aspose/3d/utilities';
import { GltfSaveOptions, GltfFormat } from '@aspose/3d/formats/gltf';
const scene = new Scene();
const parent = scene.rootNode.createChildNode('parent');
const child = parent.createChildNode('child');
const mesh = new Mesh('cube');
mesh.controlPoints.push(new Vector4(0, 0, 0, 1));
mesh.controlPoints.push(new Vector4(1, 0, 0, 1));
mesh.controlPoints.push(new Vector4(1, 1, 0, 1));
mesh.controlPoints.push(new Vector4(0, 1, 0, 1));
mesh.createPolygon(0, 1, 2, 3);
child.entity = mesh;
child.transform.translation = new Vector3(2.0, 0.0, 0.0);
const saveOpts = new GltfSaveOptions();
saveOpts.binaryMode = true; // write a single .glb file
scene.save('scene.glb', GltfFormat.getInstance(), saveOpts);
console.log('Scene saved to scene.glb');Przekaż GltfFormat.getInstance() jako argument formatu, aby biblioteka używała właściwego enkodera niezależnie od rozszerzenia pliku.
Wskazówki i najlepsze praktyki
- Użyj
createChildNode()zamiast konstruowaćNodebezpośrednio:createChildNode()automatycznie łączy relację rodzic-dziecko i rejestruje węzeł w drzewie. - Sprawdź
node.entityprzed dostępem do właściwości encji: wiele węzłów (węzły grupowe, kości, lokalizatory) nie posiada encji. Zawsze zabezpieczaj je sprawdzeniem na null lubinstanceoftestem. - Ustaw
translationna węzłach potomnych, a nie na wierzchołkach siatki: modyfikowanietransform.translationjest niedestrukcyjne i można je łączyć z transformacjami rodzica. - Preferuj
binaryMode = truedla GLB: pojedynczy.glbplik jest łatwiejszy do dystrybucji, ładowania w przeglądarkach i importowania do silników gier niż podzielony.gltf+.binformat. - Przejdź przez
for...ofnadchildNodes: unikaj indeksowania numerycznego; użyj iterowalnego bezpośrednio dla przyszłej kompatybilności.
Typowe problemy
| Objaw | Prawdopodobna przyczyna | Naprawa |
|---|---|---|
child.entity = mesh nie ma wpływu na eksport | Jednostka przypisana do niewłaściwego poziomu węzła | Przypisz entity do węzła liścia, a nie do węzła grupy |
node.entity zawsze jest null | Tylko sprawdzanie rootNode sam | Rekurencja w node.childNodes; rootNode zazwyczaj nie ma encji |
| Transform nie jest odzwierciedlony w przeglądarce GLB | globalTransform nie zaktualizowano | globalTransform jest obliczane przy zapisie; ustaw transform.translation przed wywołaniem scene.save() |
GLB generuje oddzielny .bin sidecar | binaryMode domyślnie ustawione na false | Ustaw saveOpts.binaryMode = true |
Zobacz także
- Funkcje i możliwości: pełna dokumentacja API dla wszystkich obszarów funkcji.
- Obsługa formatów: obsługiwane formaty 3D, możliwość odczytu/zapisu oraz opcje formatów.
- Jak programowo zbudować siatkę 3D.