1 /* 2 * DimensionLine3D.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 object matching the given dimension line. 27 * @param {DimensionLine} dimensionLine 28 * @param {Home} home 29 * @param {UserPreferences} preferences 30 * @param {boolean} waitForLoading 31 * @constructor 32 * @extends Object3DBranch 33 * @author Emmanuel Puybaret 34 */ 35 function DimensionLine3D(dimensionLine, home, preferences, waitForLoading) { 36 Object3DBranch.call(this, dimensionLine, home, preferences); 37 38 this.dimensionLineRotations = null; 39 this.cameraChangeListener = null; 40 this.homeCameraListener = null; 41 this.dimensionLinesListener = null; 42 43 this.setCapability(Group3D.ALLOW_CHILDREN_EXTEND); 44 45 this.update(); 46 } 47 DimensionLine3D.prototype = Object.create(Object3DBranch.prototype); 48 DimensionLine3D.prototype.constructor = DimensionLine3D; 49 50 DimensionLine3D.prototype.update = function() { 51 var dimensionLine = this.getUserData(); 52 if (dimensionLine.isVisibleIn3D() 53 && (dimensionLine.getLevel() == null 54 || dimensionLine.getLevel().isViewableAndVisible())){ 55 var dimensionLineLength = dimensionLine.getLength(); 56 var lengthText = this.getUserPreferences().getLengthUnit().getFormat().format(dimensionLineLength); 57 var lengthStyle = dimensionLine.getLengthStyle(); 58 if (lengthStyle == null){ 59 lengthStyle = this.getUserPreferences().getDefaultTextStyle(DimensionLine); 60 } 61 if (lengthStyle.getFontName() == null) { 62 lengthStyle = lengthStyle.deriveStyle(this.getUserPreferences().getDefaultFontName()); 63 } 64 var fontName = lengthStyle.getFontName(); 65 if (fontName === null) { 66 fontName = "sans-serif"; 67 } 68 var fontHeight = lengthStyle.getFontSize(); 69 if (["Times", "Serif", "Helvetica"].indexOf(fontName) === -1) { 70 fontHeight *= 1.18; 71 } 72 var fontDescent = 0.23 * fontHeight; 73 var fontAscent = fontHeight - fontDescent; 74 var offset = dimensionLine.getOffset(); 75 var zTranslation = offset <= 0 76 ? -fontDescent - 1 77 : fontAscent + 1; 78 var transformGroup; 79 var linesShape; 80 var linesAppearance; 81 var selectionLinesShape; 82 var selectionAppearance; 83 if (this.getChildren().length === 0) { 84 var group = new BranchGroup3D(); 85 86 transformGroup = new TransformGroup3D(); 87 transformGroup.setCapability(TransformGroup3D.ALLOW_TRANSFORM_WRITE); 88 labelTransformGroup = new TransformGroup3D(); 89 labelTransformGroup.setCapability(TransformGroup3D.ALLOW_TRANSFORM_WRITE); 90 transformGroup.addChild(labelTransformGroup); 91 92 var lengthLabel = new Label(lengthText, 0, zTranslation); 93 lengthLabel.setColor(dimensionLine.getColor()); 94 lengthLabel.setStyle(lengthStyle); 95 lengthLabel.setPitch(0); 96 var label3D = new Label3D(lengthLabel, null, false); 97 labelTransformGroup.addChild(label3D); 98 99 var linesShape = new Shape3D(); 100 linesAppearance = new Appearance3D(); 101 linesAppearance.setIllumination(0); 102 linesShape.setAppearance(linesAppearance); 103 linesShape.setCapability(Shape3D.ALLOW_GEOMETRY_WRITE); 104 transformGroup.addChild(linesShape); 105 106 selectionLinesShape = new Shape3D(); 107 selectionAppearance = this.getSelectionAppearance(); 108 selectionLinesShape.setAppearance(this.getSelectionAppearance()); 109 selectionLinesShape.setCapability(Shape3D.ALLOW_GEOMETRY_WRITE); 110 selectionLinesShape.setPickable(false); 111 transformGroup.addChild(selectionLinesShape); 112 113 group.addChild(transformGroup); 114 this.addChild(group); 115 } else { 116 transformGroup = this.getChild(0).getChild(0); 117 var label3D = transformGroup.getChild(0).getChild(0); 118 var lengthLabel = label3D.getUserData(); 119 lengthLabel.setText(lengthText); 120 lengthLabel.setY(zTranslation); 121 lengthLabel.setColor(dimensionLine.getColor()); 122 lengthLabel.setStyle(lengthStyle); 123 label3D.update(); 124 125 linesShape = transformGroup.getChild(1); 126 linesAppearance = linesShape.getAppearance(); 127 128 selectionLinesShape = transformGroup.getChild(2); 129 selectionAppearance = selectionLinesShape.getAppearance(); 130 } 131 132 var elevationStart = dimensionLine.getElevationStart(); 133 var startPointTransform = mat4.create(); 134 mat4.fromTranslation(startPointTransform, vec3.fromValues( 135 dimensionLine.getXStart(), dimensionLine.getLevel() != null ? dimensionLine.getLevel().getElevation() + elevationStart : elevationStart, dimensionLine.getYStart())); 136 137 if (this.dimensionLineRotations == null) { 138 this.dimensionLineRotations = mat4.create(); 139 } 140 var elevationAngle = Math.atan2(dimensionLine.getElevationEnd() - elevationStart, 141 dimensionLine.getXEnd() - dimensionLine.getXStart()); 142 mat4.fromZRotation(this.dimensionLineRotations, elevationAngle); 143 var rotation = mat4.create(); 144 var endsAngle = Math.atan2(dimensionLine.getYStart() - dimensionLine.getYEnd(), 145 dimensionLine.getXEnd() - dimensionLine.getXStart()); 146 mat4.fromYRotation(rotation, endsAngle); 147 mat4.mul(this.dimensionLineRotations, this.dimensionLineRotations, rotation); 148 rotation = mat4.create(); 149 mat4.fromXRotation(rotation, -dimensionLine.getPitch()); 150 mat4.mul(this.dimensionLineRotations, this.dimensionLineRotations, rotation); 151 mat4.mul(startPointTransform, startPointTransform, this.dimensionLineRotations); 152 153 var offsetTransform = mat4.create(); 154 mat4.fromTranslation(offsetTransform, vec3.fromValues(0, 0, offset)); 155 mat4.mul(startPointTransform, startPointTransform, offsetTransform); 156 transformGroup.setTransform(startPointTransform); 157 158 // Handle dimension lines 159 var endMarkSize = dimensionLine.getEndMarkSize() / 2; 160 var linesCoordinates = new Array(7 * 2); 161 linesCoordinates [0] = vec3.fromValues(0, 0, 0); 162 linesCoordinates [1] = vec3.fromValues(dimensionLineLength, 0, 0); 163 linesCoordinates [2] = vec3.fromValues(-endMarkSize, 0, endMarkSize); 164 linesCoordinates [3] = vec3.fromValues(endMarkSize, 0, -endMarkSize); 165 linesCoordinates [4] = vec3.fromValues(0, 0, endMarkSize); 166 linesCoordinates [5] = vec3.fromValues(0, 0, -endMarkSize); 167 linesCoordinates [6] = vec3.fromValues(dimensionLineLength - endMarkSize, 0, endMarkSize); 168 linesCoordinates [7] = vec3.fromValues(dimensionLineLength + endMarkSize, 0, -endMarkSize); 169 linesCoordinates [8] = vec3.fromValues(dimensionLineLength, 0, endMarkSize); 170 linesCoordinates [9] = vec3.fromValues(dimensionLineLength, 0, -endMarkSize); 171 linesCoordinates [10] = vec3.fromValues(0, 0, -offset); 172 linesCoordinates [11] = vec3.fromValues(0, 0, -endMarkSize); 173 linesCoordinates [12] = vec3.fromValues(dimensionLineLength, 0, -offset); 174 linesCoordinates [13] = vec3.fromValues(dimensionLineLength, 0, -endMarkSize); 175 var lines = new IndexedLineArray3D(linesCoordinates, 176 [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13]); 177 linesShape.setGeometry(lines, 0); 178 selectionLinesShape.setGeometry(lines, 0); 179 this.updateAppearanceMaterial(linesAppearance, dimensionLine.getColor() != null ? dimensionLine.getColor() : 0, 0, 0); 180 181 var home = this.getHome(); 182 var selectionVisible = this.getUserPreferences() != null 183 && this.getUserPreferences().isEditingIn3DViewEnabled() 184 && home.isItemSelected(dimensionLine); 185 // As there's no line thickness in WebGL just display either shapes 186 selectionAppearance.setVisible(selectionVisible); 187 linesAppearance.setVisible(!selectionVisible) 188 189 this.updateLengthLabelDirection(home.getCamera()); 190 191 var dimensionLine3D = this; 192 if (this.cameraChangeListener == null) { 193 // Add camera listener to update length label direction 194 this.cameraChangeListener = function(ev) { 195 var dimensionLine = dimensionLine3D.getUserData(); 196 if (dimensionLine3D.getChildren().length > 0 197 && dimensionLine.isVisibleIn3D() 198 && (dimensionLine.getLevel() == null 199 || dimensionLine.getLevel().isViewableAndVisible())) { 200 var propertyName = ev.getPropertyName(); 201 if ("X" == propertyName 202 || "Y" == propertyName 203 || "Z" == propertyName) { 204 dimensionLine3D.updateLengthLabelDirection(ev.getSource()); 205 } 206 } 207 }; 208 this.homeCameraListener = function(ev) { 209 ev.getOldValue().removePropertyChangeListener(dimensionLine3D.cameraChangeListener); 210 ev.getNewValue().addPropertyChangeListener(dimensionLine3D.cameraChangeListener); 211 dimensionLine3D.updateLengthLabelDirection(home.getCamera()); 212 }; 213 this.dimensionLinesListener = function(ev) { 214 if (ev.getType() === CollectionEvent.Type.DELETE 215 && ev.getItem() === dimensionLine) { 216 home.getCamera().removePropertyChangeListener(dimensionLine3D.cameraChangeListener); 217 home.removePropertyChangeListener("CAMERA", dimensionLine3D.homeCameraListener); 218 home.removeDimensionLinesListener(dimensionLine3D.dimensionLinesListener); 219 } 220 }; 221 home.getCamera().addPropertyChangeListener(this.cameraChangeListener); 222 home.addPropertyChangeListener("CAMERA", this.homeCameraListener); 223 home.addDimensionLinesListener(this.dimensionLinesListener); 224 } 225 } else { 226 this.removeAllChildren(); 227 this.dimensionLineRotations = null; 228 if (this.cameraChangeListener != null) { 229 this.getHome().getCamera().removePropertyChangeListener(this.cameraChangeListener); 230 this.getHome().removePropertyChangeListener("CAMERA", this.homeCameraListener); 231 this.getHome().removeDimensionLinesListener(this.dimensionLinesListener); 232 this.cameraChangeListener = null; 233 this.homeCameraListener = null; 234 this.dimensionLinesListener = null; 235 } 236 } 237 } 238 239 /** 240 * Updates length label direction to ensure it's always visible in the direction of writing. 241 * @param {Camera} camera 242 * @private 243 */ 244 DimensionLine3D.prototype.updateLengthLabelDirection = function(camera) { 245 var dimensionLine = this.getUserData(); 246 var dimensionLineNormal = vec3.fromValues(0, 1, 0); 247 vec3.transformMat4(dimensionLineNormal, dimensionLineNormal, this.dimensionLineRotations); 248 249 var cameraToDimensionLineDirection = vec3.fromValues((dimensionLine.getXEnd() + dimensionLine.getXStart()) / 2 - camera.getX(), 250 (dimensionLine.getElevationEnd() + dimensionLine.getElevationStart()) / 2 - camera.getZ(), 251 (dimensionLine.getYEnd() + dimensionLine.getYStart()) / 2 - camera.getY()); 252 253 var labelTransformGroup = this.getChild(0).getChild(0).getChild(0); 254 var labelTransform = mat4.create(); 255 mat4.fromTranslation(labelTransform, vec3.fromValues(dimensionLine.getLength() / 2, 0, 0)); 256 var labelRotation = mat4.create(); 257 mat4.fromZRotation(labelRotation, vec3.dot(dimensionLineNormal, cameraToDimensionLineDirection) > 0 ? Math.PI : 0); 258 mat4.mul(labelTransform, labelTransform, labelRotation); 259 labelTransformGroup.setTransform(labelTransform); 260 }