TextureParametersDialog.js 9.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293
  1. import * as THREE from 'three';
  2. import { UIButton, UICheckbox, UIDiv, UINumber, UIRow, UISelect, UIText } from './libs/ui.js';
  3. import { renderToCanvas } from './libs/ui.three.js';
  4. class TextureParametersDialog {
  5. constructor( editor, texture ) {
  6. this.editor = editor;
  7. this.strings = editor.strings;
  8. this.texture = texture;
  9. const dom = new UIDiv();
  10. dom.setClass( 'Dialog' );
  11. this.dom = dom.dom;
  12. const background = new UIDiv();
  13. background.setClass( 'Dialog-background' );
  14. background.dom.addEventListener( 'click', () => this.cancel() );
  15. dom.add( background );
  16. const content = new UIDiv();
  17. content.setClass( 'Dialog-content TextureParametersDialog-content' );
  18. dom.add( content );
  19. // Title
  20. const titleBar = new UIDiv();
  21. titleBar.setClass( 'Dialog-title' );
  22. titleBar.setTextContent( this.strings.getKey( 'dialog/texture/title' ) );
  23. content.add( titleBar );
  24. // Body (split into preview + form)
  25. const body = new UIDiv();
  26. body.setClass( 'Dialog-body TextureParametersDialog-body' );
  27. content.add( body );
  28. const split = new UIDiv();
  29. split.setClass( 'TextureParametersDialog-split' );
  30. body.add( split );
  31. // Preview
  32. const previewWrapper = new UIDiv();
  33. previewWrapper.setClass( 'TextureParametersDialog-preview' );
  34. split.add( previewWrapper );
  35. previewWrapper.add( createGroupHeading( this.strings.getKey( 'dialog/texture/group/preview' ) ) );
  36. const previewCanvas = document.createElement( 'canvas' );
  37. previewCanvas.width = 400;
  38. previewCanvas.height = 400;
  39. previewWrapper.dom.appendChild( previewCanvas );
  40. this.previewCanvas = previewCanvas;
  41. this.previewContext = previewCanvas.getContext( '2d' );
  42. this.previewTexture = texture.clone();
  43. const updatePreview = () => this.updatePreview();
  44. // Form
  45. const form = new UIDiv();
  46. form.setClass( 'TextureParametersDialog-form' );
  47. split.add( form );
  48. // Mapping group
  49. form.add( createGroupHeading( this.strings.getKey( 'dialog/texture/group/mapping' ) ) );
  50. this.mapping = new UISelect().setOptions( {
  51. [ THREE.UVMapping ]: 'UV',
  52. [ THREE.EquirectangularReflectionMapping ]: 'Equirectangular Reflection',
  53. [ THREE.EquirectangularRefractionMapping ]: 'Equirectangular Refraction',
  54. [ THREE.CubeReflectionMapping ]: 'Cube Reflection',
  55. [ THREE.CubeRefractionMapping ]: 'Cube Refraction',
  56. [ THREE.CubeUVReflectionMapping ]: 'CubeUV Reflection'
  57. } ).setValue( texture.mapping ).onChange( updatePreview );
  58. form.add( createRow( this.strings.getKey( 'dialog/texture/mapping' ), this.mapping ) );
  59. const wrapOptions = {
  60. [ THREE.RepeatWrapping ]: 'Repeat',
  61. [ THREE.ClampToEdgeWrapping ]: 'Clamp To Edge',
  62. [ THREE.MirroredRepeatWrapping ]: 'Mirrored Repeat'
  63. };
  64. this.wrapS = new UISelect().setOptions( wrapOptions ).setValue( texture.wrapS ).onChange( updatePreview );
  65. form.add( createRow( this.strings.getKey( 'dialog/texture/wrapS' ), this.wrapS ) );
  66. this.wrapT = new UISelect().setOptions( wrapOptions ).setValue( texture.wrapT ).onChange( updatePreview );
  67. form.add( createRow( this.strings.getKey( 'dialog/texture/wrapT' ), this.wrapT ) );
  68. // Filtering group
  69. form.add( createGroupHeading( this.strings.getKey( 'dialog/texture/group/filtering' ) ) );
  70. this.minFilter = new UISelect().setOptions( {
  71. [ THREE.NearestFilter ]: 'Nearest',
  72. [ THREE.NearestMipmapNearestFilter ]: 'Nearest Mipmap Nearest',
  73. [ THREE.NearestMipmapLinearFilter ]: 'Nearest Mipmap Linear',
  74. [ THREE.LinearFilter ]: 'Linear',
  75. [ THREE.LinearMipmapNearestFilter ]: 'Linear Mipmap Nearest',
  76. [ THREE.LinearMipmapLinearFilter ]: 'Linear Mipmap Linear'
  77. } ).setValue( texture.minFilter ).onChange( updatePreview );
  78. form.add( createRow( this.strings.getKey( 'dialog/texture/minFilter' ), this.minFilter ) );
  79. this.magFilter = new UISelect().setOptions( {
  80. [ THREE.NearestFilter ]: 'Nearest',
  81. [ THREE.LinearFilter ]: 'Linear'
  82. } ).setValue( texture.magFilter ).onChange( updatePreview );
  83. form.add( createRow( this.strings.getKey( 'dialog/texture/magFilter' ), this.magFilter ) );
  84. this.anisotropy = new UINumber( texture.anisotropy ).setPrecision( 0 ).setRange( 1, 16 ).setNudge( 1 ).setStep( 1 ).setWidth( '60px' ).onChange( updatePreview );
  85. form.add( createRow( this.strings.getKey( 'dialog/texture/anisotropy' ), this.anisotropy ) );
  86. // Transform group
  87. form.add( createGroupHeading( this.strings.getKey( 'dialog/texture/group/transform' ) ) );
  88. this.offsetX = new UINumber( texture.offset.x ).setWidth( '60px' ).onChange( updatePreview );
  89. this.offsetY = new UINumber( texture.offset.y ).setWidth( '60px' ).onChange( updatePreview );
  90. form.add( createRow( this.strings.getKey( 'dialog/texture/offset' ), this.offsetX, this.offsetY ) );
  91. this.repeatX = new UINumber( texture.repeat.x ).setWidth( '60px' ).onChange( updatePreview );
  92. this.repeatY = new UINumber( texture.repeat.y ).setWidth( '60px' ).onChange( updatePreview );
  93. form.add( createRow( this.strings.getKey( 'dialog/texture/repeat' ), this.repeatX, this.repeatY ) );
  94. this.centerX = new UINumber( texture.center.x ).setWidth( '60px' ).onChange( updatePreview );
  95. this.centerY = new UINumber( texture.center.y ).setWidth( '60px' ).onChange( updatePreview );
  96. form.add( createRow( this.strings.getKey( 'dialog/texture/center' ), this.centerX, this.centerY ) );
  97. this.rotation = new UINumber( texture.rotation * THREE.MathUtils.RAD2DEG ).setStep( 10 ).setNudge( 0.1 ).setUnit( '°' ).setWidth( '60px' ).onChange( updatePreview );
  98. form.add( createRow( this.strings.getKey( 'dialog/texture/rotation' ), this.rotation ) );
  99. // Color group
  100. form.add( createGroupHeading( this.strings.getKey( 'dialog/texture/group/color' ) ) );
  101. this.premultiplyAlpha = new UICheckbox( texture.premultiplyAlpha ).onChange( updatePreview );
  102. form.add( createRow( this.strings.getKey( 'dialog/texture/premultiplyAlpha' ), this.premultiplyAlpha ) );
  103. this.colorSpace = new UISelect().setOptions( {
  104. [ THREE.NoColorSpace ]: 'No Color Space',
  105. [ THREE.SRGBColorSpace ]: 'sRGB',
  106. [ THREE.LinearSRGBColorSpace ]: 'Linear sRGB'
  107. } ).setValue( texture.colorSpace ).onChange( updatePreview );
  108. form.add( createRow( this.strings.getKey( 'dialog/texture/colorSpace' ), this.colorSpace ) );
  109. updatePreview();
  110. // Buttons
  111. const buttonsRow = new UIDiv();
  112. buttonsRow.setClass( 'Dialog-buttons' );
  113. body.add( buttonsRow );
  114. const okButton = new UIButton( this.strings.getKey( 'dialog/ok' ) );
  115. okButton.setWidth( '80px' );
  116. okButton.onClick( () => this.confirm() );
  117. buttonsRow.add( okButton );
  118. const cancelButton = new UIButton( this.strings.getKey( 'dialog/cancel' ) );
  119. cancelButton.setWidth( '80px' );
  120. cancelButton.setMarginLeft( '8px' );
  121. cancelButton.onClick( () => this.cancel() );
  122. buttonsRow.add( cancelButton );
  123. // Promise handlers
  124. this.resolve = null;
  125. this.reject = null;
  126. }
  127. show() {
  128. document.body.appendChild( this.dom );
  129. return new Promise( ( resolve, reject ) => {
  130. this.resolve = resolve;
  131. this.reject = reject;
  132. } );
  133. }
  134. getCurrentParameters() {
  135. return {
  136. mapping: parseInt( this.mapping.getValue() ),
  137. wrapS: parseInt( this.wrapS.getValue() ),
  138. wrapT: parseInt( this.wrapT.getValue() ),
  139. magFilter: parseInt( this.magFilter.getValue() ),
  140. minFilter: parseInt( this.minFilter.getValue() ),
  141. anisotropy: this.anisotropy.getValue(),
  142. offset: { x: this.offsetX.getValue(), y: this.offsetY.getValue() },
  143. repeat: { x: this.repeatX.getValue(), y: this.repeatY.getValue() },
  144. center: { x: this.centerX.getValue(), y: this.centerY.getValue() },
  145. rotation: this.rotation.getValue() * THREE.MathUtils.DEG2RAD,
  146. premultiplyAlpha: this.premultiplyAlpha.getValue(),
  147. colorSpace: this.colorSpace.getValue()
  148. };
  149. }
  150. updatePreview() {
  151. applyParameters( this.previewTexture, this.getCurrentParameters() );
  152. const rendered = renderToCanvas( this.previewTexture );
  153. const canvas = this.previewCanvas;
  154. const context = this.previewContext;
  155. context.clearRect( 0, 0, canvas.width, canvas.height );
  156. if ( rendered.width === 0 || rendered.height === 0 ) return;
  157. const scale = Math.min( canvas.width / rendered.width, canvas.height / rendered.height );
  158. const w = rendered.width * scale;
  159. const h = rendered.height * scale;
  160. context.drawImage( rendered, ( canvas.width - w ) / 2, ( canvas.height - h ) / 2, w, h );
  161. }
  162. confirm() {
  163. const result = this.getCurrentParameters();
  164. this.previewTexture.dispose();
  165. this.dom.remove();
  166. if ( this.resolve ) this.resolve( result );
  167. }
  168. cancel() {
  169. this.previewTexture.dispose();
  170. this.dom.remove();
  171. if ( this.reject ) this.reject( new Error( 'Texture parameters edit cancelled' ) );
  172. }
  173. }
  174. function createRow( label, ...inputs ) {
  175. const row = new UIRow();
  176. row.add( new UIText( label ).setClass( 'Label' ) );
  177. for ( const input of inputs ) {
  178. row.add( input );
  179. }
  180. return row;
  181. }
  182. function createGroupHeading( label ) {
  183. const heading = new UIText( label );
  184. heading.setClass( 'TextureParametersDialog-groupHeading' );
  185. heading.setStyle( 'display', [ 'block' ] );
  186. return heading;
  187. }
  188. function applyParameters( texture, p ) {
  189. texture.mapping = p.mapping;
  190. texture.wrapS = p.wrapS;
  191. texture.wrapT = p.wrapT;
  192. texture.magFilter = p.magFilter;
  193. texture.minFilter = p.minFilter;
  194. texture.anisotropy = p.anisotropy;
  195. texture.offset.set( p.offset.x, p.offset.y );
  196. texture.repeat.set( p.repeat.x, p.repeat.y );
  197. texture.center.set( p.center.x, p.center.y );
  198. texture.rotation = p.rotation;
  199. texture.premultiplyAlpha = p.premultiplyAlpha;
  200. texture.colorSpace = p.colorSpace;
  201. texture.needsUpdate = true;
  202. }
  203. export { TextureParametersDialog };
粤ICP备19079148号