From 1def24ea4f3716c5e533ef694bde2ba63a74f4f7 Mon Sep 17 00:00:00 2001 From: ghemingway Date: Thu, 31 Dec 2015 15:04:40 -0600 Subject: [PATCH] Starting to figure out internal event dynamics. I think that I have settled on the external interface. At least for now...maybe. --- config/config.json | 2 +- src/client/models/assembly.js | 26 +-- src/client/models/cad_manager.js | 84 ++++++---- src/client/models/product.js | 10 +- src/client/models/shape.js | 100 ++++++------ src/client/views/cad/index.jsx | 179 ++++++++------------- src/client/views/cad/trackball_controls.js | 4 +- webpack.config.js | 12 +- 8 files changed, 186 insertions(+), 231 deletions(-) diff --git a/config/config.json b/config/config.json index f2b7636..d4114fb 100644 --- a/config/config.json +++ b/config/config.json @@ -5,7 +5,7 @@ "api_version": "v1", "redis": { "host": "192.168.99.100", - "port": 32768, + "port": 32769, "database": 0 }, "security": { diff --git a/src/client/models/assembly.js b/src/client/models/assembly.js index ca7a908..b6a3300 100644 --- a/src/client/models/assembly.js +++ b/src/client/models/assembly.js @@ -160,18 +160,6 @@ export default class Assembly extends THREE.EventDispatcher { } } - hideAllBoundingBoxes() { - this.dispatchEvent({ type: "_hideBounding" }); - } - - clearHighlights() { - this.dispatchEvent({ type: "_clearHighlights" }); - } - - clearOpacity() { - this.dispatchEvent({ type: "_clearOpacity" }); - } - getTree(root) { var node = { id : root, @@ -189,18 +177,6 @@ export default class Assembly extends THREE.EventDispatcher { return node; } - centerGeometry() { - if (this._product) { - var bbox = this._product.getBoundingBox(); - if (!bbox.empty()) { - var x = (bbox.max.x + bbox.min.x) / -2.0; - var y = (bbox.max.y + bbox.min.y) / -2.0; - var z = (bbox.max.z + bbox.min.z) / -2.0; - this._product.applyMatrix(new THREE.Matrix4().makeTranslation(x, y, z)); - } - } - } - select(camera, mouseX, mouseY) { if (!this._product) return undefined; var mouse = new THREE.Vector2(); @@ -226,7 +202,7 @@ export default class Assembly extends THREE.EventDispatcher { return object; } - explode(distance, timeS) { + explode(step) { } static buildBoundingBox(box) { diff --git a/src/client/models/cad_manager.js b/src/client/models/cad_manager.js index 9ddaa79..a9e2982 100644 --- a/src/client/models/cad_manager.js +++ b/src/client/models/cad_manager.js @@ -48,48 +48,44 @@ export default class CADManager extends THREE.EventDispatcher { this._loader.runLoadQueue(); } + centerModels() { + // TODO: Do we need to implement this? + // Reset all models to be centered on the origin + //if (this._product) { + // var bbox = this._product.getBoundingBox(); + // if (!bbox.empty()) { + // var x = (bbox.max.x + bbox.min.x) / -2.0; + // var y = (bbox.max.y + bbox.min.y) / -2.0; + // var z = (bbox.max.z + bbox.min.z) / -2.0; + // this._product.applyMatrix(new THREE.Matrix4().makeTranslation(x, y, z)); + // } + //} + } + bindEvents() { var self = this; // Set up handling of load events - pass them from the data-loader on - var handler = function(event) { + var loaderEventHandler = function(event) { self.dispatchEvent(event); }; - var selectHandler = function(event) { - var ids = event.id.split(':'); - var model = self._models[ids[0]]; - if (model) { - var obj = model.getByID(ids[1]); - if (!event.meta) { - model.hideAllBoundingBoxes(); - } - if (obj) { - obj.showBoundingBox(); - } else { - model.showBoundingBox(); - } - self.dispatchEvent({ type: 'invalidate', options: { tree: true } } ); - } - }; - - var msgHandler = function(event) { + var modelsEventHandler = function(event) { var keys = _.keys(this._models); _.each(keys, function(key) { self._models[key].dispatchEvent(event); }); }; - - this._loader.addEventListener("addRequest", handler); - this._loader.addEventListener("loadComplete", handler); - this._loader.addEventListener("parseComplete", handler); - this._loader.addEventListener("shellLoad", handler); - this._loader.addEventListener("workerFinish", handler); - this._loader.addEventListener("loadProgress", handler); + // Rebroadcast data loader events + this._loader.addEventListener("addRequest", loaderEventHandler); + this._loader.addEventListener("loadComplete", loaderEventHandler); + this._loader.addEventListener("parseComplete", loaderEventHandler); + this._loader.addEventListener("shellLoad", loaderEventHandler); + this._loader.addEventListener("workerFinish", loaderEventHandler); + this._loader.addEventListener("loadProgress", loaderEventHandler); // Listen for someone asking for stuff - this.addEventListener("select", selectHandler); - this.addEventListener("visibility", msgHandler); - this.addEventListener("opacity", msgHandler); - this.addEventListener("explode", msgHandler); + this.addEventListener("clear:selected", modelsEventHandler); + this.addEventListener("clear:highlights", modelsEventHandler); + // Setup socket callbacks this.onDelta = this.onDelta.bind(this); if (this.config.socket && this.socket) { @@ -97,11 +93,39 @@ export default class CADManager extends THREE.EventDispatcher { } } + clearSelected() { + this.dispatchEvent({ type: 'clear:selected' }); + } + + clearHighlights() { + this.dispatchEvent({ type: 'clear:highlights' }); + } + + toggleOpacity() {} + + toggleVisibility() {} + + explode(step) {} + + getSelected() { return []; } + getTree() { + // TODO: Needs to handle multiple models at once var keys = _.keys(this._models); return keys.length > 0 ? this._models[keys[0]].getTree(keys[0]) : {}; } + modelCount() { + return _.size(this._models); + } + + hitTest(camera, event) { + return _.reduce(this._models, function(memo, model) { + var val = model.select(camera, event.clientX, event.clientY); + return memo || val; + }, undefined); + } + onDelta(delta) { var self = this; var keys = _.keys(this._models); diff --git a/src/client/models/product.js b/src/client/models/product.js index f7e0f79..e7ca650 100644 --- a/src/client/models/product.js +++ b/src/client/models/product.js @@ -17,19 +17,11 @@ export default class Product extends THREE.EventDispatcher { this._name = name; this._isRoot = isRoot; this._shapes = []; - this._selected = false; + //this._selected = false; this._children = []; this._object3D = new THREE.Object3D(); this._overlay3D = new THREE.Object3D(); this._annotation3D = new THREE.Object3D(); - // Handle broadcast events - var self = this; - this._assembly.addEventListener('opacity', function() { - if (self._selected) self.toggleTransparency() - }); - this._assembly.addEventListener('visibility', function() { - if (self._selected) self.toggleVisibility(); - }); return this; } diff --git a/src/client/models/shape.js b/src/client/models/shape.js index 1ee33b1..96dac6b 100644 --- a/src/client/models/shape.js +++ b/src/client/models/shape.js @@ -17,7 +17,14 @@ export default class Shape extends THREE.EventDispatcher { this._parent = parent; this._unit = unit; this._instances = []; - this._selected = false; + this.state = { + selected: false, + highlighted: false, + visible: true, + opacity: 1.0, + explodeDistance: 0 + }; + this.processModelEvent = this.processModelEvent.bind(this); if (!ret) { // If we are here, this is the first one this._instanceID = 0; @@ -46,13 +53,13 @@ export default class Shape extends THREE.EventDispatcher { // Handle broadcast events var self = this; this._assembly.addEventListener('opacity', function() { - if (self._selected) self.toggleTransparency() + if (self.state.selected) self.toggleTransparency() }); this._assembly.addEventListener('visibility', function() { - if (self._selected) self.toggleVisibility(); + if (self.state.selected) self.toggleVisibility(); }); this._assembly.addEventListener('explode', function(event) { - if (self._selected) self.explode(event.step); + if (self.state.selected) self.explode(event.step); }); } @@ -163,10 +170,6 @@ export default class Shape extends THREE.EventDispatcher { return this._annotation3D; } - getName() { - return "Shape"; - } - getID() { return this._id + "_" + this._instanceID; } @@ -231,7 +234,7 @@ export default class Shape extends THREE.EventDispatcher { collapsed : this._instanceID === 0, state: { disabled: false, - selected: this._selected + selected: this.state.selected }, children: children }; @@ -260,6 +263,21 @@ export default class Shape extends THREE.EventDispatcher { return bounds; } + processModelEvent(event) { + var [type, method] = event.type.split(':'); + if (method === "selected" && this.state.selected) { + this.unselect(); + // Clear highlight via event + } else if (method === "highlights" && this.state.highlighted) { + this._object3D.traverse(function (object) { + if (object.material && object.material.uniforms.tint) { + object.material.uniforms.tint.value.setW(0); + } + }); + this._assembly.removeEventListener("clear:highlights", this.processModelEvent); + } + } + toggleVisibility() { if (this._object3D.visible) { this.hide(); @@ -309,15 +327,14 @@ export default class Shape extends THREE.EventDispatcher { highlight(colorHex) { var self = this; this._object3D.traverse(function (object) { - var color; if (object.material && object.material.uniforms.tint) { - color = new THREE.Color(colorHex); + self.state.highlighted = true; + var color = new THREE.Color(colorHex); object.material.uniforms.tint.value.set(color.r, color.g, color.b, 0.3); - self._assembly.addEventListener("_clearHighlights", function () { - object.material.uniforms.tint.value.setW(0); - }); } }); + // Start listening for a clear message + this._assembly.addEventListener("clear:highlights", this.processModelEvent); } showAnnotations() { @@ -342,28 +359,32 @@ export default class Shape extends THREE.EventDispatcher { }); } - showBoundingBox() { - this._selected = true; - var bounds = this.getBoundingBox(false); - if (!this.bbox && !bounds.empty()) { - this.bbox = Assembly.buildBoundingBox(bounds); - } - if (this.bbox) { - var self = this; - this._eventFunc = function () { - self.hideBoundingBox(); - }; - // Start listening for assembly _hideBounding events - this._assembly.addEventListener("_hideBounding", this._eventFunc); - this._overlay3D.add(this.bbox); + toggleSelection() { + // On deselection + if(this.state.selected) { + this.unselect(); + // On selection + } else { + var bounds = this.getBoundingBox(false); + if (!this.bbox && !bounds.empty()) { + this.bbox = Assembly.buildBoundingBox(bounds); + } + if (this.bbox) { + // Start listening for assembly clear events + this._assembly.addEventListener("clear:selected", this.processModelEvent); + // Add the BBox to our overlay object + this._overlay3D.add(this.bbox); + this.showAnnotations(); + // Flip state + this.state.selected = true; + } } - this.showAnnotations(); } - hideBoundingBox() { - this._selected = false; - // Stop listening for assembly _hideBounding events - this._assembly.removeEventListener("_hideBounding", this._eventFunc); + unselect() { + this.state.selected = false; + // Stop listening for assembly clear events + this._assembly.removeEventListener("clear:selected", this.processModelEvent); this._overlay3D.remove(this.bbox); this.hideAnnotations(); } @@ -401,10 +422,6 @@ export default class Shape extends THREE.EventDispatcher { this._explodeStates[child.getID()] = childDirection; // this._object3D.add( new THREE.ArrowHelper(childDirection, childCenter, 1000.0, 0xff0000, 20, 10) ); } - // After all children are loaded - start listening for assembly events -// this._assembly.addEventListener("_updateAnimation", function() { -// self._updateAnimation(); -// }); } // Make sure explosion distance does not go negative if (this._explodeDistance + distance < 0) { @@ -424,15 +441,6 @@ export default class Shape extends THREE.EventDispatcher { } } - _explodeStep(distance, step) { - - } - - _updateAnimation() { - if (this._explodeStepRemain > 0) { - } - } - resetExplode() { if (this._explodeDistance) { // Explode by the negative distance diff --git a/src/client/views/cad/index.jsx b/src/client/views/cad/index.jsx index 98079bc..4cff01c 100644 --- a/src/client/views/cad/index.jsx +++ b/src/client/views/cad/index.jsx @@ -5,6 +5,7 @@ "use strict"; +var _ = require('lodash'); import React from 'react'; import ViewerControls from './viewer_controls'; import CompassView from '../compass/compass'; @@ -27,8 +28,7 @@ export default class CADViewer extends React.Component { super(props); this.state = { modelTree: {}, - selected: [], - change: true + isViewChanging: false }; this.handleResize = this.handleResize.bind(this); this.onShellLoad = this.onShellLoad.bind(this); @@ -41,16 +41,17 @@ export default class CADViewer extends React.Component { } onShellLoad(event) { - this.state.change = false; + // Get around the fact that viewerControls calls change a bunch at startup + this.state.isViewChanging = false; this.invalidate(event); } onModelAdd(event) { var model = this.props.manager._models[event.path]; // Add the model to the scene - this.add3DObject(model.getObject3D(), 'geometry'); - this.add3DObject(model.getOverlay3D(), 'overlay'); - this.add3DObject(model.getAnnotation3D(), 'annotation'); + this.annotationScene.add( model.getAnnotation3D()); + this.geometryScene.add( model.getObject3D()); + this.overlayScene.add( model.getOverlay3D()); // calculate the scene's radius for draw distance calculations this.updateSceneBoundingBox(model.getBoundingBox()); // center the view @@ -62,6 +63,7 @@ export default class CADViewer extends React.Component { onModelRemove(event) { console.log('ModelRemove: ' + event.path); + // TODO: Need to do anything here? // Update the model tree var tree = this.props.manager.getTree(); this.setState({ modelTree: tree }); @@ -72,52 +74,34 @@ export default class CADViewer extends React.Component { switch(event.keyCode || event.charCode || event.which) { // Explode on 'x' key pressed case 120: - //this.explode(this.manager.getExplodeDistance()); - this.props.manager.dispatchEvent({ type: 'explode', step: 10 }); - this.invalidate(); + this.props.manager.explode(10); break; // Unexplode on 's' key pressed case 115: - //this.explode(-this.manager.getExplodeDistance()); - this.props.manager.dispatchEvent({ type: 'explode', step: -10 }); - this.invalidate(); + this.props.manager.explode(-10); break; // 'q' unselects all tree elements case 113: - //this._parts[0].hideAllBoundingBoxes(); - //this.tree.deselect_all(); - //this.invalidate(); + this.props.manager.clearSelected(); break; // 'o' to toggle transparency case 111: - this.props.manager.dispatchEvent({ type: 'opacity' }); - this.invalidate(); + this.props.manager.toggleOpacity(); break; // 'z' to zoomToFit case 122: - //node = this.tree.get_selected(false); - //obj = this._parts[0].getByID(node[0]); - //if (!obj) { - // obj = this._parts[0]; - //} - //this.zoomToFit(obj); + var objs = this.props.manager.getSelected(); + this.zoomToFit(objs); break; // 'j' hide/show element case 106: - this.props.manager.dispatchEvent({ type: 'visibility' }); - this.invalidate(); + this.props.manager.toggleVisibility(); break; } + this.invalidate(); } componentWillMount() { - var self = this; - this.renderTargetParametersRGBA = { - minFilter: THREE.LinearFilter, - magFilter: THREE.LinearFilter, - format: THREE.RGBAFormat - }; - this.sceneCenter = new THREE.Vector3(0,0,0); this.sceneRadius = 10000; this.props.manager.addEventListener("model:add", this.onModelAdd); @@ -181,8 +165,7 @@ export default class CADViewer extends React.Component { // CONTROL EVENT HANDLERS this.controls.addEventListener('change', function() { - console.log('Controls.change: ' + self.state.change); - self.state.change = true; + self.state.isViewChanging = true; var x0 = self.sceneCenter, x1 = self.camera.position, x2 = self.controls.target, @@ -196,14 +179,12 @@ export default class CADViewer extends React.Component { self.invalidate(); }); this.controls.addEventListener("start", function() { - console.log('Controls.start: ' + self.state.change); self.continuousRendering = true; - self.state.change = false; }); this.controls.addEventListener("end", function() { - console.log('Controls.end: ' + self.state.change); self.invalidate(); self.continuousRendering = false; + self.state.isViewChanging = false; }); // SCREEN RESIZE @@ -217,7 +198,7 @@ export default class CADViewer extends React.Component { window.removeEventListener("keypress", this.onKeypress); this.props.manager.removeEventListener("model:add", this.onModelAdd); this.props.manager.removeEventListener("model:remove", this.onModelRemove); - this.props.manager.removeEventListener("shellLoad", this.onShellLoad); + this.props.manager.removeEventListener("shellLoad", this.invalidate); this.props.manager.removeEventListener("invalidate", this.invalidate); } @@ -231,10 +212,16 @@ export default class CADViewer extends React.Component { this.drawScene(); } - zoomToFit(object) { - var object3d = object.getObject3D(), - boundingBox = object.getBoundingBox(), - radius = boundingBox.size().length() * 0.5, + zoomToFit(objects) { + var boundingBox, object3d, size = _.size(objects); + if (size === 0) return; + else if (size === 1) { + object3d = objects.getObject3D(); + boundingBox = objects.getBoundingBox(); + } else if (size > 1) { + + } + var radius = boundingBox.size().length() * 0.5, horizontalFOV = 2 * Math.atan(THREE.Math.degToRad(this.camera.fov * 0.5) * this.camera.aspect), fov = Math.min(THREE.Math.degToRad(this.camera.fov), horizontalFOV), dist = radius / Math.sin(fov * 0.5), @@ -287,85 +274,57 @@ export default class CADViewer extends React.Component { this.shouldRender = true; } - add3DObject(a3DObject, sceneName) { - switch(sceneName) { - case 'overlay': - this.overlayScene.add(a3DObject); - break; - case 'annotation': - this.annotationScene.add(a3DObject); - break; - case 'geometry': - default: - this.geometryScene.add(a3DObject); - break; - } - this.invalidate(); - } - updateSceneBoundingBox(newBoundingBox) { this.sceneCenter.copy(newBoundingBox.center()); this.sceneRadius = newBoundingBox.size().length() / 2; }; - onClick(event) { - var self = this, tree; - if (_.size(this.props.manager._models) === 0) { - return; - } - // Clear selections if meta key not pressed - if (!event.metaKey) { - _.each(this.props.manager._models, function(model) { - model.hideAllBoundingBoxes(); - }); - // Update the model tree - tree = this.props.manager.getTree(); - this.setState({ modelTree: tree }); - } - var obj = _.reduce(this.props.manager._models, function(memo, model) { - var val = model.select(self.camera, event.clientX, event.clientY); - return memo || val; - }, undefined); - // Did we find an object - if (obj) { - obj = obj.getNamedParent(); - // Show the bounding box - obj.showBoundingBox(); - // Update the model tree - tree = this.props.manager.getTree(); - this.setState({ modelTree: tree }); - } - this.invalidate(); - } - onMouseUp(event) { - console.log('MouseUp: ' + this.state.change); - if (!this.state.change) { - this.onClick(event); + if (!this.state.isViewChanging && this.props.manager.modelCount() > 0) { + var change = false; + // Clear selections if meta key not pressed + if (!event.metaKey) { + // Clear all currently selected objects + this.props.manager.clearSelected(); + change = true; + } + var obj = this.props.manager.hitTest(this.camera, event); + // Did we find an object + if (obj) { + obj = obj.getNamedParent(); + // Toggle the bounding box + obj.toggleSelection(); + change = true; + } + if (change) { + // Update the model tree + var tree = this.props.manager.getTree(); + this.setState({ modelTree: tree }); + this.invalidate(); + } } - this.state.change = false; } onMouseMove(event) { - console.log('MouseMove: ' + this.state.change); - if (!this.state.change) { - var obj, self = this; - if (_.size(this.props.manager._models) > 0) { - _.each(this.props.manager._models, function(model) { - model.clearHighlights(); - }); - obj = _.reduce(this.props.manager._models, function(memo, model) { - var val = model.select(self.camera, event.clientX, event.clientY); - return memo || val; - }, undefined); - // Did we find an object - if (obj) { - obj = obj.getNamedParent(); - // Yes, go highlight it in the tree - obj.highlight(0xffff60); - } - } + if (!this.state.isViewChanging && this.props.manager.modelCount() > 0) { + var obj = this.props.manager.hitTest(this.camera, event); + var change = false; if (obj != this._lastHovered) { + // Clear existing highlight + this.props.manager.clearHighlights(); + change = true; + } + // Did we find an object + if (obj) { + obj = obj.getNamedParent(); + // Yes, go highlight it in the tree + obj.highlight(0xffff60); + change = true; + } + // Update the model tree and redraw if things have changed + if (change) { + var tree = this.props.manager.getTree(); + this.setState({ modelTree: tree }); this.invalidate(); } this._lastHovered = obj; diff --git a/src/client/views/cad/trackball_controls.js b/src/client/views/cad/trackball_controls.js index f1c755c..14f6280 100644 --- a/src/client/views/cad/trackball_controls.js +++ b/src/client/views/cad/trackball_controls.js @@ -320,7 +320,7 @@ THREE.TrackballControls = function ( object, domElement ) { _this.object.lookAt( _this.target ); if ( lastPosition.distanceToSquared( _this.object.position ) > EPS ) { - console.log('update'); + _this.dispatchEvent( changeEvent ); lastPosition.copy( _this.object.position ); @@ -341,7 +341,7 @@ THREE.TrackballControls = function ( object, domElement ) { _eye.subVectors( _this.object.position, _this.target ); _this.object.lookAt( _this.target ); - console.log('reset'); + _this.dispatchEvent( changeEvent ); lastPosition.copy( _this.object.position ); diff --git a/webpack.config.js b/webpack.config.js index 857af98..965f430 100644 --- a/webpack.config.js +++ b/webpack.config.js @@ -6,7 +6,7 @@ var path = require("path"), module.exports = { cache: true, debug: true, - devtool: 'source-map', + devtool: 'inline-source-map', sourceMapFileName: "[file].map", context: path.join(__dirname, "/src/client"), entry: { @@ -26,12 +26,8 @@ module.exports = { // required to write "require('./style.scss')" { test: /\.scss$/, - loader: ExtractTextPlugin.extract("style-loader", "css-loader!sass-loader") + loader: ExtractTextPlugin.extract("css?sourceMap!sass?sourceMap") }, - //{ - // test: /\.css$/, - // loaders: ["style?sourceMap", "css?sourceMap"] - //}, { test: /\.png$/, loader: "url-loader?mimetype=image/png" }, { test: /\.gif$/, loader: "url-loader?mimetype=image/gif" }, // required for bootstrap icons @@ -66,7 +62,7 @@ module.exports = { "jQuery": "jquery", "Backbone": "backbone", "THREE": "three" - }), - new ExtractTextPlugin("[name].css") + }) + ,new ExtractTextPlugin("[name].css") ] };