Commits

André Schnabel committed 856afcb

initial commit

Comments (0)

Files changed (6)

src/pvmt/Constants.java

+package pvmt;
+
+public class Constants {
+
+	public static final String VERSION = "0.01";
+
+	public final static int DEF_SCR_W = 640, DEF_SCR_H = 480;
+
+	public final static String TITLE = "Primitive Vertex Modeling Tool";
+
+	public final static String START_TXT = "Left click: Create new vertex "
+			+ "(only if no vertex selected)\n" + "Right click: select vertex\n"
+			+ "Dragging: move selected vertices (only if vertex selected)\n"
+			+ "Middle click: deselect vertex\n";
+
+	public final static String ABOUT_TXT = Constants.TITLE + "\n" + "Version: "
+			+ VERSION + "\n" + "2011 by André '0x17' Schnabel";
+
+	public static final String CONFIRM_TXT = "Do you really want to quit PVMT?\n"
+			+ "All unsaved changes will be lost!";
+
+	public static final String TRIANGLE_ERR_TXT = "You need to select exactly"
+			+ " three vertices to create a new triangle!";
+	public static final String DELTRI_ERR_TXT = "You must select exactly three"
+			+ " vertices to delete a triangle!";
+
+	public static final int GEN_FACTOR = 50;
+
+	public static final String X3D_HEADER = "<?xml version=\"1.0\" encoding"
+			+ "=\"UTF-8\"?>\n"
+			+ "<X3D profile='Immersive'>\n<Scene>\n<Shape>\n<Appearance>\n"
+			+ "<Material diffuseColor=\"0.5 0.5 0.5\" />\n</Appearance>\n"
+			+ "<TriangleSet solid='false'>\n<Coordinate point='";
+	public static final String X3D_FOOTER = "' />\n</TriangleSet>\n</Shape>\n"
+			+ "</Scene>\n</X3D>\n";
+
+}

src/pvmt/EditorPanel.java

