threepipe
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

преди 11 месеца
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143
  1. import {
  2. _testFinish,
  3. _testStart,
  4. AlwaysStencilFunc,
  5. BufferGeometry2,
  6. DecrementStencilOp,
  7. EqualStencilFunc,
  8. IObject3D,
  9. LoadingScreenPlugin,
  10. Mesh2,
  11. PickingPlugin,
  12. ReplaceStencilOp,
  13. shaderReplaceString,
  14. ThreeViewer,
  15. TransformControlsPlugin,
  16. UnlitMaterial,
  17. } from 'threepipe'
  18. import {TweakpaneUiPlugin} from '@threepipe/plugin-tweakpane'
  19. // Custom outline for picking plugin by duplicating the model and using stencil buffer
  20. async function init() {
  21. const viewer = new ThreeViewer({
  22. canvas: document.getElementById('mcanvas') as HTMLCanvasElement,
  23. msaa: true,
  24. rgbm: true,
  25. plugins: [LoadingScreenPlugin, PickingPlugin, new TransformControlsPlugin(false)],
  26. stencil: true,
  27. })
  28. const ui = viewer.addPluginSync(new TweakpaneUiPlugin(true))
  29. ui.setupPlugins(TransformControlsPlugin, PickingPlugin)
  30. await Promise.all([
  31. viewer.setEnvironmentMap('https://threejs.org/examples/textures/equirectangular/venice_sunset_1k.hdr', {
  32. setBackground: false,
  33. }),
  34. viewer.load<IObject3D>('https://threejs.org/examples/models/gltf/kira.glb', {
  35. autoCenter: true,
  36. autoScale: true,
  37. autoScaleRadius: 6,
  38. }),
  39. ])
  40. const selectionMaterial = new UnlitMaterial({
  41. color: 0xe98a65,
  42. stencilRef: 1,
  43. depthWrite: false,
  44. stencilWrite: true,
  45. depthTest: true,
  46. stencilFunc: AlwaysStencilFunc,
  47. stencilZPass: ReplaceStencilOp,
  48. colorWrite: true,
  49. })
  50. selectionMaterial.registerMaterialExtensions([{
  51. shaderExtender:(shader, _material, _renderer) => {
  52. shader.vertexShader = shaderReplaceString(
  53. shader.vertexShader,
  54. '#include <begin_vertex>',
  55. '\ntransformed += normal * 0.02;',
  56. {append: true}
  57. )
  58. },
  59. isCompatible: ()=>true,
  60. computeCacheKey: ()=> 'selectionMaterial',
  61. }])
  62. const selectionMesh = new Mesh2(new BufferGeometry2(), selectionMaterial)
  63. const selectedObjectUpdateListener = (ev: {object: IObject3D})=>{
  64. if (ev.object !== selected) return
  65. selected.updateMatrixWorld()
  66. selected.matrixWorld.decompose(
  67. selectionMesh.position,
  68. selectionMesh.quaternion,
  69. selectionMesh.scale,
  70. )
  71. selectionMesh.updateMatrixWorld()
  72. // const selectionScale = 1.1
  73. // const center = new Box3B().expandByObject(selectionMesh).getCenter(new Vector3()).sub(selectionMesh.position)
  74. // const m = new Matrix4().makeTranslation(new Vector3().copy(center).negate())
  75. // .multiply(new Matrix4().makeScale(selectionScale, selectionScale, selectionScale))
  76. // .multiply(new Matrix4().makeTranslation(new Vector3().copy(center).multiplyScalar(1 / selectionScale)))
  77. // selectionMesh.matrix.premultiply(m).decompose(selectionMesh.position, selectionMesh.quaternion, selectionMesh.scale)
  78. }
  79. let selected = undefined as IObject3D | undefined
  80. let lastState = null as any
  81. viewer.getPlugin(PickingPlugin)!.addEventListener('selectedObjectChanged', ()=>{
  82. const model = viewer.getPlugin(PickingPlugin)?.getSelectedObject<IObject3D>()
  83. const geometry = model?.geometry
  84. if (selected === model) return
  85. if (selected) {
  86. // remove selection mesh from previous selected object
  87. selected.removeEventListener('objectUpdate', selectedObjectUpdateListener)
  88. const lastMaterial = selected.materials?.[0]
  89. if (lastMaterial && lastState) {
  90. lastMaterial.stencilWrite = lastState.stencilWrite
  91. lastMaterial.stencilRef = lastState.stencilRef
  92. lastMaterial.stencilFunc = lastState.stencilFunc
  93. lastMaterial.stencilZPass = lastState.stencilZPass
  94. lastMaterial.needsUpdate = true
  95. selected.renderOrder = lastState.renderOrder
  96. }
  97. lastState = null
  98. }
  99. const material = model?.materials?.[0]
  100. if (!model?.isObject3D || !geometry || !material) { // it can also be a selected material
  101. selectionMesh.geometry = undefined as any
  102. selectionMesh.removeFromParent()
  103. return
  104. }
  105. if (!geometry) return
  106. // Set selected object's material to use stencil buffer
  107. lastState = {
  108. stencilWrite: material.stencilWrite,
  109. stencilRef: material.stencilRef,
  110. stencilFunc: material.stencilFunc,
  111. stencilZPass: material.stencilZPass,
  112. renderOrder: model.renderOrder,
  113. }
  114. material.stencilWrite = true
  115. material.stencilRef = 1
  116. material.stencilFunc = EqualStencilFunc
  117. material.stencilZPass = DecrementStencilOp
  118. material.needsUpdate = true
  119. model.renderOrder = 2
  120. // Set selection mesh to match selected object
  121. selected = model
  122. selectionMesh.geometry = geometry
  123. // add listeners to update selection mesh position when its moved
  124. selected.addEventListener('objectUpdate', selectedObjectUpdateListener)
  125. selectedObjectUpdateListener({object: selected})
  126. if (!selectionMesh.parent) viewer.scene.addObject(selectionMesh, {addToRoot: true}) // add to root so it is not saved
  127. })
  128. viewer.getPlugin(PickingPlugin)!.widgetEnabled = false
  129. const chair = viewer.scene.getObjectByName('Node-Mesh003_1')
  130. viewer.getPlugin(PickingPlugin)!.setSelectedObject(chair, true)
  131. }
  132. _testStart()
  133. init().finally(_testFinish)