threepipe
Вы не можете выбрать более 25 тем Темы должны начинаться с буквы или цифры, могут содержать дефисы(-) и должны содержать не более 35 символов.

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218
  1. import {IObject3D} from '../IObject'
  2. import {IUiConfigContainer, UiObjectConfig} from 'uiconfig.js'
  3. import {ICamera} from '../ICamera'
  4. import {Vector3} from 'three'
  5. export function makeICameraCommonUiConfig(this: ICamera, config: UiObjectConfig): UiObjectConfig[] {
  6. return [
  7. {
  8. type: 'button',
  9. label: 'Set View',
  10. value: ()=>{
  11. this.setViewToMain({ui: true})
  12. config.uiRefresh?.(true, 'postFrame') // config is parent config
  13. },
  14. },
  15. {
  16. type: 'button',
  17. label: 'Activate main',
  18. hidden: ()=>this?.isMainCamera,
  19. value: ()=>{
  20. this.activateMain({ui: true})
  21. config.uiRefresh?.(true, 'postFrame')
  22. },
  23. },
  24. {
  25. type: 'button',
  26. label: 'Deactivate main',
  27. hidden: ()=>!this?.isMainCamera,
  28. value: ()=>{
  29. this.deactivateMain({ui: true})
  30. config.uiRefresh?.(true, 'postFrame')
  31. },
  32. },
  33. {
  34. type: 'checkbox',
  35. label: 'Auto LookAt Target',
  36. getValue: ()=>this.userData.autoLookAtTarget ?? false,
  37. setValue: (v: boolean)=>{
  38. this.userData.autoLookAtTarget = v
  39. config.uiRefresh?.(true, 'postFrame')
  40. },
  41. },
  42. ]
  43. }
  44. export function makeIObject3DUiConfig(this: IObject3D, isMesh?:boolean): UiObjectConfig {
  45. if (!this) return {}
  46. if (this.uiConfig) return this.uiConfig
  47. const config: UiObjectConfig = {
  48. type: 'folder',
  49. label: ()=>this.name || 'unnamed',
  50. expanded: true,
  51. onChange: (ev)=>{
  52. if (!ev.config || ev.config.onChange) return
  53. this.setDirty({uiChangeEvent: ev, refreshScene: false, refreshUi: true})
  54. },
  55. children: [
  56. {
  57. type: 'checkbox',
  58. label: 'Visible',
  59. property: [this, 'visible'],
  60. },
  61. {
  62. type: 'button',
  63. label: 'Pick/Focus', // todo: move to the plugin that does the picking
  64. value: ()=>{
  65. this.dispatchEvent({type: 'select', ui: true, object: this, bubbleToParent: true, focusCamera: true})
  66. },
  67. },
  68. {
  69. type: 'button',
  70. label: 'Pick Parent', // todo: move to the plugin that does the picking
  71. hidden: ()=>!this.parent,
  72. value: ()=>{
  73. const parent = this.parent
  74. if (parent) {
  75. parent.dispatchEvent({type: 'select', ui: true, bubbleToParent: true, object: parent})
  76. }
  77. },
  78. },
  79. {
  80. type: 'input',
  81. label: 'Name',
  82. property: [this, 'name'],
  83. onChange: (e: any)=>{
  84. if (e.last) this.setDirty?.({refreshScene: true, frameFade: false, refreshUi: true})
  85. },
  86. },
  87. {
  88. type: 'checkbox',
  89. label: 'Casts Shadow',
  90. hidden: () => !(this as any).isMesh,
  91. property: [this, 'castShadow'],
  92. },
  93. {
  94. type: 'checkbox',
  95. label: 'Receive Shadow',
  96. hidden: () => !(this as any).isMesh,
  97. property: [this, 'receiveShadow'],
  98. },
  99. {
  100. type: 'checkbox',
  101. label: 'Frustum culled',
  102. property: [this, 'frustumCulled'],
  103. },
  104. {
  105. type: 'vec3',
  106. label: 'Position',
  107. property: [this, 'position'],
  108. },
  109. {
  110. type: 'vec3',
  111. label: 'Rotation',
  112. property: [this, 'rotation'],
  113. },
  114. {
  115. type: 'vec3',
  116. label: 'Scale',
  117. property: [this, 'scale'],
  118. },
  119. {
  120. type: 'input',
  121. label: 'Render Order',
  122. property: [this, 'renderOrder'],
  123. },
  124. {
  125. type: 'button',
  126. label: 'Auto Scale',
  127. hidden: ()=>!this.autoScale,
  128. prompt: ['Auto Scale Radius: Object will be scaled to the given radius', this.userData.autoScaleRadius || '2', true],
  129. value: async()=>{
  130. const def = (this.userData.autoScaleRadius || 2) + ''
  131. const res = prompt('Auto Scale Radius: Object will be scaled to the given radius', def)
  132. if (res === null) return
  133. const rad = parseFloat(res || def)
  134. if (Math.abs(rad) > 0) {
  135. this.autoScale?.(rad)
  136. return ()=>this.autoScale?.(rad, undefined, undefined, true)
  137. }
  138. },
  139. },
  140. {
  141. type: 'button',
  142. label: 'Auto Center',
  143. value: ()=>{
  144. const res = confirm('Auto Center: Object will be centered, are you sure you want to proceed?')
  145. if (!res) return
  146. this.autoCenter?.(true)
  147. return ()=>this.autoCenter?.(true, true)
  148. },
  149. },
  150. {
  151. type: 'folder',
  152. label: 'Rotate model',
  153. children: [
  154. 'X +', 'X -', 'Y +', 'Y -', 'Z +', 'Z -',
  155. ].map((l)=>{
  156. return {
  157. type: 'button',
  158. label: 'Rotate ' + l + '90',
  159. value: ()=>{
  160. this.rotateOnAxis(new Vector3(l.includes('X') ? 1 : 0, l.includes('Y') ? 1 : 0, l.includes('Z') ? 1 : 0), Math.PI / 2 * (l.includes('-') ? -1 : 1))
  161. this.setDirty?.({refreshScene: true, refreshUi: false})
  162. },
  163. }
  164. }),
  165. },
  166. this.userData.license !== undefined ? {
  167. type: 'input',
  168. label: 'License/Credits',
  169. property: [this.userData, 'license'],
  170. } : {},
  171. ],
  172. }
  173. if ((this.isLine || this.isMesh) && isMesh !== false) {
  174. // todo: move to make mesh ui function?
  175. const ui = [
  176. // morph targets
  177. ()=>{
  178. const dict = Object.entries(this.morphTargetDictionary || {})
  179. return dict.length ? {
  180. label: 'Morph Targets',
  181. type: 'folder',
  182. children: dict.map(([name, i])=>({
  183. type: 'slider',
  184. label: name,
  185. bounds: [0, 1],
  186. stepSize: 0.0001,
  187. property: [this.morphTargetInfluences, i as any],
  188. onChange: (e: any)=>{
  189. this.setDirty?.({refreshScene: e.last, frameFade: false, refreshUi: false})
  190. },
  191. })),
  192. } : undefined
  193. },
  194. // geometry
  195. ()=>(this.geometry as IUiConfigContainer)?.uiConfig,
  196. // material(s)
  197. ()=>Array.isArray(this.material) ? this.material.length < 1 ? undefined : {
  198. label: 'Materials',
  199. type: 'folder',
  200. children: (this.material as IUiConfigContainer[]).map((a)=>a?.uiConfig).filter(a=>a),
  201. } : (this.material as IUiConfigContainer)?.uiConfig,
  202. ]
  203. ;(config.children as UiObjectConfig[]).push(...ui)
  204. }
  205. // todo: if we are replacing all the cameras in the scene, is this even required?
  206. if (this.isCamera) {
  207. const ui: UiObjectConfig[] = makeICameraCommonUiConfig.call(this as ICamera, config)
  208. ;(config.children as UiObjectConfig[]).push(...ui)
  209. }
  210. // todo: lights?
  211. this.uiConfig = config
  212. return config
  213. }