package Command;
import java.nio.FloatBuffer;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.StringTokenizer;
import java.util.TreeSet;
import javax.media.opengl.GL2;
import javax.swing.undo.UndoManager;
import Common.Box2;
import Common.Box3;
import Common.Matrix3;
import Common.Matrix4;
import Common.Ray3;
import Common.Vector2f;
import Common.Vector3f;
import Common.Vector4f;
import LDraw.Support.DispatchGroup;
import LDraw.Support.GLMatrixMath;
import LDraw.Support.ILDrawDragHandler;
import LDraw.Support.LDrawDirective;
import LDraw.Support.LDrawDragHandle;
import LDraw.Support.LDrawUtilities;
import LDraw.Support.MatrixMath;
import LDraw.Support.Range;
import LDraw.Support.SelT;
import LDraw.Support.type.CacheFlagsT;
import Renderer.ILDrawCollector;
import Renderer.ILDrawRenderer;
import Renderer.LDrawRenderColorT;
//==============================================================================
//
//File: LDrawTriangle.m
//
//Purpose: Triangle command.
// Draws a filled triangle between three points.
//
// Line format:
// 3 colour x1 y1 z1 x2 y2 z2 x3 y3 z3
//
// where
//
// * colour is a colour code: 0-15, 16, 24, 32-47, 256-511
// * x1, y1, z1 is the position of the first point
// * x2, y2, z2 is the position of the second point
// * x3, y3, z3 is the position of the third point
//
//Created by Allen Smith on 2/19/05.
//Copyright (c) 2005. All rights reserved.
//==============================================================================
public class LDrawTriangle extends LDrawDrawableElement implements ILDrawDragHandler{
/**
* @uml.property name="vertex1"
* @uml.associationEnd multiplicity="(1 1)"
*/
Vector3f vertex1= Vector3f.getZeroVector3f();
/**
* @uml.property name="vertex2"
* @uml.associationEnd multiplicity="(1 1)"
*/
Vector3f vertex2= Vector3f.getZeroVector3f();
/**
* @uml.property name="vertex3"
* @uml.associationEnd multiplicity="(1 1)"
*/
Vector3f vertex3= Vector3f.getZeroVector3f();
/**
* @uml.property name="normal"
* @uml.associationEnd multiplicity="(1 1)"
*/
Vector3f normal= Vector3f.getZeroVector3f();
/**
* @uml.property name="dragHandles"
* @uml.associationEnd multiplicity="(0 -1)" elementType="LDraw.Support.LDrawDragHandle"
*/
ArrayList<LDrawDragHandle> dragHandles;
// ========== initWithLines:inRange:parentGroup:
// ================================
//
// Purpose: Returns a triangle initialized from line of LDraw code beginning
// at the given range.
//
// directive should have the format:
//
// 3 colour x1 y1 z1 x2 y2 z2 x3 y3 z3
//
// ==============================================================================
public LDrawTriangle initWithLines(ArrayList<String> lines,
Range range
,DispatchGroup parentGroup)
{
String workingLine = lines.get(range.getLocation());
String parsedField = null;
Vector3f workingVertex = Vector3f.getZeroVector3f();
LDrawColor parsedColor = null;
try {
super.initWithLines(lines, range,parentGroup);
} catch (Exception e1) {
// TODO Auto-generated catch block
e1.printStackTrace();
}
//A malformed part could easily cause a string indexing error, which would
// raise an exception. We don't want this to happen here.
try
{
//Read in the line code and advance past it.
StringTokenizer strTokenizer = new StringTokenizer(workingLine);
parsedField = strTokenizer.nextToken();
//Only attempt to create the part if this is a valid line.
if(Integer.parseInt(parsedField) == 3)
{
//Read in the color code.
// (color)
parsedField = strTokenizer.nextToken();
parsedColor = LDrawUtilities.parseColorFromField(parsedField);
setLDrawColor(parsedColor);
//Read Vertex 1.
// (x1)
parsedField = strTokenizer.nextToken();
workingVertex.setX(Float.parseFloat(parsedField));
// (y1)
parsedField = strTokenizer.nextToken();
workingVertex.setY(Float.parseFloat(parsedField));
// (z1)
parsedField = strTokenizer.nextToken();
workingVertex.setZ(Float.parseFloat(parsedField));
setVertex1(workingVertex);
//Read Vertex 2.
// (x2)
parsedField = strTokenizer.nextToken();
workingVertex.setX(Float.parseFloat(parsedField));
// (y2)
parsedField = strTokenizer.nextToken();
workingVertex.setY(Float.parseFloat(parsedField));
// (z2)
parsedField = strTokenizer.nextToken();
workingVertex.setZ(Float.parseFloat(parsedField));
setVertex2(workingVertex);
//Read Vertex 3.
// (x3)
parsedField = strTokenizer.nextToken();
workingVertex.setX(Float.parseFloat(parsedField));
// (y3)
parsedField = strTokenizer.nextToken();
workingVertex.setY(Float.parseFloat(parsedField));
// (z3)
parsedField = strTokenizer.nextToken();
workingVertex.setZ(Float.parseFloat(parsedField));
setVertex3(workingVertex);
}
else
throw new Exception("BricksmithParseException: "+"Bad line syntax");
}
catch(Exception e)
{
System.out.println(String.format("the triangle primitive %s was fatally invalid", lines.get(range.getLocation())));
System.out.println(String.format(" raised exception %s", e.getMessage()));
}
return this;
}//end initWithLines:inRange:
// ========== drawElement:viewScale:withColor:
// ==================================
//
// Purpose: Draws the graphic of the element represented. This call is a
// subroutine of -draw: in LDrawDrawableElement.
//
// ==============================================================================
public void drawElement(GL2 gl2, HashMap<Integer, Boolean> optionsMask,
float scaleFactor, LDrawColor drawingColor) {
if (dragHandles!=null) {
if (dragHandles != null) {
for (LDrawDragHandle handle : dragHandles) {
handle.draw(gl2, optionsMask, scaleFactor, drawingColor);
}
}
}
}// end drawElement:parentColor:
// ========== drawSelf:
// ===========================================================
//
// Purpose: Draw this directive and its subdirectives by calling APIs on
// the passed in renderer, then calling drawSelf on children.
//
// Notes: Triangles use this message to get their drag handles drawn if
// needed. They do not draw their actual GL primitive because that
// has already been "collected" by some parent capable of
// accumulating a mesh.
//
// ================================================================================
public void drawSelf(GL2 gl2, ILDrawRenderer renderer) {
revalCache(CacheFlagsT.DisplayList);
if (hidden == false) {
if (dragHandles != null) {
for (LDrawDragHandle handle : dragHandles) {
handle.drawSelf(gl2, renderer);
}
}
}
}// end drawSelf:
// ========== collectSelf:
// ========================================================
//
// Purpose: Collect this is called on each directive by its parents to
// accumulate _mesh_ data into a display list for later drawing.
// The collector protocol passed in is some object capable of
// remembering the collectable data.
//
// Real GL primitives participate by passing their color and
// geometry data to the collector.
//
// ================================================================================
public void collectSelf(ILDrawCollector renderer) {
// We must mark our DL as valid - otherwise we will not invalidate our
// DL when edited, and if we don't do that, we won't pass the message
// to our parents that our DL is invalid. This passing the invalid DL up
// is what PRIMES our parent model to rebuild DLs as needed.
revalCache(CacheFlagsT.DisplayList);
if(color==null)return;
if (hidden == false) {
float v[] = { vertex1.getX(), vertex1.getY(), vertex1.getZ(),
vertex2.getX(), vertex2.getY(), vertex2.getZ(),
vertex3.getX(), vertex3.getY(), vertex3.getZ() };
float n[] = { normal.getX(), normal.getY(), normal.getZ() };
if (color.getColorCode() == LDrawColorT.LDrawCurrentColor)
renderer.drawTri(v, n, LDrawRenderColorT.LDrawRenderCurrentColor.getValue());
else if (color.getColorCode() == LDrawColorT.LDrawEdgeColor)
renderer.drawTri(v, n,
LDrawRenderColorT.LDrawRenderComplimentColor.getValue());
else {
float rgba[] = new float[4];
color.getColorRGBA(rgba);
renderer.drawTri(v, n, rgba);
}
}
}// end collectSelf:
public void getRange( Matrix4 transform, float[] range )
{
if(hidden == false)
{
Vector3f worldVertex1 = MatrixMath.V3MulPointByProjMatrix(vertex1, transform);
Vector3f worldVertex2 = MatrixMath.V3MulPointByProjMatrix(vertex2, transform);
Vector3f worldVertex3 = MatrixMath.V3MulPointByProjMatrix(vertex3, transform);
compareRange( range, worldVertex1 );
compareRange( range, worldVertex2 );
compareRange( range, worldVertex3 );
if(dragHandles!=null)
{
for(LDrawDragHandle handle : dragHandles)
{
handle.getRange(transform, range);
}
}
}
}
// ========== hitTest:transform:viewScale:boundsOnly:creditObject:hits:
// =======
//
// Purpose: Tests the directive and any of its children for intersections
// between the pickRay and the directive's drawn content.
//
// ==============================================================================
public void hitTest(Ray3 pickRay, Matrix4 transform, LDrawDirective creditObject, HashMap<LDrawDirective, Float> hits)
{
if(hidden == false)
{
Vector3f worldVertex1 = MatrixMath.V3MulPointByProjMatrix(vertex1, transform);
Vector3f worldVertex2 = MatrixMath.V3MulPointByProjMatrix(vertex2, transform);
Vector3f worldVertex3 = MatrixMath.V3MulPointByProjMatrix(vertex3, transform);
FloatBuffer intersectDepth = FloatBuffer.allocate(1);
boolean intersects = false;
intersects = MatrixMath.V3RayIntersectsTriangle(pickRay,
worldVertex1, worldVertex2, worldVertex3,
intersectDepth, null);
if(intersects)
{
LDrawUtilities.registerHitForObject(this, intersectDepth, creditObject, hits);
}
if(dragHandles!=null)
{
for(LDrawDragHandle handle : dragHandles)
{
handle.hitTest(pickRay, transform, null, hits);
}
}
}
}//end hitTest:transform:viewScale:boundsOnly:creditObject:hits:
// ========== boxTest:transform:boundsOnly:creditObject:hits:
// ===================
//
// Purpose: Check for intersections with screen-space geometry.
//
// ==============================================================================
public boolean boxTest(Box2 bounds, Matrix4 transform, boolean boundsOnly,
LDrawDirective creditObject, TreeSet<LDrawDirective> hits)
{
if(hidden == false)
{
Vector4f clipVertex1 = MatrixMath.V4MulPointByMatrix(MatrixMath.V4FromVector3f(vertex1), transform);
Vector4f clipVertex2 = MatrixMath.V4MulPointByMatrix(MatrixMath.V4FromVector3f(vertex2), transform);
Vector4f clipVertex3 = MatrixMath.V4MulPointByMatrix(MatrixMath.V4FromVector3f(vertex3), transform);
float h_tri[] = {
clipVertex1.getX(), clipVertex1.getY(), clipVertex1.getZ(), clipVertex1.getW(),
clipVertex2.getX(), clipVertex2.getY(), clipVertex2.getZ(), clipVertex2.getW(),
clipVertex3.getX(), clipVertex3.getY(), clipVertex3.getZ(), clipVertex3.getW() };
float ndc_tris[] = new float[18];
int triCount = GLMatrixMath.clipTriangle(h_tri,ndc_tris);
int i;
for(i = 0; i < triCount; ++i)
{
Vector2f tri[] = {
MatrixMath.V2Make(ndc_tris[i*9+0],ndc_tris[i*9+1]),
MatrixMath.V2Make(ndc_tris[i*9+3],ndc_tris[i*9+4]),
MatrixMath.V2Make(ndc_tris[i*9+6],ndc_tris[i*9+7])
};
if(MatrixMath.V2BoxIntersectsPolygon(bounds, tri, 3))
{
LDrawUtilities.registerHitForObject(this, creditObject, hits);
if(creditObject!=null)
return true;
}
}
}
return false;
}//end boxTest:transform:boundsOnly:creditObject:hits:
// ==========
// depthTest:inBox:transform:creditObject:bestObject:bestDepth:=======
//
// Purpose: depthTest finds the closest primitive (in screen space)
// overlapping a given point, as well as its device coordinate
// depth.
//
// ==============================================================================
public void depthTest(Vector2f pt, Box2 bounds, Matrix4 transform,
LDrawDirective creditObject, ArrayList<LDrawDirective> bestObject, FloatBuffer bestDepth)
{
if(hidden == false)
{
Vector4f clipVertex1 = MatrixMath.V4MulPointByMatrix(MatrixMath.V4FromVector3f(vertex1), transform);
Vector4f clipVertex2 = MatrixMath.V4MulPointByMatrix(MatrixMath.V4FromVector3f(vertex2), transform);
Vector4f clipVertex3 = MatrixMath.V4MulPointByMatrix(MatrixMath.V4FromVector3f(vertex3), transform);
Vector3f probe = new Vector3f(pt.getX() , pt.getY(), bestDepth.get(0));
float h_tri[] = {
clipVertex1.getX(), clipVertex1.getY(), clipVertex1.getZ(), clipVertex1.getW(),
clipVertex2.getX(), clipVertex2.getY(), clipVertex2.getZ(), clipVertex2.getW(),
clipVertex3.getX(), clipVertex3.getY(), clipVertex3.getZ(), clipVertex3.getW() };
float ndc_tris[] = new float[18];
int triCount = GLMatrixMath.clipTriangle(h_tri,ndc_tris);
int i;
for(i = 0; i < triCount; ++i)
{
Vector3f ndcVertex1 = MatrixMath.V3Make(ndc_tris[i*9+0],ndc_tris[i*9+1],ndc_tris[i*9+2]);
Vector3f ndcVertex2 = MatrixMath.V3Make(ndc_tris[i*9+3],ndc_tris[i*9+4],ndc_tris[i*9+5]);
Vector3f ndcVertex3 = MatrixMath.V3Make(ndc_tris[i*9+6],ndc_tris[i*9+7],ndc_tris[i*9+8]);
if(MatrixMath.DepthOnTriangle(ndcVertex1,ndcVertex2,ndcVertex3,probe))
{
if(probe.getZ() <= bestDepth.get(0))
{
bestDepth.put(0, probe.getZ());
bestObject.add((LDrawDirective) (creditObject!=null ? creditObject : this));
}
}
}
if(dragHandles!=null)
{
for(LDrawDragHandle handle : dragHandles)
{
handle.depthTest(pt, bounds, transform, creditObject, bestObject, bestDepth);
}
}
}
}//end depthTest:inBox:transform:creditObject:bestObject:bestDepth:
// ========== write
// =============================================================
//
// Purpose: Returns a line that can be written out to a file.
// Line format:
// 3 colour x1 y1 z1 x2 y2 z2 x3 y3 z3
//
// ==============================================================================
public String write() {
return String.format("3 %s %s %s %s %s %s %s %s %s %s",
LDrawUtilities.outputStringForColor(color),
LDrawUtilities.outputStringForFloat(vertex1.getX()),
LDrawUtilities.outputStringForFloat(vertex1.getY()),
LDrawUtilities.outputStringForFloat(vertex1.getZ()),
LDrawUtilities.outputStringForFloat(vertex2.getX()),
LDrawUtilities.outputStringForFloat(vertex2.getY()),
LDrawUtilities.outputStringForFloat(vertex2.getZ()),
LDrawUtilities.outputStringForFloat(vertex3.getX()),
LDrawUtilities.outputStringForFloat(vertex3.getY()),
LDrawUtilities.outputStringForFloat(vertex3.getZ())
);
}// end write
// ========== browsingDescription
// ===============================================
//
// Purpose: Returns a representation of the directive as a short string
// which can be presented to the user.
//
// ==============================================================================
public String browsingDescription() {
return "Triangle";
}// end browsingDescription
// ========== iconName
// ==========================================================
//
// Purpose: Returns the name of image file used to display this kind of
// object, or null if there is no icon.
//
// ==============================================================================
public String iconName() {
return "Triangle";
}// end iconName
// ========== inspectorClassName
// ================================================
//
// Purpose: Returns the name of the class used to inspect this one.
//
// ==============================================================================
public String inspectorClassName() {
return "InspectionTriangle";
}// end inspectorClassName
// #pragma mark -
// #pragma mark ACCESSORS
// #pragma mark -
// ========== boundingBox3
// ======================================================
//
// Purpose: Returns the minimum and maximum points of the box which
// perfectly contains this object.
//
// ==============================================================================
public Box3 boundingBox3() {
// Raw directive doesn't cache - we just compute our bbox on the fly.
// But
// keep our parents "in sync".
revalCache(CacheFlagsT.CacheFlagBounds);
if (hidden == true)
return Box3.getInvalidBox();
Box3 bounds;
// Compare first two points.
bounds = MatrixMath.V3BoundsFromPoints(vertex1, vertex2);
// Now toss the third vertex into the mix.
bounds = MatrixMath.V3UnionBoxAndPoint(bounds, vertex3);
return bounds;
}// end boundingBox3
// ========== position
// ==========================================================
//
// Purpose: Returns some position for the element. This is used by
// drag-and-drop. This is not necessarily human-usable information.
//
// ==============================================================================
public Vector3f position() {
return vertex1;
}// end position
// ========== vertex1
// ===========================================================
// ==============================================================================
public Vector3f vertex1() {
return vertex1;
}// end vertex1
// ========== vertex2
// ===========================================================
// ==============================================================================
public Vector3f vertex2() {
return vertex2;
}// end vertex2
// ========== vertex3
// ===========================================================
// ==============================================================================
public Vector3f vertex3() {
return vertex3;
}// end vertex3
// #pragma mark -
// ========== setSelected:
// ======================================================
//
// Purpose: Somebody make this a protocol method.
//
// ==============================================================================
public void setSelected(boolean flag) {
super.setSelected(flag);
if (flag == true) {
LDrawDragHandle handle1 = new LDrawDragHandle();
handle1.initWithTag(1, vertex1);
LDrawDragHandle handle2 = new LDrawDragHandle();
handle1.initWithTag(2, vertex2);
LDrawDragHandle handle3 = new LDrawDragHandle();
handle1.initWithTag(3, vertex3);
handle1.setTarget(this);
handle2.setTarget(this);
handle3.setTarget(this);
handle1.setAction(SelT.DragHandleChanged);
handle2.setAction(SelT.DragHandleChanged);
handle3.setAction(SelT.DragHandleChanged);
dragHandles = new ArrayList<LDrawDragHandle>();
dragHandles.add(handle1);
dragHandles.add(handle2);
dragHandles.add(handle3);
} else {
dragHandles = null;
}
}// end setSelected:
// ========== setVertex1:
// =======================================================
//
// Purpose: Sets the triangle's first vertex.
//
// ==============================================================================
/**
* @param newVertex
* @uml.property name="vertex1"
*/
public void setVertex1(Vector3f newVertex) {
vertex1.set(newVertex);
recomputeNormal();
invalCache(CacheFlagsT.CacheFlagBounds);
invalCache(CacheFlagsT.DisplayList);
if (dragHandles != null) {
dragHandles.get(0).setPosition(newVertex, false);
}
if(enclosingDirective()!=null)
enclosingDirective().setVertexesNeedRebuilding();
}// end setVertex1:
// ========== setVertex2:
// =======================================================
//
// Purpose: Sets the triangle's second vertex.
//
// ==============================================================================
/**
* @param newVertex
* @uml.property name="vertex2"
*/
public void setVertex2(Vector3f newVertex) {
vertex2.set(newVertex);
recomputeNormal();
invalCache(CacheFlagsT.CacheFlagBounds);
invalCache(CacheFlagsT.DisplayList);
if (dragHandles != null) {
dragHandles.get(1).setPosition(newVertex, false);
}
if(enclosingDirective()!=null)
enclosingDirective().setVertexesNeedRebuilding();
}// end setVertex2:
// ========== setVertex3:
// =======================================================
//
// Purpose: Sets the triangle's last vertex.
//
// ==============================================================================
/**
* @param newVertex
* @uml.property name="vertex3"
*/
public void setVertex3(Vector3f newVertex) {
vertex3.set(newVertex);
recomputeNormal();
invalCache(CacheFlagsT.CacheFlagBounds);
invalCache(CacheFlagsT.DisplayList);
if (dragHandles != null) {
dragHandles.get(2).setPosition(newVertex, false);
}
if(enclosingDirective()!=null)
enclosingDirective().setVertexesNeedRebuilding();
}// end setVertex3:
//
// #pragma mark -
// #pragma mark ACTIONS
// #pragma mark -
// ========== dragHandleChanged:
// ================================================
//
// Purpose: One of the drag handles on our vertexes has changed.
//
// ==============================================================================
public void dragHandleChanged(LDrawDragHandle sender) {
LDrawDragHandle handle = (LDrawDragHandle) sender;
Vector3f newPosition = handle.position();
int vertexNumber = handle.tag();
switch (vertexNumber) {
case 1:
setVertex1(newPosition);
break;
case 2:
setVertex2(newPosition);
break;
case 3:
setVertex3(newPosition);
break;
}
}// end dragHandleChanged:
// ========== moveBy:
// ===========================================================
//
// Purpose: Moves the receiver in the specified direction.
//
// ==============================================================================
public void moveBy(Vector3f moveVector) {
Vector3f newVertex1 = MatrixMath.V3Add(vertex1, moveVector);
Vector3f newVertex2 = MatrixMath.V3Add(vertex2, moveVector);
Vector3f newVertex3 = MatrixMath.V3Add(vertex3, moveVector);
setVertex1(newVertex1);
setVertex2(newVertex2);
setVertex3(newVertex3);
}// end moveBy:
// #pragma mark -
// #pragma mark UTILITIES
// #pragma mark -
// ========== flattenIntoLines:triangles:quadrilaterals:other:currentColor:
// =====
//
// Purpose: Appends the directive into the appropriate container.
//
// ==============================================================================
public void flattenIntoLines(ArrayList<LDrawLine> lines,
ArrayList<LDrawTriangle> triangles,
ArrayList<LDrawQuadrilateral> quadriaterals,
ArrayList<LDrawDirective> everythingElse, LDrawColor parentColor,
Matrix4 transform, Matrix3 normalTransform, boolean recursive) {
super.flattenIntoLines(lines, triangles, quadriaterals, everythingElse,
parentColor, transform, normalTransform, recursive);
vertex1 = MatrixMath.V3MulPointByProjMatrix(vertex1, transform);
vertex2 = MatrixMath.V3MulPointByProjMatrix(vertex2, transform);
vertex3 = MatrixMath.V3MulPointByProjMatrix(vertex3, transform);
normal = MatrixMath.V3MulPointByMatrix(normal, normalTransform);
triangles.add(this);
}// end flattenIntoLines:triangles:quadrilaterals:other:currentColor:
// ========== recomputeNormal
// ===================================================
//
// Purpose: Finds the normal vector for this surface.
//
// ==============================================================================
public void recomputeNormal() {
Vector3f vector1, vector2;
vector1 = MatrixMath.V3Sub(vertex2, vertex1);
vector2 = MatrixMath.V3Sub(vertex3, vertex1);
normal = MatrixMath.V3Cross(vector1, vector2);
}// end recomputeNormal
// ========== registerUndoActions
// ===============================================
//
// Purpose: Registers the undo actions that are unique to this subclass,
// not to any superclass.
//
// ==============================================================================
public void registerUndoActions(UndoManager undoManager) {
super.registerUndoActions(undoManager);
// todo
// [[undoManager prepareWithInvocationTarget:self] setVertex3:vertex3]);
// [[undoManager prepareWithInvocationTarget:self] setVertex2:vertex2]);
// [[undoManager prepareWithInvocationTarget:self] setVertex1:vertex1]);
// [undoManager setActionName:NSLocalizedString(@"UndoAttributesLine",
// null));
}// end registerUndoActions:
public void setToCW() {
Vector3f tempVector = vertex2;
vertex2 = vertex3;
vertex3 = tempVector;
recomputeNormal();
}
}