1 /*
  2  * DimensionLine3D.js
  3  *
  4  * Sweet Home 3D, Copyright (c) 2024 Space Mushrooms <info@sweethome3d.com>
  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 }