package framework.gameMain;

import javax.media.j3d.Appearance;
import javax.media.j3d.ColoringAttributes;
import javax.media.j3d.DistanceLOD;
import javax.media.j3d.IndexedQuadArray;
import javax.media.j3d.IndexedTriangleArray;
import javax.media.j3d.Material;
import javax.media.j3d.Shape3D;
import javax.media.j3d.Switch;
import javax.media.j3d.TexCoordGeneration;
import javax.media.j3d.Texture;
import javax.media.j3d.TransparencyAttributes;
import javax.vecmath.Color3f;
import javax.vecmath.Color4f;
import javax.vecmath.Point3d;
import javax.vecmath.Point3f;
import javax.vecmath.Vector3f;
import javax.vecmath.Vector4f;

import com.sun.j3d.utils.image.TextureLoader;

import framework.animation.Animation3D;
import framework.animation.PartAnimation;
import framework.model3D.BumpMapGenerator;
import framework.model3D.FresnelsReflectionMapGenerator;
import framework.model3D.Object3D;
import framework.model3D.Position3D;
import framework.model3D.ReflectionMapGenerator;

/**
 * 水面を表現するオブジェクト。
 * 波の動きはバンプマッピングをスクロールさせて表現している。
 * 反射ではフレネル効果を加味している。
 * Java3Dでは、頂点間の反射ベクトルは線形補間でしか求めていないため、
 * 精度を上げるために、LODを使って水面の視点に近い部分はメッシュ分割を細かくしている。
 * @author Nitta
 *
 */
public class Water extends Animatable {
	private static int DEFAULT_FIRST_LEVEL_MESH_SIZE = 25;
	private static int DEFAULT_SECOND_LEVEL_MESH_SIZE = 1;
	private static int DEFAULT_THIRD_LEVEL_MESH_SIZE = 40;
	
	public Water(double minX, double minZ, double maxX, double maxZ, double height, boolean bUseLOD) {
		this(minX, minZ, maxX, maxZ, height, 0.5f, bUseLOD);
	}
	
	public Water(double minX, double minZ, double maxX, double maxZ, double height, float transparency, boolean bUseLOD) {
		this(minX, minZ, maxX, maxZ, height, transparency, 0.3f, bUseLOD);		
	}
	
	public Water(double minX, double minZ, double maxX, double maxZ, double height, float transparency, float reflection, boolean bUseLOD) {
		this(minX, minZ, maxX, maxZ, height, transparency, reflection, new Color3f(0.2f, 0.5f, 1.0f), bUseLOD);				
	}

	public Water(double minX, double minZ, double maxX, double maxZ, double height, 
			float transparency, float reflection, Color3f waterColor, boolean bUseLOD) {
		this(minX, minZ, maxX, maxZ, height, transparency, reflection, new Color3f(0.2f, 0.5f, 1.0f), 
				DEFAULT_FIRST_LEVEL_MESH_SIZE, DEFAULT_SECOND_LEVEL_MESH_SIZE, DEFAULT_THIRD_LEVEL_MESH_SIZE, bUseLOD);						
	}	
	
	public Water(double minX, double minZ, double maxX, double maxZ, double height, float transparency, float reflection, 
			int firstLevelMeshSize, int secondLevelMeshSize, int thirdLevelMeshSize, boolean bUseLOD) {
		this(minX, minZ, maxX, maxZ, height, transparency, reflection, new Color3f(0.2f, 0.5f, 1.0f), 
				firstLevelMeshSize, secondLevelMeshSize, thirdLevelMeshSize, bUseLOD);						
	}	
	