+package pvmt;
+
+import java.awt.Color;
+import java.awt.Graphics;
+import java.awt.Point;
+import java.awt.event.MouseAdapter;
+import java.awt.event.MouseEvent;
+import java.util.List;
+
+import javax.swing.JFrame;
+import javax.swing.JPanel;
+
+public class EditorPanel extends JPanel {
+	private static final long serialVersionUID = 1L;
+	
+	private int axis;
+	private String axisStr;
+
+	private Color gridCol, bgCol, borderCol, vertexCol, coordSysCol, triangleCol;
+
+	private List<Vertex> vertices = null;
+	private List<Triangle> triangles = null;
+
+	private String xstr, ystr;
+
+	// offset of the origin in pixels
+	private Point originOffset;
+
+	private JFrame parent = null;
+
+	/**
+	 * Parameterless constructor
+	 */
+	public EditorPanel(List<Vertex> vertices, List<Triangle> triangles, JFrame parent) {
+		this(0, vertices, triangles, parent);
+	}
+
+	/**
+	 * Constructor
+	 * 
+	 * @param axis
+	 *            0=x, 1=y, 2=z
+	 */
+	public EditorPanel(int axis, List<Vertex> vertices, List<Triangle> triangles, JFrame parent) {
+		this.axis = axis;
+		this.vertices = vertices;
+		this.triangles = triangles;
+		this.parent = parent;
+
+		if (this.axis >= 3 || this.axis < 0)
+			this.axis = axis = 0;
+
+		bgCol = Color.LIGHT_GRAY;
+		gridCol = new Color(150, 150, 150);
+		borderCol = Color.BLACK;
+		vertexCol = Color.YELLOW;
+		coordSysCol = new Color(0, 100, 0);
+		triangleCol = new Color(100, 100, 0);
+
+		switch (axis) {
+		case 0:
+			axisStr = "side (along X)";
+			xstr = "z";
+			ystr = "y";
+			break;
+		case 1:
+			axisStr = "bottom (along Y)";
+			xstr = "x";
+			ystr = "z";
+			break;
+		case 2:
+		default:
+			axisStr = "front (opposed Z)";
+			xstr = "x";
+			ystr = "y";
+			break;
+		}
+
+		addMouseListener(new EditorMouseListener());
+		addMouseMotionListener(new EditorMouseListener());
+	}
+
+	private int oldX, oldY;
+
+	private class EditorMouseListener extends MouseAdapter {
+		
+		/**
+		 * Generate new vertex from mouse position.
+		 * @param e contains mouse position
+		 * @return the new generated vertex
+		 */
+		private Vertex vertexFromMousePos(MouseEvent e) {
+			int itsX, itsY, itsZ;
+			itsX = itsY = itsZ = 0;
+
+			switch (axis) {
+			case 0:
+				itsX = 0;
+				itsY = originOffset.y - e.getY();
+				itsZ = e.getX() - originOffset.x;
+				break;
+			case 1:
+				itsX = e.getX() - originOffset.x;
+				itsY = 0;
+				itsZ = originOffset.y - e.getY();
+				break;
+			case 2:
+				itsX = e.getX() - originOffset.x;
+				itsY = originOffset.y - e.getY();
+				itsZ = 0;
+				break;
+			}
+
+			return new Vertex(itsX, itsY, itsZ);
+		}
+
+		/**
+		 * @return is selection empty?
+		 */
+		private boolean emptySel() {
+			boolean emptySel = true;
+			for (Vertex v : vertices)
+				if (v.isSelected())
+					emptySel = false;
+			return emptySel;
+		}
+
+		/**
+		 * Move selected vertices if mouse is dragged
+		 */
+		@Override
+		public void mouseDragged(MouseEvent e) {
+			if (!emptySel()) {
+				int dX, dY;
+				dX = e.getX() - oldX;
+				dY = e.getY() - oldY;
+
+				for (Vertex v : vertices) {
+					if (v.isSelected()) {
+						switch (axis) {
+						case 0:
+							v.moveZ(dX);
+							v.moveY(-dY);
+							break;
+						case 1:
+							v.moveX(dX);
+							v.moveZ(-dY);
+							break;
+						case 2:
+							v.moveX(dX);
+							v.moveY(-dY);
+							break;
+						}
+					}
+				}
+			}
+			backupCoord(e);
+			parent.repaint();
+		}
+
+		/**
+		 * Backup mouse coordinates so we are able to calculate the
+		 * difference (delta).
+		 * @param e
+		 */
+		private void backupCoord(MouseEvent e) {
+			oldX = e.getX();
+			oldY = e.getY();
+		}
+
+		/**
+		 * A mouse button was pressed.
+		 */
+		@Override
+		public void mousePressed(MouseEvent e) {
+			int btn = e.getButton();
+
+			Vertex nv = vertexFromMousePos(e);
+
+			switch (btn) {
+			case MouseEvent.BUTTON1:
+				// add new vertex if none selected
+				if (emptySel())
+					vertices.add(nv);
+				break;
+			case MouseEvent.BUTTON2: // deselect clicked
+				for (Vertex v : vertices) {
+					Point ppos = calcProjPos(v);
+					if(ppos.distance(e.getPoint()) <= 5.0)
+						v.setSelected(false);
+				}
+				break;
+			case MouseEvent.BUTTON3: // select vertex
+				for (Vertex v : vertices) {
+					//if (v.distanceTo(nv) <= 5)
+					Point ppos = calcProjPos(v);
+					if(ppos.distance(e.getPoint()) <= 5.0)
+						v.setSelected(true);
+				}
+				break;
+			}
+
+			backupCoord(e);
+			parent.repaint();
+		}
+	}
+
+	/**
+	 * paint grid and stuff
+	 */
+	@Override
+	public void paint(Graphics g) {
+		originOffset = new Point((int) ((double) getWidth() / 2.0),
+				(int) ((double) getHeight() / 2.0));
+
+		// fill background
+		g.setColor(bgCol);
+		g.fillRect(0, 0, getWidth(), getHeight());
+
+		// draw grid
+
+		g.setColor(gridCol);
+		for (int i = 0; i < getHeight(); i += 20)
+			// horizontal lines
+			g.drawLine(0, i + 7, getWidth(), i + 7);
+		for (int i = 0; i < getWidth(); i += 20)
+			// vertical lines
+			g.drawLine(i - 2, 0, i - 2, getHeight());
+
+		// draw coordinate system
+		g.setColor(coordSysCol);
+
+		g.drawLine(0, originOffset.y, getWidth(), originOffset.y);
+		g.drawLine(getWidth(), originOffset.y, getWidth() - 8,
+				originOffset.y - 4);
+		g.drawLine(getWidth(), originOffset.y, getWidth() - 8,
+				originOffset.y + 4);
+
+		g.drawLine(originOffset.x, 0, originOffset.x, getHeight());
+		g.drawLine(originOffset.x, 0, originOffset.x - 4, 8);
+		g.drawLine(originOffset.x, 0, originOffset.x + 4, 8);
+
+		g.drawString(xstr, getWidth() - 10, getHeight() / 2 + 15);
+		g.drawString(ystr, getWidth() / 2 - 15, 10);
+		
+		// draw triangles
+		g.setColor(triangleCol);
+		for(Triangle t : triangles) {
+			Point[] pts = new Point[3];
+			pts[0] = calcProjPos(t.getA());
+			pts[1] = calcProjPos(t.getB());
+			pts[2] = calcProjPos(t.getC());
+			
+			int xs[], ys[];
+			xs = new int[3];
+			ys = new int[3];
+			for(int i=0; i<3; i++) {
+				xs[i] = pts[i].x;
+				ys[i] = pts[i].y;
+			}
+			
+			//g.fillPolygon(xs, ys, 3);
+			g.drawPolygon(xs, ys, 3);
+		}
+
+		// draw vertices
+		g.setColor(vertexCol);
+		
+		for (Vertex v : vertices) {
+			Point ppos = calcProjPos(v);
+
+			g.fillOval(ppos.x - 2, ppos.y - 2, 4, 4);
+
+			if (v.isSelected()) {
+				g.setColor(Color.RED);
+				g.drawOval(ppos.x - 2, ppos.y - 2, 4, 4);
+				g.setColor(vertexCol);
+			}
+		}
+
+		// draw title
+		g.setColor(Color.RED);
+		g.drawString(axisStr, 5, 10);
+
+		// draw border on top
+		g.setColor(borderCol);
+		g.drawRect(0, 0, getWidth(), getHeight());
+	}
+	
+	private Point calcProjPos(Vertex v) {
+		int projX = 0, projY = 0;
+		switch (axis) {
+		case 0:
+			projX = (int) v.getZ() + originOffset.x;
+			projY = originOffset.y - (int) v.getY();
+			break;
+		case 1:
+			projX = (int) v.getX() + originOffset.x;
+			projY = originOffset.y - (int) v.getZ();
+			break;
+		case 2:
+			projX = (int) v.getX() + originOffset.x;
+			projY = originOffset.y - (int) v.getY();
+			break;
+		}
+		return new Point(projX, projY);
+	}
+
+}

