MinMaxTreeTile.java
- /* Copyright 2013-2022 CS GROUP
- * Licensed to CS GROUP (CS) under one or more
- * contributor license agreements. See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * CS licenses this file to You under the Apache License, Version 2.0
- * (the "License"); you may not use this file except in compliance with
- * the License. You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
- package org.orekit.rugged.intersection.duvenhage;
- import org.hipparchus.util.FastMath;
- import org.orekit.rugged.errors.DumpManager;
- import org.orekit.rugged.raster.SimpleTile;
- import org.orekit.rugged.utils.MaxSelector;
- import org.orekit.rugged.utils.MinSelector;
- import org.orekit.rugged.utils.Selector;
- /** Implementation of a {@link org.orekit.rugged.raster.Tile} with a min/max kd tree.
- * <p>
- * A n level min/max kd-tree contains sub-tiles merging individual cells
- * together from coarse-grained (at level 0) to fine-grained (at level n-1).
- * Level n-1, which is the deepest one, is computed from the raw cells by
- * merging adjacent cells pairs columns (i.e. cells at indices (i, 2j)
- * and (i, 2j+1) are merged together by computing and storing the minimum
- * and maximum in a sub-tile. Level n-1 therefore has the same number of rows
- * but half the number of columns of the raw tile, and its sub-tiles are
- * 1 cell high and 2 cells wide. Level n-2 is computed from level n-1 by
- * merging sub-tiles rows. Level n-2 therefore has half the number of rows
- * and half the number of columns of the raw tile, and its sub-tiles are
- * 2 cells high and 2 cells wide. Level n-3 is again computed by merging
- * columns, level n-4 merging rows and so on. As depth decreases, the number
- * of sub-tiles decreases and their size increase. Level 0 is reached when
- * there is only either one row or one column of large sub-tiles.
- * </p>
- * <p>
- * During the merging process, if at some stage there is an odd number of
- * rows or columns, then the last sub-tile at next level will not be computed
- * by merging two rows/columns from the current level, but instead computed by
- * simply copying the last single row/column. The process is therefore well
- * defined for any raw tile initial dimensions. A direct consequence is that
- * the dimension of the sub-tiles in the last row or column may be smaller than
- * the dimension of regular sub-tiles.
- * </p>
- * <p>
- * If we consider for example a tall 107 ⨉ 19 raw tile, the min/max kd-tree will
- * have 9 levels:
- * </p>
- *
- * <table summary="" border="0">
- * <tr style="background-color:#EEEEFF;">
- * <td>Level</td> <td>Number of sub-tiles</td> <td>Regular sub-tiles dimension</td></tr>
- * <tr> <td align="center">8</td> <td align="center">107 ⨉ 10</td> <td align="center"> 1 ⨉ 2</td>
- * <tr> <td align="center">7</td> <td align="center"> 54 ⨉ 10</td> <td align="center"> 2 ⨉ 2</td>
- * <tr> <td align="center">6</td> <td align="center"> 54 ⨉ 5</td> <td align="center"> 2 ⨉ 4</td>
- * <tr> <td align="center">5</td> <td align="center"> 27 ⨉ 5</td> <td align="center"> 4 ⨉ 4</td>
- * <tr> <td align="center">4</td> <td align="center"> 27 ⨉ 3</td> <td align="center"> 4 ⨉ 8</td>
- * <tr> <td align="center">3</td> <td align="center"> 14 ⨉ 3</td> <td align="center"> 8 ⨉ 8</td>
- * <tr> <td align="center">2</td> <td align="center"> 14 ⨉ 2</td> <td align="center"> 8 ⨉ 16</td>
- * <tr> <td align="center">1</td> <td align="center"> 7 ⨉ 2</td> <td align="center">16 ⨉ 16</td>
- * <tr> <td align="center">0</td> <td align="center"> 7 ⨉ 1</td> <td align="center">16 ⨉ 32</td>
- * </table>
- * <p>
- * @see MinMaxTreeTileFactory
- * @author Luc Maisonobe
- */
- public class MinMaxTreeTile extends SimpleTile {
- /** Raw elevations. */
- private double[] raw;
- /** Min kd-tree. */
- private double[] minTree;
- /** Max kd-tree. */
- private double[] maxTree;
- /** Start indices of tree levels. */
- private int[] start;
- /** Simple constructor.
- * <p>
- * Creates an empty tile.
- * </p>
- */
- MinMaxTreeTile() {
- }
- /** {@inheritDoc} */
- @Override
- protected void processUpdatedElevation(final double[] elevations) {
- raw = elevations;
- final int nbRows = getLatitudeRows();
- final int nbCols = getLongitudeColumns();
- // set up the levels
- final int size = setLevels(0, nbRows, nbCols);
- minTree = new double[size];
- maxTree = new double[size];
- // compute min/max trees
- if (start.length > 0) {
- final double[] preprocessed = new double[raw.length];
- preprocess(preprocessed, raw, nbRows, nbCols, MinSelector.getInstance());
- applyRecursively(minTree, start.length - 1, nbRows, nbCols, MinSelector.getInstance(), preprocessed, 0);
- preprocess(preprocessed, raw, nbRows, nbCols, MaxSelector.getInstance());
- applyRecursively(maxTree, start.length - 1, nbRows, nbCols, MaxSelector.getInstance(), preprocessed, 0);
- }
- }
- /** Get the number of kd-tree levels (not counting raw elevations).
- * @return number of kd-tree levels
- * @see #getMinElevation(int, int, int)
- * @see #getMaxElevation(int, int, int)
- * @see #getMergeLevel(int, int, int, int)
- */
- public int getLevels() {
- return start.length;
- }
- /** Get the minimum elevation at some level tree.
- * <p>
- * Note that the min elevation is <em>not</em> computed
- * only at cell center, but considering that it is interpolated
- * considering also Eastwards and Northwards neighbors, and extends
- * up to the center of these neighbors. As an example, lets consider
- * four neighboring cells in some Digital Elevation Model:
- * <table summary="" border="0" cellpadding="5" style="background-color:#f5f5dc;">
- * <tr><th style="background-color:#c9d5c9;">j+1</th><td>11</td><td>10</td></tr>
- * <tr><th style="background-color:#c9d5c9;">j</th><td>12</td><td>11</td></tr>
- * <tr style="background-color:#c9d5c9;"><th>j/i</th><th>i</th><th>i+1</th></tr>
- * </table>
- * When we interpolate elevation at a point located slightly South-West
- * to the center of the (i+1, j+1) cell, we use all four cells in the
- * interpolation, and we will get a result very close to 10 if we start
- * close to (i+1, j+1) cell center. As the min value for this interpolation
- * is stored at (i, j) indices, this implies that {@code getMinElevation(i,
- * j, l)} must return 10 if l is chosen such that the sub-tile at
- * tree level l includes cell (i,j) but not cell (i+1, j+1). In other words,
- * interpolation implies sub-tile boundaries are overshoot by one column to
- * the East and one row to the North when computing min.
- *
- * @param i row index of the cell
- * @param j column index of the cell
- * @param level tree level
- * @return minimum value that can be reached when interpolating elevation
- * in the sub-tile
- * @see #getLevels()
- * @see #getMaxElevation(int, int, int)
- * @see #getMergeLevel(int, int, int, int)
- */
- public double getMinElevation(final int i, final int j, final int level) {
- // compute indices in level merged array
- final int k = start.length - level;
- final int rowShift = k / 2;
- final int colShift = (k + 1) / 2;
- final int levelI = i >> rowShift;
- final int levelJ = j >> colShift;
- final int levelC = 1 + ((getLongitudeColumns() - 1) >> colShift);
- if (DumpManager.isActive()) {
- final int[] min = locateMin(i, j, level);
- final int index = min[0] * getLongitudeColumns() + min[1];
- DumpManager.dumpTileCell(this, min[0], min[1], raw[index]);
- if (index + getLongitudeColumns() < raw.length) {
- DumpManager.dumpTileCell(this, min[0] + 1, min[1], raw[index + getLongitudeColumns()]);
- }
- if (index + 1 < raw.length) {
- DumpManager.dumpTileCell(this, min[0], min[1] + 1, raw[index + 1]);
- }
- if (index + getLongitudeColumns() + 1 < raw.length) {
- DumpManager.dumpTileCell(this, min[0] + 1, min[1] + 1, raw[index + getLongitudeColumns() + 1]);
- }
- }
- return minTree[start[level] + levelI * levelC + levelJ];
- }
- /** Get the maximum elevation at some level tree.
- * <p>
- * Note that the max elevation is <em>not</em> computed
- * only at cell center, but considering that it is interpolated
- * considering also Eastwards and Northwards neighbors, and extends
- * up to the center of these neighbors. As an example, lets consider
- * four neighboring cells in some Digital Elevation Model:
- * <table summary="" border="0" cellpadding="5" style="background-color:#f5f5dc;">
- * <tr><th style="background-color:#c9d5c9;">j+1</th><td>11</td><td>12</td></tr>
- * <tr><th style="background-color:#c9d5c9;">j</th><td>10</td><td>11</td></tr>
- * <tr style="background-color:#c9d5c9;"><th>j/i</th><th>i</th><th>i+1</th></tr>
- * </table>
- * When we interpolate elevation at a point located slightly South-West
- * to the center of the (i+1, j+1) cell, we use all four cells in the
- * interpolation, and we will get a result very close to 12 if we start
- * close to (i+1, j+1) cell center. As the max value for this interpolation
- * is stored at (i, j) indices, this implies that {@code getMaxElevation(i,
- * j, l)} must return 12 if l is chosen such that the sub-tile at
- * tree level l includes cell (i,j) but not cell (i+1, j+1). In other words,
- * interpolation implies sub-tile boundaries are overshoot by one column to
- * the East and one row to the North when computing max.
- *
- * @param i row index of the cell
- * @param j column index of the cell
- * @param level tree level
- * @return maximum value that can be reached when interpolating elevation
- * in the sub-tile
- * @see #getLevels()
- * @see #getMinElevation(int, int, int)
- * @see #getMergeLevel(int, int, int, int)
- */
- public double getMaxElevation(final int i, final int j, final int level) {
- // compute indices in level merged array
- final int k = start.length - level;
- final int rowShift = k / 2;
- final int colShift = (k + 1) / 2;
- final int levelI = i >> rowShift;
- final int levelJ = j >> colShift;
- final int levelC = 1 + ((getLongitudeColumns() - 1) >> colShift);
- if (DumpManager.isActive()) {
- final int[] max = locateMax(i, j, level);
- final int index = max[0] * getLongitudeColumns() + max[1];
- DumpManager.dumpTileCell(this, max[0], max[1], raw[index]);
- if (index + getLongitudeColumns() < raw.length) {
- DumpManager.dumpTileCell(this, max[0] + 1, max[1], raw[index + getLongitudeColumns()]);
- }
- if (index + 1 < raw.length) {
- DumpManager.dumpTileCell(this, max[0], max[1] + 1, raw[index + 1]);
- }
- if (index + getLongitudeColumns() + 1 < raw.length) {
- DumpManager.dumpTileCell(this, max[0] + 1, max[1] + 1, raw[index + getLongitudeColumns() + 1]);
- }
- }
- return maxTree[start[level] + levelI * levelC + levelJ];
- }
- /** Locate the cell at which min elevation is reached for a specified level.
- * <p>
- * Min is computed with respect to the continuous interpolated elevation, which
- * takes four neighboring cells into account. This implies that the cell at which
- * min value is reached for some level is either within the sub-tile for this level,
- * or in some case it may be one column outside to the East or one row outside to
- * the North. See {@link #getMinElevation()} for a more complete explanation.
- * </p>
- * @param i row index of the cell
- * @param j column index of the cell
- * @param level tree level of the sub-tile considered
- * @return row/column indices of the cell at which min elevation is reached
- */
- public int[] locateMin(final int i, final int j, final int level) {
- return locateMinMax(i, j, level, MinSelector.getInstance(), minTree);
- }
- /** Locate the cell at which max elevation is reached for a specified level.
- * <p>
- * Max is computed with respect to the continuous interpolated elevation, which
- * takes four neighboring cells into account. This implies that the cell at which
- * max value is reached for some level is either within the sub-tile for this level,
- * or in some case it may be one column outside to the East or one row outside to
- * the North. See {@link #getMaxElevation()} for a more complete explanation.
- * </p>
- * @param i row index of the cell
- * @param j column index of the cell
- * @param level tree level of the sub-tile considered
- * @return row/column indices of the cell at which min elevation is reached
- */
- public int[] locateMax(final int i, final int j, final int level) {
- return locateMinMax(i, j, level, MaxSelector.getInstance(), maxTree);
- }
- /** Locate the cell at which min/max elevation is reached for a specified level.
- * @param i row index of the cell
- * @param j column index of the cell
- * @param level tree level of the sub-tile considered
- * @param selector min/max selector to use
- * @param tree min/max tree to use
- * @return row/column indices of the cell at which min/max elevation is reached
- */
- private int[] locateMinMax(final int i, final int j, final int level,
- final Selector selector, final double[] tree) {
- final int k = start.length - level;
- int rowShift = k / 2;
- int colShift = (k + 1) / 2;
- int levelI = i >> rowShift;
- int levelJ = j >> colShift;
- int levelR = 1 + ((getLatitudeRows() - 1) >> rowShift);
- int levelC = 1 + ((getLongitudeColumns() - 1) >> colShift);
- // track the cell ancestors from merged tree at specified level up to tree at level 1
- for (int l = level + 1; l < start.length; ++l) {
- if (isColumnMerging(l)) {
- --colShift;
- levelC = 1 + ((getLongitudeColumns() - 1) >> colShift);
- levelJ = levelJ << 1;
- if (levelJ + 1 < levelC) {
- // the cell results from a regular merging of two columns
- if (selector.selectFirst(tree[start[l] + levelI * levelC + levelJ + 1],
- tree[start[l] + levelI * levelC + levelJ])) {
- levelJ++;
- }
- }
- } else {
- --rowShift;
- levelR = 1 + ((getLatitudeRows() - 1) >> rowShift);
- levelI = levelI << 1;
- if (levelI + 1 < levelR) {
- // the cell results from a regular merging of two rows
- if (selector.selectFirst(tree[start[l] + (levelI + 1) * levelC + levelJ],
- tree[start[l] + levelI * levelC + levelJ])) {
- levelI++;
- }
- }
- }
- }
- // we are now at first merge level, which always results from a column merge
- // or pre-processed data, which themselves result from merging four cells
- // used in interpolation
- // this imply the ancestor of min/max at (n, m) is one of
- // (2n, m), (2n+1, m), (2n+2, m), (2n, m+1), (2n+1, m+1), (2n+2, m+1)
- int selectedI = levelI;
- int selectedJ = 2 * levelJ;
- double selectedElevation = Double.NaN;
- for (int n = 2 * levelJ; n < 2 * levelJ + 3; ++n) {
- if (n < getLongitudeColumns()) {
- for (int m = levelI; m < levelI + 2; ++m) {
- if (m < getLatitudeRows()) {
- final double elevation = raw[m * getLongitudeColumns() + n];
- if (selector.selectFirst(elevation, selectedElevation)) {
- selectedI = m;
- selectedJ = n;
- selectedElevation = elevation;
- }
- }
- }
- }
- }
- return new int[] {
- selectedI, selectedJ
- };
- }
- /** Get the deepest level at which two cells are merged in the same min/max sub-tile.
- * @param i1 row index of first cell
- * @param j1 column index of first cell
- * @param i2 row index of second cell
- * @param j2 column index of second cell
- * @return deepest level at which two cells are merged in the same min/max sub-tile,
- * or -1 if they are never merged in the same sub-tile
- * @see #getLevels()
- * @see #getMinElevation(int, int, int)
- * @see #getMaxElevation(int, int, int)
- */
- public int getMergeLevel(final int i1, final int j1, final int i2, final int j2) {
- int largest = -1;
- for (int level = 0; level < start.length; ++level) {
- // compute indices in level merged array
- final int k = start.length - level;
- final int rowShift = k / 2;
- final int colShift = (k + 1) / 2;
- final int levelI1 = i1 >> rowShift;
- final int levelJ1 = j1 >> colShift;
- final int levelI2 = i2 >> rowShift;
- final int levelJ2 = j2 >> colShift;
- if (levelI1 != levelI2 || levelJ1 != levelJ2) {
- return largest;
- }
- largest = level;
- }
- return largest;
- }
- /** Get the index of sub-tiles start rows crossed.
- * <p>
- * When going from one row to another row at some tree level,
- * we cross sub-tiles boundaries. This method returns the index
- * of these boundaries.
- * </p>
- * @param row1 starting row
- * @param row2 ending row
- * @param level tree level
- * @return indices of rows crossed at sub-tiles boundaries, in crossing order,
- * the endpoints <em>are</em> included (i.e. if {@code row1} or {@code row2} are
- * boundary rows, they will be in returned array)
- */
- public int[] getCrossedBoundaryRows(final int row1, final int row2, final int level) {
- // number of rows in each sub-tile
- final int rows = 1 << ((start.length - level) / 2);
- // build the crossings in ascending order
- final int min = FastMath.min(row1, row2);
- final int max = FastMath.max(row1, row2) + 1;
- return buildCrossings((min + rows - 1) - ((min + rows - 1) % rows), max, rows,
- row1 <= row2);
- }
- /** Get the index of sub-tiles start columns crossed.
- * <p>
- * When going from one column to another column at some tree level,
- * we cross sub-tiles boundaries. This method returns the index
- * of these boundaries.
- * </p>
- * @param column1 starting column
- * @param column2 ending column (excluded)
- * @param level tree level
- * @return indices of columns crossed at sub-tiles boundaries, in crossing order,
- * the endpoints <em>are</em> included (i.e. if {@code column1} or {@code column2} are
- * boundary columns, they will be in returned array)
- */
- public int[] getCrossedBoundaryColumns(final int column1, final int column2, final int level) {
- // number of columns in each sub-tile
- final int columns = 1 << ((start.length + 1 - level) / 2);
- // build the crossings in ascending order
- final int min = FastMath.min(column1, column2);
- final int max = FastMath.max(column1, column2) + 1;
- return buildCrossings((min + columns - 1) - ((min + columns - 1) % columns), max, columns,
- column1 <= column2);
- }
- /** Build crossings arrays.
- * @param begin begin crossing index
- * @param end end crossing index (excluded, if equal to begin, the array is empty)
- * @param step crossing step
- * @param ascending if true, the crossings must be in ascending order
- * @return indices of rows or columns crossed at sub-tiles boundaries, in crossing order
- */
- private int[] buildCrossings(final int begin, final int end, final int step, final boolean ascending) {
- // allocate array
- final int n = FastMath.max(0, (end - begin + step + ((step > 0) ? -1 : +1)) / step);
- final int[] crossings = new int[n];
- // fill it up
- int crossing = begin;
- if (ascending) {
- for (int i = 0; i < crossings.length; ++i) {
- crossings[i] = crossing;
- crossing += step;
- }
- } else {
- for (int i = 0; i < crossings.length; ++i) {
- crossings[crossings.length - 1 - i] = crossing;
- crossing += step;
- }
- }
- return crossings;
- }
- /** Check if the merging operation between level and level-1 is a column merging.
- * @param level level to check
- * @return true if the merging operation between level and level-1 is a column
- * merging, false if is a row merging
- */
- public boolean isColumnMerging(final int level) {
- return (level & 0x1) == (start.length & 0x1);
- }
- /** Recursive setting of tree levels.
- * <p>
- * The following algorithms works for any array shape, even with
- * rows or columns which are not powers of 2 or with one
- * dimension much larger than the other. As an example, starting
- * from a 107 ⨉ 19 array, we get the following 9 levels, for a
- * total of 2187 elements in each tree:
- * </p>
- * <p>
- * <table border="0">
- * <tr BGCOLOR="#EEEEFF">
- * <td>Level</td> <td>Dimension</td> <td>Start index</td> <td>End index (inclusive)</td></tr>
- * <tr> <td>0</td> <td> 7 ⨉ 1</td> <td> 0</td> <td> 6</td> </tr>
- * <tr> <td>1</td> <td> 7 ⨉ 2</td> <td> 7</td> <td> 20</td> </tr>
- * <tr> <td>2</td> <td> 14 ⨉ 2</td> <td> 21</td> <td> 48</td> </tr>
- * <tr> <td>3</td> <td> 14 ⨉ 3</td> <td> 49</td> <td> 90</td> </tr>
- * <tr> <td>4</td> <td> 27 ⨉ 3</td> <td> 91</td> <td>171</td> </tr>
- * <tr> <td>5</td> <td> 27 ⨉ 5</td> <td> 172</td> <td>306</td> </tr>
- * <tr> <td>6</td> <td> 54 ⨉ 5</td> <td> 307</td> <td>576</td> </tr>
- * <tr> <td>7</td> <td> 54 ⨉ 10</td> <td> 577</td> <td>1116</td> </tr>
- * <tr> <td>8</td> <td>107 ⨉ 10</td> <td>1117</td> <td>2186</td> </tr>
- * </table>
- * </p>
- * @param stage number of merging stages
- * @param stageRows number of rows at current stage
- * @param stageColumns number of columns at current stage
- * @return size cumulative size from root to current level
- */
- private int setLevels(final int stage, final int stageRows, final int stageColumns) {
- if (stageRows == 1 || stageColumns == 1) {
- // we have found root, stop recursion
- start = new int[stage];
- if (stage > 0) {
- start[0] = 0;
- }
- return stageRows * stageColumns;
- }
- final int size;
- if ((stage & 0x1) == 0) {
- // columns merging
- size = setLevels(stage + 1, stageRows, (stageColumns + 1) / 2);
- } else {
- // rows merging
- size = setLevels(stage + 1, (stageRows + 1) / 2, stageColumns);
- }
- if (stage > 0) {
- // store current level characteristics
- start[start.length - stage] = size;
- return size + stageRows * stageColumns;
- } else {
- // we don't count the elements at stage 0 as they are not stored in the
- // min/max trees (they correspond to the raw elevation, without merging)
- return size;
- }
- }
- /** Preprocess recursive application of a function.
- * <p>
- * At start, the min/max should be computed for each cell using the four corners values.
- * </p>
- * @param preprocessed preprocessed array to fill up
- * @param elevations raw elevations te preprocess
- * @param nbRows number of rows
- * @param nbCols number of columns
- * @param selector selector to use
- */
- private void preprocess(final double[] preprocessed, final double[] elevations,
- final int nbRows, final int nbCols,
- final Selector selector) {
- int k = 0;
- for (int i = 0; i < nbRows - 1; ++i) {
- // regular elements with both a column at right and a row below
- for (int j = 0; j < nbCols - 1; ++j) {
- preprocessed[k] = selector.select(selector.select(elevations[k], elevations[k + 1]),
- selector.select(elevations[k + nbCols], elevations[k + nbCols + 1]));
- k++;
- }
- // last column elements, lacking a right column
- preprocessed[k] = selector.select(elevations[k], elevations[k + nbCols]);
- k++;
- }
- // last row elements, lacking a below row
- for (int j = 0; j < nbCols - 1; ++j) {
- preprocessed[k] = selector.select(elevations[k], elevations[k + 1]);
- k++;
- }
- // last element
- preprocessed[k] = elevations[k];
- }
- /** Recursive application of a function.
- * @param tree tree to fill-up with the recursive applications
- * @param level current level
- * @param levelRows number of rows at current level
- * @param levelColumns number of columns at current level
- * @param selector to apply
- * @param base base array from which function arguments are drawn
- * @param first index of the first element to consider in base array
- */
- private void applyRecursively(final double[] tree,
- final int level, final int levelRows, final int levelColumns,
- final Selector selector,
- final double[] base, final int first) {
- if (isColumnMerging(level + 1)) {
- // merge columns pairs
- int iTree = start[level];
- int iBase = first;
- final int nextColumns = (levelColumns + 1) / 2;
- final boolean odd = (levelColumns & 0x1) != 0;
- final int jEnd = odd ? nextColumns - 1 : nextColumns;
- for (int i = 0; i < levelRows; ++i) {
- // regular pairs
- for (int j = 0; j < jEnd; ++j) {
- tree[iTree++] = selector.select(base[iBase], base[iBase + 1]);
- iBase += 2;
- }
- if (odd) {
- // last column
- tree[iTree++] = base[iBase++];
- }
- }
- if (level > 0) {
- applyRecursively(tree, level - 1, levelRows, nextColumns, selector, tree, start[level]);
- }
- } else {
- // merge rows pairs
- int iTree = start[level];
- int iBase = first;
- final int nextRows = (levelRows + 1) / 2;
- final boolean odd = (levelRows & 0x1) != 0;
- final int iEnd = odd ? nextRows - 1 : nextRows;
- // regular pairs
- for (int i = 0; i < iEnd; ++i) {
- for (int j = 0; j < levelColumns; ++j) {
- tree[iTree++] = selector.select(base[iBase], base[iBase + levelColumns]);
- iBase++;
- }
- iBase += levelColumns;
- }
- if (odd) {
- // last row
- System.arraycopy(base, iBase, tree, iTree, levelColumns);
- }
- if (level > 0) {
- applyRecursively(tree, level - 1, nextRows, levelColumns, selector, tree, start[level]);
- }
- }
- }
- }