	public Water(double minX, double minZ, double maxX, double maxZ, double height, float transparency, float reflection, Color3f waterColor, 
			int firstLevelMeshSize, int secondLevelMeshSize, int thirdLevelMeshSize, boolean bUseLOD) {
		super(null, null);
		
		int firstLevelMeshSizeX = firstLevelMeshSize;
		int firstLevelMeshSizeZ = firstLevelMeshSize;
		int secondLevelMeshSizeX = secondLevelMeshSize;
		int secondLevelMeshSizeZ = secondLevelMeshSize;
		int thirdLevelMeshSizeX = thirdLevelMeshSize;
		int thirdLevelMeshSizeZ = thirdLevelMeshSize;
		
		
		// 水面のジオメトリの作成
		IndexedQuadArray coarseWaterGeometry = 
			new IndexedQuadArray((secondLevelMeshSizeX + 1) * (secondLevelMeshSizeZ + 1), 
				IndexedQuadArray.COORDINATES | IndexedQuadArray.NORMALS, 4 * secondLevelMeshSizeX * secondLevelMeshSizeZ);
		
		IndexedQuadArray fineWaterGeometry = 
			new IndexedQuadArray((secondLevelMeshSizeX * thirdLevelMeshSizeX + 1) * (secondLevelMeshSizeZ * thirdLevelMeshSizeZ + 1), 
				IndexedQuadArray.COORDINATES | IndexedQuadArray.NORMALS, 4 * secondLevelMeshSizeX * thirdLevelMeshSizeX * secondLevelMeshSizeZ * thirdLevelMeshSizeZ);
		
		double unitSizeX1 = (maxX - minX) / firstLevelMeshSizeX;
		double unitSizeZ1 = (maxZ - minZ) / firstLevelMeshSizeZ;
		double unitSizeX2 = unitSizeX1 / secondLevelMeshSizeX;
		double unitSizeZ2 = unitSizeZ1 / secondLevelMeshSizeZ;
		double unitSizeX3 = unitSizeX2 / thirdLevelMeshSizeX;
		double unitSizeZ3 = unitSizeZ2 / thirdLevelMeshSizeZ;
		double baseX = -(maxX - minX) / firstLevelMeshSizeX / 2.0;
		double baseZ = -(maxZ - minZ) / firstLevelMeshSizeZ / 2.0;
		
		// 第二、第三レベルは LOD で切り替え
		for (int z2 = 0; z2 < secondLevelMeshSizeZ + 1; z2++) {
			for (int x2 = 0; x2 < secondLevelMeshSizeX + 1; x2++) {
				int indexX2 = x2;
				int indexZ2 = z2;
				int index20 = indexZ2 * secondLevelMeshSizeX + indexX2;
				int index21 = indexZ2 * (secondLevelMeshSizeX + 1) + indexX2;
				int index22 = (indexZ2 + 1) * (secondLevelMeshSizeX + 1) + indexX2;
				double xx2 = baseX + unitSizeX2 * indexX2;
				double zz2 = baseZ + unitSizeZ2 * indexZ2;
				coarseWaterGeometry.setCoordinate(index21, new Point3d(xx2, 0.0, zz2));
				coarseWaterGeometry.setNormal(index21, new Vector3f(0.0f, 1.0f, 0.0f));
				if (indexX2 < secondLevelMeshSizeX && indexZ2 < secondLevelMeshSizeZ) {
					coarseWaterGeometry.setCoordinateIndices(index20 * 4, new int[]{index22, index22 + 1, index21 + 1, index21});
					if (bUseLOD) {
						for (int z3 = 0; z3 < thirdLevelMeshSizeZ + 1; z3++) {
							for (int x3 = 0; x3 < thirdLevelMeshSizeX + 1; x3++) {
								int indexX3 = x2 * thirdLevelMeshSizeX + x3;
								int indexZ3 = z2 * thirdLevelMeshSizeZ + z3;
								int index30 = indexZ3 * (secondLevelMeshSizeX * thirdLevelMeshSizeX) + indexX3;
								int index31 = indexZ3 * (secondLevelMeshSizeX * thirdLevelMeshSizeX + 1) + indexX3;
								int index32 = (indexZ3 + 1) * (secondLevelMeshSizeX * thirdLevelMeshSizeX + 1) + indexX3;
								double xx3 = baseX + unitSizeX3 * indexX3;
								double zz3 = baseZ + unitSizeZ3 * indexZ3;
								fineWaterGeometry.setCoordinate(index31, new Point3d(xx3, 0.0, zz3));
								fineWaterGeometry.setNormal(index31, new Vector3f(0.0f, 1.0f, 0.0f));
								if (indexX3 < secondLevelMeshSizeX * thirdLevelMeshSizeX 
										&& indexZ3 < secondLevelMeshSizeZ * thirdLevelMeshSizeZ) {
									fineWaterGeometry.setCoordinateIndices(index30 * 4, new int[]{index32, index32 + 1, index31 + 1, index31});
								}					
							}
						}
					}
				}					
			}
		}
		
		// 第一レベルは Object3D の集合で構成
		Object3D children[] = new Object3D[firstLevelMeshSizeX * firstLevelMeshSizeZ];
		TextureLoader loaderWater = new TextureLoader("data\\texture\\waternormalMap.jpg", 
				TextureLoader.BY_REFERENCE, 
				null);
		Texture textureWater = loaderWater.getTexture();		
		// 水面の表面属性の作成（全体で共有する）
		Appearance a = new Appearance();
		a.setCapability(Appearance.ALLOW_TEXTURE_UNIT_STATE_READ);
		a.setCapability(Appearance.ALLOW_TEXTURE_UNIT_STATE_WRITE);
		Material m1 = new Material();
		m1.setDiffuseColor(waterColor);	// 水の色
		m1.setSpecularColor(0.0f, 0.0f, 0.0f);
		a.setMaterial(m1);
		ColoringAttributes ca = new ColoringAttributes();
		ca.setShadeModel(ColoringAttributes.NICEST);
		a.setColoringAttributes(ca);

		TransparencyAttributes ta = new TransparencyAttributes();
		ta.setTransparency(transparency);	// 透明度
		ta.setTransparencyMode(TransparencyAttributes.BLENDED);
		a.setTransparencyAttributes(ta);
		// 拡張表面属性（全体で共有する）
		TexCoordGeneration tcg = new TexCoordGeneration(TexCoordGeneration.OBJECT_LINEAR, TexCoordGeneration.TEXTURE_COORDINATE_3);
		tcg.setPlaneS(new Vector4f(0.1f, 0.0f, 0.0f, 0.0f));
		tcg.setPlaneT(new Vector4f(0.0f, 0.0f, 0.1f, 0.0f));
		BumpMapGenerator bumpMapGenerator = new BumpMapGenerator(textureWater, tcg, true);	// テクスチャユニットを1つ使用
		FresnelsReflectionMapGenerator fresnelsReflectionMapGenerator = 
			new FresnelsReflectionMapGenerator(new Color4f(reflection, reflection, reflection, 1.0f), true);	// テクスチャユニットを2つ使用		
		
		for (int z1 = 0; z1 < firstLevelMeshSizeZ; z1++) {
			for (int x1 = 0; x1 < firstLevelMeshSizeX; x1++) {
				double xx1 = minX + (maxX - minX) / firstLevelMeshSizeX * x1;
				double zz1 = minZ + (maxZ - minZ) / firstLevelMeshSizeZ * z1;
				Object3D waterSurfaceUnit;
				if (bUseLOD) {
					DistanceLOD lod = new DistanceLOD();
					Switch surfaceSwitch = new Switch();
					surfaceSwitch.addChild(new Shape3D(fineWaterGeometry, a));
					surfaceSwitch.addChild(new Shape3D(coarseWaterGeometry, a));
					lod.addSwitch(surfaceSwitch);
					lod.setDistance(0, unitSizeX1 / 2.0);
					lod.setPosition(new Point3f(0.0f, 0.0f, 0.0f));
					waterSurfaceUnit = new Object3D("waterUnit", lod);
				} else {
					waterSurfaceUnit = new Object3D("waterUnit", new Shape3D(coarseWaterGeometry, a));					
				}
				waterSurfaceUnit.apply(new Position3D(xx1, height, zz1), false);
				
				// 拡張表面属性を設定
				waterSurfaceUnit.setBumpMapping(bumpMapGenerator);
				waterSurfaceUnit.setReflectionMapping(fresnelsReflectionMapGenerator);		
				
				children[z1 * firstLevelMeshSizeX + x1] = waterSurfaceUnit;
			}
		}		
		
		// 全体オブジェクトを構成
		Object3D waterSurface = new Object3D("water", children);
		
		body = waterSurface;
		
		// アニメーションの作成
		Animation3D waveAnimation = new Animation3D();
		PartAnimation pa = new PartAnimation("waterUnit", 0);
		pa.addTexture(0L, new Position3D(0.0, 0.0, 0.0));
		pa.addTexture(10000L, new Position3D(1.0, -1.0, 0.0));
		waveAnimation.addPartAnimation(pa);
		animation = waveAnimation;
	}

	@Override
	public void onEndAnimation() {
	}
}
