package org.geogebra.common.geogebra3D.euclidian3D.draw; import org.geogebra.common.awt.GAffineTransform; import org.geogebra.common.awt.GBufferedImage; import org.geogebra.common.awt.GColor; import org.geogebra.common.awt.GFont; import org.geogebra.common.awt.GGraphics2D; import org.geogebra.common.awt.GPoint; import org.geogebra.common.awt.GRectangle; import org.geogebra.common.euclidian.EuclidianStatic; import org.geogebra.common.factories.AwtFactory; import org.geogebra.common.geogebra3D.euclidian3D.EuclidianView3D; import org.geogebra.common.geogebra3D.euclidian3D.openGL.Manager; import org.geogebra.common.geogebra3D.euclidian3D.openGL.Renderer; import org.geogebra.common.kernel.Matrix.Coords; import org.geogebra.common.kernel.geos.GeoElement; import org.geogebra.common.kernel.geos.TextProperties; import org.geogebra.common.main.App; import org.geogebra.common.main.Feature; import com.himamis.retex.renderer.share.platform.graphics.RenderingHints; /** * Class for drawing labels of 3D elements * * @author matthieu * */ public class DrawLabel3D { /** text of the label */ protected String text; /** font of the label */ protected GFont font, fontOriginal; /** text wants serif */ private boolean serif; /** color of the label */ private Coords backgroundColor, color; /** origin of the label (left-bottom corner) */ protected Coords origin; /** x and y offset */ private float xOffset, yOffset; protected float xOffset2; protected float yOffset2; /** says if there's an anchor to do */ private boolean anchor; /** says if the label is visible */ private boolean isVisible; /** width and height of the text */ protected int height, width; /** width and height of the texture */ private int height2, width2; /** index of the texture used for this label */ private int textureIndex = -1; /** current view where this label is drawn */ protected EuclidianView3D view; /** says it wait for reset */ private boolean waitForReset; /** temp graphics used for calculate bounds */ protected GGraphics2D tempGraphics = AwtFactory.getPrototype() .newBufferedImage(1, 1, 1).createGraphics(); protected Drawable3D drawable; /** * common constructor * * @param view * 3D view * @param drawable * drawable linked to this label */ public DrawLabel3D(EuclidianView3D view, Drawable3D drawable) { this.view = view; this.drawable = drawable; } /** * update the label * * @param text * @param fontsize * @param color * @param v * @param xOffset * @param yOffset */ public void update(String text, GFont font, GColor color, Coords v, float xOffset, float yOffset) { update(text, font, null, color, v, xOffset, yOffset); } /** * * @return font scale (used for image export) */ protected double getFontScale() { return view.getFontScale(); } /** * update the label * * @param text * @param fontsize * @param fgColor * @param v * @param xOffset * @param yOffset */ public void update(String text, GFont font0, GColor bgColor, GColor fgColor, Coords v, float xOffset, float yOffset) { this.origin = v; if (text.length() == 0) { return; } this.color = new Coords((double) fgColor.getRed() / 255, (double) fgColor.getGreen() / 255, (double) fgColor.getBlue() / 255, 1); if (bgColor != null) { this.backgroundColor = new Coords( (double) bgColor.getRed() / 255, (double) bgColor.getGreen() / 255, (double) bgColor.getBlue() / 255, 1); } else { this.backgroundColor = null; } if (view.isGrayScaled()) { this.color.convertToGrayScale(); } setIsVisible(true); if (waitForReset || !text.equals(this.text) || !font0.equals(this.fontOriginal)) { this.text = text; fontOriginal = font0; int style = fontOriginal.getStyle(); int size = fontOriginal.getSize(); font = fontOriginal.deriveFont(style, (float) (size * getFontScale())); tempGraphics.setFont(font); serif = true; GeoElement geo = drawable.getGeoElement(); if (geo instanceof TextProperties) { serif = ((TextProperties) geo).isSerifFont(); } GRectangle rectangle = getBounds(); int xMin = (int) rectangle.getMinX() - 1; int xMax = (int) rectangle.getMaxX() + 1; int yMin = (int) rectangle.getMinY() - 1; int yMax = (int) rectangle.getMaxY() + 1; // Application.debug(text+"\nxMin="+xMin+", xMax="+xMax+", // advance="+textLayout.getAdvance()); width = xMax - xMin; height = yMax - yMin; xOffset2 = xMin; yOffset2 = -yMax; // creates the buffered image GBufferedImage bimg = draw(); // creates the texture view.getRenderer().createAlphaTexture(this, bimg); waitForReset = false; // Application.debug("textureIndex = "+textureIndex); } this.xOffset = xOffset;// + xOffset2; this.yOffset = yOffset;// + yOffset2; } /** * create graphics2D instance from buffered image * * @param bimg * buffered image * @return graphics2D */ protected GGraphics2D createGraphics2D(GBufferedImage bimg) { GGraphics2D g2d = bimg.createGraphics(); GAffineTransform gt = AwtFactory.getPrototype().newAffineTransform(); gt.scale(1, -1d); gt.translate(-xOffset2, yOffset2);// put the baseline on the label // anchor g2d.transform(gt); g2d.setColor(GColor.BLACK); g2d.setFont(font); g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON); g2d.setRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING, RenderingHints.VALUE_TEXT_ANTIALIAS_ON); return g2d; } /** * @return buffered image */ protected GBufferedImage createBufferedImage() { return view.getRenderer().createBufferedImage(this); } protected boolean hasIndex = false; protected GRectangle getBounds() { GRectangle rectangle = EuclidianStatic.drawMultiLineText( view.getApplication(), text, 0, 0, tempGraphics, false, font); if (text.contains("_")) { // text contains subscript hasIndex = true; GPoint p = EuclidianStatic.drawIndexedString(view.getApplication(), tempGraphics, text, 0, 0, false); rectangle.setRect(rectangle.getMinX(), rectangle.getMinY(), rectangle.getWidth(), rectangle.getHeight() + p.y); } else { hasIndex = false; } return rectangle; } private static boolean isLatex(String text) { return (text.charAt(0) == '$') && text.endsWith("$"); } /** * @return buffered image with label drawn in it */ protected GBufferedImage draw() { GBufferedImage bimg; GGraphics2D g2d; if (isLatex(text) && text.length() > 1) { GeoElement geo = drawable.getGeoElement(); int offsetY = 10 + view.getFontSize(); // make sure LaTeX labels // don't go // off bottom of screen height += offsetY; bimg = createBufferedImage(); g2d = createGraphics2D(bimg); App app = view.getApplication(); // GDimension dim = app.getDrawEquation().drawEquation(geo.getKernel().getApplication(), geo, g2d, 0, -offsetY, text.substring(1, text.length() - 1), g2d.getFont(), serif, g2d.getColor(), g2d.getBackground(), true, false, getCallBack()); return bimg; } bimg = createBufferedImage(); g2d = createGraphics2D(bimg); g2d.setFont(font); if (hasIndex) { EuclidianStatic.drawIndexedString(view.getApplication(), g2d, text, 0, 0, false); } else { g2d.drawString(text, 0, 0); } return bimg; } private Runnable callBack = null; /** * * @return callback (for JLM) */ protected Runnable getCallBack() { if (callBack == null) { callBack = new DrawLaTeXCallBack(this); } return callBack; } protected class DrawLaTeXCallBack implements Runnable { private DrawLabel3D label; public DrawLaTeXCallBack(DrawLabel3D label) { this.label = label; } @Override public void run() { label.drawable.setLabelWaitForReset(); view.repaintView(); } } /** * set the label to be reset */ public void setWaitForReset() { waitForReset = true; textIndex = -1; pickingIndex = -1; backgroundIndex = -1; } /** * * @return true if this wait for reset */ public boolean waitForReset() { return waitForReset; } /** * sets the anchor * * @param flag */ public void setAnchor(boolean flag) { anchor = flag; } /** * draws the label * * @param renderer * renderer */ public void draw(Renderer renderer) { draw(renderer, false); } protected double drawX, drawY, drawZ; /** * * @return z position (in screen coords) where the label is drawn */ public double getDrawZ() { return drawZ; } private Coords v = new Coords(3); private float[] labelOrigin = new float[3]; /** * update draw position */ public void updateDrawPosition() { if (origin == null) { return; } v.setMul(view.getToScreenMatrix(), origin); origin.get3ForGL(labelOrigin); if (view.getApplication().has(Feature.DIFFERENT_AXIS_RATIO_3D)) { labelOrigin[0] *= view.getXscale(); labelOrigin[1] *= view.getYscale(); labelOrigin[2] *= view.getZscale(); } drawX = (int) (v.getX() + xOffset); if (anchor && xOffset < 0) { drawX -= width / getFontScale(); } else { drawX += xOffset2 / getFontScale(); } drawY = (int) (v.getY() + yOffset); if (anchor && yOffset < 0) { drawY -= height / getFontScale(); } else { drawY += yOffset2 / getFontScale(); } drawZ = (int) v.getZ(); } /** * * @param x * mouse x position * @param y * mouse y position * @return true if mouse hits the label */ public boolean hit(double x, double y) { if (backgroundColor != null) { return drawX <= x && drawX + width >= x && drawY <= y && drawY + height >= y; } return drawX + pickingX <= x && drawX + pickingX + pickingW >= x && drawY + pickingY <= y && drawY + pickingY + pickingH >= y; } /** * * @param o * mouse origin * @param direction * mouse direction * @return true if mouse hits the label */ public boolean hit(Coords o, Coords direction) { double x = o.getX() + (drawZ - o.getZ()) * direction.getX() / direction.getZ(); double y = o.getY() + (drawZ - o.getZ()) * direction.getY() / direction.getZ(); return hit(x, y); } /** * draws the label * * @param renderer * renderer * @param forPicking * says if it's for picking */ public void draw(Renderer renderer, boolean forPicking) { if (!isVisible) { return; } if (textureIndex == -1) { return; } renderer.setLabelOrigin(labelOrigin); if (forPicking) { // renderer.getGeometryManager().rectangle(drawX + pickingX, drawY + // pickingY, drawZ, pickingW, pickingH); if (backgroundColor != null) { renderer.getGeometryManager().draw(backgroundIndex); } else { renderer.getGeometryManager().draw(pickingIndex); } } else { // draw background if (backgroundColor != null) { renderer.setColor(backgroundColor); renderer.disableTextures(); // renderer.getGeometryManager().rectangle(drawX, drawY, drawZ, // width, height); renderer.getGeometryManager().draw(backgroundIndex); } // draw text drawText(renderer); } } /** * draw at (x,y,z) * * @param renderer * renderer */ protected void drawText(Renderer renderer) { // draw text renderer.setColor(color); renderer.enableTextures(); renderer.getTextures().setTextureLinear(textureIndex); renderer.getGeometryManager().drawLabel(textIndex); } /** * set texture index * * @param i * index */ public void setTextureIndex(int i) { textureIndex = i; } /** * @return texture indexl * */ public int getTextureIndex() { return textureIndex; } /** * sets the visibility of the label * * @param flag */ public void setIsVisible(boolean flag) { isVisible = flag; } /** * * @return label width */ public int getWidth() { return width; } /** * * @return label height */ public int getHeight() { return height; } /** * * @return label width for texture (power of 2) */ public int getWidthPowerOfTwo() { return width2; } /** * * @return label height for texture (power of 2) */ public int getHeightPowerOfTwo() { return height2; } /** * set dimension for picking * * @param x * bottom-left x position * @param y * bottom-left y position * @param w * width * @param h * height */ public void setPickingDimension(int x, int y, int w, int h) { pickingX = x; pickingY = y; pickingW = w; pickingH = h; } /** * set power of 2 width and height * * @param w * width * @param h * height */ public void setDimensionPowerOfTwo(int w, int h) { width2 = w; height2 = h; } public void scaleRenderingDimensions(float scale) { width2 *= scale; height2 *= scale; pickingX *= scale; pickingY *= scale; pickingW *= scale; pickingH *= scale; } private int pickingX, pickingY, pickingW, pickingH; public boolean isPickable() { return drawable.hasPickableLable(); } private int textIndex = -1; private int pickingIndex = -1; protected int backgroundIndex = -1; /** * update label for view (update screen position) * * @param renderer * GL renderer */ public void updatePosition(Renderer renderer) { updateDrawPosition(); if (origin == null) { renderer.getGeometryManager().remove(textIndex); textIndex = -1; renderer.getGeometryManager().remove(pickingIndex); pickingIndex = -1; renderer.getGeometryManager().remove(backgroundIndex); backgroundIndex = -1; return; } int old = textIndex; textIndex = drawRectangle(renderer, drawX, drawY, drawZ, width2 / getFontScale(), height2 / getFontScale(), textIndex); renderer.getGeometryManager().remove(old); old = pickingIndex; pickingIndex = drawRectangle(renderer, drawX + pickingX / getFontScale(), drawY + pickingY / getFontScale(), drawZ, pickingW / getFontScale(), pickingH / getFontScale(), pickingIndex); renderer.getGeometryManager().remove(old); old = backgroundIndex; backgroundIndex = drawRectangle(renderer, drawX, drawY, drawZ, width / getFontScale(), height / getFontScale(), backgroundIndex); renderer.getGeometryManager().remove(old); // Log.debug("textIndex: "+textIndex+", pickingIndex: "+pickingIndex+", // backgroundIndex: "+backgroundIndex); } private static final int drawRectangle(Renderer renderer, double x, double y, double z, double w, double h, int index) { return renderer.getGeometryManager().rectangle(x, y, z, w, h, index); } /** * remove from GPU memory */ public void removeFromGL() { Manager manager = view.getRenderer().getGeometryManager(); manager.remove(textIndex); manager.remove(pickingIndex); manager.remove(backgroundIndex); } }