Skip to content

Commit db7d26b

Browse files
committed
feat(objwriter): add support for vtkOBJWriter
1 parent 572238a commit db7d26b

File tree

4 files changed

+628
-0
lines changed

4 files changed

+628
-0
lines changed
Lines changed: 177 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,177 @@
1+
import '@kitware/vtk.js/favicon';
2+
3+
// Load the rendering pieces we want to use (for both WebGL and WebGPU)
4+
import '@kitware/vtk.js/Rendering/Profiles/Geometry';
5+
6+
import '@kitware/vtk.js/IO/Core/DataAccessHelper/HttpDataAccessHelper'; // HTTP + zip
7+
8+
import vtkDataArray from '@kitware/vtk.js/Common/Core/DataArray';
9+
import vtkFullScreenRenderWindow from '@kitware/vtk.js/Rendering/Misc/FullScreenRenderWindow';
10+
import vtkActor from '@kitware/vtk.js/Rendering/Core/Actor';
11+
import vtkMapper from '@kitware/vtk.js/Rendering/Core/Mapper';
12+
import vtkTexture from '@kitware/vtk.js/Rendering/Core/Texture';
13+
import vtkOBJWriter from '@kitware/vtk.js/IO/Misc/OBJWriter';
14+
import vtkCubeSource from '@kitware/vtk.js/Filters/Sources/CubeSource';
15+
16+
// ----------------------------------------------------------------------------
17+
// Standard rendering code setup
18+
// ----------------------------------------------------------------------------
19+
20+
const fullScreenRenderer = vtkFullScreenRenderWindow.newInstance();
21+
const renderer = fullScreenRenderer.getRenderer();
22+
const renderWindow = fullScreenRenderer.getRenderWindow();
23+
24+
const resetCamera = renderer.resetCamera;
25+
const render = renderWindow.render;
26+
27+
// Create a colored texture for each face of the cube
28+
const canvas = document.createElement('canvas');
29+
canvas.width = 256;
30+
canvas.height = 256;
31+
document.body.appendChild(canvas);
32+
const ctx = canvas.getContext('2d');
33+
34+
// Define colors for each face (order: +X, -X, +Y, -Y, +Z, -Z)
35+
const faceColors = [
36+
'#FF0000', // +X: Red
37+
'#00FF00', // -X: Green
38+
'#0000FF', // +Y: Blue
39+
'#FFFF00', // -Y: Yellow
40+
'#00FFFF', // +Z: Cyan
41+
'#FF00FF', // -Z: Magenta
42+
];
43+
44+
// Draw each face as a square in a 3x2 grid
45+
const faceSize = 128;
46+
for (let i = 0; i < 6; i++) {
47+
const x = (i % 3) * faceSize;
48+
const y = Math.floor(i / 3) * faceSize;
49+
ctx.fillStyle = faceColors[i];
50+
ctx.fillRect(x, y, faceSize, faceSize);
51+
}
52+
53+
function callback(source, tex) {
54+
const writer = vtkOBJWriter.newInstance();
55+
writer.setInputData(source.getOutputData());
56+
writer.setTexture(tex);
57+
58+
// const objContent = writer.getOutputData();
59+
// const mtlContent = writer.getMtl();
60+
61+
const zip = writer.exportAsZip();
62+
zip.then((zipData) => {
63+
const blob = new Blob([zipData], { type: 'application/zip' });
64+
const url = URL.createObjectURL(blob);
65+
const link = document.createElement('a');
66+
link.href = url;
67+
link.text = 'Download';
68+
link.style.position = 'absolute';
69+
link.style.left = '50%';
70+
link.style.bottom = '10px';
71+
link.style.background = 'white';
72+
link.style.padding = '5px';
73+
global.writer = writer;
74+
document.body.appendChild(link);
75+
});
76+
}
77+
78+
// Create a cube source
79+
const cubeSource = vtkCubeSource.newInstance({
80+
xLength: 1,
81+
yLength: 1,
82+
zLength: 1,
83+
});
84+
85+
// Create vtkTexture from canvas
86+
const texture = vtkTexture.newInstance();
87+
const image = new Image();
88+
image.src = canvas.toDataURL();
89+
image.onload = () => {
90+
texture.setImage(image);
91+
callback(cubeSource, texture);
92+
render();
93+
};
94+
95+
// Add tcoords to the cube for proper texture mapping
96+
const addCubeTCoords = (polyData) => {
97+
const points = polyData.getPoints().getData();
98+
const numPoints = points.length / 3;
99+
const tcoords = new Float32Array(numPoints * 2);
100+
101+
// Map each face to a region of the texture
102+
const polys = polyData.getPolys().getData();
103+
const faces = [];
104+
let i = 0;
105+
while (i < polys.length) {
106+
const n = polys[i++];
107+
const face = [];
108+
for (let j = 0; j < n; j++) {
109+
face.push(polys[i++]);
110+
}
111+
faces.push(face);
112+
}
113+
114+
// Map each face's points to the corresponding region in the texture
115+
faces.forEach((face, faceIdx) => {
116+
// Compute texture region for this face
117+
const col = faceIdx % 3;
118+
const row = Math.floor(faceIdx / 3);
119+
const u0 = col / 3;
120+
const v0 = row / 2;
121+
const u1 = (col + 1) / 3;
122+
const v1 = (row + 1) / 2;
123+
124+
// Assign tcoords to each point in the face
125+
face.forEach((ptIdx, j) => {
126+
// For quads, assign corners in order
127+
// For triangles, just map to the region
128+
let u;
129+
let v;
130+
if (face.length === 4) {
131+
// Map corners: 0-bottom left, 1-bottom right, 2-top right, 3-top left
132+
if (j === 0) {
133+
u = u0;
134+
v = v0;
135+
} else if (j === 1) {
136+
u = u1;
137+
v = v0;
138+
} else if (j === 2) {
139+
u = u1;
140+
v = v1;
141+
} else {
142+
u = u0;
143+
v = v1;
144+
}
145+
} else {
146+
// For triangles, just use barycentric mapping
147+
u = u0 + (u1 - u0) * (j % 2);
148+
v = v0 + (v1 - v0) * Math.floor(j / 2);
149+
}
150+
tcoords[ptIdx * 2] = u;
151+
tcoords[ptIdx * 2 + 1] = v;
152+
});
153+
});
154+
155+
const tcoordArray = vtkDataArray.newInstance({
156+
name: 'TextureCoordinates',
157+
numberOfComponents: 2,
158+
values: tcoords,
159+
});
160+
polyData.getPointData().setTCoords(tcoordArray);
161+
};
162+
163+
const polyData = cubeSource.getOutputData();
164+
addCubeTCoords(polyData);
165+
166+
const mapper = vtkMapper.newInstance();
167+
mapper.setInputData(polyData);
168+
169+
const actor = vtkActor.newInstance();
170+
actor.setMapper(mapper);
171+
actor.addTexture(texture);
172+
173+
renderer.addActor(actor);
174+
resetCamera();
175+
render();
176+
177+
global.fullScreenRenderer = fullScreenRenderer;

