package framework.model3D;

import java.util.ArrayList;
import java.util.Collection;
import java.util.Hashtable;
import java.util.Iterator;

import javax.media.j3d.Appearance;
import javax.media.j3d.DirectionalLight;
import javax.media.j3d.Geometry;
import javax.media.j3d.GeometryArray;
import javax.media.j3d.IndexedQuadArray;
import javax.media.j3d.IndexedTriangleArray;
import javax.media.j3d.IndexedTriangleFanArray;
import javax.media.j3d.IndexedTriangleStripArray;
import javax.media.j3d.Light;
import javax.media.j3d.Material;
import javax.media.j3d.Node;
import javax.media.j3d.PointLight;
import javax.media.j3d.PolygonAttributes;
import javax.media.j3d.RenderingAttributes;
import javax.media.j3d.Shape3D;
import javax.media.j3d.Transform3D;
import javax.media.j3d.TransparencyAttributes;
import javax.media.j3d.TriangleFanArray;
import javax.media.j3d.TriangleStripArray;
import javax.vecmath.Point3d;
import javax.vecmath.Point3f;
import javax.vecmath.Vector3d;
import javax.vecmath.Vector3f;

import com.sun.j3d.utils.geometry.Box;
import com.sun.j3d.utils.geometry.Cone;
import com.sun.j3d.utils.geometry.Cylinder;
import com.sun.j3d.utils.geometry.Sphere;

/**
 * ステンシルバッファを用いたシャドウボリューム
 * @author 新田直也
 *
 */
public class ShadowVolume {
	private static final boolean WIRE_FRAME_VIEW = false;
	
	private Light light = null;
	private double shadowDepth = 10.0;
	
	private IndexedQuadArray volumeGeometry = null;
	private Shape3D shadowVolumeFront = null;
	private Shape3D shadowVolumeBack = null;
	
	private int vertexCount = 0;
	private double coordinates[] = null;
	private int indicies[] = null;
	private Hashtable<Integer, Edge> edges = null;
	private ArrayList<Triangle> triangles = null;	
	
	/**
	 * 平行光源用のシャドウボリューム
	 * @param occuluder 遮蔽オブジェクト（影を落とすオブジェクト）
	 * @param light 光源
	 * @param shadowDepth 影の深さ
	 * @param localToWorld 遮蔽オブジェクト用の座標変換
	 */
	public ShadowVolume(BaseObject3D occuluder, Light light, double shadowDepth,
			Transform3D localToWorld) {
		this.light = light;
		this.shadowDepth = shadowDepth;
		
		// 幾何情報の抽出
		if (!extractGeometricInformation(occuluder)) return;
		
		// シャドウボリュームの表面を生成
		volumeGeometry = createVolumeSurface(light, shadowDepth, localToWorld, coordinates, edges, triangles);
		
		// シャドウボリュームオブジェクトの生成
		createShadowVolumeObject(volumeGeometry, occuluder);
	}
	
	/**
	 * シャドウボリュームの更新
	 * @param localToWorld 遮蔽オブジェクト用の座標変換
	 */
	public void update(Transform3D localToWorld) {
		// シャドウボリュームの表面を再生成
		volumeGeometry = createVolumeSurface(light, shadowDepth, localToWorld, coordinates, edges, triangles);
		
		// シャドウボリュームオブジェクトの更新
		shadowVolumeFront.removeAllGeometries();
		shadowVolumeFront.addGeometry(volumeGeometry);
		shadowVolumeBack.removeAllGeometries();
		shadowVolumeBack.addGeometry(volumeGeometry);
	}
	
