Commit f7d1b998 authored by Andrey Filippov's avatar Andrey Filippov

Started debugging 2-level triangulation

parent 0ef14ecf
...@@ -12037,7 +12037,10 @@ public class QuadCLTCPU { ...@@ -12037,7 +12037,10 @@ public class QuadCLTCPU {
boolean showTri = false; // ((scanIndex < next_pass + 1) && clt_parameters.show_triangles) ||((scanIndex - next_pass) == 73); boolean showTri = false; // ((scanIndex < next_pass + 1) && clt_parameters.show_triangles) ||((scanIndex - next_pass) == 73);
try { try {
generateClusterX3d( TriMesh.generateClusterX3d(
false, // boolean full_texture, // true - full size image, false - bounds only
0, // int subdivide_mesh, // 0,1 - full tiles only, 2 - 2x2 pixels, 4 - 2x2 pixels
// null, // boolean [] alpha, // boolean alpha - true - opaque, false - transparent. Full/bounds
x3dOutput, x3dOutput,
wfOutput, // output WSavefront if not null wfOutput, // output WSavefront if not null
tri_meshes, // ArrayList<TriMesh> tri_meshes, tri_meshes, // ArrayList<TriMesh> tri_meshes,
...@@ -12048,6 +12051,9 @@ public class QuadCLTCPU { ...@@ -12048,6 +12051,9 @@ public class QuadCLTCPU {
bgndScan.getSelected(), // selected, bgndScan.getSelected(), // selected,
scan_disparity, // scan.disparity_map[ImageDtt.DISPARITY_INDEX_CM], scan_disparity, // scan.disparity_map[ImageDtt.DISPARITY_INDEX_CM],
clt_parameters.transform_size, clt_parameters.transform_size,
tp.getTilesX(), // int tilesX,
tp.getTilesY(), // int tilesY,
getGeometryCorrection(), // GeometryCorrection geometryCorrection,
clt_parameters.correct_distortions, // requires backdrop image to be corrected also clt_parameters.correct_distortions, // requires backdrop image to be corrected also
showTri, // (scanIndex < next_pass + 1) && clt_parameters.show_triangles, showTri, // (scanIndex < next_pass + 1) && clt_parameters.show_triangles,
infinity_disparity, // 0.3 infinity_disparity, // 0.3
...@@ -12136,7 +12142,10 @@ public class QuadCLTCPU { ...@@ -12136,7 +12142,10 @@ public class QuadCLTCPU {
} }
boolean showTri = !batch_mode && (debugLevel > -1) && (((scanIndex < next_pass + 1) && clt_parameters.show_triangles) ||((scanIndex - next_pass) == 73)); boolean showTri = !batch_mode && (debugLevel > -1) && (((scanIndex < next_pass + 1) && clt_parameters.show_triangles) ||((scanIndex - next_pass) == 73));
try { try {
generateClusterX3d( TriMesh.generateClusterX3d(
false, // boolean full_texture, // true - full size image, false - bounds only
0, // int subdivide_mesh, // 0,1 - full tiles only, 2 - 2x2 pixels, 4 - 2x2 pixels
// null, // boolean [] alpha, // boolean alpha - true - opaque, false - transparent. Full/bounds
x3dOutput, x3dOutput,
wfOutput, // output WSavefront if not null wfOutput, // output WSavefront if not null
tri_meshes, // ArrayList<TriMesh> tri_meshes, tri_meshes, // ArrayList<TriMesh> tri_meshes,
...@@ -12147,6 +12156,9 @@ public class QuadCLTCPU { ...@@ -12147,6 +12156,9 @@ public class QuadCLTCPU {
scan.getSelected(), scan.getSelected(),
scan_disparity, // scan.disparity_map[ImageDtt.DISPARITY_INDEX_CM], scan_disparity, // scan.disparity_map[ImageDtt.DISPARITY_INDEX_CM],
clt_parameters.transform_size, clt_parameters.transform_size,
tp.getTilesX(), // int tilesX,
tp.getTilesY(), // int tilesY,
getGeometryCorrection(), // GeometryCorrection geometryCorrection,
clt_parameters.correct_distortions, // requires backdrop image to be corrected also clt_parameters.correct_distortions, // requires backdrop image to be corrected also
showTri, // (scanIndex < next_pass + 1) && clt_parameters.show_triangles, showTri, // (scanIndex < next_pass + 1) && clt_parameters.show_triangles,
// FIXME: make a separate parameter: // FIXME: make a separate parameter:
...@@ -12187,179 +12199,6 @@ public class QuadCLTCPU { ...@@ -12187,179 +12199,6 @@ public class QuadCLTCPU {
public void generateClusterX3d( // USED in lwir
X3dOutput x3dOutput, // output x3d if not null
WavefrontExport wfOutput, // output WSavefront if not null
ArrayList<TriMesh> tri_meshes,
String texturePath,
String id,
String class_name,
Rectangle bounds,
boolean [] selected,
double [] disparity, // if null, will use min_disparity
int tile_size,
boolean correctDistortions, // requires backdrop image to be corrected also
boolean show_triangles,
double min_disparity,
double max_disparity,
double maxDispTriangle, // relative <=0 - do not use
double maxZtoXY, // 10.0. <=0 - do not use
double maxZ, // far clip (0 - do not clip). Negative - limit by max
boolean limitZ,
double [][] dbg_disp_tri_slice,
int debug_level
) throws IOException
{
// int debug_level = 1;
if (bounds == null) {
return; // not used in lwir
}
int [][] indices = tp.getCoordIndices( // starting with 0, -1 - not selected // updated 09.18.2022
bounds,
selected);
double [][] texCoord = TileProcessor.getTexCoords( // get texture coordinates for indices
indices);
double [][] worldXYZ = tp.getCoords( // get world XYZ in meters for indices // updated 09.18.2022
disparity,
min_disparity,
max_disparity,
bounds,
indices,
tile_size,
correctDistortions, // requires backdrop image to be corrected also
this.geometryCorrection);
double [] indexedDisparity = tp.getIndexedDisparities( // get disparity for each index // updated 09.18.2022
disparity,
min_disparity,
max_disparity,
bounds,
indices,
tile_size);
int [][] triangles = TileProcessor.triangulateCluster(
indices);
// double maxZtoXY = 10.0; // maximal delta_z to sqrt(dx^23 + dy^2)
int num_removed = 0;
if (maxDispTriangle > 0.0) {
int pre_num = triangles.length;
triangles = TileProcessor.filterTriangles( // remove crazy triangles with large disparity difference
triangles,
indexedDisparity, // disparities per vertex index
maxDispTriangle, // maximal disparity difference in a triangle
debug_level + 0); // int debug_level);
if (triangles.length < pre_num) {
num_removed += pre_num - triangles.length;
if (debug_level > 0) {
System.out.println("filterTriangles() removed "+ (pre_num - triangles.length)+" triangles");
}
}
}
if ((maxZ != 0.0) && limitZ) {
for (int i = 0; i < worldXYZ.length; i++) {
if (worldXYZ[i][2] < -maxZ) {
double k = -maxZ/worldXYZ[i][2];
worldXYZ[i][0] *= k;
worldXYZ[i][1] *= k;
worldXYZ[i][2] *= k;
}
}
}
if ((maxZtoXY > 0.0) || ((maxZ != 0) && !limitZ) ) {
int pre_num = triangles.length;
triangles = TileProcessor.filterTrianglesWorld(
triangles,
worldXYZ, // world per vertex index
maxZtoXY,
maxZ);
if (triangles.length < pre_num) {
num_removed += pre_num - triangles.length;
if (debug_level > 0) {
System.out.println("filterTrianglesWorld() removed "+ (pre_num - triangles.length)+" triangles");
}
}
}
if (triangles.length == 0) {
if (debug_level > 0) {
System.out.println("generateClusterX3d() no triangles left in a cluster");
}
return; // all triangles removed
}
if (num_removed > 0) {
int [] re_index = TileProcessor.reIndex( // Move to TriMesh?
indices, // will be modified if needed (if some indices are removed
triangles);
if (re_index != null) {// need to update other arrays: texCoord, worldXYZ. indexedDisparity[] will not be used
int num_indices_old = worldXYZ.length;
int [] inv_index = new int [num_indices_old];
Arrays.fill(inv_index,-1); // just to get an error
for (int i = 0; i < re_index.length; i++) {
inv_index[re_index[i]] = i;
}
double [][] texCoord_new = new double [re_index.length][];
double [][] worldXYZ_new = new double [re_index.length][];
for (int i = 0; i < re_index.length; i++) {
texCoord_new[i] = texCoord[re_index[i]];
worldXYZ_new[i] = worldXYZ[re_index[i]];
}
texCoord = texCoord_new;
worldXYZ = worldXYZ_new;
for (int i = 0; i < triangles.length; i++) {
for (int j=0; j < triangles[i].length; j++) {
triangles[i][j] = inv_index[triangles[i][j]];
}
}
}
if (debug_level > 0) {
show_triangles = true; // show after removed
}
}
if (show_triangles || (dbg_disp_tri_slice != null)) {
double [] ddisp = (disparity == null)?(new double[1]):disparity;
if (disparity == null) {
ddisp[0] = min_disparity;
}
tp.testTriangles(
(show_triangles? texturePath: null),
bounds,
selected,
ddisp, // disparity, // if disparity.length == 1 - use for all
tile_size,
indices,
triangles,
dbg_disp_tri_slice); // double [][] debug_triangles);
}
if (x3dOutput != null) {
x3dOutput.addCluster(
texturePath,
id,
class_name,
texCoord,
worldXYZ,
triangles);
}
if (wfOutput != null) {
wfOutput.addCluster(
texturePath,
id,
// class_name,
texCoord,
worldXYZ,
triangles);
}
if (tri_meshes != null) {
tri_meshes.add(new TriMesh(
texturePath, // String texture_image,
worldXYZ, // double [][] worldXYZ,
texCoord, // double [][] texCoord,
triangles)); // int [][] triangles
}
}
// public ImagePlus getBackgroundImage( // USED in lwir // public ImagePlus getBackgroundImage( // USED in lwir
public boolean[] getBackgroundImageMasks( // USED in lwir public boolean[] getBackgroundImageMasks( // USED in lwir
...@@ -14690,7 +14529,7 @@ public class QuadCLTCPU { ...@@ -14690,7 +14529,7 @@ public class QuadCLTCPU {
true, // smart, true, // smart,
true); //newAllowed, // save true); //newAllowed, // save
double ts = Double.parseDouble(image_name.replace('_', '.')); double ts = Double.parseDouble(image_name.replace('_', '.'));
(new X3dOutput()).generateKML( X3dOutput.generateKML(
kml_copy_dir+ Prefs.getFileSeparator()+image_name+".kml", // String path, kml_copy_dir+ Prefs.getFileSeparator()+image_name+".kml", // String path,
false, // boolean overwrite, false, // boolean overwrite,
"", // String icon_path, //<href>x3d/1487451413_967079.x3d</href> ? "", // String icon_path, //<href>x3d/1487451413_967079.x3d</href> ?
......
...@@ -1829,6 +1829,8 @@ public class TexturedModel { ...@@ -1829,6 +1829,8 @@ public class TexturedModel {
final int ref_index = scenes.length - 1; final int ref_index = scenes.length - 1;
final QuadCLT ref_scene = scenes[ref_index]; final QuadCLT ref_scene = scenes[ref_index];
final TileProcessor tp = ref_scene.getTileProcessor(); final TileProcessor tp = ref_scene.getTileProcessor();
final boolean split_textures = (debugLevel > 1000); // false;
final int subdiv_tiles = 4; // subdivide tiles to smaller triangles
final boolean debug_disp_tri = !batch_mode && (debugLevel > 0); // TODO: use clt_parameters final boolean debug_disp_tri = !batch_mode && (debugLevel > 0); // TODO: use clt_parameters
if (ref_scene.image_data == null){ if (ref_scene.image_data == null){
...@@ -1858,7 +1860,7 @@ public class TexturedModel { ...@@ -1858,7 +1860,7 @@ public class TexturedModel {
final int tex_cluster_gap= 2; // gap between clusters Make clt_parameters final int tex_cluster_gap= 2; // gap between clusters Make clt_parameters
final double max_disparity_lim = 100.0; // do not allow stray disparities above this final double max_disparity_lim = 100.0; // do not allow stray disparities above this
final double min_trim_disparity = 2.0; // do not try to trim texture outlines with lower disparities final double min_trim_disparity = 2.0; // do not try to trim texture outlines with lower disparities
final int width = tilesX * transform_size; // for debug only final int width = tilesX * transform_size;
final int height = tp.getTilesY() * transform_size; final int height = tp.getTilesY() * transform_size;
// get multi-scene disparity map for FG and BG and filter it // get multi-scene disparity map for FG and BG and filter it
if (combo_dsn_final == null) { if (combo_dsn_final == null) {
...@@ -1928,6 +1930,7 @@ public class TexturedModel { ...@@ -1928,6 +1930,7 @@ public class TexturedModel {
disp_adiffj, // final double disp_adiffj, disp_adiffj, // final double disp_adiffj,
disp_rdiffj, // final double disp_rdiffj, disp_rdiffj, // final double disp_rdiffj,
debugLevel); //1); // 2); // final int debugLevel) debugLevel); //1); // 2); // final int debugLevel)
/*
// Debugging up to here: // Debugging up to here:
// if (debugLevel > -1000) { // if (debugLevel > -1000) {
// return false; // return false;
...@@ -1937,7 +1940,7 @@ public class TexturedModel { ...@@ -1937,7 +1940,7 @@ public class TexturedModel {
System.out.println("Temporary exit after clusterizeFgBg()"); System.out.println("Temporary exit after clusterizeFgBg()");
return false; return false;
} }
*/
boolean [] scenes_sel = new boolean[scenes.length]; boolean [] scenes_sel = new boolean[scenes.length];
// for (int i = scenes.length - 10; i < scenes.length; i++) { // start with just one (reference) scene // for (int i = scenes.length - 10; i < scenes.length; i++) { // start with just one (reference) scene
for (int i = 0; i < scenes.length; i++) { // start with just one (reference) scene for (int i = 0; i < scenes.length; i++) { // start with just one (reference) scene
...@@ -1956,11 +1959,10 @@ public class TexturedModel { ...@@ -1956,11 +1959,10 @@ public class TexturedModel {
boolean renormalize = true;// false - use normalizations from previous scenes to keep consistent colors boolean renormalize = true;// false - use normalizations from previous scenes to keep consistent colors
final boolean no_alpha = true; final boolean no_alpha = true;
ImagePlus[] combined_textures = getInterCombinedTextures( // return ImagePlus[] matching tileClusters[], with alpha
double[][][] faded_textures = getInterCombinedTextures( // return ImagePlus[] matching tileClusters[], with alpha
clt_parameters, // final CLTParameters clt_parameters, clt_parameters, // final CLTParameters clt_parameters,
colorProcParameters, // ColorProcParameters colorProcParameters, colorProcParameters, // ColorProcParameters colorProcParameters,
rgbParameters, // EyesisCorrectionParameters.RGBParameters rgbParameters,
no_alpha, // final boolean no_alpha,
parameter_scene, // final QuadCLT parameter_scene, // to use for rendering parameters in multi-series sequences parameter_scene, // final QuadCLT parameter_scene, // to use for rendering parameters in multi-series sequences
// if null - use reference scene // if null - use reference scene
ref_index, // final int ref_index, ref_index, // final int ref_index,
...@@ -1971,10 +1973,22 @@ public class TexturedModel { ...@@ -1971,10 +1973,22 @@ public class TexturedModel {
max_disparity_lim, // final double max_disparity_lim, // 100.0; // do not allow stray disparities above this max_disparity_lim, // final double max_disparity_lim, // 100.0; // do not allow stray disparities above this
min_trim_disparity, // final double min_trim_disparity, // 2.0; // do not try to trim texture outlines with lower disparities min_trim_disparity, // final double min_trim_disparity, // 2.0; // do not try to trim texture outlines with lower disparities
debugLevel); // final int debug_level) debugLevel); // final int debug_level)
ImagePlus[] combined_textures = getInterCombinedTextures( // return ImagePlus[] matching tileClusters[], with alpha
clt_parameters, // final CLTParameters clt_parameters,
no_alpha, // final boolean no_alpha,
scenes[ref_index], // QuadCLT ref_scene,
parameter_scene, // final QuadCLT parameter_scene, // to use for rendering parameters in multi-series sequences
faded_textures, // double [][][] faded_textures,
tilesX, // int tilesX,
ref_scene.getTileProcessor().getTilesY(), // int tilesY,
transform_size, // int transform_size,
debugLevel); // final int debug_level)
boolean save_full_textures = true; // false; // true; boolean save_full_textures = true; // false; // true;
EyesisCorrectionParameters.CorrectionParameters correctionsParameters = ref_scene.correctionsParameters; EyesisCorrectionParameters.CorrectionParameters correctionsParameters = ref_scene.correctionsParameters;
String x3d_dir = ref_scene.getX3dDirectory(); String x3d_dir = ref_scene.getX3dDirectory();
if (save_full_textures) { if (save_full_textures || !split_textures) {
for (int nslice = 0; nslice < combined_textures.length; nslice++) { for (int nslice = 0; nslice < combined_textures.length; nslice++) {
EyesisCorrections.saveAndShow( EyesisCorrections.saveAndShow(
combined_textures[nslice], // imp_texture_cluster, combined_textures[nslice], // imp_texture_cluster,
...@@ -1985,9 +1999,20 @@ public class TexturedModel { ...@@ -1985,9 +1999,20 @@ public class TexturedModel {
1); // 1); //
} }
} }
double alpha_threshold = 0.5;
boolean [][] combined_alphas = null;
if (subdiv_tiles > 0) {
combined_alphas = new boolean [faded_textures.length][faded_textures[0][0].length];
for (int i = 0; i < faded_textures.length; i++) { // TODO: accelerate with multi
for (int j = 0; j < faded_textures[i][1].length; j++) {
combined_alphas[i][j] = faded_textures[i][1][j] > alpha_threshold;
}
}
}
ImagePlus [] imp_textures = null;
if (split_textures) {
// Maybe will switch to combined textures (less files) // Maybe will switch to combined textures (less files)
ImagePlus [] imp_textures = splitCombinedTextures( imp_textures = splitCombinedTextures(
tileClusters, // TileCluster [] tileClusters, //should have name <timestamp>-* tileClusters, // TileCluster [] tileClusters, //should have name <timestamp>-*
transform_size, // int transform_size, transform_size, // int transform_size,
combined_textures); // ImagePlus [] combo_textures ) combined_textures); // ImagePlus [] combo_textures )
...@@ -2000,7 +2025,7 @@ public class TexturedModel { ...@@ -2000,7 +2025,7 @@ public class TexturedModel {
-1, // jpegQuality){// <0 - keep current, 0 - force Tiff, >0 use for JPEG -1, // jpegQuality){// <0 - keep current, 0 - force Tiff, >0 use for JPEG
1); // 1); //
} }
}
// create x3d file // create x3d file
if (clt_parameters.output_x3d) { if (clt_parameters.output_x3d) {
x3dOutput = new X3dOutput( x3dOutput = new X3dOutput(
...@@ -2025,7 +2050,7 @@ public class TexturedModel { ...@@ -2025,7 +2050,7 @@ public class TexturedModel {
// do nothing, just keep // do nothing, just keep
} }
} }
if (clt_parameters.output_glTF && (x3d_dir != null)) { if ((clt_parameters.output_x3d || clt_parameters.output_glTF) && (x3d_dir != null)) {
tri_meshes = new ArrayList<TriMesh>(); tri_meshes = new ArrayList<TriMesh>();
} }
...@@ -2046,21 +2071,30 @@ public class TexturedModel { ...@@ -2046,21 +2071,30 @@ public class TexturedModel {
final double [][] dbg_tri_disp = debug_disp_tri? (new double [tileClusters.length][width*height]): null; final double [][] dbg_tri_disp = debug_disp_tri? (new double [tileClusters.length][width*height]): null;
final double [][] dbg_tri_tri = debug_disp_tri? (new double [tileClusters.length][width*height]): null; final double [][] dbg_tri_tri = debug_disp_tri? (new double [tileClusters.length][width*height]): null;
boolean showTri = !batch_mode && (debugLevel > -1) && (clt_parameters.show_triangles);
int dbg_scale_mesh = 4; // <=0 - do not show
int dbg_scaled_width = tp.getTilesX() * transform_size * dbg_scale_mesh;
int dbg_scaled_height = tp.getTilesY() * transform_size * dbg_scale_mesh;
double [][] dbg_mesh_imgs = null;
if (dbg_scale_mesh > 0) {
dbg_mesh_imgs = new double[tileClusters.length][dbg_scaled_width * dbg_scaled_height];
// maybe fill with NaN?
}
// double [][] dbg
/*
tp.getTilesX(), // int tilesX,
tp.getTilesY(), // int tilesY,
*/
for (int nslice = 0; nslice < tileClusters.length; nslice++){ for (int nslice = 0; nslice < tileClusters.length; nslice++){
if (dbg_tri_disp != null) { if (dbg_tri_disp != null) {
Arrays.fill(dbg_tri_disp[nslice], Double.NaN); Arrays.fill(dbg_tri_disp[nslice], Double.NaN);
} }
// keep them all 0, not NaN
// if (dbg_tri_tri != null) {
// Arrays.fill(dbg_tri_tri[nslice], Double.NaN);
// }
final double [][] dbg_disp_tri_slice = (dbg_tri_tri != null) ? final double [][] dbg_disp_tri_slice = (dbg_tri_tri != null) ?
((dbg_tri_disp != null)? (new double[][] {dbg_tri_disp[nslice], dbg_tri_tri[nslice]}): ((dbg_tri_disp != null)? (new double[][] {dbg_tri_disp[nslice], dbg_tri_tri[nslice]}):
(new double[][] {dbg_tri_tri[nslice]}) ): null; (new double[][] {dbg_tri_tri[nslice]}) ): null;
if (debugLevel > -1){ if (debugLevel > -1){
// System.out.println("Generating cluster images (limit is set to "+clt_parameters.max_clusters+") largest, scan #"+scanIndex);
System.out.println("Generating cluster images from texture slice "+nslice); System.out.println("Generating cluster images from texture slice "+nslice);
} }
int [] indices = tileClusters[nslice].getSubIndices(); int [] indices = tileClusters[nslice].getSubIndices();
...@@ -2069,20 +2103,37 @@ public class TexturedModel { ...@@ -2069,20 +2103,37 @@ public class TexturedModel {
for (int sub_i = 0; sub_i < indices.length; sub_i++) { for (int sub_i = 0; sub_i < indices.length; sub_i++) {
Rectangle roi = bounds[sub_i]; Rectangle roi = bounds[sub_i];
int cluster_index = indices[sub_i]; int cluster_index = indices[sub_i];
ImagePlus imp_texture_cluster = imp_textures[cluster_index]; ImagePlus imp_texture_cluster = combined_textures[nslice];
if (imp_textures[cluster_index] == null) { boolean [] alpha = (combined_alphas != null) ? combined_alphas[nslice] : null;
if (imp_textures != null) {
imp_texture_cluster = imp_textures[cluster_index];
if (combined_alphas != null) {
alpha = new boolean[roi.height * roi.width];
for (int row = 0; row < roi.height; row++) {
System.arraycopy(
combined_alphas[nslice],
(roi.y + row) * width + roi.x,
alpha,
row * roi.width,
roi.width);
}
}
}
if (imp_texture_cluster == null) {
if (debugLevel > -1){ if (debugLevel > -1){
System.out.println("Empty cluster #"+cluster_index); System.out.println("Empty cluster #"+cluster_index);
} }
continue; continue;
} }
String texturePath = imp_texture_cluster.getTitle()+".png"; String texturePath = imp_texture_cluster.getTitle()+".png";
double [] scan_disparity = tileClusters[nslice].getSubDisparity(sub_i); double [] scan_disparity = tileClusters[nslice].getSubDisparity(sub_i); // limited to cluster bounds
boolean [] scan_selected = tileClusters[nslice].getSubSelected(sub_i); boolean [] scan_selected = tileClusters[nslice].getSubSelected(sub_i); // limited to cluster bounds
// skipping averaging disparity fro a whole cluster (needs strength and does not seem to be useful) // skipping averaging disparity for a whole cluster (needs strength and does not seem to be useful)
boolean showTri = !batch_mode && (debugLevel > -1) && (clt_parameters.show_triangles) && (cluster_index == dbg_tri_indx);
try { try {
ref_scene.generateClusterX3d( // also generates wavefront obj if (alpha == null) {
TriMesh.generateClusterX3d( // old version also generates wavefront obj
(imp_textures == null), // boolean full_texture, // true - full size image, false - bounds only
0, // int subdivide_mesh, // 0,1 - full tiles only, 2 - 2x2 pixels, 4 - 2x2 pixels
x3dOutput, x3dOutput,
wfOutput, // output WSavefront if not null wfOutput, // output WSavefront if not null
tri_meshes, // ArrayList<TriMesh> tri_meshes, tri_meshes, // ArrayList<TriMesh> tri_meshes,
...@@ -2093,8 +2144,11 @@ public class TexturedModel { ...@@ -2093,8 +2144,11 @@ public class TexturedModel {
scan_selected, // scan.getSelected(), scan_selected, // scan.getSelected(),
scan_disparity, // scan.disparity_map[ImageDtt.DISPARITY_INDEX_CM], scan_disparity, // scan.disparity_map[ImageDtt.DISPARITY_INDEX_CM],
clt_parameters.transform_size, clt_parameters.transform_size,
tp.getTilesX(), // int tilesX,
tp.getTilesY(), // int tilesY,
ref_scene.getGeometryCorrection(), // GeometryCorrection geometryCorrection,
clt_parameters.correct_distortions, // requires backdrop image to be corrected also clt_parameters.correct_distortions, // requires backdrop image to be corrected also
showTri, // (scanIndex < next_pass + 1) && clt_parameters.show_triangles, showTri && (cluster_index == dbg_tri_indx), // (scanIndex < next_pass + 1) && clt_parameters.show_triangles,
// FIXME: make a separate parameter: // FIXME: make a separate parameter:
infinity_disparity, // 0.25 * clt_parameters.bgnd_range, // 0.3 infinity_disparity, // 0.25 * clt_parameters.bgnd_range, // 0.3
clt_parameters.grow_disp_max, // other_range, // 2.0 'other_range - difference from the specified (*_CM) clt_parameters.grow_disp_max, // other_range, // 2.0 'other_range - difference from the specified (*_CM)
...@@ -2104,6 +2158,38 @@ public class TexturedModel { ...@@ -2104,6 +2158,38 @@ public class TexturedModel {
clt_parameters.limitZ, clt_parameters.limitZ,
dbg_disp_tri_slice, // double [][] dbg_disp_tri_slice, dbg_disp_tri_slice, // double [][] dbg_disp_tri_slice,
debugLevel + 1); // int debug_level) > 0 debugLevel + 1); // int debug_level) > 0
} else {
TriMesh.generateClusterX3d( // new version with small triangles for alpha also generates wavefront obj
(imp_textures == null), // boolean full_texture, // true - full size image, false - bounds only
subdiv_tiles, // int subdivide_mesh, // 0,1 - full tiles only, 2 - 2x2 pixels, 4 - 2x2 pixels
alpha, // boolean [] alpha, // boolean alpha - true - opaque, false - transparent. Full/bounds
x3dOutput,
wfOutput, // output WSavefront if not null
tri_meshes, // ArrayList<TriMesh> tri_meshes,
texturePath,
"shape_id-"+cluster_index, // id
null, // class
roi, // scan.getTextureBounds(),
scan_selected, // scan.getSelected(),
scan_disparity, // scan.disparity_map[ImageDtt.DISPARITY_INDEX_CM],
clt_parameters.transform_size,
tp.getTilesX(), // int tilesX,
tp.getTilesY(), // int tilesY,
ref_scene.getGeometryCorrection(), // GeometryCorrection geometryCorrection,
clt_parameters.correct_distortions, // requires backdrop image to be corrected also
dbg_mesh_imgs[nslice], // double [] tri_img, //
dbg_scaled_width, // int tri_img_width,
// showTri, // (scanIndex < next_pass + 1) && clt_parameters.show_triangles,
// FIXME: make a separate parameter:
infinity_disparity, // 0.25 * clt_parameters.bgnd_range, // 0.3
clt_parameters.grow_disp_max, // other_range, // 2.0 'other_range - difference from the specified (*_CM)
clt_parameters.maxDispTriangle,
clt_parameters.maxZtoXY, // double maxZtoXY, // 10.0. <=0 - do not use
clt_parameters.maxZ,
clt_parameters.limitZ,
// dbg_disp_tri_slice, // double [][] dbg_disp_tri_slice,
debugLevel + 1); // int debug_level) > 0
}
//dbg_disp_tri_slice //dbg_disp_tri_slice
} catch (IOException e) { } catch (IOException e) {
e.printStackTrace(); e.printStackTrace();
...@@ -2145,6 +2231,12 @@ public class TexturedModel { ...@@ -2145,6 +2231,12 @@ public class TexturedModel {
ref_scene.getImageName()+"-mesh_disparity_triangles", ref_scene.getImageName()+"-mesh_disparity_triangles",
dbg_titles); dbg_titles);
} }
boolean exit_now = (debugLevel > 1000);
if (exit_now) {
return false;
}
if ((x3d_dir != null) && (x3dOutput != null)){ if ((x3d_dir != null) && (x3dOutput != null)){
x3dOutput.generateX3D(x3d_dir+Prefs.getFileSeparator() + ref_scene.correctionsParameters.getModelName(ref_scene.getImageName())+".x3d"); x3dOutput.generateX3D(x3d_dir+Prefs.getFileSeparator() + ref_scene.correctionsParameters.getModelName(ref_scene.getImageName())+".x3d");
...@@ -2155,7 +2247,7 @@ public class TexturedModel { ...@@ -2155,7 +2247,7 @@ public class TexturedModel {
System.out.println("Wavefront object file saved to "+wfOutput.obj_path); System.out.println("Wavefront object file saved to "+wfOutput.obj_path);
System.out.println("Wavefront material file saved to "+wfOutput.mtl_path); System.out.println("Wavefront material file saved to "+wfOutput.mtl_path);
} }
if (tri_meshes != null) { if (clt_parameters.output_glTF && (tri_meshes != null)) {
try { try {
GlTfExport.glTFExport( GlTfExport.glTFExport(
x3d_dir, // String x3d_dir, x3d_dir, // String x3d_dir,
...@@ -2967,7 +3059,7 @@ public class TexturedModel { ...@@ -2967,7 +3059,7 @@ public class TexturedModel {
final double min_edge_variance, final double min_edge_variance,
final int width, final int width,
final int transform_size) { final int transform_size) {
boolean dbg = true; boolean dbg = false; // true;
final int num_slices = texture_en.length; final int num_slices = texture_en.length;
final int img_size = texture_en[0].length; final int img_size = texture_en[0].length;
final int tilesX = width/transform_size; final int tilesX = width/transform_size;
...@@ -3030,7 +3122,6 @@ public class TexturedModel { ...@@ -3030,7 +3122,6 @@ public class TexturedModel {
dbg_img[4][i] = erase[i]? 1.0 : 0.0; dbg_img[4][i] = erase[i]? 1.0 : 0.0;
dbg_img[5][i] = texture_en[nslice][i]? 1.0 : 0.0; dbg_img[5][i] = texture_en[nslice][i]? 1.0 : 0.0;
} }
}
ShowDoubleFloatArrays.showArrays( ShowDoubleFloatArrays.showArrays(
dbg_img, dbg_img,
...@@ -3041,6 +3132,7 @@ public class TexturedModel { ...@@ -3041,6 +3132,7 @@ public class TexturedModel {
dbg_titles); dbg_titles);
} }
} }
}
}; };
} }
ImageDtt.startAndJoin(threads); ImageDtt.startAndJoin(threads);
...@@ -4111,11 +4203,12 @@ public class TexturedModel { ...@@ -4111,11 +4203,12 @@ public class TexturedModel {
* @param debugLevel debug level - controls generation of images. * @param debugLevel debug level - controls generation of images.
* @return array of ImagePlus instances corresponding to tileClusters array * @return array of ImagePlus instances corresponding to tileClusters array
*/ */
public static ImagePlus[] getInterCombinedTextures( // return ImagePlus[] matching tileClusters[], with alpha // public static ImagePlus[] getInterCombinedTextures( // return ImagePlus[] matching tileClusters[], with alpha
public static double[][][] getInterCombinedTextures( // return ImagePlus[] matching tileClusters[], with alpha
final CLTParameters clt_parameters, final CLTParameters clt_parameters,
ColorProcParameters colorProcParameters, ColorProcParameters colorProcParameters,
EyesisCorrectionParameters.RGBParameters rgbParameters, // EyesisCorrectionParameters.RGBParameters rgbParameters,
final boolean no_alpha, // final boolean no_alpha,
QuadCLT parameter_scene, QuadCLT parameter_scene,
final int ref_index, final int ref_index,
final QuadCLT [] scenes, final QuadCLT [] scenes,
...@@ -4158,8 +4251,8 @@ public class TexturedModel { ...@@ -4158,8 +4251,8 @@ public class TexturedModel {
final double tex_hist_amount = clt_parameters.tex_hist_amount; // clt_parameters. 0.7; final double tex_hist_amount = clt_parameters.tex_hist_amount; // clt_parameters. 0.7;
final int tex_hist_bins = clt_parameters.tex_hist_bins; // 1024 ; final int tex_hist_bins = clt_parameters.tex_hist_bins; // 1024 ;
final int tex_hist_segments = clt_parameters.tex_hist_segments; // 32 ; final int tex_hist_segments = clt_parameters.tex_hist_segments; // 32 ;
final boolean tex_color = clt_parameters.tex_color; // true; /// final boolean tex_color = clt_parameters.tex_color; // true;
final int tex_palette = clt_parameters.tex_palette; // 2 ; /// final int tex_palette = clt_parameters.tex_palette; // 2 ;
// final boolean extend_sky = true; // final boolean extend_sky = true;
final int shrink_sky_tiles = 4; // 2; sum of 2 +bg extend final int shrink_sky_tiles = 4; // 2; sum of 2 +bg extend
final boolean grow_sky = true; final boolean grow_sky = true;
...@@ -4412,7 +4505,7 @@ public class TexturedModel { ...@@ -4412,7 +4505,7 @@ public class TexturedModel {
tileClusters, // final TileCluster[] tileClusters, // to process blue_sky? tileClusters, // final TileCluster[] tileClusters, // to process blue_sky?
max_disparity_lim, // final double max_disparity_lim, // do not allow stray disparities above this max_disparity_lim, // final double max_disparity_lim, // do not allow stray disparities above this
min_trim_disparity, // final double min_trim_disparity, // do not try to trim texture outlines with lower disparities min_trim_disparity, // final double min_trim_disparity, // do not try to trim texture outlines with lower disparities
ref_scene.getImageName()); // null); // ref_scene.getImageName()); // final String dbg_prefix); null); // ref_scene.getImageName()); // null); // ref_scene.getImageName()); // final String dbg_prefix);
if (debugLevel > -1) { if (debugLevel > -1) {
double [][] dbg_textures = new double [faded_textures.length * faded_textures[0].length][faded_textures[0][0].length]; double [][] dbg_textures = new double [faded_textures.length * faded_textures[0].length][faded_textures[0][0].length];
String [] dbg_titles = new String[dbg_textures.length]; String [] dbg_titles = new String[dbg_textures.length];
...@@ -4618,7 +4711,8 @@ public class TexturedModel { ...@@ -4618,7 +4711,8 @@ public class TexturedModel {
ref_scene.getImageName()+"-texture_weights"); ref_scene.getImageName()+"-texture_weights");
} }
} }
return faded_textures;
/*
double [] minmax = parameter_scene.getColdHot(); // used in linearStackToColor double [] minmax = parameter_scene.getColdHot(); // used in linearStackToColor
ImagePlus [] imp_tex = new ImagePlus[num_slices]; ImagePlus [] imp_tex = new ImagePlus[num_slices];
for (int nslice = 0; nslice < num_slices; nslice++) { for (int nslice = 0; nslice < num_slices; nslice++) {
...@@ -4647,6 +4741,53 @@ public class TexturedModel { ...@@ -4647,6 +4741,53 @@ public class TexturedModel {
} }
// Process accumulated textures: average, apply borders, convert to color or apply UM, add synthetic mesh, ... // Process accumulated textures: average, apply borders, convert to color or apply UM, add synthetic mesh, ...
return imp_tex; // ImagePlus[] ? with alpha, to be split into png and saved with alpha. return imp_tex; // ImagePlus[] ? with alpha, to be split into png and saved with alpha.
*/
}
public static ImagePlus[] getInterCombinedTextures( // return ImagePlus[] matching tileClusters[], with alpha
final CLTParameters clt_parameters,
boolean no_alpha,
QuadCLT ref_scene,
QuadCLT parameter_scene,
double [][][] faded_textures,
int tilesX,
int tilesY,
int transform_size,
int debugLevel)
{
final boolean tex_color = clt_parameters.tex_color; // true;
final int tex_palette = clt_parameters.tex_palette; // 2 ;
int num_slices = faded_textures.length;
if (parameter_scene == null) {
parameter_scene = ref_scene;
}
double [] minmax = parameter_scene.getColdHot(); // used in linearStackToColor
ImagePlus [] imp_tex = new ImagePlus[num_slices];
for (int nslice = 0; nslice < num_slices; nslice++) {
String title=String.format("%s-combo%03d-texture",ref_scene.getImageName(), nslice);
double [][] rendered_texture = faded_textures[nslice].clone(); // shallow !
if (no_alpha) {
rendered_texture[1] = new double [rendered_texture[0].length];
for (int i = 0; i < rendered_texture[0].length; i++) {
rendered_texture[1][i] = Double.isNaN(rendered_texture[0][i])? 0.0: 1.0;
}
}
imp_tex[nslice] = QuadCLTCPU.linearStackToColorLWIR(
clt_parameters, // CLTParameters clt_parameters,
tex_palette, // int lwir_palette, // <0 - do not convert
minmax, // double [] minmax,
title, // String name,
"", // String suffix, // such as disparity=...
tex_color, // boolean toRGB,
rendered_texture, // faded_textures[nslice], // double [][] texture_data,
tilesX * transform_size, // int width, // int tilesX,
tilesY * transform_size, // int height, // int tilesY,
debugLevel); // int debugLevel )
// Add synthetic mesh only with higher resolution? or just any by a specified period?what king of mesh - vertical random, ...
// Split and save as png
}
// Process accumulated textures: average, apply borders, convert to color or apply UM, add synthetic mesh, ...
return imp_tex; // ImagePlus[] ? with alpha, to be split into png and saved with alpha.
} }
......
...@@ -40,7 +40,9 @@ public class TileNeibs{ ...@@ -40,7 +40,9 @@ public class TileNeibs{
final public static int DIR_LEFT = 2; // Right final public static int DIR_LEFT = 2; // Right
final public static int DIR_DOWN = 4; // Down final public static int DIR_DOWN = 4; // Down
final public static int DIR_RIGHT = 6; // Left final public static int DIR_RIGHT = 6; // Left
final public static int DIRS = 8; // total dirs final public static int [][] DIR_XY = {{0,-1}, {1,-1}, {1,0}, {1,1}, {0,1}, {-1,1}, {-1,0}, {-1,-1}};
final public static int DIRS = DIR_XY.length; //8; // total dirs
public static int reverseDir(int dir) { public static int reverseDir(int dir) {
if ((dir < 0) || (dir >= DIRS)) { if ((dir < 0) || (dir >= DIRS)) {
......
...@@ -8619,167 +8619,9 @@ ImageDtt.startAndJoin(threads); ...@@ -8619,167 +8619,9 @@ ImageDtt.startAndJoin(threads);
return rslt; return rslt;
} }
public int [][] getCoordIndices( // starting with 0, -1 - not selected
Rectangle bounds,
boolean [] selected
)
{
int [][] indices = new int [bounds.height][bounds.width];
int indx = 0;
if (selected.length > (bounds.height * bounds.width)) { // old version - selected is full size
for (int y = 0; y < bounds.height; y++) {
for (int x = 0; x < bounds.width; x++){
if (selected[this.tilesX * (bounds.y + y) + (bounds.x + x)]){
indices[y][x] = indx++;
} else {
indices[y][x] = -1;
}
}
}
} else { // 09.18.2022
for (int y = 0; y < bounds.height; y++) {
for (int x = 0; x < bounds.width; x++){
if (selected[bounds.width * y + x]){
indices[y][x] = indx++;
} else {
indices[y][x] = -1;
}
}
}
}
return indices;
}
public static double [][] getTexCoords( // get texture coordinates for indices // preparing to move to TriMesh
int [][] indices)
{
int maxIndex = -1;
int height = indices.length;
int width = indices[0].length;
outer_label:{
for (int y = height - 1 ; y >= 0; y--) {
for (int x = width - 1; x >= 0; x--){
if (indices[y][x] >=0){
maxIndex = indices[y][x];
break outer_label;
}
}
}
}
double [][] textureCoordinate = new double [maxIndex+1][2];
int indx = 0;
for (int y = 0; indx <= maxIndex; y++) {
for (int x = 0; (x < width) && (indx <= maxIndex); x++){
if (indices[y][x] >=0){
textureCoordinate[indx][0] = (x + 0.5)/width;
textureCoordinate[indx][1] = (height - y - 0.5) / height; // y is up
indx ++;
}
}
}
return textureCoordinate;
}
public double [] getIndexedDisparities( // get disparity for each index
double [] disparity,
double min_disparity,
double max_disparity,
Rectangle bounds,
int [][] indices,
int tile_size)
{
// int height = indices.length;
int width = indices[0].length;
int maxIndex = getMaxIndex(indices);
double [] indexedDisparity = new double [maxIndex+1];
int indx = 0;
if (disparity.length > (bounds.height * bounds.width)) { // old version - selected is full size
for (int y = 0; indx <= maxIndex; y++) {
for (int x = 0; (x < width) && (indx <= maxIndex); x++){
if (indices[y][x] >=0){
// center coordinates for 8*8 tile is [3.5,3.5]
double disp = (disparity == null)? min_disparity:( disparity[(bounds.y + y) * tilesX + (bounds.x + x)]);
if (disp < min_disparity) disp = min_disparity;
else if (disp > max_disparity) disp = max_disparity;
indexedDisparity[indx] =disp;
indx ++;
}
}
}
} else { // 09.18.2022
for (int y = 0; indx <= maxIndex; y++) {
for (int x = 0; (x < width) && (indx <= maxIndex); x++){
if (indices[y][x] >=0){
// center coordinates for 8*8 tile is [3.5,3.5]
double disp = (disparity == null)? min_disparity:( disparity[bounds.width *y + x]);
if (disp < min_disparity) disp = min_disparity;
else if (disp > max_disparity) disp = max_disparity;
indexedDisparity[indx] =disp;
indx ++;
}
}
}
}
return indexedDisparity;
}
public double [][] getCoords( // get world XYZ in meters for indices
double [] disparity, // null - use min_disparity
double min_disparity,
double max_disparity,
Rectangle bounds,
int [][] indices,
int tile_size,
boolean correctDistortions, // requires backdrop image to be corrected also
GeometryCorrection geometryCorrection)
{
// int height = indices.length;
int width = indices[0].length;
int maxIndex = getMaxIndex(indices);
double [][] coordinate = new double [maxIndex+1][];
int indx = 0;
if (disparity.length > (bounds.height * bounds.width)) { // old version - selected is full size
for (int y = 0; indx <= maxIndex; y++) {
for (int x = 0; (x < width) && (indx <= maxIndex); x++){
if (indices[y][x] >=0){
// center coordinates for 8*8 tile is [3.5,3.5]
double px = (bounds.x + x + 0.5) * tile_size - 0.5;
double py = (bounds.y + y + 0.5) * tile_size - 0.5;
double disp = (disparity == null)? min_disparity:( disparity[(bounds.y + y) * tilesX + (bounds.x + x)]);
if (disp < min_disparity) disp = min_disparity;
else if (disp > max_disparity) disp = max_disparity;
coordinate[indx] = geometryCorrection.getWorldCoordinates(
px,
py,
disp,
correctDistortions);
indx ++;
}
}
}
} else { // 09.18.2022
for (int y = 0; indx <= maxIndex; y++) {
for (int x = 0; (x < width) && (indx <= maxIndex); x++){
if (indices[y][x] >=0){
// center coordinates for 8*8 tile is [3.5,3.5]
double px = (bounds.x + x + 0.5) * tile_size - 0.5;
double py = (bounds.y + y + 0.5) * tile_size - 0.5;
double disp = (disparity == null)? min_disparity:( disparity[bounds.width * y + x]);
if (disp < min_disparity) disp = min_disparity;
else if (disp > max_disparity) disp = max_disparity;
coordinate[indx] = geometryCorrection.getWorldCoordinates(
px,
py,
disp,
correctDistortions);
indx ++;
}
}
}
}
return coordinate;
}
static int getMaxIndex(int [][] indices) static int getMaxIndex(int [][] indices)
{ {
...@@ -8795,292 +8637,6 @@ ImageDtt.startAndJoin(threads); ...@@ -8795,292 +8637,6 @@ ImageDtt.startAndJoin(threads);
return -1; return -1;
} }
public static int [][] filterTriangles(
int [][] triangles,
double [] disparity, // disparities per vertex index
double maxDispDiff, // maximal relative disparity difference in a triangle
int debug_level)
{
final double min_avg = 3.0; // 0.5; // minimal average disparity to normalize triangle
class Triangle {
int [] points = new int [3];
Triangle (int i1, int i2, int i3){
points[0] = i1;
points[1] = i2;
points[2] = i3;
}
}
ArrayList<Triangle> triList = new ArrayList<Triangle>();
for (int i = 0; i < triangles.length; i++){
double disp_avg = (disparity[triangles[i][0]] + disparity[triangles[i][1]]+ disparity[triangles[i][2]])/3.0; // fixed 09.18.2022!
if (disp_avg < min_avg) disp_avg = min_avg;
loop:{
for (int j = 0; j < 3; j++){
int j1 = (j + 1) % 3;
if (Math.abs(disparity[triangles[i][j]] - disparity[triangles[i][j1]]) > (disp_avg* maxDispDiff)) {
if (debug_level > 1) {
System.out.println("removed triangle "+i+": "+
disparity[triangles[i][0]]+". "+disparity[triangles[i][1]]+". "+disparity[triangles[i][2]]+
". Avg = "+disp_avg);
}
break loop;
}
}
triList.add(new Triangle(
triangles[i][0],
triangles[i][1],
triangles[i][2]));
}
}
int [][] filteredTriangles = new int [triList.size()][3];
for (int i = 0; i < filteredTriangles.length; i++){
filteredTriangles[i] = triList.get(i).points;
}
return filteredTriangles;
}
public static int [][] filterTrianglesWorld(
int [][] triangles,
double [][] worldXYZ, // world per vertex index
double maxZtoXY,
double maxZ)
{
final double maxZtoXY2 = maxZtoXY * maxZtoXY;
class Triangle {
int [] points = new int [3];
Triangle (int i1, int i2, int i3){
points[0] = i1;
points[1] = i2;
points[2] = i3;
}
}
ArrayList<Triangle> triList = new ArrayList<Triangle>();
for (int i = 0; i < triangles.length; i++){
double [][] min_max = new double[3][2];
boolean not_too_far = true;
for (int di = 0; di < 3; di++) {
min_max[di][0] = worldXYZ[triangles[i][0]][di];
min_max[di][1] = min_max[di][0]; // both min and max to the same vertex 0
}
if (maxZ != 0) {
not_too_far &= worldXYZ[triangles[i][0]][2] > -maxZ;
}
for (int vi = 1; vi < 3; vi++) {
for (int di = 0; di < 3; di++) {
min_max[di][0] = Math.min(min_max[di][0], worldXYZ[triangles[i][vi]][di]);
min_max[di][1] = Math.max(min_max[di][1], worldXYZ[triangles[i][vi]][di]);
}
}
double dx = min_max[0][1]-min_max[0][0];
double dy = min_max[1][1]-min_max[1][0];
double dz = min_max[2][1]-min_max[2][0];
double ratio2 = dz*dz/(dx*dx+dy*dy + 0.001);
if (not_too_far && ((maxZtoXY == 0) || (ratio2 < maxZtoXY2))) {
triList.add(new Triangle(
triangles[i][0],
triangles[i][1],
triangles[i][2]));
}
}
int [][] filteredTriangles = new int [triList.size()][3];
for (int i = 0; i < filteredTriangles.length; i++){
filteredTriangles[i] = triList.get(i).points;
}
return filteredTriangles;
}
public static int [] reIndex(
int [][] indices,
int [][] triangles) {
int last_index = -1;
for (int i = 0; i < indices.length; i++) {
for (int j = 0; j < indices[i].length; j++) {
if (indices[i][j] > last_index) {
last_index = indices[i][j];
}
}
}
boolean [] used_indices = new boolean[last_index+1];
for (int i = 0; i < triangles.length; i++) {
for (int j = 0; j < triangles[i].length; j++) { // always 3
used_indices[triangles[i][j]] = true;
}
}
int new_len = 0;
for (int i = 0; i < used_indices.length; i++) if (used_indices[i]) {
new_len++;
}
if (new_len == used_indices.length) {
return null; // no re-indexing is needed
}
int [] re_index = new int [new_len];
int indx = 0;
for (int i = 0; i < indices.length; i++) {
for (int j = 0; j < indices[i].length; j++) {
int old_index=indices[i][j];
if (old_index >= 0) {
if (used_indices[old_index]) { // keep
re_index[indx] = old_index;
indices[i][j] = indx++;
} else {
indices[i][j] = -1;
}
}
}
}
return re_index;
}
public static int [][] triangulateCluster(
int [][] indices)
{
int height = indices.length;
int width = indices[0].length;
class Triangle {
int [] points = new int [3];
Triangle (int i1, int i2, int i3){
points[0] = i1;
points[1] = i2;
points[2] = i3;
}
}
ArrayList<Triangle> triList = new ArrayList<Triangle>();
for (int y = 0; y < (height - 1); y++){
for (int x = 0; x < width; x++){
if (indices[y][x] >= 0){
if ((x > 0) && (indices[y + 1][x - 1] >= 0) && (indices[y + 1][x] >= 0)){
triList.add(new Triangle(
indices[y][x],
indices[y + 1][x],
indices[y + 1][x - 1]));
}
if (x < (width - 1)) {
if (indices[y + 1][x] >= 0){
if (indices[y][x + 1] >= 0){
triList.add(new Triangle(
indices[y][x],
indices[y][x + 1],
indices[y + 1][x]));
} else if (indices[y + 1][x + 1] >= 0){
triList.add(new Triangle(
indices[y][x],
indices[y + 1][x + 1],
indices[y + 1][x]));
}
} else if ((indices[y][x + 1] >= 0) && (indices[y + 1][x + 1] >= 0)) {
triList.add(new Triangle(
indices[y][x],
indices[y][x + 1],
indices[y + 1][x + 1]));
}
}
}
}
}
int [][] triangles = new int [triList.size()][3];
for (int i = 0; i < triangles.length; i++){
triangles[i] = triList.get(i).points;
}
return triangles;
}
static int iSign (int a) {return (a > 0) ? 1 : ((a < 0)? -1 : 0);}
public void testTriangles(
String texturePath, // if not null - will show
Rectangle bounds,
boolean [] selected,
double [] disparity,
int tile_size,
int [][] indices,
int [][] triangles,
double [][] debug_triangles) // if not null - should be [2][width* height], will mark disparity and triangles
{
String [] titles = {"disparity","triangles"};
double [][] dbg_img = new double [titles.length][tilesX*tilesY*tile_size*tile_size];
Arrays.fill(dbg_img[0], Double.NaN);
if (selected.length > (bounds.height * bounds.width)) { // old version - selected is full size
for (int i = 0; i < selected.length; i++ ){
double d = selected[i]? ((disparity.length >1) ? disparity[i] : disparity[0]):Double.NaN;
int y = i / tilesX;
int x = i % tilesX;
for (int dy = 0; dy <tile_size; dy ++){
for (int dx = 0; dx <tile_size; dx ++){
dbg_img[0][(y * tile_size + dy)*(tile_size*tilesX) + (x * tile_size + dx)] = d;
}
}
}
} else { // 09.18.2022
for (int i = 0; i < selected.length; i++ ){
double d = selected[i]? ((disparity.length > 1) ? disparity[i] : disparity[0]):Double.NaN;
int y = i / bounds.width + bounds.y;
int x = i % bounds.width + bounds.x;
for (int dy = 0; dy <tile_size; dy ++){
for (int dx = 0; dx <tile_size; dx ++){
dbg_img[0][(y * tile_size + dy)*(tile_size*tilesX) + (x * tile_size + dx)] = d;
}
}
}
}
int maxIndex = getMaxIndex(indices);
int [][] pxy = new int [maxIndex+1][2];
int height = indices.length;
int width = indices[0].length;
for (int y = 0; y < height; y++){
for (int x = 0; x < width; x++){
if (indices[y][x] >= 0){
pxy[indices[y][x]][0] = (bounds.x + x)*tile_size + (tile_size/2);
pxy[indices[y][x]][1] = (bounds.y + y)*tile_size + (tile_size/2);
}
}
}
for (int i = 0; i < triangles.length; i++ ){
for (int side = 0; side < triangles[i].length; side++){
int [] pntIndx = {
triangles[i][side],
(side == (triangles[i].length -1)? triangles[i][0]:triangles[i][side+1])};
int dx = iSign(pxy[pntIndx[1]][0] - pxy[pntIndx[0]][0]);
int dy = iSign(pxy[pntIndx[1]][1] - pxy[pntIndx[0]][1]);
for (int j = 0; j < tile_size; j++){
int x = pxy[pntIndx[0]][0] + dx*j;
int y = pxy[pntIndx[0]][1] + dy*j;
dbg_img[1][y * tile_size * tilesX + x] = 10.0; //1711748
}
}
}
if (texturePath != null) {
ShowDoubleFloatArrays.showArrays(
dbg_img,
tilesX * tile_size,
tilesY * tile_size,
true,
"triangles-"+texturePath,
titles);
}
if (debug_triangles != null) {
int indx_tri = (debug_triangles.length>1) ? 1 : 0;
for (int i = 0; i < debug_triangles[indx_tri].length; i++) {
if (dbg_img[1][i] > 0) {
debug_triangles[indx_tri][i] = dbg_img[1][i]; // 10.0 to have the same scale as disparity
}
}
if (indx_tri > 0) {
for (int i = 0; i < debug_triangles[indx_tri].length; i++) {
if (!Double.isNaN(dbg_img[0][i])) {
debug_triangles[0][i] = dbg_img[0][i]; // disparity if not NaN
}
}
}
}
}
public static double [] fillNaNs( public static double [] fillNaNs(
final double [] data, final double [] data,
int width, int width,
......
package com.elphel.imagej.x3d.export; package com.elphel.imagej.x3d.export;
import java.awt.Rectangle;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.concurrent.atomic.AtomicInteger;
import com.elphel.imagej.common.ShowDoubleFloatArrays;
import com.elphel.imagej.tileprocessor.GeometryCorrection;
import com.elphel.imagej.tileprocessor.ImageDtt;
import com.elphel.imagej.tileprocessor.TexturedModel;
import com.elphel.imagej.tileprocessor.TileNeibs;
/** /**
** **
** TriMesh - triangular mesh representation ** TriMesh - triangular mesh representation
...@@ -24,6 +37,19 @@ package com.elphel.imagej.x3d.export; ...@@ -24,6 +37,19 @@ package com.elphel.imagej.x3d.export;
*/ */
public class TriMesh { public class TriMesh {
public static final int TRI_DOWN_LEFT = 0; // down, then left (independent)
public static final int TRI_RIGHT_DOWNLEFT = 1; // right, then down-left (one of this and two next)
public static final int TRI_DOWNRIGHT_LEFT = 2; // down-right, then left
public static final int TRI_RIGHT_DOWN = 3; // right, then down
public static final int[] TRI_NONE = {-1,-1,-1,-1}; // four possible triangles
public static final int[][] TRI_OFFS_XY = {{0,0}, {1,0}, {1,1}, {0,1}}; // X and Y relative to top-left
public static final int[][][] TRI_SET_xy = {
{{0,0},{0,1},{-1,1}}, // TRI_DOWN_LEFT
{{0,0},{1,0},{ 0,1}}, // TRI_RIGHT_DOWNLEFT
{{0,0},{1,1},{ 0,1}}, // TRI_DOWNRIGHT_LEFT
{{0,0},{1,0},{ 1,1}}};// TRI_RIGHT_DOWN
public String texture_image; public String texture_image;
public double [][] worldXYZ; public double [][] worldXYZ;
public double [][] texCoord; public double [][] texCoord;
...@@ -136,5 +162,1856 @@ public class TriMesh { ...@@ -136,5 +162,1856 @@ public class TriMesh {
} }
// moved here from TileProcessor
/**
* Enumerate selected tiles. Input boolean array maybe either full size (tilesY*tilesX)
* or bounds.width*bounds.height
* @param bounds Rectangle specifing selection area
* @param selected boolean array of populated tiles
* @param tilesX full width of image array
* @return [y][x] array of incremental indices, -1 for unselected tiles.
*/
public static int [][] getCoordIndices( // starting with 0, -1 - not selected
Rectangle bounds,
boolean [] selected,
int tilesX)
{
int [][] indices = new int [bounds.height][bounds.width];
int indx = 0;
if (selected.length > (bounds.height * bounds.width)) { // old version - selected is full size
for (int y = 0; y < bounds.height; y++) {
for (int x = 0; x < bounds.width; x++){
if (selected[tilesX * (bounds.y + y) + (bounds.x + x)]){
indices[y][x] = indx++;
} else {
indices[y][x] = -1;
}
}
}
} else { // 09.18.2022
for (int y = 0; y < bounds.height; y++) {
for (int x = 0; x < bounds.width; x++){
if (selected[bounds.width * y + x]){
indices[y][x] = indx++;
} else {
indices[y][x] = -1;
}
}
}
}
return indices;
}
/**
* Enumerate "large" and "small" tiles, where "large" are actual tiles and "small" are
* subdivided (by subdiv in each direction) ones to increase lateral mesh resolution.
* sub-tiles are populated if at least one pixel in it is opaque. Input selected_tiles
* and alpha arrays may correspond to either full image or rectangular bounds, output
* array always corresponds to bounds.
*
* @param bounds Rectangle roi for output and optionally input data
* @param selected_tiles selected tiles, all unselected are ignored
* @param tilesX full image width in tiles
* @param tile_size tile size (8)
* @param alpha pixel array, where true means "opaque". may be either full image
* or be bound to bounds (scaled to pixels from tiles)
* @param subdiv subdivide tiles. Best if is equal to 1,2,4 and 8 that results in
* uniform tiles
* @param num_indices return parameter if int[1] - will provide total number of selected
* tiles - large and small.
* @return array with tile indices (all different) int [bounds.height][bounds.width][][]
* Each "large" tile may be null if empty, contain a single int [][] {{index}}
* for "large" tiles or [subdiv][subdiv] array of the sub-tile indices with
* -1 for empty subtile.
*/
public static int [][][][] getCoordIndices( // starting with 0, -1 - not selected
final Rectangle bounds,
final boolean [] selected_tiles,
final int tilesX,
final int tile_size,
final boolean [] alpha,
final int subdiv,
final int [] num_indices
) {
//TODO: add optimization (merging some in-tile triangles)
final boolean full_image = selected_tiles.length > (bounds.height * bounds.width);
final int [][][][] indices = new int [bounds.height][bounds.width][][];
final Thread[] threads = ImageDtt.newThreadArray(TexturedModel.THREADS_MAX);
final AtomicInteger ai = new AtomicInteger(0);
final int source_tile_width = full_image? tilesX : bounds.width;
final int source_pix_width = source_tile_width * tile_size;
final int source_tile_offsx = full_image? bounds.x : 0;
final int source_tile_offsy = full_image? bounds.y : 0;
// final int source_tile_offs = source_tile_offsx + source_tile_offsy * source_tile_width;
final int source_pix_offsx = source_tile_offsx * tile_size;
final int source_pix_offsy = source_tile_offsy * tile_size;
// final int source_pix_offs = source_pix_offsx + source_pix_offsy * source_pix_width;
// final int sub_size = Math.max(tile_size / subdiv, 1);
final int btiles = bounds.width * bounds.height;
final AtomicInteger aindx = new AtomicInteger(0);
ai.set(0);
for (int ithread = 0; ithread < threads.length; ithread++) {
threads[ithread] = new Thread() {
public void run() {
boolean [][] pix_sel = new boolean[tile_size][tile_size];
for (int btile = ai.getAndIncrement(); btile < btiles; btile = ai.getAndIncrement()) {
int btilex = btile % bounds.width;
int btiley = btile / bounds.width;
int stile = (btilex + source_tile_offsx) + (btiley + source_tile_offsy) * source_tile_width;
if (selected_tiles[stile]) {
boolean all_sel = true, some_sel = false;
int pindx0 = (source_pix_offsx + btilex * tile_size) + (source_pix_offsy + btiley * tile_size) * source_pix_width;
for (int y = 0; y < tile_size; y++) {
int pindx1 = pindx0 + y*source_pix_width;
for (int x = 0; x < tile_size; x++) {
int pindx = pindx1 + x;
boolean psel = alpha[pindx];
pix_sel[y][x] = psel;
all_sel &= psel;
some_sel |= psel;
}
}
if (some_sel) {
if (all_sel) {
indices[btiley][btilex] = new int [][] {{aindx.getAndIncrement()}}; // single center index
} else {
indices[btiley][btilex] = new int [subdiv][subdiv];
for (int ny = 0; ny < subdiv; ny++) {
int y0 = (ny * tile_size)/subdiv;
int y1 = Math.min(((ny+1) * tile_size)/subdiv, tile_size);
for (int nx = 0; nx < subdiv; nx++) {
int x0 = (nx * tile_size)/subdiv;
int x1 = Math.min(((nx+1) * tile_size)/subdiv, tile_size);
boolean has_any = false;
lhas_any:
for (int y = y0; y < y1; y++) {
for (int x = x0; x < x1; x++) {
if (pix_sel[y][x]) {
has_any = true;
break lhas_any;
}
}
}
if (has_any) {
indices[btiley][btilex][ny][nx] = aindx.getAndIncrement();
} else {
indices[btiley][btilex][ny][nx] = -1;
}
}
}
}
}
}
}
}
};
}
ImageDtt.startAndJoin(threads);
if (num_indices != null) {
num_indices[0] = aindx.get();
}
return indices;
}
/**
* Convert "large" tiles to arrays of small ones if it has a small-tile neighbor with
* gaps along the border with this one
* @param indices - array of [height][width]{{index}} for large tiles and [heigh][width][py][px]
* for small ones. This array will be modified and re-indexed if needed.
* @param subdiv subdivide tiles. Best if is equal to 1,2,4 and 8 that results in
* uniform tiles
* @return number of indices after re-indexing.
*/
public static int splitLargeTileIndices(
int [][][][] indices,
final int subdiv) {
final int bwidth=indices[0].length;
final int bheight=indices.length;
final boolean [][] need_split = new boolean[bheight][bwidth];
final int btiles = bwidth * bheight;
final int [][] dxy = {{0, -1},{1,0},{0,1},{-1,0}};
final Thread[] threads = ImageDtt.newThreadArray(TexturedModel.THREADS_MAX);
final AtomicInteger ai = new AtomicInteger(0);
for (int ithread = 0; ithread < threads.length; ithread++) {
threads[ithread] = new Thread() {
public void run() {
for (int btile = ai.getAndIncrement(); btile < btiles; btile = ai.getAndIncrement()) {
int btilex = btile % bwidth;
int btiley = btile / bwidth;
if ((indices[btiley][btilex] != null) && (indices[btiley][btilex].length == 1)) { // full tile, not split
l_split:
for (int dir = 0; dir < dxy.length; dir++) {
int btilex1 = btilex+dxy[dir][0];
int btiley1 = btiley+dxy[dir][1];
if ((btilex1 >= 0) && (btiley1 >= 0) && (btilex1 < bwidth) && (btiley1 < bheight)) {
int [][] index_xy= indices[btiley1][btilex1];
if ((index_xy != null) && (index_xy.length >1)) { // split tile
int x0 = (dxy[dir][0] > 0) ? 0: (index_xy[0].length - 1);
int y0 = (dxy[dir][1] > 0) ? 0: (index_xy.length - 1);
if (dxy[dir][0] == 0) { // top or bottom row
for (int x = 0; x < index_xy[0].length; x++) {
if (index_xy[y0][x] < 0) {
need_split[btiley][btilex] = true;
break l_split;
}
}
} else {// right or left column
for (int y = 0; y < index_xy.length; y++) {
if (index_xy[y][x0] < 0) {
need_split[btiley][btilex] = true;
break l_split;
}
}
}
}
}
}
}
}
}
};
}
ImageDtt.startAndJoin(threads);
// re-index
final AtomicInteger aindx = new AtomicInteger(0);
ai.set(0);
for (int ithread = 0; ithread < threads.length; ithread++) {
threads[ithread] = new Thread() {
public void run() {
for (int btile = ai.getAndIncrement(); btile < btiles; btile = ai.getAndIncrement()) {
int btilex = btile % bwidth;
int btiley = btile / bwidth;
int [][] index_xy= indices[btiley][btilex];
if (index_xy != null) {
if (need_split[btiley][btilex]) {
indices[btiley][btilex] = new int [subdiv][subdiv];
for (int y = 0; y < subdiv; y++) {
for (int x = 0; x < subdiv; x++) {
indices[btiley][btilex][y][x] = aindx.getAndIncrement();
}
}
} else {
for (int y = 0; y < index_xy.length; y++) {
for (int x = 0; x < index_xy[y].length; x++) {
if (index_xy[y][x] >= 0) {
index_xy[y][x] = aindx.getAndIncrement(); // re-index any non-negative
}
}
}
}
}
}
}
};
}
ImageDtt.startAndJoin(threads);
return aindx.get();
}
/**
* Assuming only neighbor tiles
* @param x
* @param y
* @param size
* @return
*/
static int [] getNeibNode(int x, int y, int size) {
int dir = 8;
if (x < 0) {
if (y < 0) {
x += size;
y += size;
dir = 7;
} else if (y >= size) {
x += size;
y -= size;
dir = 5;
} else {
x += size;
dir = 6;
}
} else if (x >= size) {
if (y < 0) {
x -= size;
y += size;
dir = 1;
} else if (y >= size) {
x -= size;
y -= size;
dir = 3;
} else {
x -= size;
dir = 2;
}
} else {
if (y < 0) {
y += size;
dir = 0;
} else if (y >= size) {
y -= size;
dir = 4;
} else {
dir=8;
}
}
return new int[] {x, y, dir};
}
/**
* get edge indices, CCW: 0 - top edge, 1 right edge, 2 - bottom edge, 3 - left edge
* @param tile square tile [y][x]
* @param dir direction/edge 0..3
* @return edge values of the tile in counter-clockwise order
*/
public static int [] getEdgeIndices(int [][] tile, int dir) {
if (tile == null) return null;
int subdiv = tile.length;
int [] edge = new int[subdiv];
switch (dir) {
case 0: for (int i = 0; i < subdiv; i++) {edge[i] = tile[0][subdiv - i - 1];} break;
case 1: for (int i = 0; i < subdiv; i++) {edge[i] = tile[subdiv - i - 1][subdiv-1];} break;
case 4: for (int i = 0; i < subdiv; i++) {edge[i] = tile[subdiv-1][i];} break;
case 3: for (int i = 0; i < subdiv; i++) {edge[i] = tile[i][0];} break;
}
return edge;
}
/**
* Triangulate large and small equilateral 45-degree triangles
* @param indices - array of [height][width]{{index}} for large tiles and [heigh][width][py][px]
* for small ones. This array will be modified and re-indexed if needed.
* @return int [][3] - array of triangles 3 vertex indices, clockwise
*/
public static int [][] triangulateSameSize(
int [][][][] indices)
{
final int bwidth=indices[0].length;
final int bheight=indices.length;
final int btiles = bwidth * bheight;
final int [][][][][] tris = new int [bheight][bwidth][][][];
final Thread[] threads = ImageDtt.newThreadArray(TexturedModel.THREADS_MAX);
final AtomicInteger ai = new AtomicInteger(0);
final AtomicInteger atri = new AtomicInteger(0);
// initialize triangles array
for (int ithread = 0; ithread < threads.length; ithread++) {
threads[ithread] = new Thread() {
public void run() {
for (int btile = ai.getAndIncrement(); btile < btiles; btile = ai.getAndIncrement()) {
int btilex = btile % bwidth;
int btiley = btile / bwidth;
if (indices[btiley][btilex] != null) {
tris[btiley][btilex] = new int [indices[btiley][btilex].length][indices[btiley][btilex][0].length][];
}
}
}
};
}
ImageDtt.startAndJoin(threads);
// triangulate macro (large triangles) - similar as tit was implemented before
ai.set(0);
final int btiles_wo_last_row = bwidth * (bheight - 1);
for (int ithread = 0; ithread < threads.length; ithread++) {
threads[ithread] = new Thread() {
public void run() {
for (int btile = ai.getAndIncrement(); btile < btiles_wo_last_row; btile = ai.getAndIncrement()) {
int x = btile % bwidth;
int y = btile / bwidth;
if ((indices[y][x] != null) && (indices[y][x].length == 1)){
int tris_en = 0;
if ((x > 0) &&
(indices[y + 1][x - 1] !=null) && (indices[y + 1][x - 1].length == 1) &&
(indices[y + 1][x] != null) && (indices[y + 1][x].length == 1)){
tris_en |= (1 << TRI_DOWN_LEFT);
}
if (x < (bwidth - 1)) {
if ((indices[y + 1][x] != null) && (indices[y + 1][x].length == 1)){
if ((indices[y][x + 1] != null) && (indices[y][x + 1].length == 1)){
tris_en |= (1 << TRI_RIGHT_DOWNLEFT);
} else if ((indices[y + 1][x + 1] != null) && (indices[y + 1][x + 1].length ==1)){
tris_en |= (1 << TRI_DOWNRIGHT_LEFT);
}
} else if ((indices[y][x + 1] != null) && (indices[y][x + 1].length == 1) &&
(indices[y + 1][x + 1] != null) && (indices[y + 1][x + 1].length ==1)) {
tris_en |= (1 << TRI_RIGHT_DOWN);
}
}
if (tris_en != 0) {
tris[y][x][0][0]=TRI_NONE.clone();
for (int i = 0; i < TRI_NONE.length; i++) {
if ((tris_en & (1 << i)) != 0) {
tris[y][x][0][0][i] = atri.getAndIncrement();
}
}
}
}
}
}
};
}
ImageDtt.startAndJoin(threads);
// triangulate micro (small triangles) - without crossing tiles borders
ai.set(0);
for (int ithread = 0; ithread < threads.length; ithread++) {
threads[ithread] = new Thread() {
public void run() {
for (int btile = ai.getAndIncrement(); btile < btiles; btile = ai.getAndIncrement()) {
int btilex = btile % bwidth;
int btiley = btile / bwidth;
if ((indices[btiley][btilex] != null) && (indices[btiley][btilex].length > 1)){ // subdivided
int [][] tindices = indices[btiley][btilex];
for (int y = 0; y < (tindices.length - 1); y++){
for (int x = 0; x < tindices[y].length; x++){
if (tindices[y][x] >= 0){
int tris_en = 0;
if ((x > 0) && (tindices[y + 1][x - 1] >= 0) && (tindices[y + 1][x] >= 0)){
tris_en |= (1 << TRI_DOWN_LEFT);
}
if (x < (tindices[y].length - 1)) {
if (tindices[y + 1][x] >= 0){
if (tindices[y][x + 1] >= 0){
tris_en |= (1 << TRI_RIGHT_DOWNLEFT);
} else if (tindices[y + 1][x + 1] >= 0){
tris_en |= (1 << TRI_DOWNRIGHT_LEFT);
}
} else if ((tindices[y][x + 1] >= 0) && (tindices[y + 1][x + 1] >= 0)) {
tris_en |= (1 << TRI_RIGHT_DOWN);
}
}
if (tris_en != 0) {
tris[btiley][btilex][y][x]=TRI_NONE.clone();
for (int i = 0; i < TRI_NONE.length; i++) {
if ((tris_en & (1 << i)) != 0) {
tris[btiley][btilex][y][x][i] = atri.getAndIncrement();
}
}
}
}
}
}
}
}
}
};
}
ImageDtt.startAndJoin(threads);
// now triangulate between subdivided tiles - first right and down
ai.set(0);
for (int ithread = 0; ithread < threads.length; ithread++) {
threads[ithread] = new Thread() {
public void run() {
boolean [] quad_corners = new boolean [TRI_NONE.length]; // 4 corners: top-left, top=right, bottom-right and borrom-left
for (int btile = ai.getAndIncrement(); btile < btiles; btile = ai.getAndIncrement()) {
int btilex = btile % bwidth;
int btiley = btile / bwidth;
if ((indices[btiley][btilex] != null) && (indices[btiley][btilex].length > 1)){ // subdivided
int [][][] tneib_indices = new int [TileNeibs.DIR_XY.length + 1][][];
tneib_indices[8] = indices[btiley][btilex];
for (int dir = 2; dir <7; dir++) { // not all directions needed
int btx = btilex + TileNeibs.DIR_XY[dir][0];
int bty = btiley + TileNeibs.DIR_XY[dir][1];
if ((btx >= 0) && (btx < bwidth) && (bty >= 0) && (bty < bheight) &&
(indices[bty][btx] != null) && (indices[bty][btx].length > 1)) {
tneib_indices[dir] = indices[bty][btx];
}
}
int subdiv = tneib_indices[8].length; // assuming square
// First trying TRI_DOWN_LEFT going along left and bottom edge. This point is top-right, not top-left as for others
for (int i = 0; i < (2 * subdiv -1); i++) {
int x = i;
int y = subdiv - 1;
if (i >= subdiv) {
x = 0;
y = i - subdiv;
}
int num_corn = 0;
for (int dir = 1; dir < 4; dir++) { // skipping top-left corner
int x1 = x + TRI_OFFS_XY[dir][0] - 1; // -1 as this point is top-right, not top-left
int y1 = y + TRI_OFFS_XY[dir][1];
int [] xyd = getNeibNode(x1, y1, subdiv);
boolean exists = false;
if (tneib_indices[xyd[2]] != null) {
exists = tneib_indices[xyd[2]][xyd[1]][xyd[0]] >= 0; // is populated
quad_corners[dir] = exists;
}
if (exists) {
num_corn ++;
} else {
break; // here all 3 tested corners should be good
}
}
if (num_corn >= 3) { // all 3 corners exist
if (tris[btiley][btilex][y][x] == null) {
tris[btiley][btilex][y][x] = TRI_NONE.clone();
}
tris[btiley][btilex][y][x][1 << TRI_DOWN_LEFT] = atri.getAndIncrement();
}
}
Arrays.fill(quad_corners, false);
// now try 3 other triangles
for (int i = 0; i < (2 * subdiv -1); i++) {
int x = i;
int y = subdiv - 1;
if (i >= subdiv) {
x = subdiv - 1;
y = i - subdiv;
}
int num_corn = 0;
for (int dir = 1; dir < 4; dir++) { // skipping top-left corner
int x1 = x + TRI_OFFS_XY[dir][0];
int y1 = y + TRI_OFFS_XY[dir][1];
int [] xyd = getNeibNode(x1, y1, subdiv);
boolean exists = false;
if (tneib_indices[xyd[2]] != null) {
exists = tneib_indices[xyd[2]][xyd[1]][xyd[0]] >= 0; // is populated
quad_corners[dir] = exists;
}
if (exists) {
num_corn ++;
}
}
if ((num_corn >= 3) && quad_corners[0]) { // that triangle (TRI_DOWN_LEFT) should already exist
if (tris[btiley][btilex][y][x] == null) {
tris[btiley][btilex][y][x] = TRI_NONE.clone();
}
if (quad_corners[3]) {
if (quad_corners[1]) {
tris[btiley][btilex][y][x][1 << TRI_RIGHT_DOWNLEFT] = atri.getAndIncrement();
} else {
tris[btiley][btilex][y][x][1 << TRI_DOWNRIGHT_LEFT] = atri.getAndIncrement();
}
} else {
tris[btiley][btilex][y][x][1 << TRI_RIGHT_DOWN] = atri.getAndIncrement();
}
}
}
}
}
}
};
}
ImageDtt.startAndJoin(threads);
int num_tris = atri.get();
// initialize triangles array
final int [][] tri_indices = new int [num_tris][3];
// Collect equilateral large and small triangles, generate triangles array
ai.set(0);
for (int ithread = 0; ithread < threads.length; ithread++) {
threads[ithread] = new Thread() {
public void run() {
for (int btile = ai.getAndIncrement(); btile < btiles; btile = ai.getAndIncrement()) {
int btilex = btile % bwidth;
int btiley = btile / bwidth;
int [][][] tile_tris = tris[btiley][btilex];
int subdiv = 1;
if (tile_tris != null) {
int [][][] tneib_indices = null;
subdiv = tile_tris.length;
if (subdiv > 1) {
tneib_indices = new int [TileNeibs.DIR_XY.length + 1][][];
tneib_indices[8] = indices[btiley][btilex];
for (int dir = 2; dir <7; dir++) { // not all directions needed
int btx = btilex + TileNeibs.DIR_XY[dir][0];
int bty = btiley + TileNeibs.DIR_XY[dir][1];
if ((btx >= 0) && (btx < bwidth) && (bty >= 0) && (bty < bheight) &&
(indices[bty][btx] != null) && (indices[bty][btx].length > 1)) {
tneib_indices[dir] = indices[bty][btx];
}
}
}
for (int y = 0; y < tile_tris.length; y++) {
for (int x = 0; x < tile_tris[y].length; x++) {
if (tile_tris[y][x] != null) {
for (int nt = 0; nt < tile_tris[y][x].length; nt++) {
int tri_indx = tile_tris[y][x][nt];
if (tri_indx >= 0) {
if (tile_tris.length > 1) { // mini
for (int v = 0; v < 3; v++) {
int x1 = x + TRI_SET_xy[nt][v][0];
int y1 = y + TRI_SET_xy[nt][v][1];
int [] xyd = getNeibNode(x1, y1, subdiv);
tri_indices[tri_indx][v] =
tneib_indices[xyd[2]][xyd[1]][xyd[0]];
}
} else { // macro
for (int v = 0; v < 3; v++) {
tri_indices[tri_indx][v] =
indices [btiley + TRI_SET_xy[nt][v][1]]
[btilex + TRI_SET_xy[nt][v][0]][0][0];
}
}
}
}
}
}
}
}
}
}
};
}
ImageDtt.startAndJoin(threads);
return tri_indices;
}
/**
* Triangulate connections between large (tile centers) and small triangles (subdivided tiles).
* All subdivided tiles have full row/column facing non-divided tiles.
* @param indices - array of [height][width]{{index}} for large tiles and [heigh][width][py][px]
* for small ones. This array will be modified and re-indexed if needed.
* @return int [][3] - array of triangles 3 vertex indices, clockwise
*/
public static int [][] connectLargeSmallTriangles(
int [][][][] indices)
{
final int bwidth=indices[0].length;
final int bheight=indices.length;
final int btiles = bwidth * bheight;
final int [][][][] tris = new int [bheight][bwidth][][];
// subdivided tiles that have non-subdivided neighbors have or may have different triangle configurations:
// Below subdivided is 2, full tile - 1, empty 0
// type 0 (4 dirs, subdiv-1 triangles):
// 2->1 in any of 4 directions produce subdiv - 1 triangles (subdiv-1) connected to tile center
// type 1 (4 dirs, 1 triangle):
// two 1 in ortho directions with 0 or 1 between them - produces 1 triangle between
// centers of 1 and the corner of center 2.
// type 2 (4 dirs, 1 triangle):
// 1 in ortho, 2 CCW from it, 0 or 2 (not 1) CCW from 2
// type 3 (4 dirs, 1 triangle) - mirror of type2:
// 1 in ortho, 2 CW from it, 0 or 2 (not 1) CW from 2
// type 4 (4 dirs, 2 triangles):
// 1 in ortho, 1 CCW from it, 2 CCW from 1. Does not need mirror, as the mirror will be if looking
// from the last 2 (90 degrees CCW from the first 1)
// First pass - reserving triangles indices
final Thread[] threads = ImageDtt.newThreadArray(TexturedModel.THREADS_MAX);
final AtomicInteger ai = new AtomicInteger(0);
final AtomicInteger atri = new AtomicInteger(0);
// initialize triangles array
for (int ithread = 0; ithread < threads.length; ithread++) {
threads[ithread] = new Thread() {
public void run() {
for (int btile = ai.getAndIncrement(); btile < btiles; btile = ai.getAndIncrement()) {
int btilex = btile % bwidth;
int btiley = btile / bwidth;
if ((indices[btiley][btilex] != null) && (indices[btiley][btilex].length >1)) { // only for subdivided
int [] tneib_types = new int [TileNeibs.DIR_XY.length + 1];
tneib_types[8] = 2;
int subdiv = indices[btiley][btilex].length;
boolean has_full_neib = false;
for (int dir = 0; dir < 8; dir++) { // not all directions needed
int btx = btilex + TileNeibs.DIR_XY[dir][0];
int bty = btiley + TileNeibs.DIR_XY[dir][1];
if ((btx >= 0) && (btx < bwidth) && (bty >= 0) && (bty < bheight) &&
(indices[bty][btx] != null)) {
tneib_types[dir] = 0;
if (indices[bty][btx] != null) {
tneib_types[dir] = (indices[bty][btx].length > 1) ? 2 : 1;
}
has_full_neib |= (tneib_types[dir] == 1);
}
}
if (has_full_neib) {
tris[btiley][btilex] = new int [5][4]; // [types][directions
// reserve indices for type0:
for (int dir = 0; dir < 4; dir++) {
int tneib = tneib_types[2*dir];
if (tneib == 1) {
tris[btiley][btilex][0][dir] = atri.getAndAdd(subdiv - 1);
}
}
// reserve indices for type1:
for (int dir = 0; dir < 4; dir++) {
int tneib = tneib_types[2*dir]; // pointed
int tneib1= tneib_types[(2*dir+7) % 8]; // CCW 1 from pointed
int tneib2= tneib_types[(2*dir+6) % 8]; // CCW 2 from pointed
if ((tneib == 1) && (tneib1 != 2) && (tneib2 == 1)) {
tris[btiley][btilex][1][dir] = atri.getAndAdd(1);
}
}
// reserve indices for type2:
for (int dir = 0; dir < 4; dir++) {
int tneib = tneib_types[2*dir]; // pointed
int tneib1= tneib_types[(2*dir+7) % 8]; // CCW 1 from pointed
int tneib2= tneib_types[(2*dir+6) % 8]; // CCW 2 from pointed
if ((tneib == 1) && (tneib1 == 2) && (tneib2 != 1)) {
tris[btiley][btilex][2][dir] = atri.getAndAdd(1);
}
}
// reserve indices for type3:
for (int dir = 0; dir < 4; dir++) {
int tneib = tneib_types[2*dir]; // pointed
int tneib1= tneib_types[(2*dir+1) % 8]; // CW 1 from pointed
int tneib2= tneib_types[(2*dir+2) % 8]; // CW 2 from pointed
if ((tneib == 1) && (tneib1 == 2) && (tneib2 != 1)) {
tris[btiley][btilex][3][dir] = atri.getAndAdd(1);
}
}
// reserve indices for type4:
for (int dir = 0; dir < 4; dir++) {
int tneib = tneib_types[2*dir]; // pointed
int tneib1= tneib_types[(2*dir+7) % 8]; // CCW 1 from pointed
int tneib2= tneib_types[(2*dir+6) % 8]; // CCW 2 from pointed
if ((tneib == 1) && (tneib1 == 1) && (tneib2 == 2)) {
tris[btiley][btilex][4][dir] = atri.getAndAdd(2);
}
}
}
}
}
}
};
}
ImageDtt.startAndJoin(threads);
int num_tris = atri.get();
// initialize triangles array
final int [][] tri_indices = new int [num_tris][3];
// Collect seam triangles, generate triangles array
ai.set(0);
for (int ithread = 0; ithread < threads.length; ithread++) {
threads[ithread] = new Thread() {
public void run() {
for (int btile = ai.getAndIncrement(); btile < btiles; btile = ai.getAndIncrement()) {
int btilex = btile % bwidth;
int btiley = btile / bwidth;
if (tris[btiley][btilex] != null) { // only for subdivided
int [][][] tneib_indices = new int [TileNeibs.DIR_XY.length + 1][][];
tneib_indices [8] = indices[btiley][btilex];
int subdiv_m1 = indices[btiley][btilex].length - 1;
for (int dir = 0; dir < 8; dir++) { // not all directions needed
int btx = btilex + TileNeibs.DIR_XY[dir][0];
int bty = btiley + TileNeibs.DIR_XY[dir][1];
if ((btx >= 0) && (btx < bwidth) && (bty >= 0) && (bty < bheight) &&
(indices[bty][btx] != null)) {
tneib_indices[dir] = indices[bty][btx];
}
}
// build triangles for type0:
for (int dir4 = 0; dir4 < 4; dir4++) {
int tri_index = tris[btiley][btilex][0][dir4]; // type0
if (tri_index >= 0) {
int [] edge = getEdgeIndices(tneib_indices [8], dir4);
int indx_1 = tneib_indices[2 * dir4][0][0];
for (int i = 0; i < subdiv_m1; i++) {
tri_indices[tri_index + i][0] = indx_1;
tri_indices[tri_index + i][1] = edge[i];
tri_indices[tri_index + i][2] = edge[i + 1];
}
}
}
// build triangles for type1:
for (int dir4 = 0; dir4 < 4; dir4++) {
int tri_index = tris[btiley][btilex][1][dir4]; // type1
if (tri_index >= 0) {
int [] edge = getEdgeIndices(tneib_indices [8], dir4);
int indx_1 = tneib_indices[2 * dir4][0][0];
int indx_2 = tneib_indices[(2 * dir4 + 6) % 8][0][0];
tri_indices[tri_index][0] = indx_1;
tri_indices[tri_index][1] = edge[subdiv_m1];
tri_indices[tri_index][2] = indx_2;
}
}
// build triangles for type2:
for (int dir4 = 0; dir4 < 4; dir4++) {
int tri_index = tris[btiley][btilex][2][dir4]; // type2
if (tri_index >= 0) {
int [] edge = getEdgeIndices(tneib_indices [8], dir4);
int [] edge1 = getEdgeIndices(tneib_indices [(2 * dir4 + 7) % 8], (dir4 + 1) % 4);
int indx_1 = tneib_indices[2 * dir4][0][0];
tri_indices[tri_index][0] = indx_1;
tri_indices[tri_index][1] = edge[subdiv_m1];
tri_indices[tri_index][2] = edge1[0];
}
}
// build triangles for type3:
for (int dir4 = 0; dir4 < 4; dir4++) {
int tri_index = tris[btiley][btilex][3][dir4]; // type3
if (tri_index >= 0) {
int [] edge = getEdgeIndices(tneib_indices [8], dir4);
int [] edge1 = getEdgeIndices(tneib_indices [(2 * dir4 + 1) % 8], (dir4 + 3) % 4);
int indx_1 = tneib_indices[2 * dir4][0][0];
tri_indices[tri_index][0] = indx_1;
tri_indices[tri_index][1] = edge1[subdiv_m1];
tri_indices[tri_index][2] = edge[0];
}
}
// build triangles for type4:
for (int dir4 = 0; dir4 < 4; dir4++) {
int tri_index = tris[btiley][btilex][4][dir4]; // type4
if (tri_index >= 0) {
int [] edge = getEdgeIndices(tneib_indices [8], dir4);
int indx_1 = tneib_indices[2 * dir4][0][0];
int indx_2 = tneib_indices[(2 * dir4 + 7) % 8][0][0];
int [] edge1 = getEdgeIndices(tneib_indices [(2 * dir4 + 6) % 8], dir4);
tri_indices[tri_index][0] = indx_1;
tri_indices[tri_index][1] = edge[subdiv_m1];
tri_indices[tri_index][2] = indx_2;
tri_indices[tri_index + 1][0] = indx_2;
tri_indices[tri_index + 1][1] = edge[subdiv_m1];
tri_indices[tri_index + 1][2] = edge[0];
}
}
}
}
}
};
}
ImageDtt.startAndJoin(threads);
return tri_indices;
}
/**
* Triangulate all vertice indices - combine triangulation of same-size equailateral 45-degree
* large (tile size) and small (tile subdivisions) and add connections between large and small ones
* @param indices - array of [height][width]{{index}} for large tiles and [heigh][width][py][px]
* for small ones. This array will be modified and re-indexed if needed.
* @return int [][3] - array of triangles 3 vertex indices, clockwise
*/
public static int [][] triangulateAll(
int [][][][] indices)
{
int [][] tri_same = triangulateSameSize(indices);
int [][] tri_inter = connectLargeSmallTriangles(indices);
int [][] triangles = new int [tri_same.length + tri_inter.length][];
System.arraycopy(tri_same, 0, triangles, 0, tri_same.length);
System.arraycopy(tri_inter, 0, triangles, tri_same.length, tri_inter.length);
return triangles;
}
/**
* Get texture coordinates (0..1) for horizontal (positive - to the right) and vertical (positive - up)
* @param wh null if texture image is cropped one, or {full_width, full_height} otherwise
* @param indices pairs of [x,y] in integer tiles
* @return texture x,y for the tile centers
*/
public static double [][] getTexCoords( // get texture coordinates for indices
int [] wh, // 0 or full width, full height of the image in tiles
int [][] indices)
{
int maxIndex = -1;
int height = indices.length;
int width = indices[0].length;
int tex_width = (wh != null)? wh[0]: width;
int tex_height = (wh != null)? wh[1]: height;
outer_label:{
for (int y = height - 1 ; y >= 0; y--) {
for (int x = width - 1; x >= 0; x--){
if (indices[y][x] >=0){
maxIndex = indices[y][x];
break outer_label;
}
}
}
}
double [][] textureCoordinate = new double [maxIndex+1][2];
int indx = 0;
for (int y = 0; indx <= maxIndex; y++) {
for (int x = 0; (x < width) && (indx <= maxIndex); x++){
if (indices[y][x] >=0){
textureCoordinate[indx][0] = (x + 0.5)/tex_width;
textureCoordinate[indx][1] = (tex_height - y - 0.5) / tex_height; // y is up
indx ++;
}
}
}
return textureCoordinate;
}
/**
* Create texture coordinates for 2 levels of triangles - tile centers and subdivided tiles
* @param rect null if texture image matches indices[][][][] array, otherwise
* x,y - top left corner corresponding indices, width, height - full image
* size in tiles
* @param num_indices total number of vertices in indices[][][][] array
* @param indices two level vertices indices - [tilesy][tilesx][1][1] for "large" tiles
* (single vertice in the tile centre) or [tilesy][tilesx][subdiv][subdiv]
* for subdivided indices[tilesy][tilesx] may be null if the tile is empty,
* [tilesy][tilesx][y][x] is either unique index or -1 for missing subtile.
* @return [num_indices][2] texture coordinates in 0..1.0 range, positive texture y is up
*/
public static double [][] getTexCoords( // get texture coordinates for indices
final Rectangle rect, // 0 or full width, full height of the image in tiles
final int num_indices,
final int [][][][] indices)
{
final int bwidth=indices[0].length;
final int bheight=indices.length;
final int btiles = bwidth * bheight;
final int tex_width = (rect != null)? rect.width: bwidth;
final int tex_height = (rect != null)? rect.height: bheight;
final int tex_x = (rect != null)? rect.x: 0;
final int tex_y = (rect != null)? rect.y: 0;
final double [][] textureCoordinate = new double [num_indices][2];
final Thread[] threads = ImageDtt.newThreadArray(TexturedModel.THREADS_MAX);
final AtomicInteger ai = new AtomicInteger(0);
for (int ithread = 0; ithread < threads.length; ithread++) {
threads[ithread] = new Thread() {
public void run() {
for (int btile = ai.getAndIncrement(); btile < btiles; btile = ai.getAndIncrement()) {
int btilex = btile % bwidth;
int btiley = btile / bwidth;
if (indices[btiley][btilex] != null) {
int subdiv = indices[btiley][btilex].length;
for (int y = 0; y < indices[btiley][btilex].length; y++) {
for (int x = 0; x < indices[btiley][btilex][y].length; x++) {
int indx = indices[btiley][btilex][y][x];
if (indx >= 0) {
textureCoordinate[indx][0] = (tex_x + btilex + (x + 0.5) / subdiv) / tex_width;
textureCoordinate[indx][1] = (tex_height - tex_y - btiley - (y + 0.5) / subdiv) / tex_height;
}
}
}
}
}
}
};
}
ImageDtt.startAndJoin(threads);
return textureCoordinate;
}
public static double [] getIndexedDisparities( // get disparity for each index
final double [] disparity,
final double min_disparity,
final double max_disparity,
final Rectangle bounds,
final int num_indices,
final int [][][][] indices,
final int tilesX)
{
final int bwidth=indices[0].length;
final int bheight=indices.length;
final int btiles = bwidth * bheight;
final boolean full_disparity = disparity.length > (bounds.height * bounds.width);
final double [] indexedDisparity = new double [num_indices];
final Thread[] threads = ImageDtt.newThreadArray(TexturedModel.THREADS_MAX);
final AtomicInteger ai = new AtomicInteger(0);
for (int ithread = 0; ithread < threads.length; ithread++) {
threads[ithread] = new Thread() {
public void run() {
for (int btile = ai.getAndIncrement(); btile < btiles; btile = ai.getAndIncrement()) {
int btilex = btile % bwidth;
int btiley = btile / bwidth;
int tile = btile;
if (full_disparity) {
tile = (btiley + bounds.y) * tilesX + (btilex + bounds.x);
}
if (indices[btiley][btilex] != null) {
int subdiv = indices[btiley][btilex].length;
double disp = disparity[tile];
if (disp < min_disparity) disp = min_disparity;
else if (disp > max_disparity) disp = max_disparity;
for (int y = 0; y < indices[btiley][btilex].length; y++) {
for (int x = 0; x < indices[btiley][btilex][y].length; x++) {
int indx = indices[btiley][btilex][y][x];
if (indx >= 0) {
indexedDisparity[indx] = disp; // currently - same disparity for subdivisions
// TODO - linear interpolate
}
}
}
}
}
}
};
}
ImageDtt.startAndJoin(threads);
return indexedDisparity;
}
public static double [][] getCoords( // get disparity for each index
final double [] disparity,
final double min_disparity,
final double max_disparity,
final Rectangle bounds,
final int num_indices,
final int [][][][] indices,
final int tilesX,
final int tile_size,
final boolean correctDistortions, // requires backdrop image to be corrected also
final GeometryCorrection geometryCorrection)
{
final int bwidth=indices[0].length;
final int bheight=indices.length;
final int btiles = bwidth * bheight;
final boolean full_disparity = disparity.length > (bounds.height * bounds.width);
final double [][] coordinate = new double [num_indices][];
final Thread[] threads = ImageDtt.newThreadArray(TexturedModel.THREADS_MAX);
final AtomicInteger ai = new AtomicInteger(0);
for (int ithread = 0; ithread < threads.length; ithread++) {
threads[ithread] = new Thread() {
public void run() {
for (int btile = ai.getAndIncrement(); btile < btiles; btile = ai.getAndIncrement()) {
int btilex = btile % bwidth;
int btiley = btile / bwidth;
int tile = btile;
if (full_disparity) {
tile = (btiley + bounds.y) * tilesX + (btilex + bounds.x);
}
if (indices[btiley][btilex] != null) {
int subdiv = indices[btiley][btilex].length;
double disp = disparity[tile];
if (disp < min_disparity) disp = min_disparity;
else if (disp > max_disparity) disp = max_disparity;
for (int y = 0; y < indices[btiley][btilex].length; y++) {
for (int x = 0; x < indices[btiley][btilex][y].length; x++) {
// TODO - linear interpolate disparity here
int indx = indices[btiley][btilex][y][x];
if (indx >= 0) {
double px = (bounds.x + btilex + (x + 0.5) / subdiv) * tile_size - 0.5;
double py = (bounds.y + btiley + (y + 0.5) / subdiv) * tile_size - 0.5;
coordinate[indx] = geometryCorrection.getWorldCoordinates(
px,
py,
disp, // currently - same for all tile
correctDistortions);
}
}
}
}
}
}
};
}
ImageDtt.startAndJoin(threads);
return coordinate;
}
/** Plot generated triangles on a provided canvas. Can combine multiple meshes on the same canvas
*
* @param canvas 2D canvas in a line-scan order
* @param width canvas width in pixels
* @param tex_coord texture coordinates as [vertice_index]{x,y}, where 0 <=x <1.0, 0 <=y <1.0, y - upward
* @param triangles [tri_index]{p0_index, p1_index, p2_index}, where p*_index is vertice_index in tex_coord
* @param plot_center plot triangle center
* @param line_color pixel to use for lines
* @param center_color pixel to use for triangle centers
*/
public static void plotMesh(
final double [] canvas,
final int width,
final double [][] tex_coord,
final int [][] triangles,
final boolean plot_center,
final double line_color,
final double center_color)
{
final int height = canvas.length / width;
final Thread[] threads = ImageDtt.newThreadArray(TexturedModel.THREADS_MAX);
final AtomicInteger ai = new AtomicInteger(0);
for (int ithread = 0; ithread < threads.length; ithread++) {
threads[ithread] = new Thread() {
public void run() {
int width_m1 = width-1;
int height_m1 = height-1;
for (int ntri = ai.getAndIncrement(); ntri < triangles.length; ntri = ai.getAndIncrement()) {
for (int i0 = 0; i0 < 3; i0++) {
int i1 = (i0+1) % 3;
double [] pxy0 = {
width*tex_coord[triangles[ntri][i0]][0],
height*(1.0 - tex_coord[triangles[ntri][i0]][1])};
double [] pxy1 = {
width*tex_coord[triangles[ntri][i1]][0],
height*(1.0 - tex_coord[triangles[ntri][i1]][1])};
double dx = pxy1[0] - pxy0[0];
double dy = pxy1[1] - pxy0[1];
double l = Math.sqrt(dx*dx+dy*dy);
for (int j = 0; j < l; j++) {
int px = (int) Math.round(pxy0[0]+j*dx/l);
int py = (int) Math.round(pxy0[1]+j*dy/l);
px = Math.min(Math.max(0, px), width_m1);
py = Math.min(Math.max(0, py), height_m1);
canvas[py*width+px] = line_color;
}
}
if (plot_center) {
double sx = 0.0, sy = 0.0;
for (int i = 0; i < 3; i++) {
sx+= width*tex_coord[triangles[ntri][i]][0];
sy+= height*(1.0 - tex_coord[triangles[ntri][i]][1]);
}
sx /= 3;
sy /= 3;
int px = (int) Math.round(sx);
int py = (int) Math.round(sy);
px = Math.min(Math.max(0, px), width_m1);
py = Math.min(Math.max(0, py), height_m1);
canvas[py*width+px] = center_color;
}
}
}
};
}
ImageDtt.startAndJoin(threads);
}
public static double [] getIndexedDisparities( // get disparity for each index
double [] disparity,
double min_disparity,
double max_disparity,
Rectangle bounds,
int [][] indices,
int tile_size,
int tilesX)
{
// int height = indices.length;
int width = indices[0].length;
int maxIndex = getMaxIndex(indices);
double [] indexedDisparity = new double [maxIndex+1];
int indx = 0;
if (disparity.length > (bounds.height * bounds.width)) { // old version - selected is full size
for (int y = 0; indx <= maxIndex; y++) {
for (int x = 0; (x < width) && (indx <= maxIndex); x++){
if (indices[y][x] >=0){
// center coordinates for 8*8 tile is [3.5,3.5]
double disp = (disparity == null)? min_disparity:( disparity[(bounds.y + y) * tilesX + (bounds.x + x)]);
if (disp < min_disparity) disp = min_disparity;
else if (disp > max_disparity) disp = max_disparity;
indexedDisparity[indx] =disp;
indx ++;
}
}
}
} else { // 09.18.2022
for (int y = 0; indx <= maxIndex; y++) {
for (int x = 0; (x < width) && (indx <= maxIndex); x++){
if (indices[y][x] >=0){
// center coordinates for 8*8 tile is [3.5,3.5]
double disp = (disparity == null)? min_disparity:( disparity[bounds.width *y + x]);
if (disp < min_disparity) disp = min_disparity;
else if (disp > max_disparity) disp = max_disparity;
indexedDisparity[indx] =disp;
indx ++;
}
}
}
}
return indexedDisparity;
}
public static double [][] getCoords( // get world XYZ in meters for indices
double [] disparity, // null - use min_disparity
double min_disparity,
double max_disparity,
Rectangle bounds,
int [][] indices,
int tile_size,
int tilesX,
boolean correctDistortions, // requires backdrop image to be corrected also
GeometryCorrection geometryCorrection)
{
// int height = indices.length;
int width = indices[0].length;
int maxIndex = getMaxIndex(indices);
double [][] coordinate = new double [maxIndex+1][];
int indx = 0;
if (disparity.length > (bounds.height * bounds.width)) { // old version - selected is full size
for (int y = 0; indx <= maxIndex; y++) {
for (int x = 0; (x < width) && (indx <= maxIndex); x++){
if (indices[y][x] >=0){
// center coordinates for 8*8 tile is [3.5,3.5]
double px = (bounds.x + x + 0.5) * tile_size - 0.5;
double py = (bounds.y + y + 0.5) * tile_size - 0.5;
double disp = (disparity == null)? min_disparity:( disparity[(bounds.y + y) * tilesX + (bounds.x + x)]);
if (disp < min_disparity) disp = min_disparity;
else if (disp > max_disparity) disp = max_disparity;
coordinate[indx] = geometryCorrection.getWorldCoordinates(
px,
py,
disp,
correctDistortions);
indx ++;
}
}
}
} else { // 09.18.2022
for (int y = 0; indx <= maxIndex; y++) {
for (int x = 0; (x < width) && (indx <= maxIndex); x++){
if (indices[y][x] >=0){
// center coordinates for 8*8 tile is [3.5,3.5]
double px = (bounds.x + x + 0.5) * tile_size - 0.5;
double py = (bounds.y + y + 0.5) * tile_size - 0.5;
double disp = (disparity == null)? min_disparity:( disparity[bounds.width * y + x]);
if (disp < min_disparity) disp = min_disparity;
else if (disp > max_disparity) disp = max_disparity;
coordinate[indx] = geometryCorrection.getWorldCoordinates(
px,
py,
disp,
correctDistortions);
indx ++;
}
}
}
}
return coordinate;
}
public static int [][] filterTriangles(
int [][] triangles,
double [] disparity, // disparities per vertex index
double maxDispDiff, // maximal relative disparity difference in a triangle
int debug_level)
{
final double min_avg = 3.0; // 0.5; // minimal average disparity to normalize triangle
class Triangle {
int [] points = new int [3];
Triangle (int i1, int i2, int i3){
points[0] = i1;
points[1] = i2;
points[2] = i3;
}
}
ArrayList<Triangle> triList = new ArrayList<Triangle>();
for (int i = 0; i < triangles.length; i++){
double disp_avg = (disparity[triangles[i][0]] + disparity[triangles[i][1]]+ disparity[triangles[i][2]])/3.0; // fixed 09.18.2022!
if (disp_avg < min_avg) disp_avg = min_avg;
loop:{
for (int j = 0; j < 3; j++){
int j1 = (j + 1) % 3;
if (Math.abs(disparity[triangles[i][j]] - disparity[triangles[i][j1]]) > (disp_avg* maxDispDiff)) {
if (debug_level > 1) {
System.out.println("removed triangle "+i+": "+
disparity[triangles[i][0]]+". "+disparity[triangles[i][1]]+". "+disparity[triangles[i][2]]+
". Avg = "+disp_avg);
}
break loop;
}
}
triList.add(new Triangle(
triangles[i][0],
triangles[i][1],
triangles[i][2]));
}
}
int [][] filteredTriangles = new int [triList.size()][3];
for (int i = 0; i < filteredTriangles.length; i++){
filteredTriangles[i] = triList.get(i).points;
}
return filteredTriangles;
}
public static int [][] filterTrianglesWorld(
int [][] triangles,
double [][] worldXYZ, // world per vertex index
double maxZtoXY,
double maxZ)
{
final double maxZtoXY2 = maxZtoXY * maxZtoXY;
class Triangle {
int [] points = new int [3];
Triangle (int i1, int i2, int i3){
points[0] = i1;
points[1] = i2;
points[2] = i3;
}
}
ArrayList<Triangle> triList = new ArrayList<Triangle>();
for (int i = 0; i < triangles.length; i++){
double [][] min_max = new double[3][2];
boolean not_too_far = true;
for (int di = 0; di < 3; di++) {
min_max[di][0] = worldXYZ[triangles[i][0]][di];
min_max[di][1] = min_max[di][0]; // both min and max to the same vertex 0
}
if (maxZ != 0) {
not_too_far &= worldXYZ[triangles[i][0]][2] > -maxZ;
}
for (int vi = 1; vi < 3; vi++) {
for (int di = 0; di < 3; di++) {
min_max[di][0] = Math.min(min_max[di][0], worldXYZ[triangles[i][vi]][di]);
min_max[di][1] = Math.max(min_max[di][1], worldXYZ[triangles[i][vi]][di]);
}
}
double dx = min_max[0][1]-min_max[0][0];
double dy = min_max[1][1]-min_max[1][0];
double dz = min_max[2][1]-min_max[2][0];
double ratio2 = dz*dz/(dx*dx+dy*dy + 0.001);
if (not_too_far && ((maxZtoXY == 0) || (ratio2 < maxZtoXY2))) {
triList.add(new Triangle(
triangles[i][0],
triangles[i][1],
triangles[i][2]));
}
}
int [][] filteredTriangles = new int [triList.size()][3];
for (int i = 0; i < filteredTriangles.length; i++){
filteredTriangles[i] = triList.get(i).points;
}
return filteredTriangles;
}
public static int [] reIndex(
int [][] indices,
int [][] triangles) {
int last_index = -1;
for (int i = 0; i < indices.length; i++) {
for (int j = 0; j < indices[i].length; j++) {
if (indices[i][j] > last_index) {
last_index = indices[i][j];
}
}
}
boolean [] used_indices = new boolean[last_index+1];
for (int i = 0; i < triangles.length; i++) {
for (int j = 0; j < triangles[i].length; j++) { // always 3
used_indices[triangles[i][j]] = true;
}
}
int new_len = 0;
for (int i = 0; i < used_indices.length; i++) if (used_indices[i]) {
new_len++;
}
if (new_len == used_indices.length) {
return null; // no re-indexing is needed
}
int [] re_index = new int [new_len];
int indx = 0;
for (int i = 0; i < indices.length; i++) {
for (int j = 0; j < indices[i].length; j++) {
int old_index=indices[i][j];
if (old_index >= 0) {
if (used_indices[old_index]) { // keep
re_index[indx] = old_index;
indices[i][j] = indx++;
} else {
indices[i][j] = -1;
}
}
}
}
return re_index;
}
public static int [][] triangulateCluster(
int [][] indices)
{
int height = indices.length;
int width = indices[0].length;
class Triangle {
int [] points = new int [3];
Triangle (int i1, int i2, int i3){
points[0] = i1;
points[1] = i2;
points[2] = i3;
}
}
ArrayList<Triangle> triList = new ArrayList<Triangle>();
for (int y = 0; y < (height - 1); y++){
for (int x = 0; x < width; x++){
if (indices[y][x] >= 0){
if ((x > 0) && (indices[y + 1][x - 1] >= 0) && (indices[y + 1][x] >= 0)){
triList.add(new Triangle(
indices[y][x],
indices[y + 1][x],
indices[y + 1][x - 1]));
}
if (x < (width - 1)) {
if (indices[y + 1][x] >= 0){
if (indices[y][x + 1] >= 0){
triList.add(new Triangle(
indices[y][x],
indices[y][x + 1],
indices[y + 1][x]));
} else if (indices[y + 1][x + 1] >= 0){
triList.add(new Triangle(
indices[y][x],
indices[y + 1][x + 1],
indices[y + 1][x]));
}
} else if ((indices[y][x + 1] >= 0) && (indices[y + 1][x + 1] >= 0)) {
triList.add(new Triangle(
indices[y][x],
indices[y][x + 1],
indices[y + 1][x + 1]));
}
}
}
}
}
int [][] triangles = new int [triList.size()][3];
for (int i = 0; i < triangles.length; i++){
triangles[i] = triList.get(i).points;
}
return triangles;
}
public static void testTriangles(
String texturePath, // if not null - will show
Rectangle bounds,
boolean [] selected,
double [] disparity,
int tile_size,
int tilesX,
int tilesY,
int [][] indices,
int [][] triangles,
double [][] debug_triangles) // if not null - should be [2][width* height], will mark disparity and triangles
{
String [] titles = {"disparity","triangles"};
double [][] dbg_img = new double [titles.length][tilesX*tilesY*tile_size*tile_size];
Arrays.fill(dbg_img[0], Double.NaN);
if (selected.length > (bounds.height * bounds.width)) { // old version - selected is full size
for (int i = 0; i < selected.length; i++ ){
double d = selected[i]? ((disparity.length >1) ? disparity[i] : disparity[0]):Double.NaN;
int y = i / tilesX;
int x = i % tilesX;
for (int dy = 0; dy <tile_size; dy ++){
for (int dx = 0; dx <tile_size; dx ++){
dbg_img[0][(y * tile_size + dy)*(tile_size*tilesX) + (x * tile_size + dx)] = d;
}
}
}
} else { // 09.18.2022
for (int i = 0; i < selected.length; i++ ){
double d = selected[i]? ((disparity.length > 1) ? disparity[i] : disparity[0]):Double.NaN;
int y = i / bounds.width + bounds.y;
int x = i % bounds.width + bounds.x;
for (int dy = 0; dy <tile_size; dy ++){
for (int dx = 0; dx <tile_size; dx ++){
dbg_img[0][(y * tile_size + dy)*(tile_size*tilesX) + (x * tile_size + dx)] = d;
}
}
}
}
int maxIndex = getMaxIndex(indices);
int [][] pxy = new int [maxIndex+1][2];
int height = indices.length;
int width = indices[0].length;
for (int y = 0; y < height; y++){
for (int x = 0; x < width; x++){
if (indices[y][x] >= 0){
pxy[indices[y][x]][0] = (bounds.x + x)*tile_size + (tile_size/2);
pxy[indices[y][x]][1] = (bounds.y + y)*tile_size + (tile_size/2);
}
}
}
for (int i = 0; i < triangles.length; i++ ){
for (int side = 0; side < triangles[i].length; side++){
int [] pntIndx = {
triangles[i][side],
(side == (triangles[i].length -1)? triangles[i][0]:triangles[i][side+1])};
int dx = iSign(pxy[pntIndx[1]][0] - pxy[pntIndx[0]][0]);
int dy = iSign(pxy[pntIndx[1]][1] - pxy[pntIndx[0]][1]);
for (int j = 0; j < tile_size; j++){
int x = pxy[pntIndx[0]][0] + dx*j;
int y = pxy[pntIndx[0]][1] + dy*j;
dbg_img[1][y * tile_size * tilesX + x] = 10.0; //1711748
}
}
}
if (texturePath != null) {
ShowDoubleFloatArrays.showArrays(
dbg_img,
tilesX * tile_size,
tilesY * tile_size,
true,
"triangles-"+texturePath,
titles);
}
if (debug_triangles != null) {
int indx_tri = (debug_triangles.length>1) ? 1 : 0;
for (int i = 0; i < debug_triangles[indx_tri].length; i++) {
if (dbg_img[1][i] > 0) {
debug_triangles[indx_tri][i] = dbg_img[1][i]; // 10.0 to have the same scale as disparity
}
}
if (indx_tri > 0) {
for (int i = 0; i < debug_triangles[indx_tri].length; i++) {
if (!Double.isNaN(dbg_img[0][i])) {
debug_triangles[0][i] = dbg_img[0][i]; // disparity if not NaN
}
}
}
}
}
static int iSign (int a) {return (a > 0) ? 1 : ((a < 0)? -1 : 0);}
static int getMaxIndex(int [][] indices)
{
int height = indices.length;
int width = indices[0].length;
for (int y = height - 1 ; y >= 0; y--) {
for (int x = width - 1; x >= 0; x--){
if (indices[y][x] >= 0){
return indices[y][x];
}
}
}
return -1;
}
public static void generateClusterX3d(
boolean full_texture, // true - full size image, false - bounds only
int subdivide_mesh, // 0,1 - full tiles only, 2 - 2x2 pixels, 4 - 2x2 pixels
// boolean [] alpha, // boolean alpha - true - opaque, false - transparent. Full/bounds
// matching selection
X3dOutput x3dOutput, // output x3d if not null
WavefrontExport wfOutput, // output WSavefront if not null
ArrayList<TriMesh> tri_meshes,
String texturePath,
String id,
String class_name,
Rectangle bounds,
boolean [] selected, // may be either tilesX * tilesY or bounds.width*bounds.height
double [] disparity, // if null, will use min_disparity
int tile_size,
int tilesX,
int tilesY,
GeometryCorrection geometryCorrection,
boolean correctDistortions, // requires backdrop image to be corrected also
boolean show_triangles,
double min_disparity,
double max_disparity,
double maxDispTriangle, // relative <=0 - do not use
double maxZtoXY, // 10.0. <=0 - do not use
double maxZ, // far clip (0 - do not clip). Negative - limit by max
boolean limitZ,
double [][] dbg_disp_tri_slice,
int debug_level
) throws IOException
{
// int debug_level = 1;
if (bounds == null) {
return; // not used in lwir
}
int [][] indices = getCoordIndices( // starting with 0, -1 - not selected // updated 09.18.2022
bounds,
selected,
tilesX);
double [][] texCoord = getTexCoords( // get texture coordinates for indices
full_texture ? (new int[] {tilesX, tilesY}): null,
indices);
double [][] worldXYZ = getCoords( // get world XYZ in meters for indices // updated 09.18.2022
disparity,
min_disparity,
max_disparity,
bounds,
indices,
tile_size,
tilesX,
correctDistortions, // requires backdrop image to be corrected also
geometryCorrection);
double [] indexedDisparity = getIndexedDisparities( // get disparity for each index // updated 09.18.2022
disparity,
min_disparity,
max_disparity,
bounds,
indices,
tile_size,
tilesX);
int [][] triangles = triangulateCluster(
indices);
int num_removed = 0;
if (maxDispTriangle > 0.0) {
int pre_num = triangles.length;
triangles = filterTriangles( // remove crazy triangles with large disparity difference
triangles,
indexedDisparity, // disparities per vertex index
maxDispTriangle, // maximal disparity difference in a triangle
debug_level + 0); // int debug_level);
if (triangles.length < pre_num) {
num_removed += pre_num - triangles.length;
if (debug_level > 0) {
System.out.println("filterTriangles() removed "+ (pre_num - triangles.length)+" triangles");
}
}
}
if ((maxZ != 0.0) && limitZ) {
for (int i = 0; i < worldXYZ.length; i++) {
if (worldXYZ[i][2] < -maxZ) {
double k = -maxZ/worldXYZ[i][2];
worldXYZ[i][0] *= k;
worldXYZ[i][1] *= k;
worldXYZ[i][2] *= k;
}
}
}
if ((maxZtoXY > 0.0) || ((maxZ != 0) && !limitZ) ) {
int pre_num = triangles.length;
triangles = filterTrianglesWorld(
triangles,
worldXYZ, // world per vertex index
maxZtoXY,
maxZ);
if (triangles.length < pre_num) {
num_removed += pre_num - triangles.length;
if (debug_level > 0) {
System.out.println("filterTrianglesWorld() removed "+ (pre_num - triangles.length)+" triangles");
}
}
}
if (triangles.length == 0) {
if (debug_level > 0) {
System.out.println("generateClusterX3d() no triangles left in a cluster");
}
return; // all triangles removed
}
if (num_removed > 0) {
int [] re_index = reIndex( // Move to TriMesh?
indices, // will be modified if needed (if some indices are removed
triangles);
if (re_index != null) {// need to update other arrays: texCoord, worldXYZ. indexedDisparity[] will not be used
int num_indices_old = worldXYZ.length;
int [] inv_index = new int [num_indices_old];
Arrays.fill(inv_index,-1); // just to get an error
for (int i = 0; i < re_index.length; i++) {
inv_index[re_index[i]] = i;
}
double [][] texCoord_new = new double [re_index.length][];
double [][] worldXYZ_new = new double [re_index.length][];
for (int i = 0; i < re_index.length; i++) {
texCoord_new[i] = texCoord[re_index[i]];
worldXYZ_new[i] = worldXYZ[re_index[i]];
}
texCoord = texCoord_new;
worldXYZ = worldXYZ_new;
for (int i = 0; i < triangles.length; i++) {
for (int j=0; j < triangles[i].length; j++) {
triangles[i][j] = inv_index[triangles[i][j]];
}
}
}
if (debug_level > 0) {
show_triangles = true; // show after removed
}
}
if (show_triangles || (dbg_disp_tri_slice != null)) {
double [] ddisp = (disparity == null)?(new double[1]):disparity;
if (disparity == null) {
ddisp[0] = min_disparity;
}
testTriangles(
(show_triangles? texturePath: null),
bounds,
selected,
ddisp, // disparity, // if disparity.length == 1 - use for all
tile_size,
tilesX, // int tilesX,
tilesY, // int tilesY,
indices,
triangles,
dbg_disp_tri_slice); // double [][] debug_triangles);
}
if (x3dOutput != null) {
x3dOutput.addCluster(
texturePath,
id,
class_name,
texCoord,
worldXYZ,
triangles);
}
if (wfOutput != null) {
wfOutput.addCluster(
texturePath,
id,
// class_name,
texCoord,
worldXYZ,
triangles);
}
if (tri_meshes != null) {
tri_meshes.add(new TriMesh(
texturePath, // String texture_image,
worldXYZ, // double [][] worldXYZ,
texCoord, // double [][] texCoord,
triangles)); // int [][] triangles
}
}
// New version with subdivision
public static void generateClusterX3d( // New version with alpha
boolean full_texture, // true - full size image, false - bounds only
int subdivide_mesh, // 0,1 - full tiles only, 2 - 2x2 pixels, 4 - 2x2 pixels
boolean [] alpha, // boolean alpha - true - opaque, false - transparent. Full/bounds
// matching selection
X3dOutput x3dOutput, // output x3d if not null
WavefrontExport wfOutput, // output WSavefront if not null
ArrayList<TriMesh> tri_meshes,
String texturePath,
String id,
String class_name,
Rectangle bounds,
// Below selected and disparity are bounds.width*bounds.height
boolean [] selected, // may be either tilesX * tilesY or bounds.width*bounds.height
double [] disparity, // if null, will use min_disparity
int tile_size,
int tilesX,
int tilesY,
GeometryCorrection geometryCorrection,
boolean correctDistortions, // requires backdrop image to be corrected also
double [] tri_img, //
int tri_img_width,
double min_disparity,
double max_disparity,
double maxDispTriangle, // relative <=0 - do not use
double maxZtoXY, // 10.0. <=0 - do not use
double maxZ, // far clip (0 - do not clip). Negative - limit by max
boolean limitZ,
// double [][] dbg_disp_tri_slice,
int debug_level
) throws IOException
{
boolean show_triangles = tri_img != null;
if (bounds == null) {
return; // not used in lwir
}
/*
int [][] indices = getCoordIndices( // starting with 0, -1 - not selected // updated 09.18.2022
bounds,
selected,
tilesX);
*/
int [] pnum_indices = new int[1];
/*
* Enumerate "large" and "small" tiles, where "large" are actual tiles and "small" are
* subdivided (by subdiv in each direction) ones to increase lateral mesh resolution.
* sub-tiles are populated if at least one pixel in it is opaque. Input selected_tiles
* and alpha arrays may correspond to either full image or rectangular bounds, output
* array always corresponds to bounds.
*/
int [][][][] indices = getCoordIndices( // starting with 0, -1 - not selected
bounds, // final Rectangle bounds,
selected, // final boolean [] selected_tiles, can not be null
tilesX, // final int tilesX,
tile_size, // final int tile_size,
alpha, // final boolean [] alpha,
subdivide_mesh, // final int subdiv,
pnum_indices); // final int [] num_indices
/*
* Convert "large" tiles to arrays of small ones if it has a small-tile neighbor with
* gaps along the border with this one
*/
pnum_indices[0] = splitLargeTileIndices(
indices, // int [][][][] indices)
subdivide_mesh); // final int [] num_indices) {
Rectangle tex_rect = full_texture ? new Rectangle(bounds.x, bounds.y, tilesX, tilesY) : null;
/*
* Get texture coordinates (0..1) for horizontal (positive - to the right) and vertical (positive - up)
*/
double [][] texCoord = getTexCoords( // get texture coordinates for indices
tex_rect, // final Rectangle rect, // 0 or full width, full height of the image in tiles
pnum_indices[0], // final int num_indices,
indices); // final int [][][][] indices)
double [][] worldXYZ = getCoords( // get world XYZ in meters for indices // updated 09.18.2022
disparity, // final double [] disparity,
min_disparity, // final double min_disparity,
max_disparity, // final double max_disparity,
bounds, // final Rectangle bounds,
pnum_indices[0], // final int num_indices,
indices, // final int [][][][] indices,
tilesX, // final int tilesX,
tile_size, // final int tile_size,
correctDistortions, // requires backdrop image to be corrected also
geometryCorrection);// final GeometryCorrection geometryCorrection)
/*
double [] indexedDisparity = getIndexedDisparities( // get disparity for each index // updated 09.18.2022
disparity,
min_disparity,
max_disparity,
bounds,
pnum_indices[0], // final int num_indices,
indices,
tilesX);
*/
/*
* Triangulate all vertice indices - combine triangulation of same-size equailateral 45-degree
* large (tile size) and small (tile subdivisions) and add connections between large and small ones
*/
int [][] triangles = triangulateAll(
indices);
final boolean plot_center = true;
final double line_color = 1.0;
final double center_color = 3.0;
if (tri_img != null) {
plotMesh(
tri_img, // final double [] canvas,
tri_img_width, // final int width,
texCoord, // final double [][] tex_coord,
triangles, // final int [][] triangles,
plot_center, // final boolean plot_center,
line_color, // final double line_color,
center_color); // final double center_color)
}
if (x3dOutput != null) {
x3dOutput.addCluster(
texturePath,
id,
class_name,
texCoord,
worldXYZ,
triangles);
}
if (wfOutput != null) {
wfOutput.addCluster(
texturePath,
id,
texCoord,
worldXYZ,
triangles);
}
if (tri_meshes != null) {
tri_meshes.add(new TriMesh(
texturePath, // String texture_image,
worldXYZ, // double [][] worldXYZ,
texCoord, // double [][] texCoord,
triangles)); // int [][] triangles
}
}
} }
...@@ -248,7 +248,7 @@ public class X3dOutput { ...@@ -248,7 +248,7 @@ public class X3dOutput {
return bbox; return bbox;
} }
public void generateKML( public static void generateKML(
String path, String path,
boolean overwrite, boolean overwrite,
String icon_path, //<href>x3d/1487451413_967079.x3d</href> ? String icon_path, //<href>x3d/1487451413_967079.x3d</href> ?
...@@ -341,14 +341,14 @@ public class X3dOutput { ...@@ -341,14 +341,14 @@ public class X3dOutput {
} }
Element addTextElement(Document doc, Element parent, String name, String text) static Element addTextElement(Document doc, Element parent, String name, String text)
{ {
Element child = doc.createElement(name); Element child = doc.createElement(name);
child.setTextContent(text); child.setTextContent(text);
parent.appendChild(child); parent.appendChild(child);
return child; return child;
} }
Element addElement(Document doc, Element parent, String name) static Element addElement(Document doc, Element parent, String name)
{ {
Element child = doc.createElement(name); Element child = doc.createElement(name);
parent.appendChild(child); parent.appendChild(child);
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment