UNPKG

18.1 kBJavaScriptView Raw
1'use strict';
2
3import * as THREE from 'three';
4import EdgesHelper from './EdgesHelper.js';
5import FluxRenderer from './FluxRenderer.js';
6import FluxCameras from './FluxCameras.js';
7import * as FluxJsonToThree from 'flux-json-to-three';
8import {scene} from 'flux-modelingjs';
9import * as constants from './controls/constants.js';
10import * as print from './utils/debugPrint.js';
11
12/**
13 * UI widget to render 3D geometry.
14 * This class provides all the interface you need to add the flux web
15 * viewer to your SDK app. This allows you to interpret Flux json data
16 * and render that geometry. You may also create any number of viewports
17 * and they will share the finite number of WebGL contexts available
18 * from the browser.<br>
19 * The most commonly used functions are <a href="#setGeometryEntity">setGeometryEntity</a> (to set geometry to render)
20 * and <a href="#.isKnownGeom">isKnownGeom</a> (determine if JSON is geometry) so you might want to start reading there. <br>
21 * Note: If you are using <a href="https://community.flux.io/content/kbentry/2718/materials-1.html">Flux materials</a> that have the parameter roughness
22 * set then you will need to configure your server to have a <a href="https://content-security-policy.com">content security
23 * policy</a> that allows content from https://object-library.storage.googleapis.com
24 * so that our environment texture images can be loaded.
25 * @class FluxViewport
26 * @param {Element} domParent The div container for the canvas
27 * @param {Object} optionalParams Object containing all other parameters
28 * @param {Number} optionalParams.width The width of the canvas
29 * @param {Number} optionalParams.height The height of the canvas
30 * @param {String} optionalParams.tessUrl The url for making brep tessellation requests (overrides projectId) (Used when server is not flux.io)
31 * @param {Enumeration} optionalParams.selection Whether to enable user selection
32 * @param {String} optionalParams.projectId Id of a flux project (required to render breps)
33 * @param {String} optionalParams.token The current flux auth token (required to render breps)
34 */
35export default function FluxViewport (domParent, optionalParams) {
36 var width;
37 var height;
38 var tessUrl;
39 var token;
40 var selection = constants.Selection.NONE;
41 if (optionalParams) {
42 width = optionalParams.width;
43 height = optionalParams.height;
44 if (optionalParams.tessUrl) {
45 tessUrl = optionalParams.tessUrl;
46 } else if (optionalParams.projectId) {
47 tessUrl = _getTessUrl(optionalParams.projectId);
48 }
49 token = optionalParams.token;
50 if (optionalParams.selection) {
51 selection = optionalParams.selection;
52 }
53 }
54
55 var renderWidth = 100;//px
56 if (width == null) {
57 renderWidth = domParent.clientWidth;
58 } else {
59 renderWidth = Math.max(renderWidth, width);
60 }
61
62 var renderHeight = 100;//px
63 if (height == null) {
64 renderHeight = domParent.clientHeight;
65 } else {
66 renderHeight = Math.max(renderHeight, height);
67 }
68
69 if (!domParent) {
70 throw new Error('domParent must be specified to FluxViewport');
71 }
72
73 this._sceneBuilder = new FluxJsonToThree.SceneBuilder(tessUrl, token);
74 // Only allow merging when selection is disabled
75 this._sceneBuilder.setAllowMerge(constants.Selection.NONE === selection);
76
77 this._renderer = new FluxRenderer(domParent, renderWidth, renderHeight, selection);
78 this._initCallback();
79
80 // Make sure to render on mouse over in case the renderer has swapped contexts
81 var _this = this;
82 domParent.addEventListener('mouseenter', function(){
83 _this.render();
84 });
85
86 // Cache of the Flux entity objects for downloading
87 this._entities = null;
88
89 this._latestSceneResults = null;
90
91 // Track the last blob that was downloaded for memory cleanup
92 this._downloadUrl = null;
93
94 // Whether the viewport is locked on the current geometry and will automatically focus on new geometry when updating the entities
95 this._autoFocus = true;
96
97 // Track whether geometry is being converted, so we don't try two at once
98 this.running = false;
99}
100
101FluxViewport.prototype = Object.create( THREE.EventDispatcher.prototype );
102FluxViewport.prototype.constructor = FluxViewport;
103
104/**
105 * Build the url to use for tessellation requests to flux.
106 * @private
107 * @param {String} projectId Hashed project id to use in url
108 * @return {String} Url to use for tessellation requests
109 */
110function _getTessUrl(projectId) {
111 return 'https://flux.io/p/'+projectId+
112 '/api/blockexec?block=flux-internal/parasolid/Parasolid';
113}
114
115/**
116 * @summary Enumeration of edges rendering modes.
117 * This determines whether edges will be shown when rendering the front face, back face or both.
118 * Front edge rendering can be used to achieve hidden line rendering.<br>
119 * Options are NONE, FRONT, BACK, BOTH
120 * @return {Object} enumeration
121 */
122FluxViewport.getEdgesModes = function () {
123 return EdgesHelper.EDGES_MODES;
124};
125
126/**
127 * @summary Enumeration of selection modes.
128 * This determines when selection events occur in response to mouse events.
129 * Options are NONE, CLICK, HOVER
130 * @return {Object} enumeration
131 */
132FluxViewport.getSelectionModes = function () {
133 return constants.Selection;
134};
135
136/**
137 * Name of the event fired when the camera changes
138 *
139 * This can be used to observe those changes and register a callback.<br>
140 * For example:
141 * fluxViewport.addEventListener(FluxViewport.getChangeEvent(), function() {});
142 * @return {String} Event name
143 */
144FluxViewport.getChangeEvent = function () {
145 return constants.Events.CHANGE;
146};
147
148/**
149 * Enumeration of different event types
150 * This can be used to differentiate events in the EventListener.
151 * fluxViewport.addEventListener(FluxViewport.getChangeEvent(), function(e) {
152 * FluxViewport.getEvents().SELECT === e.event;});
153 * @return {Object} Enumeration object
154 */
155FluxViewport.getEvents = function () {
156 return constants.Events;
157};
158
159/**
160 * Determines whether the entity or list of entities contains geometry
161 * @param {Array.<Object>|Object} entities Geometry data
162 * @return {Boolean} True for objects/lists containing geometry
163 */
164FluxViewport.isKnownGeom = function (entities) {
165 return scene.isGeometry(entities);
166};
167
168//---- Class member functions
169
170/**
171 * Set up the callback to render when the camera changes
172 * @private
173 */
174FluxViewport.prototype._initCallback = function() {
175 var _this = this;
176 this._renderer.addEventListener(constants.Events.CHANGE, function(event) {
177 _this.dispatchEvent(event);
178 _this.render();
179 });
180};
181
182/**
183 * Add a new plugin for user interaction controls.
184 * See ViewportControls.js for more information.
185 * @param {ViewportControls} CustomControls A constructor that implements the controls interface.
186 * @return {CustomControls} The new instance
187 */
188FluxViewport.prototype.addControls = function(CustomControls) {
189 return this._renderer.addControls(CustomControls);
190};
191
192
193/**
194 * Get an object that maps from id string to Three.Object3D in the current scene
195 * @return {Object} Id to object scene map
196 */
197FluxViewport.prototype.getObjectMap = function() {
198 return this._latestSceneResults.getObjectMap();
199};
200
201/**
202 * Get the currently selected geometry as a list of id strings
203 * @return {Array.<String>} Current selection
204 */
205FluxViewport.prototype.getSelection = function() {
206 var selectionMap = this._renderer.getSelection();
207 var keys = Object.keys(selectionMap);
208 var selection = [];
209 for (var i=0;i<keys.length;i++) {
210 var obj = selectionMap[keys[i]];
211 if (obj != null) {
212 selection.push(obj.userData.id);
213 }
214 }
215 return selection;
216};
217
218/**
219 * Define the material that is applied on selected objects
220 * @param {Object} data Flux json description of a material
221 */
222FluxViewport.prototype.setSelectionMaterial = function(data) {
223 this._renderer.setSelectionMaterial(data);
224};
225
226/**
227 * Set the currently selected geometry
228 * @param {Array.<String>} ids New selection
229 */
230FluxViewport.prototype.setSelection = function(ids) {
231 if (ids == null || ids.constructor !== Array) return;
232 var map = this._latestSceneResults.getObjectMap();
233 var objects = ids.map(function (id) { return map[id];});
234 this._renderer.setSelection(objects);
235};
236
237/**
238 * Get the JSON representation of the objects with the given ids
239 * @param {Array.<String>} ids List of object ids
240 * @return {Array.<Object>} List of Flux JSON objects
241 */
242FluxViewport.prototype.getJson = function(ids) {
243 if (ids == null || ids.constructor !== Array) return [];
244 var map = this._latestSceneResults.getObjectMap();
245 return ids.map(function (id) { return map[id].userData.data;});
246};
247
248/**
249 * Actually render the geometry!<br>
250 * This is called automatically when the camera changes.
251 * You may call it on demand as needed when changing properties.
252 * @memberOf FluxViewport
253 */
254FluxViewport.prototype.render = function() {
255 this._renderer.doRender();
256};
257
258/**
259 * Focus the camera on the current geometry
260 * @param {THREE.Object3D} [obj] Object to focus on
261 */
262FluxViewport.prototype.focus = function(obj) {
263 this._renderer.focus(obj);
264};
265
266/**
267 * Restore the camera to a default location
268 */
269FluxViewport.prototype.homeCamera = function() {
270 this._renderer.homeCamera();
271};
272
273/**
274 * Whether to draw helpers (axis and grid)
275 *
276 * @param {Boolean} visible False to hide them
277 */
278FluxViewport.prototype.setHelpersVisible = function(visible) {
279 this._renderer.setHelpersVisible(visible);
280};
281
282/**
283 * Set the viewport geomtery from a JSON string
284 * @param {String} dataString The geometry to render formatted as JSON containing Flux entities
285 * @return {Object} Promise to resolve after geometry is created
286 */
287FluxViewport.prototype.setGeometryJson = function(dataString) {
288 var dataObj = JSON.parse(dataString);
289 return this.setGeometryEntity(dataObj);
290};
291
292/**
293 * Set the viewport geometry from a data object containing Flux entities.
294 * See documentation for more explanation of <a href="https://community.flux.io/content/kbentry/2579/geometric-primitives.html">entities</a>
295 * and <a href="https://community.flux.io/content/kbentry/3087/what-is-a-scene.html">scene primitives</a>
296 * @param {Object} data The geometry entities to render
297 * @return {Object} Promise to resolve after geometry is created
298 */
299FluxViewport.prototype.setGeometryEntity = function(data) {
300 var _this = this;
301 // The flow sends the same value twice, so we assume that requests
302 // sent while there is already one pending are redundant
303 // TODO(Kyle): This is a hack that we can remove once there are not always duplicate requests
304 return new Promise(function (resolve, reject) {
305 if (!_this.running) {
306 _this.running = true;
307 return _this._sceneBuilder.convert(data).then(function (results) {
308 var object = results.getObject();
309 _this._entities = object ? data : null;
310 _this._updateModel(object);
311 _this._latestSceneResults = results;
312 _this.running = false;
313 resolve(results);
314 }).catch(function (err) {
315 console.warn(err); // eslint-disable-line no-console
316 _this.running = false;
317 });
318 } else {
319 reject(new Error('Already running. You can only convert one entity at a time.'));
320 }
321 }).catch(function (err) {
322 if (err.message.indexOf('running') === -1) {
323 console.log(err); // eslint-disable-line no-console
324 }
325 throw err;
326 });
327};
328
329/**
330 * Change the geometry being rendered
331 * @private
332 * @param {THREE.Object3D} newModel The new model to render
333 * @param {THREE.Object3D} oldModel The old model to remove
334 */
335FluxViewport.prototype._updateModel = function(newModel) {
336 this._renderer.setModel(newModel);
337 if (this._autoFocus) {
338 this.focus(); // changing the controls will trigger a render
339 this._autoFocus = false;
340 } else {
341 this.render();
342 }
343};
344
345/**
346 * Make serializable by pruning all references and building an object property tree
347 * @return {Object} Data to stringify
348 */
349FluxViewport.prototype.toJSON = function() {
350 var serializableState = {
351 entities: this._entities,
352 renderer: this._renderer.toJSON(),
353 autoFocus: this._autoFocus
354 };
355 return serializableState;
356};
357
358/**
359 * Take a data object and use it to update the viewport's internal state<br>
360 * Warning this is async when it sets entities
361 * @param {Object} state The properties to set
362 * @return {Promise} Completion promise
363 */
364FluxViewport.prototype.fromJSON = function(state) {
365 if (!state) return Promise.resolve();
366 var _this = this;
367 if (state.entities) {
368 return this.setGeometryEntity(state.entities).then(function () {
369 _this._fromJSONHelper(state);
370 });
371 } else {
372 this._fromJSONHelper(state);
373 return Promise.resolve();
374 }
375};
376
377/**
378 * Rehydrate everything but the entities.
379 * @private
380 * @param {Object} state Parameter data
381 */
382FluxViewport.prototype._fromJSONHelper = function(state) {
383 if (state.renderer != null) {
384 this._renderer.fromJSON(state.renderer);
385 }
386 if (state.autoFocus != null) {
387 this._autoFocus = state.autoFocus;
388 }
389};
390
391/**
392 * Download all the geometry settings and raster image that are the state of this viewport.
393 * Used for QA testing.
394 * @param {String} prefix File name prefix for download path
395 */
396FluxViewport.prototype.downloadState = function(prefix) {
397 this._downloadJson(this.toJSON(), prefix);
398 this._download(this._renderer.getGlCanvas().toDataURL('image/png'), prefix+'.png');
399};
400
401/**
402 * Helper function to download some data from a url
403 * @private
404 * @param {DOMString} dataUrl The url containing the data to download
405 * @param {String} filename The name of the file when it downloads
406 */
407FluxViewport.prototype._download = function(dataUrl, filename) {
408 var a = document.createElement('a');
409 a.href = dataUrl;
410 a.download = filename;
411 a.click();
412};
413
414/**
415 * Create a link and a temporary blob url to use to download from.
416 * @private
417 * @param {Object} data The serializable data to write as JSON
418 * @param {String} prefix The file name prefix
419 */
420FluxViewport.prototype._downloadJson = function(data, prefix) {
421 if (this._downloadUrl) {
422 window.URL.revokeObjectURL(this._downloadUrl);
423 }
424 var jsonString = JSON.stringify(data, null, 2);
425 this._downloadUrl = window.URL.createObjectURL(new Blob([jsonString]), {type: 'text/json'});
426 this._download(this._downloadUrl, prefix+'.json');
427};
428
429/**
430 * Create a default 3 light rig on the renderer's scene.
431 */
432FluxViewport.prototype.setupDefaultLighting = function() {
433 var lighting = new THREE.Object3D();
434 lighting.name = 'Lights';
435
436 //TODO(Kyle) non static lighting
437 this._keyLight = new THREE.DirectionalLight();
438 this._keyLight.position.set(60, 80, 50);
439 this._keyLight.intensity = 2.95;
440 lighting.add(this._keyLight);
441
442 var backLight = new THREE.DirectionalLight();
443 backLight.position.set(-250, 50, -200);
444 backLight.intensity = 1.7;
445 lighting.add(backLight);
446
447 var fillLight = new THREE.DirectionalLight();
448 fillLight.position.set(-500, -500, 0);
449 fillLight.intensity = 0.9;
450 lighting.add(fillLight);
451
452 this._renderer.setLights(lighting);
453};
454
455//---- Pass through functions
456
457/**
458 * Set the size of the render canvas
459 * @param {Number} width Pixels
460 * @param {Number} height Pixels
461 */
462FluxViewport.prototype.setSize = function(width, height) {
463 this._renderer.setSize(width, height);
464};
465
466/**
467 * Set the background color of the render canvas
468 * @param {THREE.color} color Background color
469 * @param {Number} alpha Opacity
470 */
471FluxViewport.prototype.setClearColor = function(color, alpha) {
472 this._renderer.setClearColor(color, alpha);
473};
474
475/**
476 * Set which camera view to use (ex perspective, top etc.).
477 * @param {FluxCameras.VIEWS} view The new view mode
478 */
479FluxViewport.prototype.setView = function(view) {
480 this._renderer.setView(view);
481 this.focus();
482};
483
484/**
485 * Return the views enumeration.
486 * Allows you to change the view to perspective, top, etc.
487 * @return {Object} Enumeration of view options for cameras
488 */
489FluxViewport.getViews = function() {
490 return FluxCameras.VIEWS;
491};
492
493/**
494 * Set the density of the exponential fog. Generally on the order of 0.0001
495 * @param {Number} density How much fog
496 */
497FluxViewport.prototype.setFogDensity = function(density) {
498 this._renderer._fog.density = density;
499};
500
501/**
502 * Set the url of the tessellation service.
503 * This can be used to replace the value if you did not set it on the constructor.
504 * @param {String} newUrl The url of the tessellation server
505 */
506FluxViewport.prototype.setTessUrl = function(newUrl) {
507 this._sceneBuilder.setTessUrl(newUrl);
508};
509
510/**
511 * Set whether the viewport should focus the geometry when it is changed
512 * @param {Boolean} focus Whether to auto focus
513 */
514FluxViewport.prototype.setAutoFocus = function(focus) {
515 this._autoFocus = focus;
516};
517
518/**
519 * Get whether the viewport will focus the geometry when it is changed
520 * @return {Boolean} Whether to auto focus
521 */
522FluxViewport.prototype.getAutoFocus = function() {
523 return this._autoFocus;
524};
525
526/**
527 * Set the edges rendering mode for hidden line rendering
528 * @param {FluxViewport.EDGES_MODES} mode Whether to render front, back, both or none
529 */
530FluxViewport.prototype.setEdgesMode = function(mode) {
531 this._renderer.setEdgesMode(mode);
532};
533
534/**
535 * Get the canvas for use in QA scripts
536 * @return {Canvas} WebGL canvas dom element
537 */
538FluxViewport.prototype.getGlCanvas = function() {
539 return this._renderer.getGlCanvas();
540};
541
542/**
543 * Turn on shadow rendering (not implemented)
544 */
545FluxViewport.prototype.activateShadows = function() {
546 print.warn('Shadows are not implemented yet');
547 // https://vannevar.atlassian.net/browse/LIB3D-97
548};