src/pvmt/InspectorPanel.java

+package pvmt;
+
+import java.awt.BorderLayout;
+import java.awt.Color;
+import java.awt.Font;
+import java.awt.Graphics;
+import java.awt.GridLayout;
+import java.awt.event.ActionEvent;
+import java.awt.event.ActionListener;
+
+import javax.swing.JButton;
+import javax.swing.JLabel;
+import javax.swing.JPanel;
+
+public class InspectorPanel extends JPanel {
+	private static final long serialVersionUID = 1L;
+
+	private class InspectorActionsListener implements ActionListener {
+		@Override
+		public void actionPerformed(ActionEvent e) {
+			JButton b = (JButton)e.getSource();
+			String s = b.getText();
+			if(s == "Delete selected")
+				MainWindow.getInstance().deleteSelected();
+			else if(s == "Select all")
+				MainWindow.getInstance().selectAll();
+			else if(s == "Deselect all")
+				MainWindow.getInstance().deselectAll();
+			else if(s == "Clear")
+				MainWindow.getInstance().clear();
+			else if(s == "Quit")
+				MainWindow.getInstance().quit();
+			else if(s == "Delete all triangles")
+				MainWindow.getInstance().delAllTriangles();
+			else if(s == "Delete sel. triangle")
+				MainWindow.getInstance().delSelTriangle();
+			
+			MainWindow.getInstance().repaint();
+		}
+	}
+
+	private JButton triBtn;
+	private JPanel centerGrid;
+
+	private JLabel numVertLbl, numTriLbl;
+
+	public InspectorPanel() {
+		super(new BorderLayout());
+
+		centerGrid = new JPanel(new GridLayout(5, 2));
+		centerGrid.setBackground(Color.LIGHT_GRAY);
+
+		numVertLbl = new JLabel("0");
+		numTriLbl = new JLabel("0");
+
+		centerGrid.add(new JLabel(" Number of vertices: "));
+		centerGrid.add(numVertLbl);
+
+		centerGrid.add(new JLabel(" Number of triangles: "));
+		centerGrid.add(numTriLbl);
+		
+		String[] actNames = { "Delete selected", "Select all", "Deselect all", "Clear", "Delete all triangles", "Delete sel. triangle" };
+		for(String a : actNames) {
+			JButton btn = new JButton(a);
+			btn.addActionListener(new InspectorActionsListener());
+			centerGrid.add(btn);
+		}			
+
+		JLabel titleLbl = new JLabel(" Inspector");
+		titleLbl.setFont(new Font(Font.SANS_SERIF, Font.PLAIN, 14));
+
+		triBtn = new JButton("Create Triangle");
+		triBtn.addActionListener(new ActionListener() {
+			@Override
+			public void actionPerformed(ActionEvent e) {
+				if (MainWindow.getInstance().getSelectedVertices().size() == 3) {
+					MainWindow.getInstance().createTriangle();
+				} else
+					MainWindow.getInstance().showError(Constants.TRIANGLE_ERR_TXT);
+			}
+		});
+
+		add(titleLbl, BorderLayout.NORTH);
+		add(centerGrid, BorderLayout.CENTER);
+		add(triBtn, BorderLayout.SOUTH);
+	}
+
+	private void updateValues() {
+		numVertLbl.setText(String.valueOf(MainWindow.getInstance()
+				.getVertices().size()));
+		numTriLbl.setText(String.valueOf(MainWindow.getInstance()
+				.getTriangles().size()));
+	}
+
+	@Override
+	public void paint(Graphics g) {
+		updateValues();
+
+		setBackground(Color.LIGHT_GRAY);
+
+		super.paint(g);
+
+		// draw border on top
+		g.setColor(Color.BLACK);
+		g.drawRect(0, 0, getWidth() - 1, getHeight());
+	}
+
+}

