/*
* Geotoolkit - An Open Source Java GIS Toolkit
* http://www.geotoolkit.org
*
* (C) 2013, Geomatys
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation;
* version 2.1 of the License.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*/
package org.geotoolkit.display3d.scene.quadtree;
import com.jogamp.opengl.GLAutoDrawable;
import com.jogamp.opengl.util.texture.TextureData;
import java.awt.Dimension;
import java.awt.Point;
import org.apache.sis.geometry.GeneralEnvelope;
import org.geotoolkit.math.XMath;
import org.opengis.geometry.Envelope;
import javax.vecmath.Point3i;
import java.util.Arrays;
import org.geotoolkit.display3d.Map3D;
import org.geotoolkit.display3d.scene.ContextContainer3D;
import org.geotoolkit.display3d.scene.SceneNode3D;
import org.geotoolkit.display3d.scene.Terrain;
import org.geotoolkit.display3d.scene.component.Tile3D;
import org.geotoolkit.gui.swing.tree.Trees;
/**
* @author Thomas Rouby (Geomatys))
*/
public class QuadTreeNode extends SceneNode3D {
/**
* The id of this node. The id contains the id of the parent node and the node position on parent node.
*/
private final Point parentPosition;
/**
* position in pyramid x,y,depth
*/
private final Point3i pyramidPosition;
/**
* The Tile3D SceneNode to draw
*/
private Tile3D data;
/**
* The envelope for the Tile3D sceneNode on create
*/
private Envelope envelope;
private boolean isDataImageLoaded = false, isDataMNTLoaded = false;
/**
* Arbitrary tileSize in pixel of each Tile3D of the node
*/
private static final Dimension tileSize = new Dimension(256,256);
private static final Dimension ptsSize = new Dimension(25,25);
private final QuadTreeNode quadParent;
private QuadTreeNode[][] quadchildren;
/**
* Constructor for root QuadTree node
*
* @param map
* @param parent
* @param envelope if null, get map.getTerrain().getEnvelope()
* @param position
*/
public QuadTreeNode(Map3D map, QuadTreeNode parent, Envelope envelope, Point position) {
super(map);
this.envelope = envelope;
if (this.envelope == null) {
final Terrain terrain = ((ContextContainer3D)map.getContainer()).getTerrain();
if (terrain != null) {
this.envelope = terrain.getEnvelope();
}
}
quadParent = parent;
if(parent == null){
this.pyramidPosition = new Point3i(0,0,0);
this.parentPosition = null;
}else{
this.pyramidPosition = new Point3i(
parent.pyramidPosition.x*2 + position.x,
parent.pyramidPosition.y*2 + position.y,
parent.getPosition().z+1);
this.parentPosition = position;
}
}
public void setDataImage(TextureData textureData) {
if (data != null && !isDataImageLoaded) {
isDataImageLoaded = true;
data.setTextureDataImg(textureData);
}
}
public boolean isDataImageLoaded() {
return isDataImageLoaded;
}
public void setDataMNT(float[] vertices) {
if (data != null && !isDataMNTLoaded) {
isDataMNTLoaded = true;
data.setMNT(vertices);
}
}
public boolean isDataMNTLoaded() {
return isDataMNTLoaded;
}
private synchronized QuadTreeNode[][] getQuadChildren(boolean create){
if(quadchildren==null && create){
quadchildren = new QuadTreeNode[2][2];
quadchildren[0][0] = createQuadChild(0, 0);
quadchildren[0][1] = createQuadChild(0, 1);
quadchildren[1][0] = createQuadChild(1, 0);
quadchildren[1][1] = createQuadChild(1, 1);
}
return quadchildren;
}
private QuadTreeNode createQuadChild(int x, int y){
final Envelope env = this.getEnvelope();
GeneralEnvelope childEnv = new GeneralEnvelope(env.getCoordinateReferenceSystem());
if (x == 0) {
childEnv.setRange(0, env.getMinimum(0), env.getMedian(0));
} else {
childEnv.setRange(0, env.getMedian(0), env.getMaximum(0));
}
if (y == 0) {
childEnv.setRange(1, env.getMedian(1), env.getMaximum(1));
} else {
childEnv.setRange(1, env.getMinimum(1), env.getMedian(1));
}
return new QuadTreeNode(getCanvas(), this, childEnv, new Point(x, y));
}
/**
* Get or create the child at position of succession of couple (x,y)
* Couple (x,y) can be :
* (0,0),(0,1),(1,0) or (1,1)
*
* @param id
* @return
*/
public QuadTreeNode getOrCreateChild(Point[] id) {
if (id == null) return null;
QuadTreeNode searchNode = this;
for (Point pos : id) {
searchNode = searchNode.getOrCreateChild(pos);
}
return searchNode;
}
/**
* Get or create the child at position (x,y)
* Couple (x,y) can be :
* (0,0),(0,1),(1,0) or (1,1)
*
* @param pos
* @return
*/
public QuadTreeNode getOrCreateChild(Point pos) {
if (pos == null) return null;
final QuadTreeNode[][] children = getQuadChildren(true);
return children[pos.x][pos.y];
}
/**
* Return the object id of this node
* @return
*/
public Point[] getId() {
final Point[] stack = new Point[getTreeDepth()];
QuadTreeNode node = this;
int i=stack.length-1;
while(i>=0){
stack[i] = node.parentPosition;
node = node.quadParent;
i--;
}
return stack;
}
/**
* Get the sceneNode to draw
* @return
*/
public SceneNode3D getData() {
return this.data;
}
public QuadTreeNode getQuadParent() {
return quadParent;
}
/**
* Create data and initialize with first parents nodes with data
*
* @return
*/
public SceneNode3D getOrCreateData() {
if (this.data == null) {
this.data = new Tile3D(getCanvas(), this.envelope, this.pyramidPosition, this.ptsSize.width, this.ptsSize.height);
this.initDataMnt();
this.initDataImage();
this.getChildren().add(data);
}
return this.data;
}
public void removeData() {
if (this.data != null) {
this.getChildren().remove(this.data);
this.data.dispose();
this.data = null;
}
this.isDataImageLoaded = false;
this.isDataMNTLoaded = false;
}
public boolean isData(){
return this.data != null;
}
private void initDataImage() {
final QuadTreeNode parentImage = this.getFirstParentWithDataImage();
if (parentImage != null && parentImage.isData() && parentImage.isDataImageLoaded()) {
final Tile3D dataImage = parentImage.data;
if (dataImage != null) {
final float[] rangeUVs = dataImage.getUVsRange();
float minU = rangeUVs[0],
minV = rangeUVs[1],
maxU = rangeUVs[2],
maxV = rangeUVs[3];
final Point[] ids = getId();
for (int i = parentImage.getTreeDepth(); i<this.getTreeDepth(); i++) {
final Point pos = ids[i];
final float medianU = (maxU+minU)/2.0f;
final float medianV = (maxV+minV)/2.0f;
if (pos.x == 0) {
maxU = medianU;
} else {
minU = medianU;
}
if (pos.y == 0) {
maxV = medianV;
} else {
minV = medianV;
}
}
final float[] uvs = this.data.getUVsAsArray();
for (int j=0; j<this.data.getNumVertex(); j++){
final float oldU = uvs[j*2];
uvs[j*2] = oldU * maxU + (1.0f-oldU)*minU;
final float oldV = uvs[j*2+1];
uvs[j*2 + 1] = oldV * maxV + (1.0f-oldV)*minV;
}
this.data.setTextureImg(dataImage.getTextureImg());
this.data.setUVs(uvs);
this.data.setUVsRange(minU, minV, maxU, maxV);
}
}
}
private void initDataMnt() {
final QuadTreeNode parentMnt = this.getFirstParentWithDataMNT();
if (parentMnt != null && parentMnt.isData() && parentMnt.isDataMNTLoaded()) {
final Tile3D dataMNT = parentMnt.data;
if (dataMNT != null && parentMnt.isDataMNTLoaded()) {
float minU, minV, maxU, maxV;
minU = minV = 0.0f;
maxU = maxV = 1.0f;
final Point[] ids = getId();
for (int i = parentMnt.getTreeDepth(); i<this.getTreeDepth(); i++) {
final Point pos = ids[i];
final float medianU = (maxU+minU)/2.0f;
final float medianV = (maxV+minV)/2.0f;
if (pos.x == 0) {
maxU = medianU;
} else {
minU = medianU;
}
if (pos.y == 0) {
maxV = medianV;
} else {
minV = medianV;
}
}
final float[] vertices = this.data.getVerticesAsArray();
final Dimension ptsSize = this.data.getPtsNumber();
for (int x=0; x<ptsSize.width; x++) {
for (int y=0; y<ptsSize.height; y++) {
// (i+j*axis0Pts)*3
final int pixel0 = XMath.clamp(x, 1, ptsSize.width-1);
final int pixel1 = XMath.clamp(y, 1, ptsSize.height-1);
final double valX = (pixel0-1.0) / (ptsSize.width-2.0);
final double valY = (pixel1-1.0) / (ptsSize.height-2.0);
final double scaleX = valX * maxU + (1.0f-valX)*minU;
final double scaleY = valY * maxV + (1.0f-valY)*minV;
vertices[(x+y*ptsSize.width)*3 + 2] = (float)dataMNT.getZValue(scaleX, scaleY);
if (x == 0 || y == 0 || x == ptsSize.width-1 || y == ptsSize.height-1) {
vertices[(x+y*ptsSize.width)*3 + 2] -= 2000.0f;
}
}
}
this.data.setVertices(vertices);
}
}
}
/**
* Return first parent with data != null
* return null else or if this node is the root node
*
* @return
*/
private QuadTreeNode getFirstParentWithDataImage() {
if (this.getTreeDepth() == 0) return null;
QuadTreeNode parent = quadParent;
while (parent!= null){
if (parent.isData() && parent.isDataImageLoaded()) {
return parent;
}
parent = parent.quadParent;
}
return null;
}
/**
* Return first parent with data != null
* return null else or if this node is the root node
*
* @return
*/
private QuadTreeNode getFirstParentWithDataMNT() {
if (this.getTreeDepth() == 0) return null;
QuadTreeNode parent = quadParent;
while (parent!=null){
if (parent.isData() && parent.isDataMNTLoaded()) {
return parent;
}
parent = parent.quadParent;
}
return null;
}
/**
* Shortcut to id.length
* @return
*/
public int getTreeDepth() {
return this.pyramidPosition.z;
}
/**
* Return the QuadTreeNode position relative to his parent
* @return
*/
public Point getNodePosition() {
return this.parentPosition;
}
/**
* Return the QuadTreeNode position relative to mosaic
* @return
*/
public Point3i getPosition() {
return this.pyramidPosition;
}
public Dimension getTileSize() {
return this.tileSize;
}
public Dimension getPtsSize() {
return this.ptsSize;
}
public Envelope getEnvelope() {
return this.envelope;
}
@Override
public String toString() {
final QuadTreeNode[][] children = getQuadChildren(false);
if(children == null){
return pyramidPosition.toString() +isData();
}else{
return Trees.toString(pyramidPosition.toString() +isData(),
Arrays.asList(children[0][0],children[0][1],children[1][0],children[1][1]));
}
}
@Override
public void dispose(GLAutoDrawable glDrawable) {
super.dispose(glDrawable);
if (data != null) {
data.dispose(glDrawable);
this.removeData();
}
}
}