UNPKG

awv3

Version:
295 lines (274 loc) 11.5 kB
import * as THREE from 'three'; import Object3 from '../../../three/object3'; import Ccfuturef from '../ccfuturef'; import Ccref from '../ccref'; import { addCommand, restrictionCommands } from '../command/highlevel'; import { Assign, IdToReal, Variable, Return, Sequence } from '../command/lowlevel'; import { incidence } from '../constraint/type'; import { drawPointBy_S, drawLineBy_S_E, drawArcBy_S_E_C, drawArcBy_S_E_M, drawArcBy_S_T_E, drawCircleBy_C_E, getTangent, } from '../geomutils'; import { updateGeomObjectContainer } from '../preview'; import BaseHandler from './base'; const Mode = Object.freeze({ point: 'point', line: 'line', arccenter: 'arccenter', arcmiddle: 'arcmiddle', arctangent: 'arctangent', circle: 'circle' }); export default class DrawHandler extends BaseHandler { constructor(sketcher, name) { super(sketcher, name); this.endPoint = null; // ccref of the last point of the polyline this.endTangent = null; // normalized THREE.Vector3() tangent of the last edge this.positions = [new THREE.Vector3(), new THREE.Vector3(), new THREE.Vector3()]; this.preview = new THREE.Group(); this.sketcher.pool.add(this.preview); this.queue = new PromiseQueue(); this.startPointRestrictions = {}; this.restrictions = {}; this.startPoint = null; // ccref of the first point of the polyline this.state = 0; // number of element filled in this.positions this.statesCount = 2; // number of states needed to create an edge } destroy() { this.preview.destroy(); this.sketcher.setCursorCoordinates(undefined); super.destroy(); } filterObjectsWithInteraction(object) { return object.id === this.sketch.id; } [Object3.Events.Interaction.Clicked](object, hitObject) { const ray = hitObject.ray.clone().applyMatrix4(new THREE.Matrix4().getInverse(this.sketch.matrixWorld)); const position = ray.intersectPlane(new THREE.Plane(new THREE.Vector3(0, 0, 1))); return this.queue.run(() => this.afterEdgeAdded(this.click(position))); } async afterEdgeAdded(geomParams) { if (!geomParams) return; const object = new Ccfuturef(Variable(), geomParams); const objectStartPoint = object.isCircle() ? object.center : object.startPoint; const manualConstrCommand = this.endPoint && !this.sketcher.autoconstraintIncremental ? addCommand(this.sketch, { class: incidence.type, entities: [this.endPoint, objectStartPoint], value: {}, }) : null; const command = Sequence( Assign(object, addCommand(this.sketch, geomParams)), ...this.sketcher.autoconstraintCommands(object), ...restrictionCommands(this.sketch, objectStartPoint, this.startPointRestrictions), ...restrictionCommands(this.sketch, object, this.restrictions), manualConstrCommand, Return(IdToReal(object)), ); const p = this.sketcher.run(command); this.sketcher.incrementalSolveConstraints(); if (!object.isLine() && !object.isArc()) { this.endPolyline(); return p; } const ccref = new Ccref(this.sketcher, (await p)); this.startPointRestrictions = {}; this.restrictions = {}; this.startPoint = this.startPoint || ccref.startPoint; this.endPoint = ccref.endPoint || this.endPoint; this.endTangent = getTangent(ccref.geomParams, this.endPoint.pos); this.click(this.endPoint.pos); } cancel() { if (this.state !== 0) this.endPolyline(); else super.cancel(); } endPolyline() { this.endPoint = null; this.endTangent = null; this.startPointRestrictions = {}; this.restrictions = {}; this.startPoint = null; this.state = 0; this.onMouseMove(); } async closePolyline() { if (!this.startPoint || !this.endPoint) return; const geomParams = { start: this.startPoint.pos, end: this.endPoint.pos }; const object = new Ccfuturef(Variable(), geomParams); const command = Sequence( Assign(object, addCommand(this.sketch, geomParams)), addCommand(this.sketch, { class: incidence.type, entities: [this.endPoint, object.startPoint], value: {} }), addCommand(this.sketch, { class: incidence.type, entities: [object.endPoint, this.startPoint], value: {} }), ); await this.sketcher.run(command); this.endPolyline(); } click(position, doSnapping = true) { const geomParams = this.move(position, true, doSnapping); if (!this.handleClick(this.positions[this.state])) return; if (++this.state === this.statesCount) { this.state = 0; return geomParams; } else if (this.state === 1) { this.startPointRestrictions = this.restrictions; this.restrictions = {}; } } getPreviewGeomParams() { if (this.state === 0) return drawPointBy_S(this.positions, this.restrictions); switch (this.name) { case Mode.point: return drawPointBy_S(this.positions, this.restrictions); case Mode.line: return drawLineBy_S_E(this.positions, this.restrictions); case Mode.arccenter: return drawArcBy_S_E_C(this.positions, this.restrictions); case Mode.arcmiddle: return drawArcBy_S_E_M(this.positions, this.restrictions); case Mode.arctangent: return drawArcBy_S_T_E([this.positions[0], this.endTangent, this.positions[1]], this.restrictions); case Mode.circle: return drawCircleBy_C_E(this.positions, this.restrictions); } } move(position, force, doSnapping = true) { if (!force && this.queue.isBusy()) return; if (doSnapping) position.copy(this.doSnapping(position)); this.sketcher.setCursorCoordinates(position); for (let i = this.state; i < this.positions.length; ++i) this.positions[i].copy(position); const previewParams = this.getPreviewGeomParams(); if (this.state === 0) this.positions[0].copy(previewParams.start); updateGeomObjectContainer(this.preview, { coordinateSystem: this.sketch.graphics.matrix, scale: this.sketcher.graphicScale, ...previewParams, }); return previewParams; } onMouseMove() { this.move(this.getRecentMousePosition()); this.sketcher.refresh(); } switchMode(mode) { if (this.state > 1) this.state = 1; this.statesCount = mode === Mode.point ? 1 : (mode === Mode.arccenter || mode === Mode.arcmiddle) ? 3 : 2; // handler switching logic in handler/index.js expects mode to be stored in this.name this.name = mode; } consoleComplete(cmd) { cmd = cmd.trim(); const tokens = cmd === '' ? [] : cmd.split(/\s+/); const completions = super.consoleComplete(cmd); if (completions.indexOf(cmd) !== -1) return completions; return [cmd].concat( this.name.startsWith('point') ? [`${cmd} ax`, `${cmd} ay`] : this.name.startsWith('line') ? [`${cmd} l`, `${cmd} a`, `${cmd} x`, `${cmd} y`] : this.name.startsWith('arc') ? [`${cmd} a`, `${cmd} r`, `${cmd} cw`, `${cmd} ccw`] : [], completions, ); } consoleExecute(cmd) { if (super.consoleExecute(cmd)) return; // switch handlers if (cmd === 'cl') return this.closePolyline(); else return this.afterEdgeAdded(this.click(this.getRecentMousePosition(), false)); } parseRestrictions(cmd) { let tokens = cmd.trim().split(/\s+/); this.restrictions = {}; while (tokens.length !== 0) { let name = tokens.shift(); let value = Number.NaN; switch (name) { case 'x': name = 'xoffset'; value = Number(tokens.shift()); break; case 'y': name = 'yoffset'; value = Number(tokens.shift()); break; case 'ax': name = 'xabsolute'; value = Number(tokens.shift()); break; case 'ay': name = 'yabsolute'; value = Number(tokens.shift()); break; case 'l': name = 'length'; value = Number(tokens.shift()); break; case 'a': name = 'angle'; value = Math.PI / 180 * Number(tokens.shift()); break; case 'r': name = 'radius'; value = Number(tokens.shift()); break; case 'cw': name = 'clockwise'; value = true; break; case 'ccw': name = 'clockwise'; value = false; break; } if (!isFinite(value)) continue; this.restrictions[name] = value; } // update priview this.onMouseMove(); } doSnapping(position) { if (this.preview.geomType === 'CC_Point') return this.createSnapper().snapPoint(position).position; if (this.preview.geomType === 'CC_Line') return this.createSnapper().snapPoint(position).position; if (this.preview.geomType === 'CC_Arc') return this.createSnapper().snapPoint(position).position; return position; } handleClick(position) { if (!this.endTangent) { let variants = []; // for all lines/arcs on the sketch for (let object of this.sketch.children) { if (!object.isLine() && !object.isArc()) continue; // for their endpoint matching position for (let end of ['startPoint', 'endPoint']) { if (!object[end].pos.equals(position)) continue; const tangent = getTangent(object.geomParams, position); if (tangent.lengthSq() === 0) continue; if (end === 'startPoint') tangent.negate(); variants.push(tangent); } } // deny click in arctangent mode if doesn't uniquely determine the tangent if (this.name === 'arctangent' && variants.length !== 1) return false; this.endTangent = variants[0] || new THREE.Vector3(); return true; } return true; } } class PromiseQueue { constructor() { this.nPending = 0; this.promise = Promise.resolve(); } run(functor) { this.promise = (async promise => { ++this.nPending; await promise; await functor(); --this.nPending; })(this.promise); return this.promise; } isBusy() { return this.nPending > 0; } }