/**
* ORIPA - Origami Pattern Editor
* Copyright (C) 2005-2009 Jun Mitani http://mitani.cs.tsukuba.ac.jp/
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program 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 General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package oripa.view.estimation;
import java.awt.AlphaComposite;
import java.awt.Color;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Image;
import java.awt.RenderingHints;
import java.awt.Transparency;
import java.awt.event.MouseEvent;
import java.awt.event.MouseListener;
import java.awt.event.MouseMotionListener;
import java.awt.event.MouseWheelEvent;
import java.awt.event.MouseWheelListener;
import java.awt.geom.AffineTransform;
import java.awt.geom.Point2D;
import java.awt.image.BufferedImage;
import java.awt.image.MemoryImageSource;
import java.io.File;
import java.util.List;
import javax.imageio.ImageIO;
import javax.swing.JPanel;
import javax.vecmath.Vector2d;
import oripa.ORIPA;
import oripa.doc.Doc;
import oripa.fold.BoundBox;
import oripa.fold.FoldedModelInfo;
import oripa.fold.OriFace;
import oripa.fold.OrigamiModel;
import oripa.fold.TriangleFace;
import oripa.fold.TriangleVertex;
/**
* A screen to show whether Maekawa theorem and Kawasaki theorem holds.
* @author Koji
*
*/
public class FoldedModelScreen extends JPanel
implements MouseListener, MouseMotionListener, MouseWheelListener {
private BufferedImage bufferImage;
private Graphics2D bufferg;
static private int pbuf[]; //32bit pixel buffer
static private int zbuf[]; //32bit z buffer
static private int BUFFERW; // width
static private int BUFFERH; // height
static private int min[];
static private int max[];
static private int minr[];
static private int maxr[];
static private int ming[];
static private int maxg[];
static private int minb[];
static private int maxb[];
static private double minu[];
static private double maxu[];
static private double minv[];
static private double maxv[];
private boolean m_bUseColor = true;
private boolean m_bFillFaces = true;
private boolean m_bAmbientOcclusion = false;
private static boolean m_bFaceOrderFlip = false;
static private double m_rotAngle = 0;
static private double m_scale = 0.8;
static private boolean m_bDrawEdges = true;
private Image renderImage;
double rotateAngle;
double scale;
double transX;
double transY;
private Point2D preMousePoint;
private AffineTransform affineTransform;
private BufferedImage textureImage = null;
private boolean bUseTexture = false;
public FoldedModelScreen() {
addMouseListener(this);
addMouseMotionListener(this);
addMouseWheelListener(this);
BUFFERW = 600;
BUFFERH = 600;
pbuf = new int[BUFFERW * BUFFERH];
zbuf = new int[BUFFERW * BUFFERH];
min = new int[BUFFERH];
max = new int[BUFFERH];
minr = new int[BUFFERH];
maxr = new int[BUFFERH];
ming = new int[BUFFERH];
maxg = new int[BUFFERH];
minb = new int[BUFFERH];
maxb = new int[BUFFERH];
maxu = new double[BUFFERH];
maxv = new double[BUFFERH];
minu = new double[BUFFERH];
minv = new double[BUFFERH];
clear();
drawOrigami();
rotateAngle = 0;
scale = 1.0;
affineTransform = new AffineTransform();
updateAffineTransform();
if (bUseTexture) {
try {
textureImage = ImageIO.read(new File("c:\\chiyo2-1024.bmp"));
} catch (Exception e) {
e.printStackTrace();
textureImage = null;
}
}
}
public void resetViewMatrix() {
rotateAngle = 0;
scale = 1;
updateAffineTransform();
redrawOrigami();
}
public void redrawOrigami() {
clear();
drawOrigami();
repaint();
}
public void setUseColor(boolean b) {
m_bUseColor = b;
redrawOrigami();
}
public void setFillFace(boolean bFillFace) {
m_bFillFaces = bFillFace;
redrawOrigami();
}
public void drawEdge(boolean bEdge) {
m_bDrawEdges = bEdge;
redrawOrigami();
}
public void flipFaces(boolean bFlip) {
setM_bFaceOrderFlip(bFlip);
redrawOrigami();
}
public void shadeFaces(boolean bShade) {
m_bAmbientOcclusion = bShade;
redrawOrigami();
}
private static int getIndex(int x, int y) {
return y * BUFFERW + x;
}
public static void clear() {
for (int i = 0; i < BUFFERW * BUFFERH; i++) {
pbuf[i] = 0xffffffff;
zbuf[i] = -1;
}
}
private void updateAffineTransform() {
affineTransform.setToIdentity();
affineTransform.translate(getWidth() * 0.5, getHeight() * 0.5);
affineTransform.scale(scale, -scale);
affineTransform.translate(transX, -transY);
affineTransform.rotate(rotateAngle);
affineTransform.translate(-getWidth() * 0.5, -getHeight() * 0.5);
}
/**
* Convenience method that returns a scaled instance of the provided {@code BufferedImage}.
*
* @param img the original image to be scaled
* @param targetWidth the desired width of the scaled instance, in pixels
* @param targetHeight the desired height of the scaled instance, in pixels
* @param hint one of the rendering hints that corresponds to
* {@code RenderingHints.KEY_INTERPOLATION} (e.g.
* {@code RenderingHints.VALUE_INTERPOLATION_NEAREST_NEIGHBOR},
* {@code RenderingHints.VALUE_INTERPOLATION_BILINEAR},
* {@code RenderingHints.VALUE_INTERPOLATION_BICUBIC})
* @param higherQuality if true, this method will use a multi-step scaling
* technique that provides higher quality than the usual one-step technique
* (only useful in downscaling cases, where
* {@code targetWidth} or {@code targetHeight} is smaller than the original
* dimensions, and generally only when the {@code BILINEAR} hint is
* specified)
* @return a scaled version of the original {@code BufferedImage}
*/
public BufferedImage getScaledInstance(BufferedImage img,
int targetWidth,
int targetHeight,
Object hint,
boolean higherQuality) {
int type = (img.getTransparency() == Transparency.OPAQUE)
? BufferedImage.TYPE_INT_RGB : BufferedImage.TYPE_INT_ARGB;
BufferedImage ret = (BufferedImage) img;
int w, h;
if (higherQuality) {
// Use multi-step technique: start with original size, then
// scale down in multiple passes with drawImage()
// until the target size is reached
w = img.getWidth();
h = img.getHeight();
if (w < targetWidth) {
w = targetWidth;
}
if (h < targetHeight) {
h = targetHeight;
}
} else {
// Use one-step technique: scale directly from original
// size to target size with a single drawImage() call
w = targetWidth;
h = targetHeight;
}
do {
if (higherQuality && w > targetWidth) {
w /= 2;
if (w < targetWidth) {
w = targetWidth;
}
}
if (higherQuality && h > targetHeight) {
h /= 2;
if (h < targetHeight) {
h = targetHeight;
}
}
BufferedImage tmp = new BufferedImage(w, h, type);
Graphics2D g2 = tmp.createGraphics();
g2.setRenderingHint(RenderingHints.KEY_INTERPOLATION, hint);
g2.drawImage(ret, 0, 0, w, h, null);
g2.dispose();
ret = tmp;
} while (w != targetWidth || h != targetHeight);
return ret;
}
@Override
public void paintComponent(Graphics g) {
super.paintComponent(g);
if (bufferImage == null) {
bufferImage = new BufferedImage(getWidth(), getHeight(), BufferedImage.TYPE_INT_RGB);
bufferg = (Graphics2D) bufferImage.getGraphics();
updateAffineTransform();
}
bufferg.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
RenderingHints.VALUE_ANTIALIAS_ON);
bufferg.setTransform(new AffineTransform());
// Clear image
bufferg.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_OVER, 1.0f));
bufferg.setColor(Color.WHITE);
bufferg.fillRect(0, 0, getWidth(), getHeight());
bufferg.setTransform(affineTransform);
Graphics2D g2d = bufferg;
if (renderImage != null) {
g2d.drawImage(renderImage, 0, 0, null);
}
g.drawImage(bufferImage, 0, 0, this);
}
public void drawOrigami() {
Doc document = ORIPA.doc;
OrigamiModel origamiModel = document.getOrigamiModel();
FoldedModelInfo foldedModelInfo = document.getFoldedModelInfo();
List<OriFace> faces = origamiModel.getFaces();
boolean folded = origamiModel.isFolded();
if (!folded) {
return;
}
long time0 = System.currentTimeMillis();
BoundBox boundBox = foldedModelInfo.getBoundBox();
Vector2d leftAndTop = boundBox.getLeftAndTop();
Vector2d rightAndBottom = boundBox.getRightAndBottom();
Vector2d center = new Vector2d((leftAndTop.x + rightAndBottom.x) / 2,
(leftAndTop.y + rightAndBottom.y) / 2);
double localScale = Math.min(
BUFFERW / (rightAndBottom.x - leftAndTop.x),
BUFFERH / (rightAndBottom.y - leftAndTop.y)) * 0.95;
double angle = m_rotAngle * Math.PI / 180;
localScale *= m_scale;
for (OriFace face : faces) {
face.trianglateAndSetColor(m_bUseColor, isM_bFaceOrderFlip());
for (TriangleFace tri : face.triangles) {
for (int i = 0; i < 3; i++) {
double x = (tri.v[i].p.x - center.x) * localScale;
double y = (tri.v[i].p.y - center.y) * localScale;
tri.v[i].p.x = x * Math.cos(angle) + y * Math.sin(angle) + BUFFERW * 0.5;
tri.v[i].p.y = x * Math.sin(angle) - y * Math.cos(angle) + BUFFERW * 0.5;
}
drawTriangle(tri, face.tmpInt, face.intColor);
}
}
if (m_bDrawEdges) {
for (int y = 1; y < BUFFERH - 1; y++) {
for (int x = 1; x < BUFFERW - 1; x++) {
int val_h = -1 * zbuf[getIndex(x - 1, y - 1)]
+ zbuf[getIndex(x + 1, y - 1)]
+ -2 * zbuf[getIndex(x - 1, y)]
+ 2 * zbuf[getIndex(x + 1, y)]
+ -1 * zbuf[getIndex(x - 1, y + 1)]
+ zbuf[getIndex(x + 1, y + 1)];
int val_v = -1 * zbuf[getIndex(x - 1, y - 1)]
+ zbuf[getIndex(x - 1, y + 1)]
+ -2 * zbuf[getIndex(x, y - 1)]
+ 2 * zbuf[getIndex(x, y + 1)]
+ -1 * zbuf[getIndex(x + 1, y - 1)]
+ zbuf[getIndex(x + 1, y + 1)];
if (val_h != 0 || val_v != 0) {
pbuf[getIndex(x, y)] = 0xff888888;
}
}
}
}
if (m_bAmbientOcclusion) {
int renderFace = isM_bFaceOrderFlip() ? oripa.doc.Doc.UPPER : oripa.doc.Doc.LOWER;
int r = 10;
int s = (int) (r * r * Math.PI);
// For every pixel
for (int y = 1; y < BUFFERH - 1; y++) {
for (int x = 1; x < BUFFERW - 1; x++) {
int f_id = zbuf[getIndex(x, y)];
// Within a circle of radius r, Count the pixels of the surface
//that is above their own
int cnt = 0;
for (int dy = -r; dy <= r; dy++) {
for (int dx = -r; dx <= r; dx++) {
if (dx * dx + dy * dy > r * r) {
continue;
}
if (y + dy < 0 || y + dy > BUFFERH - 1) {
continue;
}
if (x + dx < 0 || x + dx > BUFFERW - 1) {
continue;
}
int f_id2 = zbuf[getIndex(x + dx, y + dy)];
if (f_id == -1 && f_id2 != -1) {
cnt++;
} else {
int[][] overlapRelation = foldedModelInfo.getOverlapRelation();
if (f_id2 != -1 && overlapRelation[f_id][f_id2] == renderFace) {
cnt++;
}
}
}
}
if (cnt > 0) {
int prev = pbuf[getIndex(x, y)];
double ratio = 1.0 - ((double) cnt) / s;
int p_r = (int) Math.max(0, ((prev & 0x00ff0000) >> 16) * ratio);
int p_g = (int) Math.max(0, ((prev & 0x0000ff00) >> 8) * ratio);
int p_b = (int) Math.max(0, (prev & 0x000000ff) * ratio);
pbuf[getIndex(x, y)] = (p_r << 16) | (p_g << 8) | p_b | 0xff000000;
}
}
}
}
long time1 = System.currentTimeMillis();
System.out.println("render time = " + (time1 - time0) + "ms");
renderImage = createImage(new MemoryImageSource(BUFFERW, BUFFERH, pbuf, 0, BUFFERW));
}
//--------------------------------------------------------------------
//Polygon drawing
//
//--------------------------------------------------------------------
private void drawTriangle(TriangleFace tri, int id, int color) {
Doc document = ORIPA.doc;
FoldedModelInfo foldedModelInfo = document.getFoldedModelInfo();
//(For speed) set the range of use of the buffer
int top = +2147483647;
int btm = -2147483648;
if (top > (int) tri.v[0].p.y) {
top = (int) tri.v[0].p.y;
}
if (top > (int) tri.v[1].p.y) {
top = (int) tri.v[1].p.y;
}
if (top > (int) tri.v[2].p.y) {
top = (int) tri.v[2].p.y;
}
if (btm < (int) tri.v[0].p.y) {
btm = (int) tri.v[0].p.y;
}
if (btm < (int) tri.v[1].p.y) {
btm = (int) tri.v[1].p.y;
}
if (btm < (int) tri.v[2].p.y) {
btm = (int) tri.v[2].p.y;
}
if (top < 0) {
top = 0;
}
if (btm > BUFFERH) {
btm = BUFFERH;
}
//Maximum and minimum buffer initialization
for (int i = top; i < btm; i++) {
min[i] = +2147483647;
max[i] = -2147483648;
}
ScanEdge(tri.v[0], tri.v[1]);
ScanEdge(tri.v[1], tri.v[2]);
ScanEdge(tri.v[2], tri.v[0]);
//To be drawn on the basis of the maximum and minimum buffer.
for (int y = top; y < btm; y++) {
//Skip if the buffer is not updated
if (min[y] == +2147483647) {
continue;
}
int offset = y * BUFFERW;
//Increment calculation
int l = (max[y] - min[y]) + 1;
int addr = (maxr[y] - minr[y]) / l;
int addg = (maxg[y] - ming[y]) / l;
int addb = (maxb[y] - minb[y]) / l;
double addu = (maxu[y] - minu[y]) / l;
double addv = (maxv[y] - minv[y]) / l;
int r = minr[y];
int g = ming[y];
int b = minb[y];
double u = minu[y];
double v = minv[y];
for (int x = min[y]; x <= max[y]; x++, r += addr, g += addg, b += addb, u += addu, v += addv) {
if (x < 0 || x >= BUFFERW) {
continue;
}
int p = offset + x;
int renderFace = isM_bFaceOrderFlip() ? oripa.doc.Doc.UPPER : oripa.doc.Doc.LOWER;
int[][] overlapRelation = foldedModelInfo.getOverlapRelation();
if (zbuf[p] == -1 || overlapRelation[zbuf[p]][id] == renderFace) {
int tr = r >> 16;
int tg = g >> 16;
int tb = b >> 16;
if (!m_bFillFaces) {
pbuf[p] = 0xffffffff;
} else {
if (bUseTexture) {
int tx = (int) (textureImage.getWidth() * u);
int ty = (int) (textureImage.getHeight() * v);
tx = tx % textureImage.getWidth();
ty = ty % textureImage.getHeight();
int textureColor = textureImage.getRGB(tx, ty);
if (m_bFillFaces && (tri.face.faceFront ^ isM_bFaceOrderFlip())) {
pbuf[p] = textureColor;
} else {
pbuf[p] = (tr << 16) | (tg << 8) | tb | 0xff000000;
}
} else {
if (m_bFillFaces && (tri.face.faceFront ^ isM_bFaceOrderFlip())) {
pbuf[p] = (tr << 16) | (tg << 8) | tb | 0xff000000;
} else {
pbuf[p] = (tr << 16) | (tg << 8) | tb | 0xff000000;
}
}
}
zbuf[p] = id;
}
}
}
}
//--------------------------------------------------------------------
//ScanEdge
//
//Vector v1 ...Starting point
//Vector v2 ...Starting point
//--------------------------------------------------------------------
private void ScanEdge(TriangleVertex v1, TriangleVertex v2) {
int l = Math.abs((int) (v2.p.y - v1.p.y)) + 1;
//Increment calculation
int addx = (int) ((v2.p.x - v1.p.x) * 0xffff) / l;
int addy = (int) ((v2.p.y - v1.p.y) * 0xffff) / l;
int addr = (int) (255 * 0xffff * (v2.color.x - v1.color.x) / l);
int addg = (int) (255 * 0xffff * (v2.color.y - v1.color.y) / l);
int addb = (int) (255 * 0xffff * (v2.color.z - v1.color.z) / l);
double addu = (v2.uv.x - v1.uv.x) / l;
double addv = (v2.uv.y - v1.uv.y) / l;
//Initial value setting
int x = (int) (v1.p.x * 0xffff);
int y = (int) (v1.p.y * 0xffff);
int r = (int) (255 * 0xffff * v1.color.x);
int g = (int) (255 * 0xffff * v1.color.y);
int b = (int) (255 * 0xffff * v1.color.z);
double u = v1.uv.x;
double v = v1.uv.y;
//Scan
for (int i = 0; i < l; i++, x += addx, y += addy, r += addr, g += addg,
b += addb, u += addu, v += addv) {
int py = y >> 16;
int px = x >> 16;
if (py < 0 || py >= BUFFERH) {
continue;
}
if (min[py] > px) {
min[py] = px;
minr[py] = r;
ming[py] = g;
minb[py] = b;
minu[py] = u;
minv[py] = v;
}
if (max[py] < px) {
max[py] = px;
maxr[py] = r;
maxg[py] = g;
maxb[py] = b;
maxu[py] = u;
maxv[py] = v;
}
}
}
public void setScale(double newScale){
scale = newScale;
}
@Override
public void mouseClicked(MouseEvent e) {
// TODO Auto-generated method stub
}
@Override
public void mouseEntered(MouseEvent e) {
// TODO Auto-generated method stub
}
@Override
public void mouseExited(MouseEvent e) {
// TODO Auto-generated method stub
}
@Override
public void mousePressed(MouseEvent e) {
preMousePoint = e.getPoint();
}
@Override
public void mouseReleased(MouseEvent e) {
// TODO Auto-generated method stub
}
@Override
public void mouseDragged(MouseEvent e) {
if (javax.swing.SwingUtilities.isLeftMouseButton(e)) {
rotateAngle -= ((double) e.getX() - preMousePoint.getX()) / 100.0;
preMousePoint = e.getPoint();
updateAffineTransform();
repaint();
}else if (javax.swing.SwingUtilities.isRightMouseButton(e)) {
transX += (double) (e.getX() - preMousePoint.getX()) / scale;
transY += (double) (e.getY() - preMousePoint.getY()) / scale;
preMousePoint = e.getPoint();
updateAffineTransform();
repaint();
}
}
@Override
public void mouseMoved(MouseEvent e) {
// TODO Auto-generated method stub
}
@Override
public void mouseWheelMoved(MouseWheelEvent e) {
double scale_ = (100.0 - e.getWheelRotation() * 5) / 100.0;
scale *= scale_;
updateAffineTransform();
repaint();
}
public AffineTransform getAffineTransform() {
return affineTransform;
}
public BufferedImage getBufferImage() {
return bufferImage;
}
public static boolean isM_bFaceOrderFlip() {
return m_bFaceOrderFlip;
}
public static void setM_bFaceOrderFlip(boolean m_bFaceOrderFlip) {
FoldedModelScreen.m_bFaceOrderFlip = m_bFaceOrderFlip;
}
}