package tk.amberide.ide.gui.editor.map; import tk.amberide.engine.renderer.GLMapRenderer3D; import tk.amberide.Amber; import tk.amberide.engine.data.map.LevelMap; import static tk.amberide.engine.data.map.Angle.*; import tk.amberide.engine.data.math.vec.Ray; import tk.amberide.engine.data.math.vec.Vec3d; import tk.amberide.engine.input.AbstractKeyboard; import tk.amberide.engine.gl.FrameTimer; import tk.amberide.engine.gl.GLColor; import static tk.amberide.engine.gl.GLE.*; import tk.amberide.engine.gl.Sprite; import tk.amberide.engine.gl.TrueTypeFont; import tk.amberide.engine.gl.camera.EulerCamera; import static tk.amberide.ide.gui.editor.map.MapContext.*; import tk.amberide.ide.gui.editor.map.tool.BrushTool; import tk.amberide.ide.gui.editor.map.tool.EraserTool; import tk.amberide.ide.gui.editor.map.tool.FillTool; import tk.amberide.ide.gui.editor.map.tool.Tool; import tk.amberide.engine.input.AbstractMouse; import tk.amberide.ide.swing.MenuBuilder; import java.awt.Cursor; import java.awt.Dimension; import java.awt.MouseInfo; import java.awt.Point; import java.awt.event.ActionEvent; import java.awt.event.HierarchyEvent; import java.awt.event.HierarchyListener; import javax.swing.AbstractAction; import javax.swing.JMenu; import javax.swing.SwingUtilities; import org.lwjgl.LWJGLException; import org.lwjgl.input.Keyboard; import static org.lwjgl.opengl.GL11.*; import static tk.amberide.engine.input.AbstractMouse.*; import tk.amberide.ide.swing.misc.TransferableImage; import java.awt.BorderLayout; import java.awt.Component; import java.awt.Panel; import java.awt.Toolkit; import java.awt.datatransfer.Clipboard; import java.awt.datatransfer.ClipboardOwner; import java.awt.datatransfer.Transferable; import java.awt.image.BufferedImage; import java.nio.ByteBuffer; import javax.swing.UIManager; import org.lwjgl.BufferUtils; import tk.amberide.engine.data.map.Angle; /** * @author Tudor */ public class GLMapComponent3D extends AbstractGLMapComponent { protected FrameTimer timer = new FrameTimer(); protected Vec3d cursorPos = new Vec3d(); protected Angle currentAngle = HORIZONTAL; protected EulerCamera cam = new EulerCamera.Builder() .setPosition(0, 3, 0) .setFieldOfView(60) .setRotation(50, 135, 0) .setFarClippingPane(1000f) .build(); protected TrueTypeFont font; protected Sprite compassRose; protected GLMapRenderer3D renderer; protected Panel display = new Panel(new BorderLayout()); public GLMapComponent3D(LevelMap map) throws LWJGLException { super(map); setMinimumSize(new Dimension(0, 0)); setPreferredSize(new Dimension(0, 0)); setFocusable(true); setCursor(Cursor.getPredefinedCursor(Cursor.CROSSHAIR_CURSOR)); context.EXT_cardinalSupported = true; context.EXT_modelSelectionSupported = true; display.add(this); renderer = new GLMapRenderer3D(map); } @Override public void initGL() { gleClearColor(UIManager.getColor("MapEditor.background")); font = new TrueTypeFont(UIManager.getFont("MapEditor.font"), true); cam.applyOptimalStates(); cam.applyPerspectiveMatrix(); glEnable(GL_COLOR_MATERIAL); glEnable(GL_TEXTURE_2D); glDisable(GL_DITHER); glDepthFunc(GL_LEQUAL); glEnable(GL_BLEND); glBlendFunc(GL_ONE, GL_ONE_MINUS_SRC_ALPHA); glEnable(GL_ALPHA_TEST); glAlphaFunc(GL_GREATER, 0.1f); glEnable(GL_POLYGON_OFFSET_FILL); glHint(GL_PERSPECTIVE_CORRECTION_HINT, GL_NICEST); glHint(GL_POLYGON_SMOOTH_HINT, GL_NICEST); glShadeModel(GL_SMOOTH); glEnable(GL_POLYGON_OFFSET_FILL); timer.start(); addHierarchyListener(new HierarchyListener() { public void hierarchyChanged(HierarchyEvent e) { // Hierarchy change can signify the AWTGLCanvas destroying the original GL context, // so we have to clear the now invalid texture cache. renderer.invalidate(); compassRose = null; AbstractKeyboard.destroy(); // Prevent double events AbstractMouse.destroy(); } }); } @Override protected void pollInput() { super.pollInput(); if (!isFocusOwner()) { while (AbstractKeyboard.next()) ; while (AbstractMouse.next()) ; return; } if (isGrabbed()) { cam.processMouse(1, 80, -80); } if (!(AbstractKeyboard.isKeyDown(Keyboard.KEY_RCONTROL) || AbstractKeyboard.isKeyDown(Keyboard.KEY_LCONTROL))) { // Frame-rate independant movement float dxyz = (float) timer.getDelta() * 8f * 0.1f; dxyz = Math.min(dxyz, 100); cam.processKeyboard(12, dxyz, dxyz, dxyz); } // Cast ray from mouse, then use the properties of // similar triangles to find the xy-plane intercept, // or an offset of it based off the current layer Y. Ray ray = Ray.getRay(AbstractMouse.getX(this), getHeight() - AbstractMouse.getY(this)); float ratio = -((ray.point.y - cursorPos.y) / ray.dir.y); Vec3d intercept = new Vec3d((ray.dir.x * ratio) + cam.x(), 0, (ray.dir.z * ratio) + cam.z()); cursorPos = new Vec3d((int) Math.floor(intercept.x), cursorPos.y, (int) Math.floor(intercept.z)); Point mouse = MouseInfo.getPointerInfo().getLocation(); SwingUtilities.convertPointFromScreen(mouse, Amber.getUI()); if (Amber.getUI().findComponentAt(mouse) == this) { if (isButtonDown(0)) { LevelMap pre = context.map.clone(); if (currentTool().apply((int) cursorPos.x, (int) cursorPos.z, (int) cursorPos.y)) { context.undoStack.push(pre); modified = true; } } else if (isButtonDown(1)) { if (AbstractKeyboard.isKeyDown(Keyboard.KEY_RCONTROL) || AbstractKeyboard.isKeyDown(Keyboard.KEY_LCONTROL)) { AbstractMouse.setGrabbed(true); } else { AbstractMouse.setGrabbed(false); setCursor(Cursor.getPredefinedCursor(Cursor.CROSSHAIR_CURSOR)); } } AbstractMouse.poll(); } } @Override protected void doKey(int keycode) { switch (keycode) { case Keyboard.KEY_SUBTRACT: if (cursorPos.y > 0) { cursorPos.y--; } break; case Keyboard.KEY_ADD: cursorPos.y++; break; case Keyboard.KEY_I: if (AbstractKeyboard.isKeyDown(Keyboard.KEY_LCONTROL) || AbstractKeyboard.isKeyDown(Keyboard.KEY_RCONTROL)) { int w = getWidth(); int h = getHeight(); glReadBuffer(GL_FRONT); int bpp = 4; // Assuming a 32-bit display with a byte each for red, green, blue, and alpha. ByteBuffer buffer = BufferUtils.createByteBuffer(w * h * bpp); glReadPixels(0, 0, w, h, GL_RGBA, GL_UNSIGNED_BYTE, buffer); BufferedImage shot = new BufferedImage(w, h, BufferedImage.TYPE_INT_RGB); for (int x = 0; x < w; x++) { for (int y = 0; y < h; y++) { int i = (x + (w * y)) * bpp; int r = buffer.get(i) & 0xFF; int g = buffer.get(i + 1) & 0xFF; int b = buffer.get(i + 2) & 0xFF; shot.setRGB(x, h - (y + 1), (0xFF << 24) | (r << 16) | (g << 8) | b); } } TransferableImage trans = new TransferableImage(shot); Clipboard c = Toolkit.getDefaultToolkit().getSystemClipboard(); c.setContents(trans, new ClipboardOwner() { public void lostOwnership(Clipboard clipboard, Transferable contents) { } }); } break; default: if (currentTool() != null) currentTool().doKey(keycode); } } @Override protected void doScroll(int delta) { if (currentTool() != null) currentTool().doScroll(delta); } /** * Paints the preview */ @Override public void paintGL() { super.paintGL(); float aspect = (float) getWidth() / (float) getHeight(); if (aspect != cam.aspectRatio()) { glViewport(0, 0, getWidth(), getHeight()); cam.setAspectRatio(aspect); cam.applyPerspectiveMatrix(); } glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); glLoadIdentity(); if (wireframe) { if (glGetInteger(GL_POLYGON_MODE) == GL_FILL) { gleToggleWireframe(); } } else if (glGetInteger(GL_POLYGON_MODE) == GL_LINE) { gleToggleWireframe(); } cam.applyTranslations(); if (renderer.getMap() != context.map) { renderer = new GLMapRenderer3D(context.map); } renderer.render(); glDisable(GL_TEXTURE_2D); drawGrid(); glEnable(GL_TEXTURE_2D); glPushAttrib(GL_CURRENT_BIT | GL_LINE_BIT); GLColor.BLACK.bind(); glLineWidth(2); if (!AbstractMouse.isGrabbed() && currentTool() != null) { currentTool().draw((int) cursorPos.x, (int) cursorPos.y, (int) cursorPos.z); } glPopAttrib(); if (info || compass) { glePushOrthogonalMode(0, getWidth(), 0, getHeight()); if (compass) { if (compassRose == null) { compassRose = new Sprite("icon/MapEditor.Compass-Rose-small.png"); } glPushMatrix(); glTranslatef(getWidth() / 2 + getWidth() / 3f, getHeight() / 2 + getHeight() / 3.5f, 0); glRotatef(cam.yaw() - 90, 0, 0, 1); float ratio = ((float) getWidth()) / ((float) getHeight()) * .7f; //System.out.println(ratio); ratio = 0.7f; glScalef(ratio, ratio, ratio); compassRose.draw(compassRose.getWidth() / 2, -compassRose.getHeight() / 2); glPopMatrix(); } if (info) { glPushAttrib(GL_CURRENT_BIT | GL_POLYGON_BIT); glPolygonMode(GL_FRONT_AND_BACK, GL_FILL); // Ensure we're not in wireframe mode GLColor.BLACK.bind(); font.drawString(0, getHeight() - font.getHeight(), "FPS: " + timer.fps() + "\n" + "Position: (" + (int) cam.x() + ", " + (int) cam.y() + ", " + (int) cam.z() + ")\n" + "Altitude: " + cursorPos.y + "\n" + "Direction: " + cam.getFacingDirection() + "\n" + (!AbstractMouse.isGrabbed() ? "Cursor: (" + (int) cursorPos.x + ", " + (int) cursorPos.z + ")" : ""), 1f, 1f, TrueTypeFont.ALIGN_LEFT); glPopAttrib(); } glePushFrustrumMode(); } try { swapBuffers(); } catch (LWJGLException e) { e.printStackTrace(); } timer.updateFPS(); } protected void drawGrid() { glPushAttrib(GL_CURRENT_BIT | GL_LINE_BIT); if (grid) { glBegin(GL_LINES); { GLColor.GRAY.bind(); for (int x = 0; x <= context.map.getWidth(); x++) { gleLine(x, cursorPos.y, 0, x, cursorPos.y, context.map.getLength()); } for (int y = 0; y <= context.map.getLength(); y++) { gleLine(0, cursorPos.y, y, context.map.getWidth(), cursorPos.y, y); } } glEnd(); } glLineWidth(3); GLColor.BLACK.bind(); glBegin(GL_LINES); { gleLine(0, cursorPos.y, 0, context.map.getWidth(), cursorPos.y, 0); gleLine(0, cursorPos.y, 0, 0, cursorPos.y, context.map.getLength()); gleLine(context.map.getWidth(), cursorPos.y, 0, context.map.getWidth(), cursorPos.y, context.map.getLength()); gleLine(0, cursorPos.y, context.map.getLength(), context.map.getWidth(), cursorPos.y, context.map.getWidth()); } glEnd(); glPopAttrib(); } @Override public Component getComponent() { return display; } protected Tool brushTool = new BrushTool(context, cam); protected Tool eraseTool = new EraserTool(context, cam); protected Tool fillTool = new FillTool(context, cam); private Tool currentTool() { switch (context.drawMode) { case MODE_BRUSH: return brushTool; case MODE_FILL: return fillTool; case MODE_ERASE: return eraseTool; } return null; } protected boolean info = true, compass = true, wireframe = false, grid = true; public JMenu[] getContextMenus() { return new JMenu[]{new MenuBuilder("View").addCheckbox("Info", true, new AbstractAction() { public void actionPerformed(ActionEvent e) { info = !info; repaint(); } }).addCheckbox("Grid", true, new AbstractAction() { public void actionPerformed(ActionEvent e) { grid = !grid; repaint(); } }).addCheckbox("Compass", true, new AbstractAction() { public void actionPerformed(ActionEvent e) { compass = !compass; repaint(); } }).addCheckbox("Wireframe", false, new AbstractAction() { public void actionPerformed(ActionEvent e) { wireframe = !wireframe; repaint(); } }).create()}; } }