1 /* 2 * Label3D.js 3 * 4 * Sweet Home 3D, Copyright (c) 2024 Space Mushrooms <[email protected]> 5 * 6 * This program is free software; you can redistribute it and/or modify 7 * it under the terms of the GNU General Public License as published by 8 * the Free Software Foundation; either version 2 of the License, or 9 * (at your option) any later version. 10 * 11 * This program is distributed in the hope that it will be useful, 12 * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 * GNU General Public License for more details. 15 * 16 * You should have received a copy of the GNU General Public License 17 * along with this program; if not, write to the Free Software 18 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA 19 */ 20 21 // Requires scene3d.js 22 // Object3DBranch.js 23 24 25 /** 26 * Creates the 3D label matching the given home <code>label</code>. 27 * @param {Label} label 28 * @param {Home} home 29 * @param {UserPreferences} [preferences] 30 * @param {boolean} waitModelAndTextureLoadingEnd 31 * @constructor 32 * @extends Object3DBranch 33 * @author Emmanuel Puybaret 34 */ 35 function Label3D(label, home, preferences, waitModelAndTextureLoadingEnd) { 36 if (waitModelAndTextureLoadingEnd === undefined) { 37 // 3 parameters 38 waitModelAndTextureLoadingEnd = preferences; 39 preferences = null; 40 } 41 Object3DBranch.call(this, label, home, preferences); 42 43 this.setCapability(Group3D.ALLOW_CHILDREN_EXTEND); 44 45 this.update(); 46 } 47 Label3D.prototype = Object.create(Object3DBranch.prototype); 48 Label3D.prototype.constructor = Label3D; 49 50 Label3D.dummyContext = document.createElement("canvas").getContext("2d"); 51 52 Label3D.prototype.update = function() { 53 var label = this.getUserData(); 54 var pitch = label.getPitch(); 55 var style = label.getStyle(); 56 if (pitch != null 57 && style != null 58 && (label.getLevel() == null 59 || label.getLevel().isViewableAndVisible())) { 60 var text = label.getText(); 61 var color = label.getColor(); 62 var outlineColor = label.getOutlineColor(); 63 if (text != this.text 64 || (style == null && this.style != null) 65 || (style != null && !style.equals(this.style)) 66 || (color == null && this.color != null) 67 || (color != null && color !== this.color)) { 68 var fontStyle = ""; 69 if (style.isBold()) { 70 fontStyle = "bold "; 71 } 72 if (style.isItalic()) { 73 fontStyle += "italic "; 74 } 75 var fontName = style.getFontName(); 76 if (fontName === null) { 77 fontName = "sans-serif"; 78 } 79 80 var fontSize = 50; // Size to get a similar outline as in Java 81 var fontHeight = fontSize; 82 if (["Times", "Serif", "Helvetica"].indexOf(fontName) === -1) { 83 fontHeight *= 1.18; 84 } 85 var fontScale = fontSize / style.getFontSize(); 86 var descent = 0.23 * fontHeight; 87 var font = fontStyle + " " + fontSize + "px " + fontName; 88 Label3D.dummyContext.font = font; 89 90 var lines = text.replace(/\n*$/, "").split("\n"); 91 var lineWidths = new Array(lines.length); 92 var textWidth = -Infinity; 93 var baseLineShift = -descent + fontHeight * lines.length; 94 for (var i = 0; i < lines.length; i++) { 95 lineWidths [i] = Label3D.dummyContext.measureText(lines [i]).width; 96 if (style.isItalic()) { 97 lineWidths [i] += fontHeight * 0.154; 98 } 99 textWidth = Math.max(lineWidths [i], textWidth); 100 } 101 102 var textHeight = fontHeight * lines.length; 103 var textRatio = Math.sqrt(textWidth / textHeight); 104 var textRatio = Math.sqrt(textWidth / textHeight); 105 var width; 106 var height; 107 var scale; 108 if (textRatio > 1) { 109 width = Math.ceil(Math.max(255 * textRatio, Math.min(textWidth, 511 * textRatio))); 110 scale = width / textWidth; 111 height = Math.ceil(scale * textHeight); 112 } else { 113 height = Math.ceil(Math.max(255 * textRatio, Math.min(textHeight, 511 / textRatio))); 114 scale = height / textHeight; 115 width = Math.ceil(scale * textWidth); 116 } 117 if (width > 0 && height > 0) { 118 var textureImage = document.createElement("canvas"); 119 textureImage.width = Appearance3D.getNextHighestPowerOfTwo(width) / 2; 120 textureImage.height = Appearance3D.getNextHighestPowerOfTwo(height) / 2; 121 textureImage.transparent = true; 122 var context = textureImage.getContext("2d"); 123 124 context.scale(scale / width * textureImage.width, scale / height * textureImage.height); 125 context.translate(0, baseLineShift); 126 context.font = font; 127 if (color !== null) { 128 context.fillStyle = "rgb(" 129 + ((color >>> 16) & 0xFF) + "," 130 + ((color >>> 8) & 0xFF) + "," 131 + (color & 0xFF) + ")"; 132 } 133 if (outlineColor !== null) { 134 context.strokeStyle = "rgb(" 135 + ((outlineColor >>> 16) & 0xFF) + "," 136 + ((outlineColor >>> 8) & 0xFF) + "," 137 + (outlineColor & 0xFF) + ")" 138 } 139 for (var i = lines.length - 1; i >= 0; i--) { 140 var line = lines [i]; 141 var translationX; 142 if (style.getAlignment() === TextStyle.Alignment.LEFT) { 143 translationX = 0; 144 } else if (style.getAlignment() === TextStyle.Alignment.RIGHT) { 145 translationX = textWidth - lineWidths [i]; 146 } else { // CENTER 147 translationX = (textWidth - lineWidths [i]) / 2; 148 } 149 context.translate(translationX, 0); 150 context.fillText(line, 0, 0); 151 if (outlineColor !== null) { 152 // Fill then stroke to be able to view outline drawn in each character 153 context.strokeText(line, 0, 0); 154 } 155 context.translate(-translationX, -fontHeight); 156 } 157 158 var scaleTransform = mat4.create(); 159 mat4.scale(scaleTransform, scaleTransform, vec3.fromValues(textWidth / fontScale, 1, textHeight / fontScale)); 160 this.baseLineTransform = mat4.create(); 161 var translationX; 162 if (style.getAlignment() == TextStyle.Alignment.LEFT) { 163 translationX = textWidth / 2; 164 } else if (style.getAlignment() == TextStyle.Alignment.RIGHT) { 165 translationX = -textWidth / 2; 166 } else { // CENTER 167 translationX = 0; 168 } 169 mat4.fromTranslation(this.baseLineTransform, vec3.fromValues(translationX / fontScale, 0, (textHeight / 2 - baseLineShift) / fontScale)); 170 mat4.mul(this.baseLineTransform, this.baseLineTransform, scaleTransform); 171 this.texture = textureImage; 172 this.text = text; 173 this.style = style; 174 this.color = color; 175 } else { 176 this.clear(); 177 } 178 } 179 if (this.texture !== null) { 180 var transformGroup; 181 var selectionAppearance; 182 if (this.getChildren().length === 0) { 183 var group = new BranchGroup3D(); 184 transformGroup = new TransformGroup3D(); 185 transformGroup.setCapability(TransformGroup3D.ALLOW_TRANSFORM_WRITE); 186 group.addChild(transformGroup); 187 188 var appearance = new Appearance3D(); 189 this.updateAppearanceMaterial(appearance, Object3DBranch.DEFAULT_COLOR, Object3DBranch.DEFAULT_AMBIENT_COLOR, 0); 190 appearance.setCullFace(Appearance3D.CULL_NONE); 191 var shape = new Shape3D( 192 new IndexedTriangleArray3D( 193 [vec3.fromValues(-0.5, 0, -0.5), 194 vec3.fromValues(-0.5, 0, 0.5), 195 vec3.fromValues(0.5, 0, 0.5), 196 vec3.fromValues(0.5, 0, -0.5)], 197 [0, 1, 2, 0, 2, 3], 198 [vec2.fromValues(0., 0.), 199 vec2.fromValues(1., 0.), 200 vec2.fromValues(1., 1.), 201 vec2.fromValues(0., 1.)], 202 [3, 0, 1, 3, 1, 2], 203 [vec3.fromValues(0., 1., 0.)], 204 [0, 0, 0, 0, 0, 0]), appearance); 205 transformGroup.addChild(shape); 206 207 var selectionCoordinates = [vec3.fromValues(-0.5, 0, -0.5), vec3.fromValues(0.5, 0, -0.5), 208 vec3.fromValues(0.5, 0, 0.5), vec3.fromValues(-0.5, 0, 0.5)]; 209 var selectionGeometry = new IndexedLineArray3D(selectionCoordinates, [0, 1, 1, 2, 2, 3, 3, 0]); 210 211 selectionAppearance = this.getSelectionAppearance(); 212 var selectionLinesShape = new Shape3D(selectionGeometry, selectionAppearance); 213 selectionLinesShape.setPickable(false); 214 transformGroup.addChild(selectionLinesShape); 215 216 this.addChild(group); 217 } else { 218 transformGroup = this.getChild(0).getChild(0); 219 var selectionLinesShape = transformGroup.getChild(1); 220 selectionAppearance = selectionLinesShape.getAppearance(); 221 } 222 223 var transformGroup = this.getChild(0).getChild(0); 224 var pitchRotation = mat4.create(); 225 mat4.fromXRotation(pitchRotation, pitch); 226 mat4.mul(pitchRotation, pitchRotation, this.baseLineTransform); 227 var rotationY = mat4.create(); 228 mat4.fromYRotation(rotationY, -label.getAngle()); 229 mat4.mul(rotationY, rotationY, pitchRotation); 230 var transform = mat4.create(); 231 mat4.fromTranslation(transform, vec3.fromValues(label.getX(), label.getGroundElevation() + (pitch == 0 && label.getElevation() < 0.1 ? 0.1 : 0), label.getY())); 232 mat4.mul(transform, transform, rotationY); 233 transformGroup.setTransform(transform); 234 transformGroup.getChild(0).getAppearance().setTextureImage(this.texture); 235 236 selectionAppearance.setVisible(this.getUserPreferences() != null 237 && this.getUserPreferences().isEditingIn3DViewEnabled() 238 && this.getHome().isItemSelected(label)); 239 } 240 } else { 241 this.clear(); 242 } 243 } 244 245 /** 246 * Removes children and clear fields. 247 * @private 248 */ 249 Label3D.prototype.clear = function () { 250 this.removeAllChildren(); 251 this.text = null; 252 this.style = null; 253 this.color = null; 254 this.texture = null; 255 this.baseLineTransform = null; 256 }