diff --git a/lib/TrackballControls.js b/lib/TrackballControls.js new file mode 100644 index 0000000..d110778 --- /dev/null +++ b/lib/TrackballControls.js @@ -0,0 +1,754 @@ +import { + EventDispatcher, + MOUSE, + Quaternion, + Vector2, + Vector3 +} from '../../../build/three.module.js'; + +var TrackballControls = function ( object, domElement ) { + + if ( domElement === undefined ) console.warn( 'THREE.TrackballControls: The second parameter "domElement" is now mandatory.' ); + if ( domElement === document ) console.error( 'THREE.TrackballControls: "document" should not be used as the target "domElement". Please use "renderer.domElement" instead.' ); + + var scope = this; + var STATE = { NONE: - 1, ROTATE: 0, ZOOM: 1, PAN: 2, TOUCH_ROTATE: 3, TOUCH_ZOOM_PAN: 4 }; + + this.object = object; + this.domElement = domElement; + + // API + + this.enabled = true; + + this.screen = { left: 0, top: 0, width: 0, height: 0 }; + + this.rotateSpeed = 1.0; + this.zoomSpeed = 1.2; + this.panSpeed = 0.3; + + this.noRotate = false; + this.noZoom = false; + this.noPan = false; + + this.staticMoving = false; + this.dynamicDampingFactor = 0.2; + + this.minDistance = 0; + this.maxDistance = Infinity; + + this.keys = [ 65 /*A*/, 83 /*S*/, 68 /*D*/ ]; + + this.mouseButtons = { LEFT: MOUSE.ROTATE, MIDDLE: MOUSE.DOLLY, RIGHT: MOUSE.PAN }; + + // internals + + this.target = new Vector3(); + + var EPS = 0.000001; + + var lastPosition = new Vector3(); + var lastZoom = 1; + + var _state = STATE.NONE, + _keyState = STATE.NONE, + + _eye = new Vector3(), + + _movePrev = new Vector2(), + _moveCurr = new Vector2(), + + _lastAxis = new Vector3(), + _lastAngle = 0, + + _zoomStart = new Vector2(), + _zoomEnd = new Vector2(), + + _touchZoomDistanceStart = 0, + _touchZoomDistanceEnd = 0, + + _panStart = new Vector2(), + _panEnd = new Vector2(); + + // for reset + + this.target0 = this.target.clone(); + this.position0 = this.object.position.clone(); + this.up0 = this.object.up.clone(); + this.zoom0 = this.object.zoom; + + // events + + var changeEvent = { type: 'change' }; + var startEvent = { type: 'start' }; + var endEvent = { type: 'end' }; + + + // methods + + this.handleResize = function () { + + var box = scope.domElement.getBoundingClientRect(); + // adjustments come from similar code in the jquery offset() function + var d = scope.domElement.ownerDocument.documentElement; + scope.screen.left = box.left + window.pageXOffset - d.clientLeft; + scope.screen.top = box.top + window.pageYOffset - d.clientTop; + scope.screen.width = box.width; + scope.screen.height = box.height; + + }; + + var getMouseOnScreen = ( function () { + + var vector = new Vector2(); + + return function getMouseOnScreen( pageX, pageY ) { + + vector.set( + ( pageX - scope.screen.left ) / scope.screen.width, + ( pageY - scope.screen.top ) / scope.screen.height + ); + + return vector; + + }; + + }() ); + + var getMouseOnCircle = ( function () { + + var vector = new Vector2(); + + return function getMouseOnCircle( pageX, pageY ) { + + vector.set( + ( ( pageX - scope.screen.width * 0.5 - scope.screen.left ) / ( scope.screen.width * 0.5 ) ), + ( ( scope.screen.height + 2 * ( scope.screen.top - pageY ) ) / scope.screen.width ) // screen.width intentional + ); + + return vector; + + }; + + }() ); + + this.rotateCamera = ( function () { + + var axis = new Vector3(), + quaternion = new Quaternion(), + eyeDirection = new Vector3(), + objectUpDirection = new Vector3(), + objectSidewaysDirection = new Vector3(), + moveDirection = new Vector3(), + angle; + + return function rotateCamera() { + + moveDirection.set( _moveCurr.x - _movePrev.x, _moveCurr.y - _movePrev.y, 0 ); + angle = moveDirection.length(); + + if ( angle ) { + + _eye.copy( scope.object.position ).sub( scope.target ); + + eyeDirection.copy( _eye ).normalize(); + objectUpDirection.copy( scope.object.up ).normalize(); + objectSidewaysDirection.crossVectors( objectUpDirection, eyeDirection ).normalize(); + + objectUpDirection.setLength( _moveCurr.y - _movePrev.y ); + objectSidewaysDirection.setLength( _moveCurr.x - _movePrev.x ); + + moveDirection.copy( objectUpDirection.add( objectSidewaysDirection ) ); + + axis.crossVectors( moveDirection, _eye ).normalize(); + + angle *= scope.rotateSpeed; + quaternion.setFromAxisAngle( axis, angle ); + + _eye.applyQuaternion( quaternion ); + scope.object.up.applyQuaternion( quaternion ); + + _lastAxis.copy( axis ); + _lastAngle = angle; + + } else if ( ! scope.staticMoving && _lastAngle ) { + + _lastAngle *= Math.sqrt( 1.0 - scope.dynamicDampingFactor ); + _eye.copy( scope.object.position ).sub( scope.target ); + quaternion.setFromAxisAngle( _lastAxis, _lastAngle ); + _eye.applyQuaternion( quaternion ); + scope.object.up.applyQuaternion( quaternion ); + + } + + _movePrev.copy( _moveCurr ); + + }; + + }() ); + + + this.zoomCamera = function () { + + var factor; + + if ( _state === STATE.TOUCH_ZOOM_PAN ) { + + factor = _touchZoomDistanceStart / _touchZoomDistanceEnd; + _touchZoomDistanceStart = _touchZoomDistanceEnd; + + if ( scope.object.isPerspectiveCamera ) { + + _eye.multiplyScalar( factor ); + + } else if ( scope.object.isOrthographicCamera ) { + + scope.object.zoom *= factor; + scope.object.updateProjectionMatrix(); + + } else { + + console.warn( 'THREE.TrackballControls: Unsupported camera type' ); + + } + + } else { + + factor = 1.0 + ( _zoomEnd.y - _zoomStart.y ) * scope.zoomSpeed; + + if ( factor !== 1.0 && factor > 0.0 ) { + + if ( scope.object.isPerspectiveCamera ) { + + _eye.multiplyScalar( factor ); + + } else if ( scope.object.isOrthographicCamera ) { + + scope.object.zoom /= factor; + scope.object.updateProjectionMatrix(); + + } else { + + console.warn( 'THREE.TrackballControls: Unsupported camera type' ); + + } + + } + + if ( scope.staticMoving ) { + + _zoomStart.copy( _zoomEnd ); + + } else { + + _zoomStart.y += ( _zoomEnd.y - _zoomStart.y ) * this.dynamicDampingFactor; + + } + + } + + }; + + this.panCamera = ( function () { + + var mouseChange = new Vector2(), + objectUp = new Vector3(), + pan = new Vector3(); + + return function panCamera() { + + mouseChange.copy( _panEnd ).sub( _panStart ); + + if ( mouseChange.lengthSq() ) { + + if ( scope.object.isOrthographicCamera ) { + + var scale_x = ( scope.object.right - scope.object.left ) / scope.object.zoom / scope.domElement.clientWidth; + var scale_y = ( scope.object.top - scope.object.bottom ) / scope.object.zoom / scope.domElement.clientWidth; + + mouseChange.x *= scale_x; + mouseChange.y *= scale_y; + + } + + mouseChange.multiplyScalar( _eye.length() * scope.panSpeed ); + + pan.copy( _eye ).cross( scope.object.up ).setLength( mouseChange.x ); + pan.add( objectUp.copy( scope.object.up ).setLength( mouseChange.y ) ); + + scope.object.position.add( pan ); + scope.target.add( pan ); + + if ( scope.staticMoving ) { + + _panStart.copy( _panEnd ); + + } else { + + _panStart.add( mouseChange.subVectors( _panEnd, _panStart ).multiplyScalar( scope.dynamicDampingFactor ) ); + + } + + } + + }; + + }() ); + + this.checkDistances = function () { + + if ( ! scope.noZoom || ! scope.noPan ) { + + if ( _eye.lengthSq() > scope.maxDistance * scope.maxDistance ) { + + scope.object.position.addVectors( scope.target, _eye.setLength( scope.maxDistance ) ); + _zoomStart.copy( _zoomEnd ); + + } + + if ( _eye.lengthSq() < scope.minDistance * scope.minDistance ) { + + scope.object.position.addVectors( scope.target, _eye.setLength( scope.minDistance ) ); + _zoomStart.copy( _zoomEnd ); + + } + + } + + }; + + this.update = function () { + + _eye.subVectors( scope.object.position, scope.target ); + + if ( ! scope.noRotate ) { + + scope.rotateCamera(); + + } + + if ( ! scope.noZoom ) { + + scope.zoomCamera(); + + } + + if ( ! scope.noPan ) { + + scope.panCamera(); + + } + + scope.object.position.addVectors( scope.target, _eye ); + + if ( scope.object.isPerspectiveCamera ) { + + scope.checkDistances(); + + scope.object.lookAt( scope.target ); + + if ( lastPosition.distanceToSquared( scope.object.position ) > EPS ) { + + scope.dispatchEvent( changeEvent ); + + lastPosition.copy( scope.object.position ); + + } + + } else if ( scope.object.isOrthographicCamera ) { + + scope.object.lookAt( scope.target ); + + if ( lastPosition.distanceToSquared( scope.object.position ) > EPS || lastZoom !== scope.object.zoom ) { + + scope.dispatchEvent( changeEvent ); + + lastPosition.copy( scope.object.position ); + lastZoom = scope.object.zoom; + + } + + } else { + + console.warn( 'THREE.TrackballControls: Unsupported camera type' ); + + } + + }; + + this.reset = function () { + + _state = STATE.NONE; + _keyState = STATE.NONE; + + scope.target.copy( scope.target0 ); + scope.object.position.copy( scope.position0 ); + scope.object.up.copy( scope.up0 ); + scope.object.zoom = scope.zoom0; + + scope.object.updateProjectionMatrix(); + + _eye.subVectors( scope.object.position, scope.target ); + + scope.object.lookAt( scope.target ); + + scope.dispatchEvent( changeEvent ); + + lastPosition.copy( scope.object.position ); + lastZoom = scope.object.zoom; + + }; + + // listeners + + function onPointerDown( event ) { + + if ( scope.enabled === false ) return; + + switch ( event.pointerType ) { + + case 'mouse': + case 'pen': + onMouseDown( event ); + break; + + // TODO touch + + } + + } + + function onPointerMove( event ) { + + if ( scope.enabled === false ) return; + + switch ( event.pointerType ) { + + case 'mouse': + case 'pen': + onMouseMove( event ); + break; + + // TODO touch + + } + + } + + function onPointerUp( event ) { + + if ( scope.enabled === false ) return; + + switch ( event.pointerType ) { + + case 'mouse': + case 'pen': + onMouseUp( event ); + break; + + // TODO touch + + } + + } + + function keydown( event ) { + + if ( scope.enabled === false ) return; + + window.removeEventListener( 'keydown', keydown ); + + if ( _keyState !== STATE.NONE ) { + + return; + + } else if ( event.keyCode === scope.keys[ STATE.ROTATE ] && ! scope.noRotate ) { + + _keyState = STATE.ROTATE; + + } else if ( event.keyCode === scope.keys[ STATE.ZOOM ] && ! scope.noZoom ) { + + _keyState = STATE.ZOOM; + + } else if ( event.keyCode === scope.keys[ STATE.PAN ] && ! scope.noPan ) { + + _keyState = STATE.PAN; + + } + + } + + function keyup() { + + if ( scope.enabled === false ) return; + + _keyState = STATE.NONE; + + window.addEventListener( 'keydown', keydown ); + + } + + function onMouseDown( event ) { + + event.preventDefault(); + event.stopPropagation(); + + if ( _state === STATE.NONE ) { + + switch ( event.button ) { + + case scope.mouseButtons.LEFT: + _state = STATE.ROTATE; + break; + + case scope.mouseButtons.MIDDLE: + _state = STATE.ZOOM; + break; + + case scope.mouseButtons.RIGHT: + _state = STATE.PAN; + break; + + default: + _state = STATE.NONE; + + } + + } + + var state = ( _keyState !== STATE.NONE ) ? _keyState : _state; + + if ( state === STATE.ROTATE && ! scope.noRotate ) { + + _moveCurr.copy( getMouseOnCircle( event.pageX, event.pageY ) ); + _movePrev.copy( _moveCurr ); + + } else if ( state === STATE.ZOOM && ! scope.noZoom ) { + + _zoomStart.copy( getMouseOnScreen( event.pageX, event.pageY ) ); + _zoomEnd.copy( _zoomStart ); + + } else if ( state === STATE.PAN && ! scope.noPan ) { + + _panStart.copy( getMouseOnScreen( event.pageX, event.pageY ) ); + _panEnd.copy( _panStart ); + + } + + scope.domElement.ownerDocument.addEventListener( 'pointermove', onPointerMove ); + scope.domElement.ownerDocument.addEventListener( 'pointerup', onPointerUp ); + + scope.dispatchEvent( startEvent ); + + } + + function onMouseMove( event ) { + + if ( scope.enabled === false ) return; + + event.preventDefault(); + event.stopPropagation(); + + var state = ( _keyState !== STATE.NONE ) ? _keyState : _state; + + if ( state === STATE.ROTATE && ! scope.noRotate ) { + + _movePrev.copy( _moveCurr ); + _moveCurr.copy( getMouseOnCircle( event.pageX, event.pageY ) ); + + } else if ( state === STATE.ZOOM && ! scope.noZoom ) { + + _zoomEnd.copy( getMouseOnScreen( event.pageX, event.pageY ) ); + + } else if ( state === STATE.PAN && ! scope.noPan ) { + + _panEnd.copy( getMouseOnScreen( event.pageX, event.pageY ) ); + + } + + } + + function onMouseUp( event ) { + + if ( scope.enabled === false ) return; + + event.preventDefault(); + event.stopPropagation(); + + _state = STATE.NONE; + + scope.domElement.ownerDocument.removeEventListener( 'pointermove', onPointerMove ); + scope.domElement.ownerDocument.removeEventListener( 'pointerup', onPointerUp ); + + scope.dispatchEvent( endEvent ); + + } + + function mousewheel( event ) { + + if ( scope.enabled === false ) return; + + if ( scope.noZoom === true ) return; + + event.preventDefault(); + event.stopPropagation(); + + switch ( event.deltaMode ) { + + case 2: + // Zoom in pages + _zoomStart.y -= event.deltaY * 0.025; + break; + + case 1: + // Zoom in lines + _zoomStart.y -= event.deltaY * 0.01; + break; + + default: + // undefined, 0, assume pixels + _zoomStart.y -= event.deltaY * 0.00025; + break; + + } + + scope.dispatchEvent( startEvent ); + scope.dispatchEvent( endEvent ); + + } + + function touchstart( event ) { + + if ( scope.enabled === false ) return; + + event.preventDefault(); + + switch ( event.touches.length ) { + + case 1: + _state = STATE.TOUCH_ROTATE; + _moveCurr.copy( getMouseOnCircle( event.touches[ 0 ].pageX, event.touches[ 0 ].pageY ) ); + _movePrev.copy( _moveCurr ); + break; + + default: // 2 or more + _state = STATE.TOUCH_ZOOM_PAN; + var dx = event.touches[ 0 ].pageX - event.touches[ 1 ].pageX; + var dy = event.touches[ 0 ].pageY - event.touches[ 1 ].pageY; + _touchZoomDistanceEnd = _touchZoomDistanceStart = Math.sqrt( dx * dx + dy * dy ); + + var x = ( event.touches[ 0 ].pageX + event.touches[ 1 ].pageX ) / 2; + var y = ( event.touches[ 0 ].pageY + event.touches[ 1 ].pageY ) / 2; + _panStart.copy( getMouseOnScreen( x, y ) ); + _panEnd.copy( _panStart ); + break; + + } + + scope.dispatchEvent( startEvent ); + + } + + function touchmove( event ) { + + if ( scope.enabled === false ) return; + + event.preventDefault(); + event.stopPropagation(); + + switch ( event.touches.length ) { + + case 1: + _movePrev.copy( _moveCurr ); + _moveCurr.copy( getMouseOnCircle( event.touches[ 0 ].pageX, event.touches[ 0 ].pageY ) ); + break; + + default: // 2 or more + var dx = event.touches[ 0 ].pageX - event.touches[ 1 ].pageX; + var dy = event.touches[ 0 ].pageY - event.touches[ 1 ].pageY; + _touchZoomDistanceEnd = Math.sqrt( dx * dx + dy * dy ); + + var x = ( event.touches[ 0 ].pageX + event.touches[ 1 ].pageX ) / 2; + var y = ( event.touches[ 0 ].pageY + event.touches[ 1 ].pageY ) / 2; + _panEnd.copy( getMouseOnScreen( x, y ) ); + break; + + } + + } + + function touchend( event ) { + + if ( scope.enabled === false ) return; + + switch ( event.touches.length ) { + + case 0: + _state = STATE.NONE; + break; + + case 1: + _state = STATE.TOUCH_ROTATE; + _moveCurr.copy( getMouseOnCircle( event.touches[ 0 ].pageX, event.touches[ 0 ].pageY ) ); + _movePrev.copy( _moveCurr ); + break; + + } + + scope.dispatchEvent( endEvent ); + + } + + function contextmenu( event ) { + + if ( scope.enabled === false ) return; + + event.preventDefault(); + + } + + this.dispose = function () { + + scope.domElement.removeEventListener( 'contextmenu', contextmenu ); + + scope.domElement.removeEventListener( 'pointerdown', onPointerDown ); + scope.domElement.removeEventListener( 'wheel', mousewheel ); + + scope.domElement.removeEventListener( 'touchstart', touchstart ); + scope.domElement.removeEventListener( 'touchend', touchend ); + scope.domElement.removeEventListener( 'touchmove', touchmove ); + + scope.domElement.ownerDocument.removeEventListener( 'pointermove', onPointerMove ); + scope.domElement.ownerDocument.removeEventListener( 'pointerup', onPointerUp ); + + window.removeEventListener( 'keydown', keydown ); + window.removeEventListener( 'keyup', keyup ); + + }; + + this.domElement.addEventListener( 'contextmenu', contextmenu ); + + this.domElement.addEventListener( 'pointerdown', onPointerDown ); + this.domElement.addEventListener( 'wheel', mousewheel ); + + this.domElement.addEventListener( 'touchstart', touchstart ); + this.domElement.addEventListener( 'touchend', touchend ); + this.domElement.addEventListener( 'touchmove', touchmove ); + + this.domElement.ownerDocument.addEventListener( 'pointermove', onPointerMove ); + this.domElement.ownerDocument.addEventListener( 'pointerup', onPointerUp ); + + window.addEventListener( 'keydown', keydown ); + window.addEventListener( 'keyup', keyup ); + + this.handleResize(); + + // force an update at start + this.update(); + +}; + +TrackballControls.prototype = Object.create( EventDispatcher.prototype ); +TrackballControls.prototype.constructor = TrackballControls; + +export { TrackballControls }; diff --git a/src/Scene.js b/src/Scene.js index 04a6027..1c58282 100644 --- a/src/Scene.js +++ b/src/Scene.js @@ -7,7 +7,7 @@ import { Sketch } from './Sketch' import Stats from '../lib/stats.module.js'; import { extrude } from './extrude' -import { onHover, onPick } from './mouseEvents'; +import { onHover, onPick, setHover } from './mouseEvents'; import { _vec2, _vec3, color, awaitSelection, ptObj } from './shared' import { AxesHelper } from './axes' @@ -50,7 +50,6 @@ export class Scene { cameraDist * Math.cos(xzAngle) ); - // const controls = new OrbitControls(camera, view1Elem); const controls = new TrackballControls(this.camera, this.canvas); controls.target.set(0, 0, 0); controls.update(); @@ -126,6 +125,7 @@ export class Scene { this.extrude = extrude.bind(this); this.onHover = onHover.bind(this); this.onPick = onPick.bind(this); + this.setHover = setHover.bind(this); this.awaitSelection = awaitSelection.bind(this); this.obj3d.addEventListener('change', this.render); @@ -184,7 +184,7 @@ export class Scene { } else if (k[0] == 'm') { entries[k] = loader.parse(state.treeEntries.byId[k]) - console.log(entries[k]) + // console.log(entries[k]) this.obj3d.add(entries[k]) } @@ -207,7 +207,7 @@ export class Scene { this.selected = [] for (let x = 0; x < this.hovered.length; x++) { - const obj = this.selected[x] + const obj = this.hovered[x] obj.material.color.set(color[obj.userData.type]) if (obj.userData.type == 'plane') { obj.material.opacity = 0.05 @@ -221,12 +221,84 @@ export class Scene { } + hover(obj) { + + if (typeof obj == 'object' && !this.selected.includes(obj)) { + + if (obj.userData.type == 'plane') { + obj.material.opacity = 0.02 + obj.children[0].material.color.set(color['planeBorder']) + } else { + if (obj.userData.type == 'mesh') { + obj.material.emissive.set(color.emissive) + } + obj.material.color.set(color[obj.userData.type]) + } + + } + + + if (typeof obj == 'object' && !this.selected.includes(obj)) { + + if (obj.userData.type == 'plane') { + obj.material.opacity = 0.02 + obj.children[0].material.color.set(color['planeBorder']) + } else { + if (obj.userData.type == 'mesh') { + obj.material.emissive.set(color.emissive) + } + obj.material.color.set(color[obj.userData.type]) + } + + } + + + obj.material.color.set(color[obj.userData.type]) + + if (obj.userData.type == 'mesh') { + obj.material.emissive.set(color.emissive) + } else if (obj.userData.type == 'plane') { + obj.material.opacity = 0.02 + obj.children[0].material.color.set(color['planeBorder']) + } + + if (obj.userData.type == 'selpoint') { + obj.visible = false + } + + + + + + if (typeof obj == 'object') { + + if (obj.userData.type == 'plane') { + obj.material.opacity = 0.06 + obj.children[0].material.color.set(hoverColor['planeBorder']) + } else { + if (obj.userData.type == 'mesh') { + obj.material.emissive.set(hoverColor.emissive) + } + obj.material.color.set(hoverColor[obj.userData.type]) + } + + } + + + + + + + + } subtract(m1, m2) { let bspA = CSG.fromMesh(m1) let bspB = CSG.fromMesh(m2) - m1.traverse(e => e.layers.disableAll()) - m2.traverse(e => e.layers.disableAll()) + m1.visible = false + m2.visible = false + m1.traverse(e => e.layers.disable(1)) + m2.traverse(e => e.layers.disable(1)) // // Subtract one bsp from the other via .subtract... other supported modes are .union and .intersect @@ -241,9 +313,8 @@ export class Scene { - const vertices = new THREE.Points(mesh.geometry, new THREE.PointsMaterial()); + const vertices = new THREE.Points(mesh.geometry, new THREE.PointsMaterial({ size: 0 })); vertices.userData.type = 'point' - vertices.layers.disable(0) vertices.layers.enable(1) // mesh.add(line) @@ -251,6 +322,15 @@ export class Scene { sc.obj3d.add(mesh) + + this.store.dispatch({ + type: 'set-entry-visibility', obj: { + [m1.name]: false, + [m2.name]: false, + [mesh.name]: true, + } + }) + return mesh } } @@ -333,18 +413,18 @@ async function addSketch() { this.clearSelection() - sketch.activate() - this.activeSketch = sketch sketch.obj3d.addEventListener('change', this.render); - this.render() console.log('render') this.store.dispatch({ type: 'rx-sketch', obj: sketch }) + sketch.activate() + + this.render() } window.sc = new Scene(store) -sc.loadState() +// sc.loadState() // sc.camera.layers.enable(1) // rc.layers.set(1) \ No newline at end of file diff --git a/src/Sketch.js b/src/Sketch.js index 87b557f..47e1cba 100644 --- a/src/Sketch.js +++ b/src/Sketch.js @@ -152,9 +152,11 @@ class Sketch { this.setDimLines() - this.obj3d.traverse(e=>e.layers.enable(0)) + this.obj3d.traverse(e=>e.layers.enable(2)) + this.obj3d.visible = true this.scene.axes.matrix = this.obj3d.matrix this.scene.axes.visible = true + this.scene.activeSketch = this window.sketcher = this } @@ -165,8 +167,10 @@ class Sketch { this.canvas.removeEventListener('pointermove', this.onHover) this.store.dispatch({ type: 'exit-sketch' }) this.labelContainer.innerHTML = "" - this.obj3d.traverse(e=>e.layers.disable(0)) + this.obj3d.visible = false + this.obj3d.traverse(e=>e.layers.disable(2)) this.scene.axes.visible = false + this.scene.activeSketch = null } diff --git a/src/drawDimension.js b/src/drawDimension.js index 6bde2ae..4b63f28 100644 --- a/src/drawDimension.js +++ b/src/drawDimension.js @@ -38,7 +38,8 @@ export async function drawDimension() { line.userData.ids = selection.map(e => e.name) - + line.layers.enable(2) + point.layers.enable(2) diff --git a/src/drawEvents.js b/src/drawEvents.js index 86e3830..1273bd2 100644 --- a/src/drawEvents.js +++ b/src/drawEvents.js @@ -21,6 +21,10 @@ export function drawOnClick1(e) { this.toPush = drawPoint.call(this, mouseLoc) } + this.toPush.forEach(element => { + element.layers.enable(2) + }); + this.updatePoint = this.obj3d.children.length this.obj3d.add(...this.toPush) this.linkedObjs.set(this.l_id, [this.mode, this.toPush.map(e => e.name)]) diff --git a/src/extrude.js b/src/extrude.js index 96afbab..21bb1d0 100644 --- a/src/extrude.js +++ b/src/extrude.js @@ -72,12 +72,10 @@ export function extrude(sketch) { const geometry = new THREE.ExtrudeGeometry(shape, extrudeSettings); - // const material = new THREE.MeshLambertMaterial({ const material = new THREE.MeshPhongMaterial({ color: color.mesh, emissive: color.emissive, - // flatShading:true, }); const mesh = new THREE.Mesh(geometry, material) @@ -95,9 +93,14 @@ export function extrude(sketch) { this.obj3d.add(mesh) - this.render() - this.store.dispatch({ type: 'rx-extrusion', mesh, sketchId: sketch.obj3d.name }) + + if (this.activeSketch == sketch) { + this.activeSketch = null + sketch.deactivate() + } + + this.render() } diff --git a/src/mouseEvents.js b/src/mouseEvents.js index 467a0ed..b5e3fc1 100644 --- a/src/mouseEvents.js +++ b/src/mouseEvents.js @@ -22,7 +22,8 @@ export function onHover(e) { raycaster.layers.set(1) hoverPts = raycaster.intersectObjects(this.obj3d.children, true) } else { - raycaster.layers.set(0) + // raycaster.layers.set(0) + raycaster.layers.set(2) hoverPts = raycaster.intersectObjects([...this.obj3d.children[1].children, ...this.obj3d.children]) } @@ -58,12 +59,7 @@ export function onHover(e) { for (let x = 0; x < this.hovered.length; x++) { // first clear old hovers that are not selected const obj = this.hovered[x] if (typeof obj == 'object' && !this.selected.includes(obj)) { - if (obj.userData.type == 'plane') { - obj.material.opacity = 0.02 - obj.children[0].material.color.set(color['planeBorder']) - } else { - obj.material.color.set(color[obj.userData.type]) - } + setHover(obj, 0) } } this.hovered = [] @@ -71,37 +67,24 @@ export function onHover(e) { for (let x = 0; x < idx.length; x++) { let obj = hoverPts[idx[x]].object - if (this.obj3d.userData.type == 'sketch') { - obj.material.color.set(hoverColor[obj.userData.type]) - } else { + setHover(obj, 1, false) - if (obj.userData.type == 'mesh') { - obj.material.color.set(color['meshTempHover']) - } else if (obj.userData.type == 'plane') { - obj.material.opacity = 0.06 - obj.children[0].material.color.set(hoverColor['planeBorder']) - } else if (obj.userData.type == 'point') { - - ptLoc = obj.geometry.attributes.position.array - .slice( - 3 * hoverPts[idx[x]].index, - 3 * hoverPts[idx[x]].index + 3 - ) - - // const pp = this.obj3d.children[0].children[this.fptIdx % 3] - const pp = this.obj3d.children[0].children[0] - pp.geometry.attributes.position.array.set(ptLoc) - pp.matrix = obj.parent.matrix - pp.geometry.attributes.position.needsUpdate = true - pp.visible = true - - obj = hoverPts[idx[x]].index - - } + if (this.obj3d.userData.type != 'sketch' && obj.userData.type == 'point') { + ptLoc = obj.geometry.attributes.position.array + .slice( + 3 * hoverPts[idx[x]].index, + 3 * hoverPts[idx[x]].index + 3 + ) + // const pp = this.obj3d.children[0].children[this.fptIdx % 3] + const pp = this.obj3d.children[0].children[0] + pp.geometry.attributes.position.array.set(ptLoc) + pp.matrix = obj.parent.matrix + pp.geometry.attributes.position.needsUpdate = true + pp.visible = true + obj = hoverPts[idx[x]].index } - this.hovered.push(obj) } @@ -114,14 +97,12 @@ export function onHover(e) { for (let x = 0; x < this.hovered.length; x++) { const obj = this.hovered[x] + if (typeof obj == 'object' && !this.selected.includes(obj)) { - if (obj.userData.type == 'plane') { - obj.material.opacity = 0.02 - obj.children[0].material.color.set(color['planeBorder']) - } else { - obj.material.color.set(color[obj.userData.type]) - } + setHover(obj, 0) } + + } this.hovered = [] @@ -145,12 +126,9 @@ export function onPick(e) { this.selected.push(obj) } else { if (typeof obj == 'object') { - if (obj.userData.type == 'plane') { - obj.material.opacity = 0.06 - obj.children[0].material.color.set(hoverColor['planeBorder']) - } else { - obj.material.color.set(hoverColor[obj.userData.type]) - } + + setHover(obj, 1) + } else { const pp = this.obj3d.children[0].children[this.fptIdx % 3 + 1] const p0 = this.obj3d.children[0].children[0] @@ -198,16 +176,11 @@ export function onPick(e) { } else { for (let x = 0; x < this.selected.length; x++) { const obj = this.selected[x] - obj.material.color.set(color[obj.userData.type]) + + setHover(obj, 0) + if (obj.userData.type == 'selpoint') { obj.visible = false - } else { - if (obj.userData.type == 'plane') { - obj.material.opacity = 0.02 - obj.children[0].material.color.set(color['planeBorder']) - } else { - obj.material.color.set(color[obj.userData.type]) - } } } this.obj3d.dispatchEvent({ type: 'change' }) @@ -237,22 +210,55 @@ export function onDrag(e) { } +export function setHover(obj, state, meshHover = true) { + let colObj, visible + if (state == 1) { + colObj = hoverColor + visible = true + } else { + colObj = color + visible = false + } + + switch (obj.userData.type) { + case 'plane': + obj.material.opacity = colObj.opacity + obj.children[0].material.color.set(colObj['planeBorder']) + break; + case 'sketch': + obj.visible = visible + break; + case 'mesh': + if (meshHover) { + obj.material.emissive.set(colObj.emissive) + } else { + break + } + default: + obj.material.color.set(colObj[obj.userData.type]) + break; + } + + + // if (obj.userData.type == 'plane') { + // obj.material.opacity = colObj.opacity + // obj.children[0].material.color.set(colObj['planeBorder']) + // } else if (obj.userData.type != 'mesh') { + // obj.material.color.set(colObj[obj.userData.type]) + // } else if (meshHover) { + // obj.material.emissive.set(colObj.emissive) + // obj.material.color.set(colObj[obj.userData.type]) + // } + +} + export function onRelease() { this.canvas.removeEventListener('pointermove', this.onDrag) this.canvas.removeEventListener('pointermove', this.onDragDim) this.canvas.removeEventListener('pointerup', this.onRelease) - // for (let x = 3; x < this.obj3d.children.length; x++) { - // const obj = this.obj3d.children[x] - // obj.geometry.computeBoundingSphere() - // } - - // for (let x = 0; x < this.obj3d.children[1].children.length; x++) { - // const obj = this.obj3d.children[1].children[x] - // obj.geometry.computeBoundingSphere() - // } - this.updateBoundingSpheres() + if (draggedLabel) { draggedLabel.style.zIndex = 0; } diff --git a/src/react/app.css b/src/react/app.css index ee19777..2ced98c 100644 --- a/src/react/app.css +++ b/src/react/app.css @@ -1,10 +1,7 @@ -/* @tailwind base; */ @tailwind utilities; * { - -webkit-font-smoothing: antialiased; - -moz-osx-font-smoothing: grayscale; box-sizing: border-box; } @@ -52,19 +49,12 @@ body { hover:bg-gray-500 hover:text-gray-200; } -.btn-light { - cursor: pointer; - @apply fill-current - bg-transparent text-gray-700 - hover:bg-gray-300 hover:text-gray-700; -} - .btn-green { cursor: pointer; @apply fill-current bg-transparent text-gray-200 - hover:bg-transparent hover:text-green-200; + hover:bg-transparent hover:text-green-400; } diff --git a/src/react/navBar.jsx b/src/react/navBar.jsx index dcb74ac..c640b56 100644 --- a/src/react/navBar.jsx +++ b/src/react/navBar.jsx @@ -13,8 +13,7 @@ import * as Icon from "./icons"; export const NavBar = () => { const dispatch = useDispatch() const treeEntries = useSelector(state => state.treeEntries) - const activeSketchId = useSelector(state => state.activeSketchId) - + const activeSketchId = useSelector(state => state.treeEntries.activeSketchId) useEffect(() => { if (!activeSketchId) { @@ -35,6 +34,7 @@ export const NavBar = () => { // dispatch({ type: 'update-descendents', sketch}) sc.activeSketch = null sc.render() + forceUpdate() // sc.activeDim = this.activeSketch.obj3d.children[1].children }, 'Finish'] : [FaEdit, sc.addSketch, 'Sketch [s]'] diff --git a/src/react/reducer.js b/src/react/reducer.js index 20101e1..12e66d6 100644 --- a/src/react/reducer.js +++ b/src/react/reducer.js @@ -10,10 +10,9 @@ export const preloadedState = { allIds: [], tree: {}, order: {}, + visible: {}, + activeSketchId: "" }, - ui: { - toolTipImmediate: false - } } export function reducer(state = {}, action) { @@ -24,18 +23,33 @@ export function reducer(state = {}, action) { byId: { [action.obj.obj3d.name]: { $set: action.obj } }, allIds: { $push: [action.obj.obj3d.name] }, tree: { [action.obj.obj3d.name]: { $set: {} } }, - order: { [action.obj.obj3d.name]: { $set: state.treeEntries.allIds.length } } + order: { [action.obj.obj3d.name]: { $set: state.treeEntries.allIds.length } }, + visible: { [action.obj.obj3d.name]: { $set: true } }, }, }) + case 'set-entry-visibility': { + return update(state, { + treeEntries: { + visible: { $merge: action.obj }, + }, + }) + } + case 'set-active-sketch': return update(state, { - activeSketchId: { $set: action.sketch }, + treeEntries: { + visible: { [action.sketch]: { $set: true } }, + activeSketchId: { $set: action.sketch }, + }, }) case 'exit-sketch': - return { - ...state, activeSketchId: '' - } + return update(state, { + treeEntries: { + activeSketchId: { $set: "" }, + visible: { [state.treeEntries.activeSketchId]: { $set: false } }, + }, + }) case 'rx-extrusion': return update(state, { @@ -48,7 +62,10 @@ export function reducer(state = {}, action) { [action.sketchId]: { [action.mesh.name]: { $set: true } }, [action.mesh.name]: { $set: {} } }, - order: { [action.mesh.name]: { $set: state.treeEntries.allIds.length } } + order: { [action.mesh.name]: { $set: state.treeEntries.allIds.length } }, + visible: { + [action.mesh.name]: { $set: true } + } } }) case 'rx-boolean': @@ -62,6 +79,7 @@ export function reducer(state = {}, action) { tree: { [action.deps[0]]: { [action.mesh.name]: { $set: true } }, [action.deps[1]]: { [action.mesh.name]: { $set: true } }, + [action.mesh.name]: { $set: {} } }, order: { [action.mesh.name]: { $set: state.treeEntries.allIds.length } } } diff --git a/src/react/tree.jsx b/src/react/tree.jsx index 4db93ee..3eb8d4f 100644 --- a/src/react/tree.jsx +++ b/src/react/tree.jsx @@ -1,8 +1,8 @@ -import React, { useReducer } from 'react'; +import React, { useReducer, useState } from 'react'; import { useDispatch, useSelector } from 'react-redux' -import { MdEdit, MdVisibilityOff, MdVisibility, MdDelete } from 'react-icons/md' +import { MdVisibilityOff, MdVisibility, MdDelete } from 'react-icons/md' import { FaCube, FaEdit } from 'react-icons/fa' @@ -29,89 +29,100 @@ const TreeEntry = ({ entId }) => { const treeEntries = useSelector(state => state.treeEntries.byId) const dispatch = useDispatch() - const activeSketchId = useSelector(state => state.activeSketchId) + const activeSketchId = useSelector(state => state.treeEntries.activeSketchId) + // const activeSketchId = treeEntries.activeSketchId - let obj3d, entry; + const visible = useSelector(state => state.treeEntries.visible[entId]) + + let obj3d, sketch; - entry = treeEntries[entId] if (treeEntries[entId].obj3d) { obj3d = treeEntries[entId].obj3d + sketch = treeEntries[entId] } else { obj3d = treeEntries[entId] } - console.log(obj3d.userData.type) let Icon = treeIcons[obj3d.userData.type] const [_, forceUpdate] = useReducer(x => x + 1, 0); - // const vis = obj3d.visible - const vis = obj3d.layers.mask & 1 + + // const vis = obj3d.layers.mask & 1 return
{ - activeSketchId && treeEntries[activeSketchId].deactivate() - console.log(entry) - entry.activate() - sc.clearSelection() - sc.activeSketch = entry; + if (entId[0] == 's') { + activeSketchId && treeEntries[activeSketchId].deactivate() + sketch.activate() + sc.clearSelection() + sc.activeSketch = sketch; + } + + }} + + onPointerEnter={() => { + sc.setHover(obj3d, 1) + sc.render() + }} + onPointerLeave={() => { + // console.log('activeid',activeSketchId,'visstate',visState) + if (visible & entId[0] == 's') return + if (sc.selected.includes(obj3d) || activeSketchId == obj3d.name) return + sc.setHover(obj3d, 0) + sc.render() + }} + onClick={() => { + if (entId[0] == 'm') { + sc.selected.push( + obj3d + ) + sc.render() + } }} > -
{ - if (entId[0] == 'm') { - // entry.material.color.set(color.hover) - sc.render() - } - }} - onPointerLeave={() => { - const obj = entry - if (entId[0] == 'm' && !sc.selected.includes(obj)) { - // obj.material.color.set(color.mesh) - sc.render() - } - }} - onPointerDown={() => { - if (entId[0] == 'm') { - sc.selected.push( - entry - ) - sc.render() - } - }} - > +
{entId}
{ + onClick={(e) => { dispatch({ type: 'delete-node', id: entId }) + sc.render() + e.stopPropagation() }} /> { - vis ? + visible ? { - obj3d.traverse((e) => e.layers.disableAll()) + onClick={(e) => { + e.stopPropagation() + console.log('hide') + dispatch({ type: "set-entry-visibility", obj: {[entId]:false} }) + obj3d.visible = false; + if (obj3d.userData.type == 'mesh') { + obj3d.traverse((e) => e.layers.disable(1)) + } + sc.render() forceUpdate() }} /> : { - if (obj3d.userData.type == 'sketch') { - obj3d.traverse((e) => e.layers.enable(0)) - } else { + onClick={(e) => { + e.stopPropagation() + console.log('show') + obj3d.visible = true; + dispatch({ type: "set-entry-visibility", obj: {[entId]:true} }) + if (obj3d.userData.type == 'mesh') { obj3d.traverse((e) => { - e.layers.enable(0) e.layers.enable(1) }) - } sc.render() forceUpdate() diff --git a/src/shared.js b/src/shared.js index 64cb667..27067b5 100644 --- a/src/shared.js +++ b/src/shared.js @@ -22,18 +22,23 @@ const color = { line: 0xffffff, mesh: 0x9DCFED, dimension: 0x0000ff, + plane: 0xffff00, planeBorder: 0x2e2e00, + opacity: 0.02 } const hoverColor = { + emissive: 0x343407, point: 0x00ff00, selpoint: 0xff0000, line: 0x00ff00, mesh: 0xFAB601, dimension: 0x00ff00, + plane: 0xffff00, planeBorder: 0x919100, + opacity: 0.06 } diff --git a/todo.txt b/todo.txt index 81aba93..5fb86b9 100644 --- a/todo.txt +++ b/todo.txt @@ -12,15 +12,16 @@ boolean flesh out refresh / replace mesh - hidden bodies messes up hover highlight \\ fixed - add for union and intersect - consume skeches after extrude // done +- selection hover disspates when rehovered //fixed +- boolean unable to select click //fixed vertical // done horizontal // done - +- hover sync between tree and work area - select sketch for extrusion -- boolean unable to select click auto update extrude extrude dialogue