threepipe
Nelze vybrat více než 25 témat Téma musí začínat písmenem nebo číslem, může obsahovat pomlčky („-“) a může být dlouhé až 35 znaků.

SimplifyModifierPlugin.ts 7.4KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207
  1. import {AViewerPluginSync, ThreeViewer} from '../../viewer'
  2. import {PickingPlugin} from '../interaction/PickingPlugin'
  3. import {uiButton, uiSlider} from 'uiconfig.js'
  4. import {IGeometry, IObject3D} from '../../core'
  5. import {ValOrArr} from 'ts-browser-helpers'
  6. import {Vector3} from 'three'
  7. export interface SimplifyOptions{
  8. /**
  9. * Number of vertices to remove.
  10. * Factor is not used when count is set.
  11. */
  12. count?: number
  13. /**
  14. * Factor of vertices to remove. eg 0.5 will remove half of the vertices.
  15. */
  16. factor?: number
  17. /**
  18. * Replace the geometry with the simplified version in all meshes that use it.
  19. */
  20. replace?: boolean
  21. /**
  22. * Displace the simplified geometry in the scene. Only used when replace is true
  23. * If set to true, the geometry will be disposed when replaced.
  24. * Default is false.
  25. * This will automatically be done when disposeOnIdle is not false in the geometry.userData.
  26. */
  27. disposeOnReplace?: boolean
  28. }
  29. /**
  30. * Boilerplate for implementing a plugin for simplifying geometries.
  31. * This is a base class and cannot be used directly.
  32. * See {@link MeshOptSimplifyModifierPlugin} the [simplify-modifier-plugin](https://threepipe.org/examples/#simplify-modifier-plugin) example for a sample implementation.
  33. */
  34. export abstract class SimplifyModifierPlugin extends AViewerPluginSync {
  35. public static readonly PluginType: string = 'SimplifyModifierPlugin'
  36. enabled = true
  37. toJSON: any = undefined
  38. constructor() {
  39. super()
  40. }
  41. get initialized() { return true }
  42. async initialize() {return}
  43. private _pickingPlugin?: PickingPlugin
  44. onAdded(viewer: ThreeViewer) {
  45. super.onAdded(viewer)
  46. this._pickingPlugin = viewer.getPlugin(PickingPlugin)
  47. }
  48. /**
  49. * Factor of vertices to remove. eg 0.5 will remove half of the vertices.
  50. * Default is 0.5
  51. * This is used when no factor or count is provided in the options to simplifyGeometry or simplifyGeometries.
  52. */
  53. @uiSlider('Simplify Factor', [0, 1])
  54. simplifyFactor = 0.5
  55. simplifyGeometries(geometry?: ValOrArr<IGeometry>, options?: SimplifyOptions) {
  56. if (!geometry) {
  57. const selected = this._pickingPlugin?.getSelectedObject<IObject3D>()
  58. if (!selected?.isObject3D) return
  59. const geom: IGeometry[] = []
  60. selected?.traverse((o) => {
  61. if (o.geometry && !geom.includes(o.geometry)) geom.push(o.geometry)
  62. })
  63. geometry = geom
  64. if (!geometry || !geometry.length) return
  65. }
  66. if (!Array.isArray(geometry)) geometry = [geometry]
  67. const res: IGeometry[] = []
  68. for (const g of geometry) {
  69. res.push(this.simplifyGeometry(g, options)!)
  70. }
  71. return res
  72. }
  73. simplifyGeometry(geometry?: IGeometry, {
  74. factor,
  75. count,
  76. replace = true,
  77. disposeOnReplace = false,
  78. }: SimplifyOptions = {}): IGeometry|undefined {
  79. if (!geometry) {
  80. const selected = this._pickingPlugin?.getSelectedObject<IObject3D>()
  81. geometry = selected?.geometry
  82. if (!geometry) return undefined
  83. }
  84. if (!geometry.attributes.position) {
  85. this._viewer?.console.error('SimplifyModifierPlugin: Geometry does not have position attribute', geometry)
  86. return geometry
  87. }
  88. factor = factor || this.simplifyFactor
  89. count = count || geometry.attributes.position.count * factor
  90. if (!geometry.boundingBox) geometry.computeBoundingBox()
  91. const simplified = this._simplify(geometry, count)
  92. simplified.computeBoundingBox()
  93. simplified.computeBoundingSphere()
  94. simplified.computeVertexNormals()
  95. const bbox = simplified.boundingBox
  96. const size = bbox!.getSize(new Vector3())
  97. if (!isFinite(size.x) || !isFinite(size.y) || !isFinite(size.z)) {
  98. this._viewer?.console.error('SimplifyModifierPlugin: Unable to simplify', geometry, simplified, size)
  99. return geometry
  100. }
  101. const oldBB = geometry.boundingBox
  102. const oldSize = oldBB!.getSize(new Vector3())
  103. const diff = size.clone().sub(oldSize)
  104. const diffPerc = diff.clone().divide(oldSize)
  105. if (diffPerc.lengthSq() > 0.001) {
  106. // todo: add option to skip this
  107. console.warn('Simplify', geometry, simplified, bbox, oldBB, size, oldSize, diff, diffPerc)
  108. }
  109. // simplified.setDirty()
  110. if (!replace) return simplified
  111. // not working?
  112. // geometry.copy(simplified)
  113. // geometry.setDirty()
  114. // simplified.dispose()
  115. const meshes = geometry.appliedMeshes
  116. if (!meshes) {
  117. console.error('No meshes found for geometry, cannot replace', geometry)
  118. return simplified
  119. }
  120. for (const mesh of meshes) {
  121. mesh.geometry = simplified
  122. }
  123. if (disposeOnReplace) {
  124. geometry.dispose(true)
  125. }
  126. return simplified
  127. }
  128. /**
  129. * Sample for three.js addons SimplifyModifier:
  130. * `
  131. * import {SimplifyModifier} from 'three/examples/jsm/modifiers/SimplifyModifier'
  132. * protected _simplify(geometry: IGeometry, count: number): IGeometry {
  133. * const modifier = new SimplifyModifier()
  134. * return modifier.modify(geometry, count) as IGeometry
  135. * }
  136. * `
  137. * @param geometry
  138. * @param count
  139. */
  140. protected abstract _simplify(geometry: IGeometry, count: number): IGeometry
  141. @uiButton('Simplify All', {sendArgs: false})
  142. async simplifyAll(root?: IObject3D, options?: SimplifyOptions) {
  143. if (!root && this._viewer) root = this._viewer.scene.modelRoot
  144. if (!root) {
  145. console.error('SimplifyModifierPlugin: No root found')
  146. return
  147. }
  148. if (!this.initialized) {
  149. await this.initialize()
  150. if (!this.initialized) {
  151. this._viewer?.console.error('SimplifyModifierPlugin cannot be initialized')
  152. return
  153. }
  154. }
  155. const geometries: IGeometry[] = []
  156. root.traverse((o) => {
  157. if (o.geometry && !geometries.includes(o.geometry)) geometries.push(o.geometry)
  158. })
  159. if (!geometries.length) {
  160. console.error('SimplifyModifierPlugin: No geometries found')
  161. return
  162. }
  163. return this.simplifyGeometries(geometries, options)
  164. }
  165. @uiButton('Simplify Selected')
  166. async simplifySelected() {
  167. if (!this._viewer) return
  168. if (!this.initialized) {
  169. await this.initialize()
  170. if (!this.initialized) {
  171. await this._viewer.dialog.alert('Simplify: SimplifyModifierPlugin cannot be initialized')
  172. return
  173. }
  174. }
  175. const selected = this._pickingPlugin?.getSelectedObject<IObject3D>()
  176. if (!selected?.isObject3D) {
  177. await this._viewer.dialog.alert('Simplify: No Object Selected')
  178. return
  179. }
  180. let doAll = false
  181. if (!selected.geometry) doAll = true
  182. else if (selected.children.length === 0) doAll = true
  183. if (!doAll) {
  184. const resp = await this._viewer.dialog.confirm('Simplify: Simplify all in hierarchy?')
  185. if (resp) doAll = true
  186. }
  187. if (doAll) {
  188. this.simplifyGeometries()
  189. } else {
  190. this.simplifyGeometry(selected.geometry)
  191. }
  192. }
  193. }