123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396 |
- /*
- * InspectGeometryTool.js
- *
- * @author realor
- */
- import { Tool } from './Tool.js'
- import { Tree } from '../ui/Tree.js'
- import { TabbedPane } from '../ui/TabbedPane.js'
- import { Controls } from '../ui/Controls.js'
- import { Solid } from '../core/Solid.js'
- import { SolidOptimizer } from '../core/SolidOptimizer.js'
- import { ObjectUtils } from '../utils/ObjectUtils.js'
- import { I18N } from '../i18n/I18N.js'
- import * as THREE from '../lib/three.module.js'
- class InspectGeometryTool extends Tool {
- constructor(application, options) {
- super(application)
- this.name = 'inspect_geometry'
- this.label = 'tool.inspect_geometry.label'
- this.className = 'inspect_geometry'
- this.object = null
- this.selectedNode = null
- this.highlightGroup = null
- this.lineMaterial = new THREE.LineBasicMaterial({ color: 0, linewidth: 1.5, depthTest: false, transparent: true })
- this.pointsMaterial = new THREE.PointsMaterial({ color: 0, size: 4, sizeAttenuation: false, depthTest: false, transparent: true })
- this.vertexMaterial = new THREE.PointsMaterial({ color: 0x000080, size: 8, sizeAttenuation: false, depthTest: false, transparent: true })
- this.sceneUuid = null
- this.setOptions(options)
- this.createPanel()
- }
- createPanel() {
- this.panel = this.application.createPanel(this.label, 'left')
- this.panel.bodyElem.classList.add('padding')
- this.tabbedPane = new TabbedPane(this.panel.bodyElem)
- const geometryInventoryPanel = this.tabbedPane.addTab('geom_inventory', 'label.geometry_inventory')
- geometryInventoryPanel.classList.add('inspect_geometry')
- const geometryPanel = this.tabbedPane.addTab('geom_detail', 'label.geometry_detail')
- geometryPanel.classList.add('inspect_geometry')
- // geometry inventory panel
- this.geometryToDisplayElem = Controls.addNumberField(geometryInventoryPanel, 'geom_to_display', 'label.geometries_display', '100', 'row')
- this.geometryToDisplayElem.style.width = '60px'
- this.searchButton = Controls.addButton(geometryInventoryPanel, 'geom_search', 'button.search', () => this.searchGeometries())
- this.listInvElem = document.createElement('ul')
- this.listInvElem.className = 'summary'
- geometryInventoryPanel.appendChild(this.listInvElem)
- this.geometryCountElem = document.createElement('li')
- this.listInvElem.appendChild(this.geometryCountElem)
- this.instanceCountElem = document.createElement('li')
- this.listInvElem.appendChild(this.instanceCountElem)
- this.modeledTriangleCountElem = document.createElement('li')
- this.listInvElem.appendChild(this.modeledTriangleCountElem)
- this.renderedTriangleCountElem = document.createElement('li')
- this.listInvElem.appendChild(this.renderedTriangleCountElem)
- this.listInvElem.style.display = 'none'
- this.geometryTable = Controls.addTable(
- geometryInventoryPanel,
- 'geom_table',
- ['label.geometry_id', 'label.geometry_instances', 'label.geometry_triangles', 'label.geometry_total_triangles'],
- 'data'
- )
- this.geometryTable.style.display = 'none'
- // geometry panel
- this.messageElem = document.createElement('div')
- this.messageElem.style.padding = '4px'
- geometryPanel.appendChild(this.messageElem)
- I18N.set(this.messageElem, 'innerHTML', 'tool.inspect_geometry.help')
- this.listElem = document.createElement('ul')
- this.listElem.className = 'summary'
- geometryPanel.appendChild(this.listElem)
- this.objectNameElem = document.createElement('li')
- this.listElem.appendChild(this.objectNameElem)
- this.geometryIdElem = document.createElement('li')
- this.listElem.appendChild(this.geometryIdElem)
- this.vertexCountElem = document.createElement('li')
- this.listElem.appendChild(this.vertexCountElem)
- this.faceCountElem = document.createElement('li')
- this.listElem.appendChild(this.faceCountElem)
- this.isManifoldElem = document.createElement('li')
- this.listElem.appendChild(this.isManifoldElem)
- this.optimizeButton = Controls.addButton(geometryPanel, 'optimize', 'button.optimize', () => this.optimize())
- this.optimizeButton.style.display = 'none'
- this.geometryTree = new Tree(geometryPanel)
- this._onPointerDown = this.onPointerDown.bind(this)
- this._onSelection = this.onSelection.bind(this)
- }
- activate() {
- const application = this.application
- const container = application.container
- container.addEventListener('pointerdown', this._onPointerDown, false)
- application.addEventListener('selection', this._onSelection, false)
- if (this.sceneUuid !== application.scene.uuid) {
- this.listInvElem.style.display = 'none'
- this.geometryTable.tBodies[0].innerHTML = ''
- this.geometryTable.style.display = 'none'
- }
- this.panel.visible = true
- let object = application.selection.object
- if (object instanceof Solid) {
- if (object !== this.object) {
- this.showSolid(object)
- }
- } else {
- this.clear()
- }
- }
- deactivate() {
- const application = this.application
- const container = application.container
- container.removeEventListener('pointerdown', this._onPointerDown, false)
- application.removeEventListener('selection', this._onSelection, false)
- this.panel.visible = false
- }
- onPointerDown(event) {
- if (!this.isCanvasEvent(event)) return
- const application = this.application
- const pointerPosition = this.getEventPosition(event)
- const baseObject = application.baseObject
- const intersect = this.intersect(pointerPosition, baseObject, true)
- if (intersect) {
- let object = intersect.object
- application.selection.set(object)
- this.tabbedPane.showTab('geom_detail')
- } else {
- application.selection.clear()
- }
- }
- onSelection() {
- const application = this.application
- const object = application.selection.object
- if (object instanceof Solid) {
- if (object !== this.object) {
- this.showSolid(object)
- }
- } else {
- this.clear()
- }
- }
- showSolid(solid) {
- this.object = solid
- this.clearHighlight()
- this.messageElem.style.display = 'none'
- const formatter = new Intl.NumberFormat()
- this.listElem.style.display = ''
- I18N.set(this.objectNameElem, 'innerHTML', 'message.object_name', solid.name)
- I18N.set(this.geometryIdElem, 'innerHTML', 'message.geometry_id', solid.geometry.id)
- I18N.set(this.faceCountElem, 'innerHTML', 'message.face_count', formatter.format(solid.geometry.faces.length))
- I18N.set(this.vertexCountElem, 'innerHTML', 'message.vertex_count', formatter.format(solid.geometry.vertices.length))
- I18N.set(this.isManifoldElem, 'innerHTML', 'message.is_manifold', solid.geometry.isManifold)
- this.application.i18n.updateTree(this.listElem)
- this.optimizeButton.style.display = ''
- const tree = this.geometryTree
- tree.clear()
- let geometry = solid.geometry
- const vertices = geometry.vertices
- const round = x => {
- return Math.round(x * 1000) / 1000
- }
- const vector2String = vector => {
- return '(' + round(vector.x) + ', ' + round(vector.y) + ', ' + round(vector.z) + ')'
- }
- const addVertices = (node, loop) => {
- const indices = loop.indices
- for (let i = 0; i < indices.length; i++) {
- let vertex = vertices[loop.indices[i]]
- let vertexNode = node.addNode('v-' + loop.indices[i] + ': ' + vector2String(vertex), () => this.highlight(vertexNode, solid, [loop], vertex), 'vertex')
- }
- }
- for (let f = 0; f < geometry.faces.length; f++) {
- let face = geometry.faces[f]
- let label = 'face-' + f + ' (' + face.outerLoop.indices.length + 'v'
- if (face.holes.length > 0) label += ', ' + face.holes.length + 'h)'
- else label += ')'
- let faceNode = tree.addNode(label, () => this.highlight(faceNode, solid, [face.outerLoop, ...face.holes]), 'face' + (face.holes.length > 0 ? ' holes' : ''))
- faceNode.addNode('normal: ' + vector2String(face.normal), () => {}, 'normal')
- let outerNode = faceNode.addNode('outerLoop (' + face.outerLoop.indices.length + 'v)', () => this.highlight(outerNode, solid, [face.outerLoop]), 'loop')
- addVertices(outerNode, face.outerLoop)
- for (let h = 0; h < face.holes.length; h++) {
- let hole = face.holes[h]
- let holeNode = faceNode.addNode('hole-' + h + ' (' + hole.indices.length + 'v)', () => this.highlight(holeNode, solid, [hole]), 'hole')
- addVertices(holeNode, hole)
- }
- }
- }
- clear() {
- this.geometryTree.clear()
- this.object = null
- this.clearHighlight()
- this.messageElem.style.display = ''
- this.listElem.style.display = 'none'
- this.optimizeButton.style.display = 'none'
- }
- addLoop(solid, loop) {
- const vertices = []
- const indices = loop.indices
- const matrixWorld = solid.matrixWorld
- for (let i = 0; i <= indices.length; i++) {
- vertices.push(
- loop
- .getVertex(i % indices.length)
- .clone()
- .applyMatrix4(matrixWorld)
- )
- }
- let geometry = new THREE.BufferGeometry()
- geometry.setFromPoints(vertices)
- let lines = new THREE.Line(geometry, this.lineMaterial)
- lines.raycast = function() {}
- this.highlightGroup.add(lines)
- let points = new THREE.Points(geometry, this.pointsMaterial)
- points.raycast = function() {}
- this.highlightGroup.add(points)
- }
- highlight(node, solid, loops, vertex) {
- if (this.selectedNode !== null) {
- this.selectedNode.removeClass('selected')
- }
- node.addClass('selected')
- this.selectedNode = node
- if (this.highlightGroup !== null) {
- this.application.removeObject(this.highlightGroup)
- }
- this.highlightGroup = new THREE.Group()
- this.highlightGroup.renderOrder = 2
- for (let loop of loops) {
- this.addLoop(solid, loop)
- }
- if (vertex) {
- let geometry = new THREE.BufferGeometry()
- geometry.setFromPoints([vertex.clone().applyMatrix4(solid.matrixWorld)])
- let points = new THREE.Points(geometry, this.vertexMaterial)
- points.raycast = function() {}
- this.highlightGroup.add(points)
- }
- this.application.addObject(this.highlightGroup, this.application.overlays)
- }
- clearHighlight() {
- if (this.highlightGroup !== null) {
- this.application.removeObject(this.highlightGroup)
- this.highlightGroup = null
- }
- if (this.selectedNode !== null) {
- this.selectedNode.removeClass('selected')
- this.selectedNode = null
- }
- }
- optimize() {
- const object = this.object
- if (object !== null && object.type === 'Solid') {
- let optimizer = new SolidOptimizer(object.geometry)
- let geometry = optimizer.optimize()
- object.updateGeometry(geometry)
- this.showSolid(object)
- this.application.notifyObjectsChanged(object)
- }
- }
- searchGeometries() {
- const application = this.application
- this.sceneUuid = application.scene.uuid
- let geometryMap = new Map()
- let instanceCount = 0
- let modeledTriangleCount = 0
- let renderedTriangleCount = 0
- function traverse(object) {
- if (!object.visible) return
- if (object instanceof Solid) {
- let geometry = object.geometry
- let entry = geometryMap.get(geometry.id)
- if (entry === undefined) {
- entry = {
- geometry: geometry,
- triangleCount: geometry.getTriangleCount(),
- instances: []
- }
- geometryMap.set(geometry.id, entry)
- modeledTriangleCount += entry.triangleCount
- }
- entry.instances.push(object)
- instanceCount++
- renderedTriangleCount += entry.triangleCount
- } else {
- const children = object.children
- for (let child of children) {
- traverse(child)
- }
- }
- }
- traverse(application.baseObject)
- let entries = Array.from(geometryMap.values())
- entries.sort((a, b) => {
- let sizeA = a.triangleCount * a.instances.length
- let sizeB = b.triangleCount * b.instances.length
- return sizeB - sizeA
- })
- const formatter = new Intl.NumberFormat()
- this.listInvElem.style.display = ''
- I18N.set(this.geometryCountElem, 'innerHTML', 'message.geometry_count', formatter.format(entries.length))
- I18N.set(this.instanceCountElem, 'innerHTML', 'message.instance_count', formatter.format(instanceCount))
- I18N.set(this.modeledTriangleCountElem, 'innerHTML', 'message.modeled_triangle_count', formatter.format(modeledTriangleCount))
- I18N.set(this.renderedTriangleCountElem, 'innerHTML', 'message.rendered_triangle_count', formatter.format(renderedTriangleCount))
- this.application.i18n.updateTree(this.listInvElem)
- this.geometryTable.tBodies[0].innerHTML = ''
- let entryCount = Math.min(entries.length, parseInt(this.geometryToDisplayElem.value))
- for (let i = 0; i < entryCount; i++) {
- let entry = entries[i]
- let rowElem = Controls.addTableRow(this.geometryTable)
- rowElem.children[0].innerHTML = entry.geometry.id
- let instanceLink = document.createElement('a')
- instanceLink.href = '#'
- instanceLink.innerHTML = '' + formatter.format(entry.instances.length)
- instanceLink.addEventListener('click', () => this.showInstances(entry))
- rowElem.children[1].appendChild(instanceLink)
- rowElem.children[2].innerHTML = formatter.format(entry.triangleCount)
- rowElem.children[3].innerHTML = formatter.format(entry.triangleCount * entry.instances.length)
- }
- this.geometryTable.style.display = ''
- }
- showInstances(entry) {
- this.application.selection.set(...entry.instances)
- }
- }
- export { InspectGeometryTool }
|