src/pvmt/MainWindow.java

+package pvmt;
+
+import javax.swing.JFileChooser;
+import javax.swing.JFrame;
+import javax.swing.JMenu;
+import javax.swing.JMenuBar;
+import javax.swing.JMenuItem;
+import javax.swing.JOptionPane;
+import javax.swing.UIManager;
+import javax.swing.filechooser.FileNameExtensionFilter;
+
+import java.awt.Container;
+import java.awt.GridLayout;
+import java.awt.event.ActionEvent;
+import java.awt.event.ActionListener;
+import java.awt.event.WindowAdapter;
+import java.awt.event.WindowEvent;
+import java.io.BufferedReader;
+import java.io.File;
+import java.io.FileReader;
+import java.io.FileWriter;
+import java.io.IOException;
+
+import java.util.LinkedList;
+import java.util.List;
+import java.util.SortedMap;
+import java.util.TreeMap;
+import java.util.Map;
+import java.util.HashMap;
+
+public class MainWindow extends JFrame {
+	private static final long serialVersionUID = 1L;
+
+	private SortedMap<String, JMenuItem> menuItems;
+	private SortedMap<String, JMenu> menus;
+
+	private EditorPanel[] panels;
+
+	private static MainWindow instance = null;
+
+	private List<Vertex> vertices;
+	private List<Triangle> triangles;
+
+	/**
+	 * Entry-point Instantiates one main window and parses args.
+	 * 
+	 * @param args
+	 *            give filename to open on start.
+	 */
+	public static void main(String[] args) {
+		MainWindow.getInstance().setVisible(true);
+		for(String a : args) {
+			if(a.endsWith(".pvmt"))
+				MainWindow.getInstance().loadFrom(a);
+		}		
+	}
+
+	/**
+	 * Singleton-Pattern
+	 * 
+	 * @return new or existing instance
+	 */
+	public static MainWindow getInstance() {
+		if (instance == null)
+			instance = new MainWindow();
+
+		return instance;
+	}
+
+	/**
+	 * Constructor
+	 */
+	private MainWindow() {
+		// initialize window
+		super(Constants.TITLE);
+		setDefaultCloseOperation(JFrame.DO_NOTHING_ON_CLOSE);
+		addWindowListener(new WindowAdapter() {
+			public void windowClosing(WindowEvent e) {
+				quit();
+			}
+		});
+		setSize(Constants.DEF_SCR_W, Constants.DEF_SCR_H);
+		setResizable(false);
+		setLocationRelativeTo(null);
+
+		// set native look and feel
+		try {
+			UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
+		} catch (Exception e) {
+			showError("Failed to set look and feel!");
+			e.printStackTrace();
+		}
+		
+		// apple menubar fix
+		if(System.getProperty("os.name").equals("Mac OS X"))
+			System.setProperty("apple.laf.useScreenMenuBar", "true");
+
+		// initialize vertex and triangle list
+		vertices = new LinkedList<Vertex>();
+		triangles = new LinkedList<Triangle>();
+
+		// initialize menu bar
+		JMenuBar mb = new JMenuBar();
+
+		menus = new TreeMap<String, JMenu>();
+		menuItems = new TreeMap<String, JMenuItem>();
+
+		// manually define menus, items and item actions
+
+		// ======================================================
+		// FILE MENU
+		// ======================================================
+		addMenu("File");
+		addMenuItem("f", "O:Open", new ActionListener() {
+			@Override
+			public void actionPerformed(ActionEvent e) {
+				JFileChooser fc = new JFileChooser();
+				fc.setFileFilter(new FileNameExtensionFilter("PVMT models",
+						"pvmt"));
+				int ret = fc.showOpenDialog(MainWindow.getInstance());
+				if (ret == JFileChooser.APPROVE_OPTION) {
+					loadFrom(fc.getSelectedFile().getAbsolutePath());
+				}
+			}
+		});
+		addMenuItem("f", "S:Save", new ActionListener() {
+			@Override
+			public void actionPerformed(ActionEvent e) {
+				JFileChooser fc = new JFileChooser();
+				fc.setFileFilter(new FileNameExtensionFilter("PVMT models",
+						"pvmt"));
+				fc.setSelectedFile(new File("unnamed.pvmt"));
+				int ret = fc.showSaveDialog(MainWindow.getInstance());
+				if (ret == JFileChooser.APPROVE_OPTION) {
+					saveTo(fc.getSelectedFile().getAbsolutePath());
+				}
+			}
+		});
+		addMenuItem("f", "X:Export", new ActionListener() {
+			@Override
+			public void actionPerformed(ActionEvent e) {
+				JFileChooser fc = new JFileChooser();
+				fc.setFileFilter(new FileNameExtensionFilter("X3D models",
+						"x3d"));
+				fc.setSelectedFile(new File("unnamed.x3d"));
+				int ret = fc.showSaveDialog(MainWindow.getInstance());
+				if (ret == JFileChooser.APPROVE_OPTION) {
+					exportX3DTo(fc.getSelectedFile().getAbsolutePath());
+				}
+			}
+		});
+		addMenuItem("f", "Z:Quit", new ActionListener() {
+			@Override
+			public void actionPerformed(ActionEvent e) {
+				quit();
+			}
+		});
+
+		// ======================================================
+		// GENERATE MENU
+		// ======================================================
+		addMenu("Generate");
+		addMenuItem("g", "C:Cube", new ActionListener() {			
+			@Override
+			public void actionPerformed(ActionEvent e) {
+				generateCube();
+			}
+		});
+		addMenuItem("g", "Q:Quad", new ActionListener() {
+			@Override
+			public void actionPerformed(ActionEvent e) {
+				generateQuad();
+			}
+		});
+		addMenuItem("g", "T:Triangle", new ActionListener() {
+			@Override
+			public void actionPerformed(ActionEvent e) {
+				generateTriangle();
+			}
+		});
+
+		// ======================================================
+		// HELP MENU
+		// ======================================================
+		addMenu("Help");
+		addMenuItem("h", "A:About", new ActionListener() {
+			@Override
+			public void actionPerformed(ActionEvent e) {
+				JOptionPane.showMessageDialog(MainWindow.getInstance(),
+						Constants.ABOUT_TXT, "About PVMT",
+						JOptionPane.INFORMATION_MESSAGE);
+			}
+		});
+
+		addMenuItem("h", "G:Getting started", new ActionListener() {
+			@Override
+			public void actionPerformed(ActionEvent e) {
+				JOptionPane.showMessageDialog(MainWindow.getInstance(),
+						Constants.START_TXT);
+			}
+		});
+
+		// add menus
+		for (String menuName : menus.keySet()) {
+			JMenu m = menus.get(menuName);
+
+			// add menu items belonging to this menu (correct prefix)
+			for (String itemName : menuItems.keySet()) {
+				if (itemName.startsWith(menuName)) {
+					JMenuItem mi = menuItems.get(itemName);
+					m.add(mi);
+				}
+			}
+
+			mb.add(m);
+		}
+
+		setJMenuBar(mb);
+
+		// initialize layout (panels)
+		setLayout(new GridLayout(2, 2));
+		Container cpane = getContentPane();
+
+		panels = new EditorPanel[3];
+		for (int i = 0; i < 3; i++) {
+			panels[i] = new EditorPanel(i, vertices, triangles, this);
+			cpane.add(panels[i]);
+		}
+
+		cpane.add(new InspectorPanel());
+	}
+
+	public void showError(String s) {
+		JOptionPane.showMessageDialog(this, s, "Error",
+				JOptionPane.ERROR_MESSAGE);
+	}
+	
+	//==========================================================================
+	// Generate methods
+	//==========================================================================
+	private void generateCube() {
+		int x = Constants.GEN_FACTOR;
+		
+		// Corners
+		Vertex[] c = new Vertex[8];
+		
+		// front quad corners
+		c[0] = new Vertex(-1.0*x, -1.0*x, 1.0*x);
+		c[1] = new Vertex(1.0*x, -1.0*x, 1.0*x);
+		c[2] = new Vertex(1.0*x, 1.0*x, 1.0*x);
+		c[3] = new Vertex(-1.0*x, 1.0*x, 1.0*x);
+		
+		// back quad corners
+		c[4] = new Vertex(-1.0*x, -1.0*x, -1.0*x);
+		c[5] = new Vertex(1.0*x, -1.0*x, -1.0*x);
+		c[6] = new Vertex(1.0*x, 1.0*x, -1.0*x);
+		c[7] = new Vertex(-1.0*x, 1.0*x, -1.0*x);
+		
+		addQuad(c[0], c[1], c[2], c[3]); // front side
+		addQuad(c[4], c[5], c[6], c[7]); // back side
+		addQuad(c[4], c[0], c[3], c[7]); // left side
+		addQuad(c[1], c[5], c[6], c[2]); // right side
+		addQuad(c[3], c[2], c[6], c[7]); // top side
+		addQuad(c[4], c[5], c[1], c[0]); // bottom side
+	}
+	
+	private void addQuad(Vertex p1, Vertex p2, Vertex p3, Vertex p4) {
+		Vertex[] v = new Vertex[4];
+		v[0] = p1;
+		v[1] = p2;
+		v[2] = p3;
+		v[3] = p4;
+
+		for (int i = 0; i < 4; i++)
+			vertices.add(v[i]);
+
+		triangles.add(new Triangle(v[0], v[1], v[2]));
+		triangles.add(new Triangle(v[0], v[2], v[3]));
+
+		repaint();
+	}
+	
+	private void generateQuad() {
+		int x = Constants.GEN_FACTOR;
+		addQuad(new Vertex(-1.0*x, -1.0*x, 0.0),
+				new Vertex(1.0*x, -1.0*x, 0.0),
+				new Vertex(1.0*x, 1.0*x, 0.0),
+				new Vertex(-1.0*x, 1.0*x, 0.0));
+	}
+	
+	private void generateTriangle() {
+		Vertex[] v = new Vertex[3];
+		int x = Constants.GEN_FACTOR;
+		v[0] = new Vertex(-1.0 * x, -1.0 * x, 0.0);
+		v[1] = new Vertex(1.0 * x, -1.0 * x, 0.0);
+		v[2] = new Vertex(0.0, 1.0 * x, 0.0);
+
+		for (int i = 0; i < 3; i++)
+			vertices.add(v[i]);
+
+		triangles.add(new Triangle(v[0], v[1], v[2]));
+
+		repaint();
+	}
+	
+	
+	//==========================================================================
+	// File handling
+	//==========================================================================
+	public void loadFrom(String filename) {
+		try {
+			BufferedReader br = new BufferedReader(new FileReader(filename));
+			String line, parts[];
+
+			vertices.clear();
+			triangles.clear();
+
+			Vertex nv;
+			Triangle nt;
+
+			Map<Integer, Vertex> iv = new HashMap<Integer, Vertex>();
+
+			while (br.ready()) {
+				line = br.readLine();
+				parts = line.split(";");
+
+				// vertex
+				if (parts[0].equals("v")) {
+					nv = new Vertex();
+					nv.setX(Double.valueOf(parts[2]));
+					nv.setY(Double.valueOf(parts[3]));
+					nv.setZ(Double.valueOf(parts[4]));
+					vertices.add(nv);
+					iv.put(Integer.valueOf(parts[1]), nv);
+				}
+				// triangles
+				else if (parts[0].equals("t")) {
+					nt = new Triangle();
+					nt.setA(iv.get(Integer.valueOf(parts[1])));
+					nt.setB(iv.get(Integer.valueOf(parts[2])));
+					nt.setC(iv.get(Integer.valueOf(parts[3])));
+					triangles.add(nt);
+				}
+			}
+
+			br.close();
+
+			repaint();
+		} catch (IOException e) {
+			showError("Unable to load file: " + filename + "!");
+			e.printStackTrace();
+		}
+	}
+
+	public void saveTo(String filename) {
+		try {
+			FileWriter fw = new FileWriter(filename);
+			int i = 0;
+
+			Map<Vertex, Integer> vi = new HashMap<Vertex, Integer>();
+
+			for (Vertex v : vertices) {
+				fw.write("v;" + i + ";" + v.getX() + ";" + v.getY() + ";"
+						+ v.getZ() + "\n");
+				vi.put(v, i);
+				i++;
+			}
+
+			for (Triangle t : triangles) {
+				int[] ns = new int[3];
+				ns[0] = vi.get(t.getA());
+				ns[1] = vi.get(t.getB());
+				ns[2] = vi.get(t.getC());
+				fw.write("t;" + ns[0] + ";" + ns[1] + ";" + ns[2] + "\n");
+			}
+
+			fw.flush();
+			fw.close();
+		} catch (IOException e) {
+			showError("Unable to write file: " + filename + "!");
+			e.printStackTrace();
+		}
+	}	
+	
+	private String vertexToX3D(Vertex v) {
+		return v.getX() + " " + v.getY() + " " + v.getZ() + ", ";
+	}
+	
+	public void exportX3DTo(String filename) {
+		try {
+			String tLine, tlines;
+			FileWriter fw = new FileWriter(filename);
+			fw.write(Constants.X3D_HEADER);
+			
+			tlines = new String();
+			
+			for(Triangle t : triangles) {
+				Vertex[] vs = new Vertex[3];
+				vs[0] = t.getA();
+				vs[1] = t.getB();
+				vs[2] = t.getC();
+				tLine = new String();
+				for(int i=0; i<3; i++)
+					tLine += vertexToX3D(vs[i]);
+				tLine += "\n";
+				//fw.write(tLine);
+				tlines += tLine;
+			}
+			
+			// remove last comma and newline
+			tlines = tlines.substring(0, tlines.length()-3);
+			
+			fw.write(tlines);
+			
+			fw.write(Constants.X3D_FOOTER);
+			
+			fw.flush();
+			fw.close();			
+		} catch (IOException e) {
+			showError("Unable to write file: " + filename + "!");
+			e.printStackTrace();
+		}
+	}
+	
+	//==========================================================================
+	// Menu helpers
+	//==========================================================================
+	private void addMenu(String name) {
+		JMenu m = new JMenu(name);
+		m.setMnemonic(name.charAt(0));
+		menus.put(name.toLowerCase().substring(0, 1), m);
+	}
+
+	private void addMenuItem(String menu, String name, ActionListener al) {
+		JMenuItem mi = new JMenuItem(name);
+		mi.setMnemonic(name.charAt(0));
+		mi.addActionListener(al);
+		menuItems.put(menu + name.toLowerCase(), mi);
+	}
+
+	//==========================================================================
+	// Getters
+	//==========================================================================
+	public List<Vertex> getVertices() {
+		return vertices;
+	}
+
+	public List<Triangle> getTriangles() {
+		return triangles;
+	}
+
+	//==========================================================================
+	// Editing functionality / Modify triangles and vertices
+	//==========================================================================
+	public List<Vertex> getSelectedVertices() {
+		List<Vertex> result = new LinkedList<Vertex>();
+		for (Vertex v : vertices)
+			if (v.isSelected())
+				result.add(v);
+		return result;
+	}
+
+	/**
+	 * Create triangle out of selected vertices.
+	 */
+	public void createTriangle() {
+		List<Vertex> selv = getSelectedVertices();
+		if (selv.size() == 3) {
+			Triangle nt = new Triangle(selv.get(0), selv.get(1), selv.get(2));
+			triangles.add(nt);
+			repaint();
+		}
+	}
+
+	public void deleteSelected() {
+		List<Vertex> tr = new LinkedList<Vertex>();
+		List<Triangle> trt = new LinkedList<Triangle>();
+		for (Vertex v : vertices) {
+			if (v.isSelected()) {
+				tr.add(v);
+				for(Triangle t : triangles) {
+					if(t.getA() == v || t.getB() == v || t.getC() == v)
+						trt.add(t);
+				}
+			}
+		}
+		vertices.removeAll(tr);
+		triangles.removeAll(trt);
+	}
+
+	public void selectAll() {
+		for (Vertex v : vertices)
+			v.setSelected(true);
+	}
+
+	public void deselectAll() {
+		for (Vertex v : vertices)
+			v.setSelected(false);
+	}
+
+	public void clear() {
+		vertices.clear();
+		triangles.clear();
+	}
+
+	public void delAllTriangles() {
+		triangles.clear();
+	}
+
+	public void delSelTriangle() {
+		List<Vertex> sv = getSelectedVertices();
+		if (sv.size() != 3) {
+			showError(Constants.DELTRI_ERR_TXT);
+		} else {
+			List<Triangle> tr = new LinkedList<Triangle>();
+			for (Triangle t : triangles) {
+				List<Vertex> itsVertices = new LinkedList<Vertex>();
+				itsVertices.add(t.getA());
+				itsVertices.add(t.getB());
+				itsVertices.add(t.getC());
+				if(sv.containsAll(itsVertices))
+					tr.add(t);
+			}
+
+			triangles.removeAll(tr);
+		}
+	}
+
+	//==========================================================================
+	// Shutdown
+	//==========================================================================
+	public void quit() {
+		int result = JOptionPane.showConfirmDialog(MainWindow.getInstance(),
+				Constants.CONFIRM_TXT, "Confirmation",
+				JOptionPane.YES_NO_OPTION);
+		if (result == 0)
+			System.exit(0);
+	}
+}

