123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475 |
- /*
- * DrawTool.js
- *
- * @author realor
- */
- import { Tool } from './Tool.js'
- import { Cord } from '../core/Cord.js'
- import { Profile } from '../core/Profile.js'
- import { Controls } from '../ui/Controls.js'
- import { CordGeometry } from '../core/CordGeometry.js'
- import { ProfileGeometry } from '../core/ProfileGeometry.js'
- import { PointSelector } from '../utils/PointSelector.js'
- import { GeometryUtils } from '../utils/GeometryUtils.js'
- import { ObjectUtils } from '../utils/ObjectUtils.js'
- import { I18N } from '../i18n/I18N.js'
- import * as THREE from '../lib/three.module.js'
- class DrawTool extends Tool {
- constructor(application, options) {
- super(application)
- this.name = 'draw'
- this.label = 'tool.draw.label'
- this.help = 'tool.draw.help'
- this.className = 'draw'
- this.setOptions(options)
- this.mode = 0 // 0: add, 1: edit
- this.vertices = [] // Vector3[]
- this.index = -1
- this.object = null // Cord or Profile
- this.points = null // THREE.Points
- this.position1World = null // THREE.Vector3
- this.position2World = null // THREE.Vector3
- this.inverseMatrixWorld = new THREE.Matrix4() // object inverse matrixWorld
- this.addPointsMaterial = new THREE.PointsMaterial({ color: 0x00ff00, size: 4, sizeAttenuation: false, depthTest: false, transparent: true })
- this.editPointsMaterial = new THREE.PointsMaterial({ color: 0, size: 4, sizeAttenuation: false, depthTest: false, transparent: true })
- this._onPointerUp = this.onPointerUp.bind(this)
- this._onSelection = this.onSelection.bind(this)
- this.createPanel()
- }
- createPanel() {
- this.panel = this.application.createPanel(this.label, 'left', 'panel_draw')
- this.panel.preferredHeight = 140
- // this.helpElem = document.createElement('div')
- // this.panel.bodyElem.appendChild(this.helpElem)
- // I18N.set(this.helpElem, 'innerHTML', 'tool.draw.help')
- this.offsetInputElem = Controls.addNumberField(this.panel.bodyElem, 'draw_offset', 'label.offset', 0)
- this.offsetInputElem.step = 0.1
- this.offsetInputElem.addEventListener(
- 'change',
- event => {
- this.applyOffset()
- },
- false
- )
- this.buttonsElem = document.createElement('div')
- this.panel.bodyElem.appendChild(this.buttonsElem)
- this.finishButton = Controls.addButton(this.buttonsElem, 'draw_finish', 'button.finish', event => this.finish())
- this.profileButton = Controls.addButton(this.buttonsElem, 'draw_make_profile', 'button.make_profile', event => this.makeProfile())
- this.updatePanel()
- }
- activate() {
- this.panel.visible = true
- const application = this.application
- const container = application.container
- container.addEventListener('pointerup', this._onPointerUp, false)
- application.addEventListener('selection', this._onSelection, false)
- application.pointSelector.excludeSelection = false
- application.pointSelector.activate()
- this.setObject(application.selection.object)
- }
- deactivate() {
- this.panel.visible = false
- const application = this.application
- const container = application.container
- container.removeEventListener('pointerup', this._onPointerUp, false)
- application.removeEventListener('selection', this._onSelection, false)
- application.pointSelector.deactivate()
- application.pointSelector.clearAxisGuides()
- this.clearPoints()
- }
- onSelection(event) {
- let object = event.objects.length > 0 ? event.objects[0] : null
- this.setObject(object)
- }
- onPointerUp(event) {
- const pointSelector = this.application.pointSelector
- if (!pointSelector.isPointSelectionEvent(event)) return
- const selection = this.application.selection
- let snap = pointSelector.snap
- if (snap) {
- let positionWorld = snap.positionWorld
- let position = new THREE.Vector3()
- this.transformVertex(positionWorld, position)
- let nextIndex = this.findVertex(position)
- if (this.mode === 0) {
- // add to new object
- if (nextIndex === -1) {
- // add new vertex
- this.vertices.push(position)
- this.index = this.vertices.length - 1
- if (this.vertices.length === 1) {
- selection.clear()
- this.position1World = null
- } else {
- this.position1World = this.position2World
- }
- this.position2World = positionWorld
- } else if (nextIndex === 0 && this.vertices.length >= 3) {
- // close cord
- this.makeProfile()
- this.position1World = null
- this.position2World = null
- } // delete vertices
- else {
- let deleted = this.vertices.length - nextIndex - 1
- this.vertices.splice(nextIndex + 1, deleted)
- this.index = nextIndex
- this.position1World = null
- this.position2World = positionWorld
- }
- } // edit (mode === 1)
- else {
- this.position1World = null
- this.position2World = null
- if (this.index !== -1) {
- // vertex previously selected
- if (nextIndex === -1) {
- // move vertex
- this.vertices[this.index] = position
- } else if (
- this.object instanceof Cord &&
- this.vertices.length >= 3 &&
- ((this.index === 0 && nextIndex === this.vertices.length - 1) || (this.index === this.vertices.length - 1 && nextIndex === 0))
- ) {
- this.makeProfile()
- } else if (this.object instanceof Profile && this.index === 0 && nextIndex === this.vertices.length - 1) {
- this.vertices.splice(0, 1)
- } else if (this.object instanceof Profile && this.index === this.vertices.length - 1 && nextIndex === 0) {
- this.vertices.splice(this.vertices.length - 1, 1)
- } else if (this.index === nextIndex + 1) {
- this.vertices.splice(this.index, 1)
- } else if (this.index === nextIndex - 1) {
- this.vertices.splice(nextIndex - 1, 1)
- }
- this.index = -1 // unselect vertex
- this.object.builder = null
- } // no vertex previously selected
- else {
- if (nextIndex !== -1) {
- // on object vertex
- this.index = nextIndex
- } else {
- nextIndex = this.findVertexOnEdge(position)
- if (nextIndex !== -1) {
- this.index = nextIndex
- this.vertices.splice(nextIndex, 0, position)
- } else {
- this.mode = 0
- this.index = 0
- this.object = null
- this.vertices = [positionWorld]
- this.position2World = positionWorld
- selection.clear()
- }
- }
- }
- }
- this.updateObject()
- this.updatePoints()
- this.updateAxis(snap.object)
- this.updatePanel()
- } else {
- this.finish()
- }
- }
- setObject(object) {
- if (object && !ObjectUtils.isObjectDescendantOf(object, this.application.scene)) {
- // object removed
- object = null
- }
- if (this.object !== object) {
- this.object = null
- this.vertices = []
- this.index = -1
- this.position1World = null
- this.position2World = null
- if (object instanceof Cord) {
- this.object = object
- this.vertices = [...this.object.geometry.points]
- this.mode = 1
- } else if (object instanceof Profile) {
- this.object = object
- let points2 = this.object.geometry.path.getPoints()
- this.vertices = []
- for (let i = 0; i < points2.length - 1; i++) {
- let point2 = points2[i]
- this.vertices.push(new THREE.Vector3(point2.x, point2.y, 0))
- }
- this.mode = 1
- }
- }
- this.updatePoints()
- this.updateAxis(object)
- this.updatePanel()
- }
- finish() {
- const application = this.application
- const pointSelector = application.pointSelector
- this.mode = 0 // add
- this.vertices = []
- this.index = -1
- this.object = null
- this.position1World = null
- this.position2World = null
- this.updatePoints()
- this.updatePanel()
- pointSelector.clearAxisGuides()
- this.clearPoints()
- application.selection.clear()
- }
- makeProfile() {
- if (this.object instanceof Cord && this.vertices.length >= 3) {
- let parent = this.object.parent
- let index = parent.children.indexOf(this.object)
- let object = new Profile()
- object.matrix.copy(this.object.matrix)
- this.flattenVertices(object)
- parent.children[index] = object
- object.parent = parent
- this.mode = 1
- this.index = -1
- this.object = object
- this.updateObject()
- const application = this.application
- application.notifyObjectsChanged(parent, this, 'structureChanged')
- application.selection.set(object)
- }
- }
- flattenVertices(profile) {
- const vertices = this.vertices
- let zAxis = GeometryUtils.calculateNormal(vertices)
- let yAxis = GeometryUtils.orthogonalVector(zAxis)
- let xAxis = new THREE.Vector3()
- xAxis.crossVectors(yAxis, zAxis)
- let position = GeometryUtils.centroid(vertices)
- let matrix = new THREE.Matrix4()
- matrix.makeBasis(xAxis, yAxis, zAxis)
- matrix.setPosition(position)
- let inverseMatrix = new THREE.Matrix4()
- inverseMatrix.copy(matrix).invert()
- for (let vertex of vertices) {
- vertex.applyMatrix4(inverseMatrix)
- vertex.z = 0
- }
- matrix.premultiply(profile.matrix)
- matrix.decompose(profile.position, profile.rotation, profile.scale)
- profile.updateMatrix()
- }
- updateObject() {
- const application = this.application
- const overlays = application.overlays
- if ((this.vertices.length < 2 && this.object instanceof Cord) || (this.vertices.length < 3 && this.object instanceof Profile)) {
- application.removeObject(this.object)
- this.mode = 0
- this.object = null
- this.index = -1
- this.vertices = []
- } else {
- let geometry
- if (this.object) {
- if (this.object instanceof Cord) {
- geometry = new CordGeometry(this.vertices)
- } else {
- let shape = new THREE.Shape()
- for (let i = 0; i < this.vertices.length; i++) {
- let vertex = this.vertices[i]
- vertex.z = 0
- if (i === 0) {
- shape.moveTo(vertex.x, vertex.y)
- } else {
- shape.lineTo(vertex.x, vertex.y)
- }
- }
- shape.closePath()
- geometry = new ProfileGeometry(shape)
- }
- this.object.updateGeometry(geometry)
- application.notifyObjectsChanged(this.object)
- } else if (this.vertices.length > 1) {
- // create Cord by default
- geometry = new CordGeometry(this.vertices)
- this.object = new Cord(geometry, this.lineMaterial)
- application.addObject(this.object, null, false, true)
- }
- }
- }
- updatePoints() {
- const application = this.application
- const overlays = application.overlays
- this.clearPoints()
- if (this.vertices.length > 0) {
- let geometry = new THREE.BufferGeometry()
- geometry.setFromPoints(this.vertices)
- let material = this.mode === 0 ? this.addPointsMaterial : this.editPointsMaterial
- this.points = new THREE.Points(geometry, material)
- const points = this.points
- points.raycast = function() {}
- if (this.object) {
- this.object.matrixWorld.decompose(points.position, points.rotation, points.scale)
- points.updateMatrix()
- }
- overlays.add(points)
- application.repaint()
- }
- }
- updateAxis(object) {
- const pointSelector = this.application.pointSelector
- if (this.index === -1 || this.vertices.length === 0) {
- pointSelector.clearAxisGuides()
- } else {
- let positionWorld = this.vertices[this.index].clone()
- if (this.object) {
- // positionWorld is local, convert to world
- positionWorld.applyMatrix4(this.object.matrixWorld)
- }
- let axisMatrixWorld = object ? object.matrixWorld.clone() : new THREE.Matrix4()
- axisMatrixWorld.setPosition(positionWorld)
- pointSelector.setAxisGuides(axisMatrixWorld, true)
- }
- }
- updatePanel() {
- // if (this.mode === 0) {
- // if (this.vertices.length === 0) {
- // I18N.set(this.helpElem, 'innerHTML', 'tool.draw.first_vertex')
- // } else {
- // I18N.set(this.helpElem, 'innerHTML', 'tool.draw.add_vertex')
- // }
- // } else if (this.mode === 1) {
- // if (this.index === -1) {
- // I18N.set(this.helpElem, 'innerHTML', 'tool.draw.select_vertex')
- // } else {
- // I18N.set(this.helpElem, 'innerHTML', 'tool.draw.vertex_destination')
- // }
- // }
- // this.application.i18n.update(this.helpElem)
- if (this.position1World && this.position2World) {
- let offset = this.position1World.distanceTo(this.position2World)
- this.offsetInputElem.value = offset
- this.offsetInputElem.parentElement.style.display = ''
- } else {
- this.offsetInputElem.parentElement.style.display = 'none'
- }
- if (this.object instanceof Cord && this.vertices.length >= 3) {
- this.profileButton.style.display = ''
- } else {
- this.profileButton.style.display = 'none'
- }
- }
- applyOffset() {
- let offset = parseFloat(this.offsetInputElem.value)
- let positionWorld = new THREE.Vector3()
- positionWorld.subVectors(this.position2World, this.position1World)
- positionWorld.normalize()
- positionWorld.multiplyScalar(offset)
- positionWorld.add(this.position1World)
- this.position2World = positionWorld
- let position = new THREE.Vector3()
- this.transformVertex(positionWorld, position)
- this.vertices[this.index] = position
- this.updateObject()
- this.updatePoints()
- this.updateAxis(this.object)
- }
- clearPoints() {
- if (this.points !== null) {
- this.application.removeObject(this.points)
- this.points = null
- }
- }
- findVertex(position) {
- for (let i = 0; i < this.vertices.length; i++) {
- if (this.vertices[i].distanceToSquared(position) < 0.0001) return i
- }
- return -1
- }
- findVertexOnEdge(position) {
- if (this.object instanceof Profile) {
- for (let i = 0; i < this.vertices.length; i++) {
- let p1 = this.vertices[i]
- let p2 = this.vertices[(i + 1) % this.vertices.length]
- if (GeometryUtils.isPointOnSegment(position, p1, p2)) return i + 1
- }
- } else {
- for (let i = 0; i < this.vertices.length - 1; i++) {
- let p1 = this.vertices[i]
- let p2 = this.vertices[i + 1]
- if (GeometryUtils.isPointOnSegment(position, p1, p2)) return i + 1
- }
- }
- return -1
- }
- transformVertex(pointWorld, point) {
- if (this.object) {
- // express pointWorld in object CS
- this.inverseMatrixWorld.copy(this.object.matrixWorld).invert()
- point.copy(pointWorld)
- point.applyMatrix4(this.inverseMatrixWorld)
- if (this.object instanceof Profile) {
- point.z = 0
- }
- } else {
- point.copy(pointWorld)
- }
- }
- }
- export { DrawTool }
|