	/**
	 * 幾何情報の抽出
	 * @param shadowCastingObject 抽出元のオブジェクト
	 * @return true --- 正常に抽出できた, false --- 正常に抽出できなかった
	 */
	private boolean extractGeometricInformation(BaseObject3D shadowCastingObject) {
		Node node = shadowCastingObject.getPrimitiveNode();
		if (node == null) return false;
		// GeometryArrayの習得
		ArrayList<Geometry> geometries = new ArrayList<Geometry>();
		Geometry g = null;
		if (node instanceof Shape3D) {
			g = ((Shape3D)node).getGeometry();
			geometries.add(g);
		} else if (node instanceof Cone) {
			g = ((Cone)node).getShape(Cone.BODY).getGeometry();
			geometries.add(g);
			g = ((Cone)node).getShape(Cone.CAP).getGeometry();
			geometries.add(g);
		} else if (node instanceof Cylinder) {
			g = ((Cylinder)node).getShape(Cylinder.BODY).getGeometry();			
			geometries.add(g);
			g = ((Cylinder)node).getShape(Cylinder.TOP).getGeometry();			
			geometries.add(g);
			g = ((Cylinder)node).getShape(Cylinder.BOTTOM).getGeometry();			
			geometries.add(g);
		} else if (node instanceof Box) {
			g = ((Box)node).getShape(Box.TOP).getGeometry();
			geometries.add(g);
			g = ((Box)node).getShape(Box.BOTTOM).getGeometry();
			geometries.add(g);
			g = ((Box)node).getShape(Box.FRONT).getGeometry();
			geometries.add(g);
			g = ((Box)node).getShape(Box.BACK).getGeometry();
			geometries.add(g);
			g = ((Box)node).getShape(Box.LEFT).getGeometry();
			geometries.add(g);
			g = ((Box)node).getShape(Box.RIGHT).getGeometry();
			geometries.add(g);
		} else if (node instanceof Sphere) {
			g = ((Sphere)node).getShape().getGeometry();
			geometries.add(g);
		} else {
			return false;
		}

		// 頂点の座標値を取得し、設定
		vertexCount = 0;
		for (int n = 0; n < geometries.size(); n++) {
			g = geometries.get(n);
			if (g instanceof GeometryArray) {
				// GeometryArray の場合				
				GeometryArray geometryArray = (GeometryArray)g;
				vertexCount += geometryArray.getVertexCount();
			}
		}
		coordinates = new double[vertexCount * 3 * 2];		
		indicies = new int[vertexCount * 4 * 3];		// 辺の数を頂点の数の最大3倍と考えている
		int base = 0;
		for (int n = 0; n < geometries.size(); n++) {
			g = geometries.get(n);
			if (g instanceof GeometryArray) {
				// GeometryArray の場合				
				GeometryArray geometryArray = (GeometryArray)g;
				double coordinate[] = new double[3];
				int count = geometryArray.getVertexCount();
				for (int v = 0; v < count; v++) {
					geometryArray.getCoordinate(v, coordinate);
					coordinates[(v + base) * 3] = coordinate[0];
					coordinates[(v + base) * 3 + 1] = coordinate[1];
					coordinates[(v + base) * 3 + 2] = coordinate[2];
				}
				base += count;
			}
		}
				
		// 面と辺の情報を作成
		edges = new Hashtable<Integer, Edge>();
		triangles = new ArrayList<Triangle>();
		base = 0;
		for (int n = 0; n < geometries.size(); n++) {
			g = geometries.get(n);
			if (g instanceof GeometryArray) {
				// GeometryArray の場合				
				GeometryArray geometryArray = (GeometryArray)g;
				Point3d p1 = new Point3d();
				Point3d p2 = new Point3d();
				Point3d p3 = new Point3d();
				int v1, v2, v3;
				if (g instanceof TriangleStripArray) {
					// TriangleStripArray の場合
					TriangleStripArray triStripArray = (TriangleStripArray)g;				
					int num = triStripArray.getNumStrips();
					int strips[] = new int[num];
					triStripArray.getStripVertexCounts(strips);
					int index = base;
					for (int j = 0; j < num; j++) {
						index += 2;
						for (int i = 2; i < strips[j]; i += 2) {
							addTriangle(index - 2, index - 1, index, p1, p2, p3);						
							if (i <= strips[j] - 2) {
								addTriangle(index - 1, index + 1, index, p1, p2, p3);						
								index += 1;
							}
							index += 1;
						}
					}
				} else if (g instanceof TriangleFanArray) {
						// TriangleFanArray の場合
						TriangleFanArray triFanArray = (TriangleFanArray)g;					
						int num = triFanArray.getNumStrips();
						int strips[] = new int[num];
						triFanArray.getStripVertexCounts(strips);
						int index = base;
						for (int j = 0; j < num; j++) {
							v1 = index;
							index += 1;
							for (int i = 1; i < strips[j] - 1; i += 1) {
								addTriangle(v1, index, index + 1, p1, p2, p3);						
								index += 1;
							}
						}
				} else if (g instanceof IndexedTriangleArray) {
					// IndexedTriangleArray の場合
					IndexedTriangleArray triArray = (IndexedTriangleArray)g;				
					int indexCount = triArray.getIndexCount();
					for (int i = 0; i < indexCount; i += 3) {
						v1 = triArray.getCoordinateIndex(i) + base;
						v2 = triArray.getCoordinateIndex(i + 1) + base;
						v3 = triArray.getCoordinateIndex(i + 2) + base;
						addTriangle(v1, v2, v3, p1, p2, p3);
					}
				} else if (g instanceof IndexedTriangleStripArray) {
					// IndexedTriangleStripArray の場合
					IndexedTriangleStripArray triStripArray = (IndexedTriangleStripArray)g;				
					int num = triStripArray.getNumStrips();
					int strips[] = new int[num];
					triStripArray.getStripIndexCounts(strips);
					int index = 0;
					for (int j = 0; j < num; j++) {
						index += 2;
						for (int i = 2; i < strips[j]; i += 2) {
							v1 = triStripArray.getCoordinateIndex(index - 2) + base;
							v2 = triStripArray.getCoordinateIndex(index - 1) + base;
							v3 = triStripArray.getCoordinateIndex(index) + base;
							addTriangle(v1,v2, v3, p1, p2, p3);						
							if (i <= strips[j] - 2) {
								v1 = v2;
								v2 = triStripArray.getCoordinateIndex(index + 1) + base;
								addTriangle(v1, v2, v3, p1, p2, p3);						
								index += 1;
							}
							index += 1;
						}
					}
				} else if (g instanceof IndexedTriangleFanArray) {
					// IndexedTriangleFanArray の場合
					IndexedTriangleFanArray triFanArray = (IndexedTriangleFanArray)g;				
					int num = triFanArray.getNumStrips();
					int strips[] = new int[num];
					triFanArray.getStripIndexCounts(strips);
					int index = 0;
					for (int j = 0; j < num; j++) {
						v1 = triFanArray.getCoordinateIndex(index) + base;
						index += 1;
						for (int i = 1; i < strips[j] - 1; i += 1) {
							v2 = triFanArray.getCoordinateIndex(index) + base;
							v3 = triFanArray.getCoordinateIndex(index + 1) + base;
							addTriangle(v1, v2, v3, p1, p2, p3);
							index += 1;
						}
					}
				}
				base += geometryArray.getVertexCount();
			}
		}
		return true;
	}

