package com.vitco.core.container;
import com.threed.jpct.*;
import com.vitco.core.data.container.ExtendedVector;
import com.vitco.manager.async.AsyncAction;
import com.vitco.settings.DynamicSettings;
import com.vitco.settings.VitcoSettings;
import com.vitco.util.graphic.G2DUtil;
import javax.swing.*;
import java.awt.*;
import java.awt.event.ComponentAdapter;
import java.awt.event.ComponentEvent;
import java.awt.image.BufferedImage;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.Comparator;
/**
* Defines the final draw container that draws the rendering of animation and voxels.
*/
public abstract class DrawContainer extends AbstractDrawContainer {
// initialize this container
public final void init() {
final DrawContainer container = this;
// add a border to our view
container.setBorder(BorderFactory.createMatteBorder(1, 1, 0, 1, VitcoSettings.DEFAULT_BORDER_COLOR));
// register size change of container and change buffer size accordingly
container.addComponentListener(new ComponentAdapter() {
@Override
public void componentResized(ComponentEvent e) {
if (container.getWidth() > 0 && container.getHeight() > 0) {
asyncActionManager.addAsyncAction(new AsyncAction() {
@Override
public void performAction() {
cleanup();
buffer = null; // so the gc can collect before creation if necessary
buffer = new HackedFrameBuffer(container.getWidth(),
// increase size by one to prevent black frame
container.getHeight() + 1, DynamicSettings.SAMPLING_MODE);
container.notifyAboutResize(container.getWidth(), container.getHeight());
container.doNotSkipNextWorldRender();
forceRepaint();
}
});
}
}
});
}
// set resolution of this container (refresh buffer)
public final void refreshBuffer() {
asyncActionManager.addAsyncAction(new AsyncAction() {
@Override
public void performAction() {
int w = buffer.getWidth(), h = buffer.getHeight();
buffer = null; // so the gc can collect before creation if necessary
buffer = new HackedFrameBuffer(w, h, DynamicSettings.SAMPLING_MODE);
}
});
}
// cleanup this container
public final void cleanup() {
buffer.disableRenderer(IRenderer.RENDERER_OPENGL);
buffer.dispose();
}
// define some basic functions
protected abstract void forceRepaint();
protected abstract boolean updateGhostOverlay();
protected abstract SimpleVector[][] getGhostOverlay();
protected abstract void refreshVoxels(boolean b);
// ---------------------------
// wrapper
private void drawFadingLine(Graphics2D ig, SimpleVector[] vectors, int i, int j, Color color, float distance, float[] zRange) {
float range1 = (vectors[i].z-zRange[0])/distance;
float range2 = (vectors[j].z-zRange[0])/distance;
ig.setPaint(new GradientPaint(
Math.round(vectors[i].x), Math.round(vectors[i].y),
new Color(color.getRed(), color.getGreen(), color.getBlue(), Math.round(55 + range1*150)),
Math.round(vectors[j].x), Math.round(vectors[j].y),
new Color(color.getRed(), color.getGreen(), color.getBlue(), Math.round(55 + range2*150)),
false));
ig.drawLine(Math.round(vectors[i].x), Math.round(vectors[i].y),
Math.round(vectors[j].x), Math.round(vectors[j].y));
}
// wrapper
private void drawLine(Graphics2D g, SimpleVector start, SimpleVector stop, Color color) {
final Graphics2D ig = (Graphics2D)g.create();
ig.setColor(color);
ig.drawLine(Math.round(start.x), Math.round(start.y),
Math.round(stop.x), Math.round(stop.y));
ig.dispose();
}
private static final Stroke drawingStroke1 =
new BasicStroke(1, BasicStroke.CAP_BUTT, BasicStroke.JOIN_BEVEL, 0, null, 0);
private static final Stroke drawingStroke2 =
new BasicStroke(1, BasicStroke.CAP_BUTT, BasicStroke.JOIN_BEVEL, 0, new float[]{4}, 0);
// wrapper
private void drawDashLine(Graphics2D g, SimpleVector start, SimpleVector stop, Color color1, Color color2) {
final Graphics2D ig = (Graphics2D)g.create();
ig.setStroke(drawingStroke1);
ig.setColor(color1);
ig.drawLine(Math.round(start.x), Math.round(start.y),
Math.round(stop.x), Math.round(stop.y));
ig.setStroke(drawingStroke2);
ig.setColor(color2);
ig.drawLine(Math.round(start.x), Math.round(start.y),
Math.round(stop.x), Math.round(stop.y));
ig.dispose();
}
// helper
private boolean drawBoxOutline(float[] center, float[] range,
Color color1, Color color2,
Graphics2D ig, boolean useFading) {
// true if the bounding box was drawn successfully
boolean valid = true;
// true if the text should be drawn
boolean drawText = false;
// how the bounding box is drawn depends on the view
switch (side) {
// draw any boxes for the side views as simple 2D rectangles in the zero layer
case 0:case 1:case 2: {
SimpleVector[] vectors;
if (side == 0) {
vectors = new SimpleVector[]{
new SimpleVector(center[0] + range[0], center[1] + range[1], 0),
new SimpleVector(center[0] + range[0], center[1] - range[1], 0),
new SimpleVector(center[0] - range[0], center[1] - range[1], 0),
new SimpleVector(center[0] - range[0], center[1] + range[1], 0)
};
} else if (side == 1) {
vectors = new SimpleVector[]{
new SimpleVector(center[0] + range[0], 0, center[2] - range[2]),
new SimpleVector(center[0] + range[0], 0, center[2] + range[2]),
new SimpleVector(center[0] - range[0], 0, center[2] + range[2]),
new SimpleVector(center[0] - range[0], 0, center[2] - range[2])
};
} else {
vectors = new SimpleVector[]{
new SimpleVector(0, center[1] + range[1], center[2] + range[2]),
new SimpleVector(0, center[1] + range[1], center[2] - range[2]),
new SimpleVector(0, center[1] - range[1], center[2] - range[2]),
new SimpleVector(0, center[1] - range[1], center[2] + range[2])
};
}
for (int i = 0; i < vectors.length; i++) {
// scale and convert the points
vectors[i].scalarMul(VitcoSettings.VOXEL_SIZE);
vectors[i] = convert3D2D(vectors[i]);
// check that valid
if (vectors[i] == null) {
valid = false;
}
}
if (valid) {
// draw the cube
ig.setStroke(new BasicStroke(1f, BasicStroke.CAP_ROUND, BasicStroke.JOIN_BEVEL)); // line size
if (useFading) {
drawLine(ig, vectors[0], vectors[1], color1);
drawLine(ig, vectors[1], vectors[2], color1);
drawLine(ig, vectors[2], vectors[3], color1);
drawLine(ig, vectors[3], vectors[0], color1);
} else {
drawDashLine(ig, vectors[0], vectors[1], color1, color2);
drawDashLine(ig, vectors[1], vectors[2], color1, color2);
drawDashLine(ig, vectors[2], vectors[3], color1, color2);
drawDashLine(ig, vectors[3], vectors[0], color1, color2);
drawText = true;
}
}
}
break;
// draw any boxes in 3D
default: {
// define the points of the voxel
SimpleVector[] vectors = new SimpleVector[] {
new SimpleVector(center[0] + range[0], center[1] + range[1], center[2] + range[2]),
new SimpleVector(center[0] + range[0], center[1] + range[1], center[2] - range[2]),
new SimpleVector(center[0] + range[0], center[1] - range[1], center[2] - range[2]),
new SimpleVector(center[0] + range[0], center[1] - range[1], center[2] + range[2]),
new SimpleVector(center[0] - range[0], center[1] + range[1], center[2] + range[2]),
new SimpleVector(center[0] - range[0], center[1] + range[1], center[2] - range[2]),
new SimpleVector(center[0] - range[0], center[1] - range[1], center[2] - range[2]),
new SimpleVector(center[0] - range[0], center[1] - range[1], center[2] + range[2])
};
for (int i = 0; i < vectors.length; i++) {
// scale and convert the points
vectors[i].scalarMul(VitcoSettings.VOXEL_SIZE);
vectors[i] = convert3D2D(vectors[i]);
// check that valid
if (vectors[i] == null) {
valid = false;
}
}
if (valid) {
// calculate the z range
float[] zRange = new float[] {vectors[0].z, vectors[0].z}; // min and max z value
for (int i = 1; i < 8; i ++) {
zRange[0] = Math.min(vectors[i].z, zRange[0]);
zRange[1] = Math.max(vectors[i].z, zRange[1]);
}
float distance = zRange[1] - zRange[0];
// draw the cube
ig.setStroke(new BasicStroke(1f, BasicStroke.CAP_ROUND, BasicStroke.JOIN_BEVEL)); // line size
if (useFading) {
for (int i = 0; i < 4; i++) {
drawFadingLine(ig, vectors, i, (i + 1) % 4, color1, distance, zRange);
drawFadingLine(ig, vectors, i + 4, (i + 1) % 4 + 4, color1, distance, zRange);
drawFadingLine(ig, vectors, i, i + 4, color1, distance, zRange);
}
} else {
for (int i = 0; i < 4; i++) {
drawDashLine(ig, vectors[i], vectors[(i + 1) % 4], color1, color2);
drawDashLine(ig, vectors[i + 4], vectors[(i + 1) % 4 + 4], color1, color2);
drawDashLine(ig, vectors[i], vectors[i + 4], color1, color2);
}
drawText = true;
}
}
}
break;
}
if (drawText) {
// duplicate since we are drawing the range
drawCoordinates(ig, range[2] * 2, range[1] * 2, range[0] * 2);
}
return valid;
}
private void drawCoordinates(Graphics2D ig, float v1, float v2, float v3) {
// draw size text
ig = (Graphics2D) ig.create();
String str1 = String.valueOf(((int) (v1)));
String str2 = String.valueOf(((int) (v2)));
String str3 = String.valueOf(((int) (v3)));
ig.setFont(ig.getFont().deriveFont(18f).deriveFont(Font.BOLD));
float len1 = (float) ig.getFontMetrics().getStringBounds(str1, ig).getWidth();
float len2 = (float) ig.getFontMetrics().getStringBounds(str2 + ", " + str1, ig).getWidth();
float len3 = (float) ig.getFontMetrics().getStringBounds(str3 + ", " + str2 + ", " + str1, ig).getWidth();
ig.setColor(Color.BLACK);
ig.drawString(str1, this.getWidth() - len1 - 14 - 1, 28 - 1);
ig.drawString(str1, this.getWidth() - len1 - 14 + 1, 28 - 1);
ig.drawString(str1, this.getWidth() - len1 - 14 - 1, 28 + 1);
ig.drawString(str1, this.getWidth() - len1 - 14 + 1, 28 + 1);
ig.drawString(str2, this.getWidth() - len2 - 14 - 1, 28 - 1);
ig.drawString(str2, this.getWidth() - len2 - 14 + 1, 28 - 1);
ig.drawString(str2, this.getWidth() - len2 - 14 - 1, 28 + 1);
ig.drawString(str2, this.getWidth() - len2 - 14 + 1, 28 + 1);
ig.drawString(str3, this.getWidth() - len3 - 14 - 1, 28 - 1);
ig.drawString(str3, this.getWidth() - len3 - 14 + 1, 28 - 1);
ig.drawString(str3, this.getWidth() - len3 - 14 - 1, 28 + 1);
ig.drawString(str3, this.getWidth() - len3 - 14 + 1, 28 + 1);
ig.setColor(VitcoSettings.ANIMATION_AXIS_COLOR_Z);
ig.drawString(str1, this.getWidth() - len1 - 14, 28);
ig.setColor(VitcoSettings.ANIMATION_AXIS_COLOR_Y);
ig.drawString(str2, this.getWidth() - len2 - 14, 28);
ig.setColor(VitcoSettings.ANIMATION_AXIS_COLOR_X);
ig.drawString(str3, this.getWidth() - len3 - 14, 28);
ig.dispose();
}
// draw overlay for voxels
private void drawVoxelOverlay(Graphics2D ig) {
// Anti-alias
ig.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
RenderingHints.VALUE_ANTIALIAS_ON);
// draw the select rect
if (selectedRect != null) {
ig.setColor(Color.WHITE);
ig.drawRect(selectedRect.x, selectedRect.y, selectedRect.width, selectedRect.height);
}
if (highlighted != null && isActive()) {
// draw the highlighted voxel coordinates
drawCoordinates(ig, highlighted[2], highlighted[1], highlighted[0]);
}
for (int[][] outlinedRect : outlineBoxed) {
float[] center = new float[]{
(outlinedRect[0][0] + outlinedRect[1][0]) / 2f,
(outlinedRect[0][1] + outlinedRect[1][1]) / 2f,
(outlinedRect[0][2] + outlinedRect[1][2]) / 2f
};
float[] range = new float[]{
Math.abs(outlinedRect[0][0] - outlinedRect[1][0]) / 2f + 0.5f,
Math.abs(outlinedRect[0][1] - outlinedRect[1][1]) / 2f + 0.5f,
Math.abs(outlinedRect[0][2] - outlinedRect[1][2]) / 2f + 0.5f
};
drawBoxOutline(center, range, new Color(outlinedRect[2][0]), new Color(outlinedRect[2][1]), ig, false);
}
// draw the preview voxel
int[] voxel = highlighted;
// draw selected voxel (ghost / preview voxel)
if (voxel != null) {
Color previewColor = VitcoSettings.VOXEL_PREVIEW_LINE_COLOR;
// // try to find the voxel color
// List<Voxel> list = voxelPositions.search(new float[] {voxel[0], voxel[1], voxel[2]}, ZEROS);
// if (list.size() > 0) {
// Voxel voxelObject3D = list.get(0);
// if (ColorTools.perceivedBrightness(voxelObject3D.getColor()) < 130) {
// previewColor = VitcoSettings.VOXEL_PREVIEW_LINE_COLOR_BRIGHT;
// }
// }
boolean valid = drawBoxOutline(highlightedFloat, defaultVoxel, previewColor, null, ig, true);
if (valid) {
// draw the highlighted side (grid)
if (previewPlane != -1) {
// calculate center and some variables
int RANGE = 4;
float shift = (previewPlane%2 == 0 ? 0.5f : -0.5f);
int plane = previewPlane / 2;
// the "grid" is set to the zero plane for the side views
SimpleVector center = new SimpleVector(
side == 2 ? 0 : voxel[0] + (plane == 2 ? shift : 0),
side == 1 ? 0 : voxel[1] + (plane == 1 ? shift : 0),
side == 0 ? 0 : voxel[2] + (plane == 0 ? shift : 0)
);
// calculate the points
SimpleVector[] points = new SimpleVector[(RANGE*2 - 1) * 4];
float[] range = new float[]{-RANGE, RANGE + 1};
int c = 0;
for (int i = -RANGE + 1; i < RANGE; i++) {
for (float j : range) {
points[c] = center.calcAdd(new SimpleVector(
(plane != 2 ? i - 0.5 : 0),
(plane != 1 ? j - 0.5 : 0),
(plane == 1 ? j - 0.5 : (plane == 2 ? i - 0.5 : 0))
));
points[c].scalarMul(VitcoSettings.VOXEL_SIZE);
points[c] = convert3D2D(points[c]);
points[c+1] = center.calcAdd(new SimpleVector(
(plane != 2 ? j - 0.5 : 0),
(plane != 1 ? i - 0.5 : 0),
(plane == 1 ? i - 0.5 : (plane == 2 ? j - 0.5 : 0))
));
points[c+1].scalarMul(VitcoSettings.VOXEL_SIZE);
points[c+1] = convert3D2D(points[c+1]);
if (points[c] == null || points[c+1] == null) {
valid = false;
}
c+=2;
}
}
if (valid) {
// draw the lines
float halfLen = points.length/((float)8);
Color transColor = new Color(previewColor.getRed(),
previewColor.getGreen(),
previewColor.getBlue(),
0);
for (int i = 0, len = points.length/4; i < len; i++) {
float alpha = (halfLen-Math.abs(i-halfLen))/halfLen;
Color visColor = new Color(
previewColor.getRed(),
previewColor.getGreen(),
previewColor.getBlue(),
Math.min(255,Math.max(0,Math.round(100*alpha))));
ig.setPaint(new GradientPaint(
Math.round(points[i*4].x), Math.round(points[i*4].y),
transColor,
Math.round((points[i*4].x + points[i*4 + 2].x)/2), Math.round((points[i*4].y + points[i*4 + 2].y)/2),
visColor,
true));
ig.drawLine(Math.round(points[i*4].x), Math.round(points[i*4].y),
Math.round(points[i*4 + 2].x), Math.round(points[i*4 + 2].y));
ig.setPaint(new GradientPaint(
Math.round(points[i*4 + 1].x), Math.round(points[i*4 + 1].y),
transColor,
Math.round((points[i*4 + 1].x + points[i*4 + 3].x)/2), Math.round((points[i*4 + 1].y + points[i*4 + 3].y)/2),
visColor,
true));
ig.drawLine(Math.round(points[i*4 + 1].x), Math.round(points[i*4 + 1].y),
Math.round(points[i*4 + 3].x), Math.round(points[i*4 + 3].y));
}
}
}
}
}
}
// draw dynamic overlay on top of the openGL
private void drawAnimationOverlay(Graphics2D ig) {
// Anti-alias
ig.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
RenderingHints.VALUE_ANTIALIAS_ON);
// store lines and points
ArrayList<ExtendedVector[]> objects = new ArrayList<ExtendedVector[]>();
// add lines
for (ExtendedVector[] line : data.getLines()) {
// line points are set to zero planes for side views
line = line.clone();
line[0] = new ExtendedVector(line[0]);
line[1] = new ExtendedVector(line[1]);
if (side == 2) {
line[0].x = 0;
line[1].x = 0;
}
if (side == 1) {
line[0].y = 0;
line[1].y = 0;
}
if (side == 0) {
line[0].z = 0;
line[1].z = 0;
}
ExtendedVector point2da = convertExt3D2D(line[0]);
ExtendedVector point2db = convertExt3D2D(line[1]);
if (point2da != null && point2db != null) {
ExtendedVector mid = new ExtendedVector(point2da.calcAdd(point2db), 0);
mid.scalarMul(DynamicSettings.SAMPLING_MODE_DIVIDEND);
objects.add(new ExtendedVector[] {point2da, point2db, mid});
}
}
// add preview line
ExtendedVector[] preview_line = data.getPreviewLine();
boolean connected = preview_line != null && data.areConnected(preview_line[0].id, preview_line[1].id);
if (preview_line != null && !connected) {
// line points are set to zero planes for side views
preview_line = preview_line.clone();
preview_line[0] = new ExtendedVector(preview_line[0]);
preview_line[1] = new ExtendedVector(preview_line[1]);
if (side == 2) {
preview_line[0].x = 0;
preview_line[1].x = 0;
}
if (side == 1) {
preview_line[0].y = 0;
preview_line[1].y = 0;
}
if (side == 0) {
preview_line[0].z = 0;
preview_line[1].z = 0;
}
ExtendedVector point2da = convertExt3D2D(preview_line[0]);
ExtendedVector point2db = convertExt3D2D(preview_line[1]);
if (point2da != null && point2db != null) {
ExtendedVector mid = new ExtendedVector(point2da.calcAdd(point2db), 0);
mid.scalarMul(DynamicSettings.SAMPLING_MODE_DIVIDEND);
objects.add(new ExtendedVector[] {point2da, point2db, mid});
}
}
// add points
for (ExtendedVector point : data.getPoints()) {
// points are set to zero planes for side views
point = new ExtendedVector(point);
if (side == 2) {
point.x = 0;
}
if (side == 1) {
point.y = 0;
}
if (side == 0) {
point.z = 0;
}
ExtendedVector point2d = convertExt3D2D(point);
if (point2d != null) {
objects.add(new ExtendedVector[] {point2d});
}
}
// sort the data
Collections.sort(objects, new Comparator<ExtendedVector[]>() {
@Override
public int compare(ExtendedVector[] o1, ExtendedVector[] o2) {
if (o1.length == 3 && o2.length == 3) { // two lines
return (int) Math.signum(o1[2].z - o2[2].z);
} else if (o1.length == 1 && o2.length == 1) { // two points
return (int) Math.signum(o1[0].z - o2[0].z);
} else if (o1.length == 1 && o2.length == 3) { // point and line
float diff = o1[0].z - o2[2].z;
return Math.abs(diff) < 0.00000001 ? 1 : (int)Math.signum(diff); // point to front if equal
} else if (o1.length == 3 && o2.length == 1) { // line and point
float diff = o1[2].z - o2[0].z;
return Math.abs(diff) < 0.00000001 ? -1 : (int)Math.signum(diff); // point to front if equal
}
return 0;
}
});
// draw all points and lines
int selected_point = data.getSelectedPoint();
int highlighted_point = data.getHighlightedPoint();
for (ExtendedVector[] object : objects) {
if (object.length == 1) {
if (object[0].id == selected_point) { // selected
G2DUtil.drawPoint(object[0], ig,
VitcoSettings.ANIMATION_DOT_SEL_INNER_COLOR,
VitcoSettings.ANIMATION_DOT_SEL_OUTER_COLOR,
VitcoSettings.ANIMATION_CIRCLE_RADIUS,
VitcoSettings.ANIMATION_CIRCLE_BORDER_SIZE);
} else if (object[0].id == highlighted_point) { // highlighted
G2DUtil.drawPoint(object[0], ig,
VitcoSettings.ANIMATION_DOT_HL_INNER_COLOR,
VitcoSettings.ANIMATION_DOT_HL_OUTER_COLOR,
VitcoSettings.ANIMATION_CIRCLE_RADIUS,
VitcoSettings.ANIMATION_CIRCLE_BORDER_SIZE);
} else { // default
G2DUtil.drawPoint(object[0], ig,
VitcoSettings.ANIMATION_DOT_INNER_COLOR,
VitcoSettings.ANIMATION_DOT_OUTER_COLOR,
VitcoSettings.ANIMATION_CIRCLE_RADIUS,
VitcoSettings.ANIMATION_CIRCLE_BORDER_SIZE);
}
} else {
if (preview_line != null &&
((object[0].id == preview_line[0].id && object[1].id == preview_line[1].id)
|| (object[0].id == preview_line[1].id && object[1].id == preview_line[0].id))) {
G2DUtil.drawLine(object[0], object[1], ig,
connected ? VitcoSettings.ANIMATION_LINE_PREVIEW_REMOVE_COLOR : VitcoSettings.ANIMATION_LINE_PREVIEW_ADD_COLOR,
VitcoSettings.ANIMATION_LINE_OUTER_COLOR,
VitcoSettings.ANIMATION_LINE_SIZE);
} else {
G2DUtil.drawLine(object[0], object[1], ig,
VitcoSettings.ANIMATION_LINE_INNER_COLOR, VitcoSettings.ANIMATION_LINE_OUTER_COLOR,
VitcoSettings.ANIMATION_LINE_SIZE);
}
}
}
}
// wrapper
private void drawAxeHalf(SimpleVector unitVector, boolean invert, Graphics2D ig,
Color innerColor, Color outerColor, float size,
int offsetX, int offsetY
) {
G2DUtil.drawLine(
new SimpleVector((invert?-1:1)*unitVector.x*15 + offsetX, (invert?-1:1)*unitVector.y*15 + offsetY, 0),
new SimpleVector((invert?-1:1)*unitVector.x*3 + offsetX, (invert?-1:1)*unitVector.y*3 + offsetY, 0),
ig,
innerColor,
outerColor,
size
);
}
// called for content that only changes when the opengl content changes
private void drawLinkedOverlay(final Graphics2D ig) {
// Anti-alias
ig.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
RenderingHints.VALUE_ANTIALIAS_ON);
// draw the bounding box
if (drawBoundingBox) {
drawBoundingBox(ig, side);
}
int offsetX = 25;
int offsetY = 25;
// // this moves the axis (red blue green) to the corner of the bounding box (main view)
// if (side == -1) {
// // need to convert this vector to static
// SimpleVector pos =
// convert3D2D(new SimpleVector(
// (DynamicSettings.VOXEL_PLANE_RANGE_X - 0.5) * VitcoSettings.VOXEL_SIZE, VitcoSettings.HALF_VOXEL_SIZE,
// (DynamicSettings.VOXEL_PLANE_RANGE_Z - 0.5) * VitcoSettings.VOXEL_SIZE));
// if (pos != null) {
// offsetX = Math.round(pos.x);
// offsetY = Math.round(pos.y);
// }
// }
// draw axis into corner (top left)
Matrix matrix = camera.getBack();
ExtendedVector[] vec = new ExtendedVector[]{
new ExtendedVector(1, 0, 0, 0),
new ExtendedVector(0, 1, 0, 1),
new ExtendedVector(0, 0, 1, 2)
};
for (ExtendedVector v : vec) {
v.matMul(matrix);
}
Arrays.sort(vec, new Comparator<ExtendedVector>() {
@Override
public int compare(ExtendedVector o1, ExtendedVector o2) {
return (int) Math.signum(o1.z - o2.z);
}
});
for (ExtendedVector v : vec) {
drawAxeHalf(v, true, ig,
(v.id == 0
? VitcoSettings.ANIMATION_AXIS_COLOR_X
: (v.id == 1
? VitcoSettings.ANIMATION_AXIS_COLOR_Y
: VitcoSettings.ANIMATION_AXIS_COLOR_Z)),
VitcoSettings.ANIMATION_AXIS_OUTER_COLOR, VitcoSettings.ANIMATION_AXIS_LINE_SIZE,
offsetX, offsetY
);
}
// draw center cross
if (drawBoundingBox || side != -1) {
ig.setColor(VitcoSettings.ANIMATION_CENTER_CROSS_COLOR);
ig.setStroke(new BasicStroke(1.0f));
SimpleVector center = convert3D2D(new SimpleVector(0,0,0));
if (center != null) {
ig.drawLine(Math.round(center.x - 5), Math.round(center.y), Math.round(center.x + 5), Math.round(center.y));
ig.drawLine(Math.round(center.x), Math.round(center.y - 5), Math.round(center.x), Math.round(center.y + 5));
}
}
}
// draw so that we can also see the outline of the
// box in the front (where the textured grid is hidden)
private void drawBoundingBox(Graphics2D gr, int side) {
// Anti-alias
gr.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
RenderingHints.VALUE_ANTIALIAS_ON);
switch (side) {
// draw the bounding box for the side views as a simple 2D rectangle in the zero layer
case 0: case 1: case 2: {
SimpleVector[] vectors;
if (side == 0) {
vectors = new SimpleVector[] {
new SimpleVector(DynamicSettings.VOXEL_PLANE_RANGE_X, 0.5f, 0),
new SimpleVector(DynamicSettings.VOXEL_PLANE_RANGE_X, -DynamicSettings.VOXEL_PLANE_SIZE_Y +0.5f, 0),
new SimpleVector(-DynamicSettings.VOXEL_PLANE_RANGE_X, -DynamicSettings.VOXEL_PLANE_SIZE_Y +0.5f, 0),
new SimpleVector(-DynamicSettings.VOXEL_PLANE_RANGE_X, 0.5f, 0)
};
} else if (side == 1) {
vectors = new SimpleVector[] {
new SimpleVector(DynamicSettings.VOXEL_PLANE_RANGE_X, 0, -DynamicSettings.VOXEL_PLANE_RANGE_Z),
new SimpleVector(-DynamicSettings.VOXEL_PLANE_RANGE_X, 0, -DynamicSettings.VOXEL_PLANE_RANGE_Z),
new SimpleVector(-DynamicSettings.VOXEL_PLANE_RANGE_X, 0, DynamicSettings.VOXEL_PLANE_RANGE_Z),
new SimpleVector(DynamicSettings.VOXEL_PLANE_RANGE_X, 0, DynamicSettings.VOXEL_PLANE_RANGE_Z)
};
} else {
vectors = new SimpleVector[] {
new SimpleVector(0, -DynamicSettings.VOXEL_PLANE_SIZE_Y +0.5f, -DynamicSettings.VOXEL_PLANE_RANGE_Z),
new SimpleVector(0, 0.5f, -DynamicSettings.VOXEL_PLANE_RANGE_Z),
new SimpleVector(0, 0.5f, DynamicSettings.VOXEL_PLANE_RANGE_Z),
new SimpleVector(0, -DynamicSettings.VOXEL_PLANE_SIZE_Y +0.5f, DynamicSettings.VOXEL_PLANE_RANGE_Z)
};
}
boolean valid = true;
for (int i = 0; i < vectors.length; i++) {
// scale and convert the points
if (DynamicSettings.VOXEL_PLANE_SIZE_X%2 == 0) {
// necessary if the center voxel is not the true center
vectors[i].x -= 0.5f;
}
if (DynamicSettings.VOXEL_PLANE_SIZE_Z%2 == 0) {
// necessary if the center voxel is not the true center
vectors[i].z -= 0.5f;
}
vectors[i].scalarMul(VitcoSettings.VOXEL_SIZE);
vectors[i] = convert3D2D(vectors[i]);
// check that valid
if (vectors[i] == null) {
valid = false;
}
}
if (valid) {
// draw the rect
gr.setStroke(new BasicStroke(1f, BasicStroke.CAP_ROUND, BasicStroke.JOIN_BEVEL)); // line size
drawLine(gr, vectors[0], vectors[1], VitcoSettings.BOUNDING_BOX_COLOR);
drawLine(gr, vectors[1], vectors[2], VitcoSettings.BOUNDING_BOX_COLOR);
drawLine(gr, vectors[2], vectors[3], VitcoSettings.BOUNDING_BOX_COLOR);
drawLine(gr, vectors[3], vectors[0], VitcoSettings.BOUNDING_BOX_COLOR);
}
}
break;
// draw the bounding box in 3D view
default: {
// get an instance that we can modify
SimpleVector[] vectors = new SimpleVector[14];
boolean valid = true;
for (int i = 0; i < vectors.length; i++) {
// scale and convert the points
vectors[i] = getVectorsStatic(i);
vectors[i].y += 0.5f / DynamicSettings.VOXEL_PLANE_SIZE_Y;
vectors[i].x *= DynamicSettings.VOXEL_PLANE_WORLD_SIZE_X;
vectors[i].y *= DynamicSettings.VOXEL_PLANE_WORLD_SIZE_Y;
vectors[i].z *= DynamicSettings.VOXEL_PLANE_WORLD_SIZE_Z;
if (DynamicSettings.VOXEL_PLANE_SIZE_X%2 == 0) {
// necessary if the center voxel is not the true center
vectors[i].x -= 0.5f * VitcoSettings.VOXEL_SIZE;
}
if (DynamicSettings.VOXEL_PLANE_SIZE_Z%2 == 0) {
// necessary if the center voxel is not the true center
vectors[i].z -= 0.5f * VitcoSettings.VOXEL_SIZE;
}
vectors[i] = convert3D2D(vectors[i]);
// check that valid
if (vectors[i] == null) {
valid = false;
}
}
if (valid) {
// calculate the z range
float[] zRange = new float[]{vectors[0].z, vectors[0].z}; // min and max z value
for (int i = 1; i < 8; i++) {
zRange[0] = Math.min(vectors[i].z, zRange[0]);
zRange[1] = Math.max(vectors[i].z, zRange[1]);
}
float distance = zRange[1] - zRange[0];
// draw the cube
gr.setStroke(new BasicStroke(1f, BasicStroke.CAP_ROUND, BasicStroke.JOIN_BEVEL)); // line size
for (int i = 0; i < 4; i++) {
drawFadingLine(gr, vectors, i, (i + 1) % 4, VitcoSettings.BOUNDING_BOX_COLOR, distance, zRange);
drawFadingLine(gr, vectors, i + 4, (i + 1) % 4 + 4, VitcoSettings.BOUNDING_BOX_COLOR, distance, zRange);
drawFadingLine(gr, vectors, i, i + 4, VitcoSettings.BOUNDING_BOX_COLOR, distance, zRange);
}
// draw center dots for bounding box
gr.setColor(Color.WHITE);
gr.setStroke(new BasicStroke(2f, BasicStroke.CAP_ROUND, BasicStroke.JOIN_BEVEL)); // line size
for (int i = 0; i < 6; i++) {
gr.drawLine(Math.round(vectors[8+i].x), Math.round(vectors[8+i].y), Math.round(vectors[8+i].x), Math.round(vectors[8+i].y));
}
}
}
break;
}
}
// ---------------------------
// constructor
public DrawContainer(int side) {
super(side);
// initialize the drawing buffer
notifyAboutResize(100, 100);
}
// handle the resize of this container and
// update all variables accordingly
public void notifyAboutResize(int width, int height) {
toDraw = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB);
Graphics gr = toDraw.getGraphics();
gr.setColor(bgColor);
gr.fillRect(0, 0, width, height);
gr.dispose();
// the overlay
overlayBuffer = new BufferedImage(width, height, BufferedImage.TYPE_INT_ARGB);
// draw the lines
overlayBufferGraphics = (Graphics2D)overlayBuffer.getGraphics();
// Anti-alias
overlayBufferGraphics.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
RenderingHints.VALUE_ANTIALIAS_ON);
// bg color to clear
overlayBufferGraphics.setBackground(new Color(0,0,0,0));
// set color
overlayBufferGraphics.setColor(VitcoSettings.GHOST_VOXEL_OVERLAY_LINE_COLOR);
overlayBufferGraphics.setStroke(
new BasicStroke(1f, BasicStroke.CAP_ROUND, BasicStroke.JOIN_BEVEL,
0, new float[]{4,5}, 2));
hasResized = true;
}
// enable shader for this container
private boolean enableShade = false;
public void enableShader(boolean state) {
enableShade = state;
}
// expose the z buffer of this container
public final int[] getZBuffer() {
return buffer.getZBuffer();
}
// exposes the pixel array
public final int[] getPixels() {
return buffer.getPixels();
}
// get the image currently rendered in high quality
public final BufferedImage getImage() {
Config.useFramebufferWithAlpha = true;
HackedFrameBuffer fb = new HackedFrameBuffer(getWidth()*2, getHeight()*2, FrameBuffer.SAMPLINGMODE_NORMAL);
Config.useFramebufferWithAlpha = false;
fb.clear(new Color(0, 0, 0, 0));
world.renderScene(fb);
world.draw(fb);
fb.update();
int w = fb.getWidth() * 2;
int[] zBuffer = fb.getZBuffer(); //requires hacked framebuffer
int[] pixels = fb.getPixels();
// fix t-junction anomalies
for (int c = w + 1; c < zBuffer.length - w - 1; c++) {
int x = zBuffer[c] + Integer.MAX_VALUE;
int x5 = zBuffer[c-w] + Integer.MAX_VALUE;
int x3 = zBuffer[c+w] + Integer.MAX_VALUE;
int x1 = zBuffer[c-1] + Integer.MAX_VALUE;
int x7 = zBuffer[c+1] + Integer.MAX_VALUE;
int x2 = zBuffer[c-w - 1] + Integer.MAX_VALUE;
int x8 = zBuffer[c-w + 1] + Integer.MAX_VALUE;
int x0 = zBuffer[c+w - 1] + Integer.MAX_VALUE;
int x6 = zBuffer[c+w + 1] + Integer.MAX_VALUE;
if (Math.abs(x1 - x7) < 100000 && Math.abs(x1 - x) > 100000) {
pixels[c] = pixels[c-1];
} else if (Math.abs(x5 - x3) < 100000 && Math.abs(x5 - x) > 100000) {
pixels[c] = pixels[c-w];
} else if (Math.abs(x0 - x8) < 100000 && Math.abs(x0 - x) > 100000) {
pixels[c] = pixels[c+w-1];
} else if (Math.abs(x2 - x6) < 100000 && Math.abs(x2 - x) > 100000) {
pixels[c] = pixels[c-w-1];
}
}
BufferedImage largeResult = new BufferedImage(fb.getWidth(), fb.getHeight(), BufferedImage.TYPE_INT_ARGB);
fb.display(largeResult.getGraphics());
// resize
BufferedImage result = new BufferedImage(largeResult.getWidth()/2, largeResult.getHeight()/2, BufferedImage.TYPE_INT_ARGB);
Graphics2D g2d = result.createGraphics();
g2d.setRenderingHint(RenderingHints.KEY_INTERPOLATION, RenderingHints.VALUE_INTERPOLATION_BILINEAR);
g2d.setRenderingHint(RenderingHints.KEY_RENDERING, RenderingHints.VALUE_RENDER_QUALITY);
g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
g2d.drawImage(largeResult, 0, 0, largeResult.getWidth()/2, largeResult.getHeight()/2, null);
g2d.dispose();
return result;
}
// get the image currently rendered in high quality
public final BufferedImage getDepthImage() {
HackedFrameBuffer fb = new HackedFrameBuffer(getWidth(), getHeight(), FrameBuffer.SAMPLINGMODE_OGSS);
fb.clear();
world.renderScene(fb);
world.draw(fb);
fb.update();
BufferedImage largeResult = new BufferedImage(fb.getWidth()*2, fb.getHeight()*2, BufferedImage.TYPE_INT_ARGB);
int w = fb.getWidth() * 2;
int[] zBuffer = fb.getZBuffer(); //requires hacked framebuffer
// compute mean
int count = 0;
long mean = 0;
for (int aZBuffer : zBuffer) {
if (aZBuffer != -2147483647) {
mean += aZBuffer;
count++;
}
}
mean /= count;
// compute std deviation
long sum = 0;
for (int aZBuffer : zBuffer) {
if (aZBuffer != -2147483647) {
sum += Math.pow(aZBuffer - mean, 2);
}
}
double stdDev = Math.sqrt(sum/(double)count);
// compute min and max for non outliers
int min = Integer.MAX_VALUE, max = Integer.MIN_VALUE;
for (int aZBuffer : zBuffer) {
if (Math.abs(aZBuffer - mean) < 4*stdDev) {
min = Math.min(min, aZBuffer);
max = Math.max(max, aZBuffer);
}
}
int range = max - min;
// fix t-junction anomalies
for (int c = w + 1; c < zBuffer.length - w - 1; c++) {
int x = zBuffer[c] + Integer.MAX_VALUE;
int x5 = zBuffer[c-w] + Integer.MAX_VALUE;
int x3 = zBuffer[c+w] + Integer.MAX_VALUE;
int x1 = zBuffer[c-1] + Integer.MAX_VALUE;
int x7 = zBuffer[c+1] + Integer.MAX_VALUE;
int x2 = zBuffer[c-w - 1] + Integer.MAX_VALUE;
int x8 = zBuffer[c-w + 1] + Integer.MAX_VALUE;
int x0 = zBuffer[c+w - 1] + Integer.MAX_VALUE;
int x6 = zBuffer[c+w + 1] + Integer.MAX_VALUE;
if (Math.abs(x1 - x7) < 100000 && Math.abs(x1 - x) > 100000) {
zBuffer[c] = zBuffer[c-1];
} else if (Math.abs(x5 - x3) < 100000 && Math.abs(x5 - x) > 100000) {
zBuffer[c] = zBuffer[c-w];
} else if (Math.abs(x0 - x8) < 100000 && Math.abs(x0 - x) > 100000) {
zBuffer[c] = zBuffer[c+w-1];
} else if (Math.abs(x2 - x6) < 100000 && Math.abs(x2 - x) > 100000) {
zBuffer[c] = zBuffer[c-w-1];
}
}
// compute values
for (int c = 0; c < zBuffer.length; c++) {
if (zBuffer[c] != -2147483647) {
int val = (int) Math.min(255,Math.max(0,
((zBuffer[c] - min) ) / (range/255f)
));
//if (c%w < largeResult.getWidth() && c/w < largeResult.getHeight())
largeResult.setRGB((c % w), (c / w), new Color(val, val, val, 255).getRGB());
}
}
// resize
BufferedImage result = new BufferedImage(largeResult.getWidth()/2, largeResult.getHeight()/2, BufferedImage.TYPE_INT_ARGB);
Graphics2D g2d = result.createGraphics();
g2d.setRenderingHint(RenderingHints.KEY_INTERPOLATION, RenderingHints.VALUE_INTERPOLATION_BILINEAR);
g2d.setRenderingHint(RenderingHints.KEY_RENDERING, RenderingHints.VALUE_RENDER_QUALITY);
g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
g2d.drawImage(largeResult, 0, 0, largeResult.getWidth()/2, largeResult.getHeight()/2, null);
g2d.dispose();
return result;
}
// draw shader
private void drawShader() {
// draw depth outline (software "shader")
// idea: http://coding-experiments.blogspot.de/2010/06/edge-detection.html
int w = buffer.getWidth() * DynamicSettings.SAMPLING_MODE_MULTIPLICAND;
int factor = w * DynamicSettings.SAMPLING_MODE_MULTIPLICAND * DynamicSettings.SAMPLING_MODE_MULTIPLICAND;
int[] zBuffer = buffer.getZBuffer(); //requires hacked framebuffer
@SuppressWarnings("MismatchedReadAndWriteOfArray")
int[] pixels = buffer.getPixels();
for (int c = w*2 + 2; c < zBuffer.length - w*2 - 2; c++) {
int x = zBuffer[c] + Integer.MAX_VALUE;
if (x != 0) {
int x5 = zBuffer[c-w] + Integer.MAX_VALUE;
int x3 = zBuffer[c+w] + Integer.MAX_VALUE;
int x1 = zBuffer[c-1] + Integer.MAX_VALUE;
int x7 = zBuffer[c+1] + Integer.MAX_VALUE;
int x2 = zBuffer[c-w - 1] + Integer.MAX_VALUE;
int x8 = zBuffer[c-w + 1] + Integer.MAX_VALUE;
int x0 = zBuffer[c+w - 1] + Integer.MAX_VALUE;
int x6 = zBuffer[c+w + 1] + Integer.MAX_VALUE;
// move one more outwards
int x5t = zBuffer[c-2*w] + Integer.MAX_VALUE;
int x3t = zBuffer[c+2*w] + Integer.MAX_VALUE;
int x1t = zBuffer[c-2] + Integer.MAX_VALUE;
int x7t = zBuffer[c+2] + Integer.MAX_VALUE;
int x2t = zBuffer[c-2*w - 2] + Integer.MAX_VALUE;
int x8t = zBuffer[c-2*w + 2] + Integer.MAX_VALUE;
int x0t = zBuffer[c+2*w - 2] + Integer.MAX_VALUE;
int x6t = zBuffer[c+2*w + 2] + Integer.MAX_VALUE;
int p1 = Math.abs(x1 - x7)/10;
int p2 = Math.abs(x5 - x3)/10;
int p3 = Math.abs(x0 - x8)/10;
int p4 = Math.abs(x2 - x6)/10;
int val = (Math.abs(x7 - x7t) < p1 && Math.abs(x1 - x1t) < p1 ? 1 : 0) +
(Math.abs(x5 - x5t) < p2 && Math.abs(x3 - x3t) < p2 ? 1 : 0) +
(Math.abs(x0 - x0t) < p3 && Math.abs(x8 - x8t) < p3 ? 1 : 0) +
(Math.abs(x2 - x2t) < p4 && Math.abs(x6 - x6t) < p4 ? 1 : 0);
if (val == 2 || val == 3) {
pixels[(c/factor)*w + (c/DynamicSettings.SAMPLING_MODE_MULTIPLICAND)%w] = 0;
c += DynamicSettings.SAMPLING_MODE_MULTIPLICAND -1;
} else {
int xP = x + 100;
int xM = x - 100;
int s = ((x1t > xP && x7 > xP) || (x1t < xM && x7t < xM) ? 1 : 0) +
((x5t > xP && x3 > xP) || (x5t < xM && x3t < xM) ? 1 : 0) +
((x2t > xP && x6 > xP) || (x2t < xM && x6t < xM) ? 1 : 0) +
((x0t > xP && x8 > xP) || (x0t < xM && x8t < xM) ? 1 : 0);
if (s == 2 || s == 3) {
pixels[(c/factor)*w + (c/DynamicSettings.SAMPLING_MODE_MULTIPLICAND)%w] = 0;
c += DynamicSettings.SAMPLING_MODE_MULTIPLICAND -1;
}
}
}
}
}
// render the content of this container
public final void render() {
if (skipNextWorldRender && !doNotSkipNextWorldRender) {
skipNextWorldRender = false;
} else {
if (doNotSkipNextWorldRender) {
doNotSkipNextWorldRender = false;
skipNextWorldRender = false;
}
buffer.clear(bgColor);
if (drawWorld && world != null) {
refreshVoxels(false);
world.renderScene(buffer);
if (useWireFrame) {
world.drawWireframe(buffer, VitcoSettings.WIREFRAME_COLOR);
} else {
world.draw(buffer);
if (drawSelectedVoxels) { // only draw selected voxels if enables
selectedVoxelsWorld.drawAsShiftedWireframe(buffer,
VitcoSettings.SELECTED_VOXEL_WIREFRAME_COLOR,
VitcoSettings.SELECTED_VOXEL_WIREFRAME_COLOR_SHIFTED);
}
}
}
buffer.update();
if (drawOverlay && camera != null) { // overlay part 1
drawLinkedOverlay((Graphics2D) buffer.getGraphics()); // refreshes with OpenGL
}
// draw the shader if enabled
if (enableShade) {
drawShader();
}
}
Graphics2D gr = (Graphics2D) toDraw.getGraphics();
buffer.display(gr);
// draw the under/overlay (voxels in parallel planes)
if (drawGhostOverlay) {
drawGhostOverlay(gr, cameraChanged, hasResized);
}
if (drawOverlay && drawAnimationOverlay && data != null) { // overlay part 2
drawAnimationOverlay(gr); // refreshes with animation data
}
if (drawOverlay && drawVoxelOverlay) {
drawVoxelOverlay(gr);
}
gr.dispose();
// // debug
// if (Main.isDebugMode()) {
// gr.drawString(String.valueOf(world.getVisibilityList().getSize()), buffer.getWidth() - 40, 20);
// }
cameraChanged = false; // camera is current for this redraw
hasResized = false; // no resize pending
}
// handle the redrawing of this component
// Note1: this MUSTN'T have any call to synchronized
// Note2: this method should be super fast to execute,
// otherwise we might have many pending repaint events
// in the queue!
@Override
protected final void paintComponent(Graphics g1) {
if (toDraw != null) {
g1.drawImage(toDraw, 0, 0, null);
} else {
g1.setColor(bgColor);
g1.fillRect(0, 0, this.getWidth(), this.getHeight());
}
this.setRepainting(false);
if (needRepainting) {
needRepainting = false;
this.resetSkipRenderFlags();
if (skipNextWorldRenderBuffer) {
this.skipNextWorldRender();
}
if (doNotSkipNextWorldRenderBuffer) {
this.doNotSkipNextWorldRender();
}
forceRepaint();
}
}
// draw some ghosting lines (the voxel outline)
private void drawGhostOverlay(Graphics2D g1, boolean cameraChanged, boolean hasResized) {
boolean updated = updateGhostOverlay();
if (updated || cameraChanged || hasResized) {
// clear the previous drawings
overlayBufferGraphics.clearRect(0,0,overlayBuffer.getWidth(),overlayBuffer.getHeight());
// draw
for (SimpleVector[] line : getGhostOverlay()) {
SimpleVector p1 = convert3D2D(line[0]);
SimpleVector p2 = convert3D2D(line[1]);
overlayBufferGraphics.drawLine(Math.round(p1.x), Math.round(p1.y), Math.round(p2.x), Math.round(p2.y));
}
}
g1.drawImage(overlayBuffer, 0, 0, null);
}
}