threepipe
選択できるのは25トピックまでです。 トピックは、先頭が英数字で、英数字とダッシュ('-')を使用した35文字以内のものにしてください。

SimplifyModifierPlugin.ts 7.2KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206
  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()
  58. const geom: IGeometry[] = []
  59. selected?.traverse((o) => {
  60. if (o.geometry && !geom.includes(o.geometry)) geom.push(o.geometry)
  61. })
  62. geometry = geom
  63. if (!geometry || !geometry.length) return
  64. }
  65. if (!Array.isArray(geometry)) geometry = [geometry]
  66. const res: IGeometry[] = []
  67. for (const g of geometry) {
  68. res.push(this.simplifyGeometry(g, options)!)
  69. }
  70. return res
  71. }
  72. simplifyGeometry(geometry?: IGeometry, {
  73. factor,
  74. count,
  75. replace = true,
  76. disposeOnReplace = false,
  77. }: SimplifyOptions = {}): IGeometry|undefined {
  78. if (!geometry) {
  79. const selected = this._pickingPlugin?.getSelectedObject()
  80. geometry = selected?.geometry
  81. if (!geometry) return undefined
  82. }
  83. if (!geometry.attributes.position) {
  84. this._viewer?.console.error('SimplifyModifierPlugin: Geometry does not have position attribute', geometry)
  85. return geometry
  86. }
  87. factor = factor || this.simplifyFactor
  88. count = count || geometry.attributes.position.count * factor
  89. if (!geometry.boundingBox) geometry.computeBoundingBox()
  90. const simplified = this._simplify(geometry, count)
  91. simplified.computeBoundingBox()
  92. simplified.computeBoundingSphere()
  93. simplified.computeVertexNormals()
  94. const bbox = simplified.boundingBox
  95. const size = bbox!.getSize(new Vector3())
  96. if (!isFinite(size.x) || !isFinite(size.y) || !isFinite(size.z)) {
  97. this._viewer?.console.error('SimplifyModifierPlugin: Unable to simplify', geometry, simplified, size)
  98. return geometry
  99. }
  100. const oldBB = geometry.boundingBox
  101. const oldSize = oldBB!.getSize(new Vector3())
  102. const diff = size.clone().sub(oldSize)
  103. const diffPerc = diff.clone().divide(oldSize)
  104. if (diffPerc.lengthSq() > 0.001) {
  105. // todo: add option to skip this
  106. console.warn('Simplify', geometry, simplified, bbox, oldBB, size, oldSize, diff, diffPerc)
  107. }
  108. // simplified.setDirty()
  109. if (!replace) return simplified
  110. // not working?
  111. // geometry.copy(simplified)
  112. // geometry.setDirty()
  113. // simplified.dispose()
  114. const meshes = geometry.appliedMeshes
  115. if (!meshes) {
  116. console.error('No meshes found for geometry, cannot replace', geometry)
  117. return simplified
  118. }
  119. for (const mesh of meshes) {
  120. mesh.geometry = simplified
  121. }
  122. if (disposeOnReplace) {
  123. geometry.dispose(true)
  124. }
  125. return simplified
  126. }
  127. /**
  128. * Sample for three.js addons SimplifyModifier:
  129. * `
  130. * import {SimplifyModifier} from 'three/examples/jsm/modifiers/SimplifyModifier'
  131. * protected _simplify(geometry: IGeometry, count: number): IGeometry {
  132. * const modifier = new SimplifyModifier()
  133. * return modifier.modify(geometry, count) as IGeometry
  134. * }
  135. * `
  136. * @param geometry
  137. * @param count
  138. */
  139. protected abstract _simplify(geometry: IGeometry, count: number): IGeometry
  140. @uiButton('Simplify All')
  141. async simplifyAll(root?: IObject3D, options?: SimplifyOptions) {
  142. if (!root && this._viewer) root = this._viewer.scene.modelRoot
  143. if (!root) {
  144. console.error('SimplifyModifierPlugin: No root found')
  145. return
  146. }
  147. if (!this.initialized) {
  148. await this.initialize()
  149. if (!this.initialized) {
  150. this._viewer?.console.error('SimplifyModifierPlugin cannot be initialized')
  151. return
  152. }
  153. }
  154. const geometries: IGeometry[] = []
  155. root.traverse((o) => {
  156. if (o.geometry && !geometries.includes(o.geometry)) geometries.push(o.geometry)
  157. })
  158. if (!geometries.length) {
  159. console.error('SimplifyModifierPlugin: No geometries found')
  160. return
  161. }
  162. return this.simplifyGeometries(geometries, options)
  163. }
  164. @uiButton('Simplify Selected')
  165. async simplifySelected() {
  166. if (!this._viewer) return
  167. if (!this.initialized) {
  168. await this.initialize()
  169. if (!this.initialized) {
  170. await this._viewer.dialog.alert('SimplifyModifierPlugin cannot be initialized')
  171. return
  172. }
  173. }
  174. const selected = this._pickingPlugin?.getSelectedObject()
  175. if (!selected) {
  176. await this._viewer.dialog.alert('Nothing Selected')
  177. return
  178. }
  179. let doAll = false
  180. if (!selected.geometry) doAll = true
  181. else if (selected.children.length === 0) doAll = true
  182. if (!doAll) {
  183. const resp = await this._viewer.dialog.confirm('Simplify all in hierarchy?')
  184. if (resp) doAll = true
  185. }
  186. if (doAll) {
  187. this.simplifyGeometries()
  188. } else {
  189. this.simplifyGeometry(selected.geometry)
  190. }
  191. }
  192. }