	private void addTriangle(int v1, int v2, int v3, Point3d p1, Point3d p2, Point3d p3) {
		Vector3d vec1;
		Vector3d vec2;
		Vector3d vec;
		Triangle tri;
		Edge e;
		p1.x = coordinates[v1 * 3];
		p1.y = coordinates[v1 * 3 + 1];
		p1.z = coordinates[v1 * 3 + 2];
		p2.x = coordinates[v2 * 3];
		p2.y = coordinates[v2 * 3 + 1];
		p2.z = coordinates[v2 * 3 + 2];
		p3.x = coordinates[v3 * 3];
		p3.y = coordinates[v3 * 3 + 1];
		p3.z = coordinates[v3 * 3 + 2];
		vec = new Vector3d(p1);
		vec1 = new Vector3d(p2);
		vec2 = new Vector3d(p3);
		vec1.sub(vec);
		vec2.sub(vec);
		vec1.cross(vec1, vec2);
		if (vec1.length() < GeometryUtility.TOLERANCE) {
			return;
		}
		vec1.normalize();
		tri = new Triangle(v1, v2, v3, vec1);
		triangles.add(tri);
		e = edges.get(new Integer(v2 * vertexCount + v1));	// 逆向きに回っている辺のみ共有
		if (e != null) {
			tri.edges[0] = e;
			e.triangles[1] = tri;
		} else {
			vec = new Vector3d(p2);
			vec.sub(p1);
		    e = new Edge(v1, v2, vec);
			tri.edges[0] = e;
			e.triangles[0] = tri;
			edges.put(new Integer(v1 * vertexCount + v2), e);
		}
		e = edges.get(new Integer(v3 * vertexCount + v2));	// 逆向きに回っている辺のみ共有
		if (e != null) {
			tri.edges[1] = e;
			e.triangles[1] = tri;
		} else {
			vec = new Vector3d(p3);
			vec.sub(p2);
		    e = new Edge(v2, v3, vec);
			tri.edges[1] = e;
			e.triangles[0] = tri;
			edges.put(new Integer(v2 * vertexCount + v3), e);
		}
		e = edges.get(new Integer(v1 * vertexCount + v3));	// 逆向きに回っている辺のみ共有
		if (e != null) {
			tri.edges[2] = e;
			e.triangles[1] = tri;
		} else {
			vec = new Vector3d(p1);
			vec.sub(p3);
		    e = new Edge(v3, v1, vec);
			tri.edges[2] = e;
			e.triangles[0] = tri;
			edges.put(new Integer(v3 * vertexCount + v1), e);
		}
	}