Sources/IO/Misc/OBJWriter/index.d.ts

Lines changed: 103 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,103 @@
1+
import vtkPolyData from '../../../Common/DataModel/PolyData';
2+
import vtkTexture from '../../../Rendering/Core/Texture';
3+
import { vtkAlgorithm, vtkObject } from '../../../interfaces';
4+
5+
/**
6+
*
7+
*/
8+
export interface IOBJWriterInitialValues {
9+
modelFilename?: string;
10+
materialFilename?: string;
11+
texture?: vtkTexture;
12+
textureFileName?: string;
13+
}
14+
15+
type vtkOBJWriterBase = vtkObject & vtkAlgorithm;
16+
17+
export interface vtkOBJWriter extends vtkOBJWriterBase {
18+
/**
19+
* Get the zip file containing the OBJ and MTL files.
20+
*/
21+
exportAsZip(): object;
22+
23+
/**
24+
* Get the MTL file as a string.
25+
*/
26+
getMtl(): string;
27+
28+
/**
29+
*
30+
* @param inData
31+
* @param outData
32+
*/
33+
requestData(inData: any, outData: any): void;
34+
35+
/**
36+
* Set the model filename.
37+
* @param modelFilename
38+
*/
39+
setModelFilename(modelFilename: string): boolean;
40+
41+
/**
42+
* Set the material filename.
43+
* @param materialFilename
44+
* @returns {boolean} true if the material file name was set successfully
45+
*/
46+
setMaterialFilename(materialFilename: string): boolean;
47+
48+
/**
49+
* Set the texture instance.
50+
* @param {vtkTexture} texture
51+
* @returns {boolean} true if the texture was set successfully
52+
*/
53+
setTexture(texture: vtkTexture): boolean;
54+
55+
/**
56+
* Set the texture file name.
57+
* @param {string} textureFileName
58+
* @returns {boolean} true if the texture file name was set successfully
59+
*/
60+
setTextureFileName(textureFileName: string): boolean;
61+
}
62+
63+
/**
64+
* Method used to decorate a given object (publicAPI+model) with vtkOBJWriter characteristics.
65+
*
66+
* @param publicAPI object on which methods will be bounds (public)
67+
* @param model object on which data structure will be bounds (protected)
68+
* @param {IOBJWriterInitialValues} [initialValues] (default: {})
69+
*/
70+
export function extend(
71+
publicAPI: object,
72+
model: object,
73+
initialValues?: IOBJWriterInitialValues
74+
): void;
75+
76+
/**
77+
* Method used to create a new instance of vtkOBJWriter
78+
* @param {IOBJWriterInitialValues} [initialValues] for pre-setting some of its content
79+
*/
80+
export function newInstance(
81+
initialValues?: IOBJWriterInitialValues
82+
): vtkOBJWriter;
83+
84+
/**
85+
*
86+
* @param {vktPolyData} polyData
87+
*/
88+
export function writeOBJ(polyData: vtkPolyData): vtkPolyData;
89+
90+
/**
91+
* vtkOBJWriter writes wavefront obj (.obj) files in ASCII form. OBJ files
92+
* contain the geometry including lines, triangles and polygons. Normals and
93+
* texture coordinates on points are also written if they exist.
94+
*
95+
* One can specify a texture passing a vtkTexture using `setTexture`. If a texture is
96+
* set, additional .mtl and .png files are generated.
97+
*/
98+
export declare const vtkOBJWriter: {
99+
newInstance: typeof newInstance;
100+
extend: typeof extend;
101+
writeOBJ: typeof writeOBJ;
102+
};
103+
export default vtkOBJWriter;

0 commit comments

Comments
 (0)