src/pvmt/Triangle.java

+package pvmt;
+
+public class Triangle {
+
+	private Vertex a, b, c;
+	
+	public Triangle() {
+		this(null, null, null);
+	}
+
+	public Triangle(Vertex a, Vertex b, Vertex c) {
+		this.a = a;
+		this.b = b;
+		this.c = c;
+	}
+
+	public Vertex getA() {
+		return a;
+	}
+
+	public void setA(Vertex a) {
+		this.a = a;
+	}
+
+	public Vertex getB() {
+		return b;
+	}
+
+	public void setB(Vertex b) {
+		this.b = b;
+	}
+
+	public Vertex getC() {
+		return c;
+	}
+
+	public void setC(Vertex c) {
+		this.c = c;
+	}
+
+}

src/pvmt/Vertex.java

+package pvmt;
+
+public class Vertex {
+
+	private double x, y, z;
+	private boolean selected;
+
+	public Vertex(double x, double y, double z) {
+		this.x = x;
+		this.y = y;
+		this.z = z;
+		this.selected = false;
+	}
+
+	public Vertex() {
+		this(0, 0, 0);
+	}
+
+	public double getX() {
+		return x;
+	}
+
+	public void setX(double x) {
+		this.x = x;
+	}
+
+	public double getY() {
+		return y;
+	}
+
+	public void setY(double y) {
+		this.y = y;
+	}
+
+	public double getZ() {
+		return z;
+	}
+
+	public void setZ(double z) {
+		this.z = z;
+	}
+
+	public void moveX(double dX) {
+		x += dX;
+	}
+
+	public void moveY(double dY) {
+		y += dY;
+	}
+
+	public void moveZ(double dZ) {
+		z += dZ;
+	}
+
+	public boolean isSelected() {
+		return selected;
+	}
+
+	public void setSelected(boolean selected) {
+		this.selected = selected;
+	}
+
+	public boolean equals(Vertex v) {
+		return getX() == v.getX() && getY() == v.getY() && getZ() == v.getZ();
+	}
+
+	public double distanceTo(Vertex v) {
+		double dX, dY, dZ;
+		dX = v.getX() - getX();
+		dY = v.getY() - getY();
+		dZ = v.getZ() - getZ();
+		return Math.sqrt(dX * dX + dY * dY + dZ * dZ);
+	}
+
+}