	/**
	 * シャドウボリュームのジオメトリの作成
	 * @param light
	 * @param shadowDepth
	 * @param localToWorld
	 * @param coordinates
	 * @param edges
	 * @param triangles
	 * @return シャドウボリュームのジオメトリ
	 */
	private IndexedQuadArray createVolumeSurface(Light light, double shadowDepth, Transform3D localToWorld, 
			double[] coordinates, Hashtable<Integer, Edge> edges, ArrayList<Triangle> triangles) {
		// オブジェクトに対する相対座標系に変換
		Vector3d displacement =  null;
		Point3d localLightPosition = null;
		Transform3D worldToLocal;
		Point3d worldLightPosition = null;
		if (light instanceof DirectionalLight) {
			// 平行光源の場合
			Vector3f lightDirectionF = new Vector3f();
			((DirectionalLight)light).getDirection(lightDirectionF);
			displacement = new Vector3d(lightDirectionF);
			displacement.scale(shadowDepth);			
			worldToLocal = new Transform3D(localToWorld);
			worldToLocal.invert();
			worldToLocal.transform(displacement);
		} else if (light instanceof PointLight) {
			// 点光源の場合
			Point3f worldLightPositionF = new Point3f();
			((PointLight)light).getPosition(worldLightPositionF);
			worldLightPosition = new Point3d(worldLightPositionF);
			localLightPosition = new Point3d(worldLightPosition);
			worldToLocal = new Transform3D(localToWorld);
			worldToLocal.invert();
			worldToLocal.transform(localLightPosition);
		} else {
			return null;
		}

		Triangle tri;
		// すべての面の光線に対する向きを計算
		if (displacement != null) {
			for (int n = 0; n < triangles.size(); n++) {
				tri = triangles.get(n);
				tri.innerProduct = tri.normal.dot(displacement);
			}
		} else if (localLightPosition != null) {
			for (int n = 0; n < triangles.size(); n++) {
				tri = triangles.get(n);
				Vector3d v = new Vector3d(coordinates[tri.v1*3], coordinates[tri.v1*3 + 1], coordinates[tri.v1*3 + 2]);
				v.sub(localLightPosition);
				tri.innerProduct = tri.normal.dot(v);
			}			
		}
		Collection<Edge> es = edges.values();
		Edge edge;
		int index = 0;
		boolean doesGenerateSurface = false;
		double i0 = 0, i1 = 0;
		Vector3d displacement1 = displacement;
		Vector3d displacement2 = displacement;
		Vector3d tempVec = new Vector3d();
		Point3d worldPos = new Point3d();
		if (localLightPosition != null) {
			// 点光源の場合
			displacement1 = new Vector3d();
			displacement2 = new Vector3d();
		}
		for (Iterator<Edge> it = es.iterator(); it.hasNext(); ) {
			edge = it.next();			
			// edgeに対してシャドウボリュームの表面を生成するか否かの判定
			doesGenerateSurface = false;
			if (edge.triangles[1] == null) {
				// 開多面体の場合
				i0 = edge.triangles[0].innerProduct;
				if (i0 < -GeometryUtility.TOLERANCE) {
					// 光源に対して表向きの面の辺に対してのみ生成
					doesGenerateSurface = true;	// 生成する
				}
			} else {
				i0 = edge.triangles[0].innerProduct;
				i1 = edge.triangles[1].innerProduct;
				if (i0 * i1 < GeometryUtility.TOLERANCE) {
					if (i0 * i1 > -GeometryUtility.TOLERANCE) {
						// 辺を挟む２つの面の一方が光線の向きに対して平行である
						if (i0 < -GeometryUtility.TOLERANCE || i1 < -GeometryUtility.TOLERANCE) {
							// いずれかの面が表向きだった場合
							tempVec.cross(edge.triangles[0].normal, edge.triangles[1].normal);
							if (edge.v1ToV2.dot(tempVec) > GeometryUtility.TOLERANCE) {
								// 辺が凸の場合のみ生成
								doesGenerateSurface = true;	// 生成する								
							}
						}
					} else {
						// 辺を挟む２つの面が光線の向きに対して裏表である
						doesGenerateSurface = true;	// 生成する
					}
				}
			}
			if (doesGenerateSurface) {
				// シャドウボリュームの表面を生成する
				if (localLightPosition != null) {
					// 点光源の場合
					displacement1.x = coordinates[edge.v1 * 3] - localLightPosition.x;
					displacement1.y = coordinates[edge.v1 * 3 + 1] - localLightPosition.y;
					displacement1.z = coordinates[edge.v1 * 3 + 2] - localLightPosition.z;
					worldPos.x = coordinates[edge.v1 * 3];
					worldPos.y = coordinates[edge.v1 * 3 + 1];
					worldPos.z = coordinates[edge.v1 * 3 + 2];
					localToWorld.transform(worldPos);
					displacement1.scale(shadowDepth / worldPos.distance(worldLightPosition));				
					displacement2.x = coordinates[edge.v2 * 3] - localLightPosition.x;
					displacement2.y = coordinates[edge.v2 * 3 + 1] - localLightPosition.y;
					displacement2.z = coordinates[edge.v2 * 3 + 2] - localLightPosition.z;
					worldPos.x = coordinates[edge.v2 * 3];
					worldPos.y = coordinates[edge.v2 * 3 + 1];
					worldPos.z = coordinates[edge.v2 * 3 + 2];
					localToWorld.transform(worldPos);
					displacement2.scale(shadowDepth / worldPos.distance(worldLightPosition));				
				}
				if (i0 < -GeometryUtility.TOLERANCE) {
					// 表を向いている面はedge.triangles[0]、edgeの向きは常にedge.triangles[0]の辺の向きと一致するので、
					// edgeの向きに従ってシャドウボリュームの表面を生成
					indicies[index] = edge.v2;
					indicies[index + 1] = edge.v1;
					indicies[index + 2] = edge.v1 + vertexCount;
					indicies[index + 3] = edge.v2 + vertexCount;
				} else {
					// 表を向いている面はedge.triangles[1]、edgeの向きは常にedge.triangles[1]の辺の向きと反対なので、
					// edgeの向きと逆向きにシャドウボリュームの表面を生成
					indicies[index] = edge.v1;
					indicies[index + 1] = edge.v2;
					indicies[index + 2] = edge.v2 + vertexCount;
					indicies[index + 3] = edge.v1 + vertexCount;
				}
				coordinates[(edge.v1 + vertexCount) * 3] = coordinates[edge.v1 * 3] + displacement1.x;
				coordinates[(edge.v1 + vertexCount) * 3 + 1] = coordinates[edge.v1 * 3 + 1] + displacement1.y;
				coordinates[(edge.v1 + vertexCount) * 3 + 2] = coordinates[edge.v1 * 3 + 2] + displacement1.z;
				coordinates[(edge.v2 + vertexCount) * 3] = coordinates[edge.v2 * 3] + displacement2.x;
				coordinates[(edge.v2 + vertexCount) * 3 + 1] = coordinates[edge.v2 * 3 + 1] + displacement2.y;
				coordinates[(edge.v2 + vertexCount) * 3 + 2] = coordinates[edge.v2 * 3 + 2] + displacement2.z;
				index += 4;
			}
		}
		if (index == 0) return null;
		int validIndicies[] = new int[index];
		System.arraycopy(indicies, 0, validIndicies, 0, index);
		volumeGeometry = new IndexedQuadArray(coordinates.length / 3, 
				IndexedQuadArray.COORDINATES | IndexedQuadArray.BY_REFERENCE, 
				index);
		volumeGeometry.setCapability(IndexedQuadArray.ALLOW_COORDINATE_READ);
		volumeGeometry.setCapability(IndexedQuadArray.ALLOW_COORDINATE_WRITE);
		volumeGeometry.setCapability(IndexedQuadArray.ALLOW_COORDINATE_INDEX_READ);
		volumeGeometry.setCapability(IndexedQuadArray.ALLOW_COORDINATE_INDEX_WRITE);
		
		volumeGeometry.setCoordRefDouble(coordinates);
		volumeGeometry.setCoordinateIndices(0, validIndicies);
		return volumeGeometry;
	}

	/**
	 * シャドウボリュームの生成
	 * @param volumeGeometry
	 * @param ｏｃｃｕｌｕｄｅｒ
	 */
	private void createShadowVolumeObject(IndexedQuadArray volumeGeometry, BaseObject3D ｏｃｃｕｌｕｄｅｒ) {
		// シャドウボリュームの表面のオブジェクトの生成
		Appearance frontAp = new Appearance();
		if (WIRE_FRAME_VIEW) frontAp.setMaterial(new Material());
		RenderingAttributes frontRA = new RenderingAttributes();
		if (!WIRE_FRAME_VIEW) frontRA.setStencilEnable(true);		// ステンシルバッファを用いる（ただし、Canvas3Dで適切な設定がされている必要がある）
		if (!WIRE_FRAME_VIEW) frontRA.setDepthBufferWriteEnable(false);							// Zバッファを更新しない
		frontRA.setDepthTestFunction(RenderingAttributes.LESS_OR_EQUAL);	// Zバッファの影響を受ける
		frontRA.setStencilFunction(RenderingAttributes.ALWAYS, 0, ~0);		// ステンシルバッファの影響を受けない
		frontRA.setStencilOp(RenderingAttributes.STENCIL_KEEP, 
				RenderingAttributes.STENCIL_KEEP, 
				RenderingAttributes.STENCIL_INCR);	// 見えているピクセルのみステンシルバッファの値を増やす
		frontAp.setRenderingAttributes(frontRA);
		PolygonAttributes pa1 = new PolygonAttributes();
		if (WIRE_FRAME_VIEW) pa1.setPolygonMode(PolygonAttributes.POLYGON_LINE);
		pa1.setCullFace(PolygonAttributes.CULL_BACK);	// 裏面を処理しない
		frontAp.setPolygonAttributes(pa1);
		if (!WIRE_FRAME_VIEW) {
			TransparencyAttributes frontTA = new TransparencyAttributes();	// setVisible(false)だとステンシルバッファが更新されないので、透明にする
			frontTA.setTransparencyMode(TransparencyAttributes.BLENDED);
			frontTA.setTransparency(1.0f);
			frontAp.setTransparencyAttributes(frontTA);
		}
		shadowVolumeFront = new Shape3D(volumeGeometry, frontAp);
		shadowVolumeFront.setCapability(Shape3D.ALLOW_GEOMETRY_READ);
		shadowVolumeFront.setCapability(Shape3D.ALLOW_GEOMETRY_WRITE);
		ｏｃｃｕｌｕｄｅｒ.center.addChild(shadowVolumeFront);
		
		// シャドウボリュームの裏面のオブジェクトの生成
		Appearance backAp= new Appearance();
		if (WIRE_FRAME_VIEW) backAp.setMaterial(new Material());
		RenderingAttributes backRA= new RenderingAttributes();
		if (!WIRE_FRAME_VIEW) backRA.setStencilEnable(true);		// ステンシルバッファを用いる（ただし、Canvas3Dで適切な設定がされている必要がある）
		if (!WIRE_FRAME_VIEW) backRA.setDepthBufferWriteEnable(false);							// Zバッファを更新しない
		backRA.setDepthTestFunction(RenderingAttributes.LESS_OR_EQUAL);		// Zバッファの影響を受ける
		backRA.setStencilFunction(RenderingAttributes.ALWAYS, 0, ~0);		// ステンシルバッファの影響を受けない
		backRA.setStencilOp(RenderingAttributes.STENCIL_KEEP, 
				RenderingAttributes.STENCIL_KEEP, 
				RenderingAttributes.STENCIL_DECR);	// 見えているピクセルのみステンシルバッファの値を減らす
		backAp.setRenderingAttributes(backRA);
		PolygonAttributes pa2 = new PolygonAttributes();
		if (WIRE_FRAME_VIEW) pa2.setPolygonMode(PolygonAttributes.POLYGON_LINE);
		pa2.setCullFace(PolygonAttributes.CULL_FRONT);	// 表面を処理しない
		backAp.setPolygonAttributes(pa2);
		if (!WIRE_FRAME_VIEW) {
			TransparencyAttributes backTA = new TransparencyAttributes();	// setVisible(false)だとステンシルバッファが更新されないので、透明にする
			backTA.setTransparencyMode(TransparencyAttributes.BLENDED);
			backTA.setTransparency(1.0f);
			backAp.setTransparencyAttributes(backTA);
		}
		shadowVolumeBack = new Shape3D(volumeGeometry, backAp);
		shadowVolumeBack.setCapability(Shape3D.ALLOW_GEOMETRY_READ);
		shadowVolumeBack.setCapability(Shape3D.ALLOW_GEOMETRY_WRITE);
		ｏｃｃｕｌｕｄｅｒ.center.addChild(shadowVolumeBack);
	}
	
	private class Triangle {
		Edge edges[] = new Edge[3];
		Vector3d normal;
		double innerProduct;
		int v1, v2, v3;
		
		Triangle(int v1, int v2, int v3, Vector3d normal) {
			this.v1 = v1;
			this.v2 = v2;
			this.v3 = v3;
			this.normal = normal;
		}
	}
	
	private class Edge {
		Triangle triangles[] = new Triangle[2];
		int v1, v2;
		Vector3d v1ToV2;
//		Vector3d normal = null;
		
		Edge(int v1, int v2, Vector3d v1ToV2) {
			this.v1 = v1;
			this.v2 = v2;
			this.v1ToV2 = v1ToV2;
		}
		
//		Vector3d normal() {
//			if (normal != null) return normal;
//			normal = new Vector3d(triangles[0].normal);
//			if (triangles[1] != null) {
//				normal.add(triangles[1].normal);
//				normal.scale(0.5);
//			}
//			return normal;
//		}
	}
}
