texturePropertyTabComponent.tsx 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316
  1. import * as React from "react";
  2. import { BaseTexture } from 'babylonjs/Materials/Textures/baseTexture';
  3. import { FileButtonLineComponent } from '../../sharedComponents/fileButtonLineComponent';
  4. import { Tools } from 'babylonjs/Misc/tools';
  5. import { LineContainerComponent } from '../../sharedComponents/lineContainerComponent';
  6. import { TextInputLineComponent } from '../../sharedComponents/textInputLineComponent';
  7. import { CheckBoxLineComponent } from '../../sharedComponents/checkBoxLineComponent';
  8. import { Texture } from 'babylonjs/Materials/Textures/texture';
  9. import { SliderLineComponent } from '../../sharedComponents/sliderLineComponent';
  10. import { FloatLineComponent } from '../../sharedComponents/floatLineComponent';
  11. import { ButtonLineComponent } from '../../sharedComponents/buttonLineComponent';
  12. import { CubeTexture } from 'babylonjs/Materials/Textures/cubeTexture';
  13. import { OptionsLineComponent } from '../../sharedComponents/optionsLineComponent';
  14. import { IPropertyComponentProps } from './propertyComponentProps';
  15. import { ReflectionTextureBlock } from 'babylonjs/Materials/Node/Blocks/Dual/reflectionTextureBlock';
  16. import { ReflectionBlock } from 'babylonjs/Materials/Node/Blocks/PBR/reflectionBlock';
  17. import { RefractionBlock } from 'babylonjs/Materials/Node/Blocks/PBR/refractionBlock';
  18. import { TextureBlock } from 'babylonjs/Materials/Node/Blocks/Dual/textureBlock';
  19. import { CurrentScreenBlock } from 'babylonjs/Materials/Node/Blocks/Dual/currentScreenBlock';
  20. import { GeneralPropertyTabComponent, GenericPropertyTabComponent } from './genericNodePropertyComponent';
  21. import { NodeMaterialModes } from 'babylonjs/Materials/Node/Enums/nodeMaterialModes';
  22. type ReflectionTexture = ReflectionTextureBlock | ReflectionBlock | RefractionBlock;
  23. type AnyTexture = TextureBlock | ReflectionTexture | CurrentScreenBlock;
  24. export class TexturePropertyTabComponent extends React.Component<IPropertyComponentProps, {isEmbedded: boolean, loadAsCubeTexture: boolean}> {
  25. get textureBlock(): AnyTexture {
  26. return this.props.block as AnyTexture;
  27. }
  28. constructor(props: IPropertyComponentProps) {
  29. super(props);
  30. let texture = this.textureBlock.texture as BaseTexture;
  31. this.state = {isEmbedded: !texture || texture.name.substring(0, 4) === "data", loadAsCubeTexture: texture && texture.isCube};
  32. }
  33. UNSAFE_componentWillUpdate(nextProps: IPropertyComponentProps, nextState: {isEmbedded: boolean, loadAsCubeTexture: boolean}) {
  34. if (nextProps.block !== this.props.block) {
  35. let texture = (nextProps.block as AnyTexture).texture as BaseTexture;
  36. nextState.isEmbedded = !texture || texture.name.substring(0, 4) === "data";
  37. nextState.loadAsCubeTexture = texture && texture.isCube;
  38. }
  39. }
  40. private _generateRandomForCache() {
  41. return 'xxxxxxxxxxxxxxxxxxxx'.replace(/[x]/g, (c) => {
  42. var r = Math.random() * 10 | 0;
  43. return r.toString();
  44. });
  45. }
  46. updateAfterTextureLoad() {
  47. this.props.globalState.onUpdateRequiredObservable.notifyObservers();
  48. this.props.globalState.onRebuildRequiredObservable.notifyObservers();
  49. this.forceUpdate();
  50. }
  51. removeTexture() {
  52. let texture = this.textureBlock.texture as BaseTexture;
  53. if (texture) {
  54. texture.dispose();
  55. (texture as any) = null;
  56. this.textureBlock.texture = null;
  57. }
  58. this.updateAfterTextureLoad();
  59. }
  60. _prepareTexture() {
  61. let texture = this.textureBlock.texture as BaseTexture;
  62. if (texture && texture.isCube !== this.state.loadAsCubeTexture) {
  63. texture.dispose();
  64. (texture as any) = null;
  65. }
  66. if (!texture) {
  67. if (!this.state.loadAsCubeTexture) {
  68. this.textureBlock.texture = new Texture(null, this.props.globalState.nodeMaterial.getScene(), false,
  69. this.textureBlock instanceof ReflectionTextureBlock || this.textureBlock instanceof ReflectionBlock || this.textureBlock instanceof RefractionBlock || this.props.globalState.mode === NodeMaterialModes.PostProcess);
  70. texture = this.textureBlock.texture;
  71. texture.coordinatesMode = Texture.EQUIRECTANGULAR_MODE;
  72. } else {
  73. this.textureBlock.texture = new CubeTexture("", this.props.globalState.nodeMaterial.getScene());
  74. texture = this.textureBlock.texture;
  75. texture.coordinatesMode = Texture.CUBIC_MODE;
  76. }
  77. }
  78. }
  79. /**
  80. * Replaces the texture of the node
  81. * @param file the file of the texture to use
  82. */
  83. replaceTexture(file: File) {
  84. this._prepareTexture();
  85. let texture = this.textureBlock.texture as BaseTexture;
  86. Tools.ReadFile(file, (data) => {
  87. var blob = new Blob([data], { type: "octet/stream" });
  88. var reader = new FileReader();
  89. reader.readAsDataURL(blob);
  90. reader.onloadend = () => {
  91. let base64data = reader.result as string;
  92. let extension: string | undefined = undefined;
  93. if (file.name.toLowerCase().indexOf(".dds") > 0) {
  94. extension = ".dds";
  95. } else if (file.name.toLowerCase().indexOf(".env") > 0) {
  96. extension = ".env";
  97. }
  98. (texture as Texture).updateURL(base64data, extension, () => this.updateAfterTextureLoad());
  99. }
  100. }, undefined, true);
  101. }
  102. replaceTextureWithUrl(url: string) {
  103. this._prepareTexture();
  104. let texture = this.textureBlock.texture as BaseTexture;
  105. if (texture.isCube || this.textureBlock instanceof ReflectionTextureBlock || this.textureBlock instanceof ReflectionBlock || this.textureBlock instanceof RefractionBlock) {
  106. let extension: string | undefined = undefined;
  107. if (url.toLowerCase().indexOf(".dds") > 0) {
  108. extension = ".dds";
  109. } else if (url.toLowerCase().indexOf(".env") > 0) {
  110. extension = ".env";
  111. }
  112. (texture as Texture).updateURL(url, extension, () => this.updateAfterTextureLoad());
  113. } else {
  114. (texture as Texture).updateURL(url, null, () => this.updateAfterTextureLoad());
  115. }
  116. }
  117. render() {
  118. let url = "";
  119. let texture = this.textureBlock.texture as BaseTexture;
  120. if (texture && texture.name && texture.name.substring(0, 4) !== "data") {
  121. url = texture.name;
  122. }
  123. url = url.replace(/\?nocache=\d+/, "");
  124. let isInReflectionMode = this.textureBlock instanceof ReflectionTextureBlock || this.textureBlock instanceof ReflectionBlock || this.textureBlock instanceof RefractionBlock;
  125. let isFrozenTexture = this.textureBlock instanceof CurrentScreenBlock;
  126. var reflectionModeOptions: {label: string, value: number}[] = [
  127. {
  128. label: "Cubic", value: Texture.CUBIC_MODE
  129. },
  130. {
  131. label: "Equirectangular", value: Texture.EQUIRECTANGULAR_MODE
  132. },
  133. {
  134. label: "Explicit", value: Texture.EXPLICIT_MODE
  135. },
  136. {
  137. label: "Fixed equirectangular", value: Texture.FIXED_EQUIRECTANGULAR_MODE
  138. },
  139. {
  140. label: "Fixed mirrored equirectangular", value: Texture.FIXED_EQUIRECTANGULAR_MIRRORED_MODE
  141. },
  142. {
  143. label: "Planar", value: Texture.PLANAR_MODE
  144. },
  145. {
  146. label: "Projection", value: Texture.PROJECTION_MODE
  147. },
  148. {
  149. label: "Skybox", value: Texture.SKYBOX_MODE
  150. },
  151. {
  152. label: "Spherical", value: Texture.SPHERICAL_MODE
  153. },
  154. ];
  155. return (
  156. <div>
  157. <GeneralPropertyTabComponent globalState={this.props.globalState} block={this.props.block}/>
  158. <LineContainerComponent title="PROPERTIES">
  159. <CheckBoxLineComponent label="Auto select UV" propertyName="autoSelectUV" target={this.props.block} onValueChanged={() => {
  160. this.props.globalState.onUpdateRequiredObservable.notifyObservers();
  161. }}/>
  162. {
  163. texture && !isInReflectionMode &&
  164. <CheckBoxLineComponent label="Convert to gamma space" propertyName="convertToGammaSpace" target={this.props.block} onValueChanged={() => {
  165. this.props.globalState.onUpdateRequiredObservable.notifyObservers();
  166. }}/>
  167. }
  168. {
  169. texture && !isInReflectionMode &&
  170. <CheckBoxLineComponent label="Convert to linear space" propertyName="convertToLinearSpace" target={this.props.block} onValueChanged={() => {
  171. this.props.globalState.onUpdateRequiredObservable.notifyObservers();
  172. }}/>
  173. }
  174. {
  175. texture && isInReflectionMode &&
  176. <OptionsLineComponent label="Reflection mode" options={reflectionModeOptions} target={texture} propertyName="coordinatesMode" onSelect={(value: any) => {
  177. texture.coordinatesMode = value;
  178. this.forceUpdate();
  179. this.props.globalState.onUpdateRequiredObservable.notifyObservers();
  180. }} />
  181. }
  182. {
  183. texture && !isInReflectionMode && !isFrozenTexture &&
  184. <CheckBoxLineComponent label="Clamp U" isSelected={() => texture.wrapU === Texture.CLAMP_ADDRESSMODE} onSelect={(value) => {
  185. texture.wrapU = value ? Texture.CLAMP_ADDRESSMODE : Texture.WRAP_ADDRESSMODE;
  186. this.props.globalState.onUpdateRequiredObservable.notifyObservers();
  187. }} />
  188. }
  189. {
  190. texture && !isInReflectionMode && !isFrozenTexture &&
  191. <CheckBoxLineComponent label="Clamp V" isSelected={() => texture.wrapV === Texture.CLAMP_ADDRESSMODE} onSelect={(value) => {
  192. texture.wrapV = value ? Texture.CLAMP_ADDRESSMODE : Texture.WRAP_ADDRESSMODE;
  193. this.props.globalState.onUpdateRequiredObservable.notifyObservers();
  194. }} />
  195. }
  196. {
  197. texture && !isInReflectionMode && !isFrozenTexture &&
  198. <FloatLineComponent globalState={this.props.globalState} label="Offset U" target={texture} propertyName="uOffset"
  199. onChange={() => {
  200. this.props.globalState.onUpdateRequiredObservable.notifyObservers();
  201. }}
  202. />
  203. }
  204. {
  205. texture && !isInReflectionMode && !isFrozenTexture &&
  206. <FloatLineComponent globalState={this.props.globalState} label="Offset V" target={texture} propertyName="vOffset"
  207. onChange={() => {
  208. this.props.globalState.onUpdateRequiredObservable.notifyObservers();
  209. }}
  210. />
  211. }
  212. {
  213. texture && !isInReflectionMode && !isFrozenTexture &&
  214. <FloatLineComponent globalState={this.props.globalState} label="Scale U" target={texture} propertyName="uScale"
  215. onChange={() => {
  216. this.props.globalState.onUpdateRequiredObservable.notifyObservers();
  217. }} />
  218. }
  219. {
  220. texture && !isInReflectionMode && !isFrozenTexture &&
  221. <FloatLineComponent globalState={this.props.globalState} label="Scale V" target={texture} propertyName="vScale"
  222. onChange={() => {
  223. this.props.globalState.onUpdateRequiredObservable.notifyObservers();
  224. }} />
  225. }
  226. {
  227. texture && !isInReflectionMode && !isFrozenTexture &&
  228. <SliderLineComponent label="Rotation U" target={texture} propertyName="uAng" minimum={0} maximum={Math.PI * 2} useEuler={true} step={0.1}
  229. onChange={() => {
  230. this.props.globalState.onUpdateRequiredObservable.notifyObservers();
  231. }}
  232. />
  233. }
  234. {
  235. texture && !isInReflectionMode && !isFrozenTexture &&
  236. <SliderLineComponent label="Rotation V" target={texture} propertyName="vAng" minimum={0} maximum={Math.PI * 2} useEuler={true} step={0.1}
  237. onChange={() => {
  238. this.props.globalState.onUpdateRequiredObservable.notifyObservers();
  239. }}
  240. />
  241. }
  242. {
  243. texture && !isInReflectionMode && !isFrozenTexture &&
  244. <SliderLineComponent label="Rotation W" target={texture} propertyName="wAng" minimum={0} maximum={Math.PI * 2} useEuler={true} step={0.1}
  245. onChange={() => {
  246. this.props.globalState.onUpdateRequiredObservable.notifyObservers();
  247. }}
  248. />
  249. }
  250. </LineContainerComponent>
  251. <LineContainerComponent title="SOURCE">
  252. <CheckBoxLineComponent label="Embed static texture" isSelected={() => this.state.isEmbedded} onSelect={value => {
  253. this.setState({isEmbedded: value});
  254. this.textureBlock.texture = null;
  255. this.updateAfterTextureLoad();
  256. }}/>
  257. {
  258. isInReflectionMode &&
  259. <CheckBoxLineComponent label="Load as cube texture" isSelected={() => this.state.loadAsCubeTexture}
  260. onSelect={value => this.setState({loadAsCubeTexture: value})}/>
  261. }
  262. {
  263. this.state.isEmbedded &&
  264. <FileButtonLineComponent label="Upload" onClick={(file) => this.replaceTexture(file)} accept=".jpg, .png, .tga, .dds, .env" />
  265. }
  266. {
  267. !this.state.isEmbedded &&
  268. <TextInputLineComponent label="Link" globalState={this.props.globalState} value={url} onChange={newUrl => this.replaceTextureWithUrl(newUrl)}/>
  269. }
  270. {
  271. !this.state.isEmbedded && url &&
  272. <ButtonLineComponent label="Refresh" onClick={() => this.replaceTextureWithUrl(url + "?nocache=" + this._generateRandomForCache())}/>
  273. }
  274. {
  275. texture &&
  276. <ButtonLineComponent label="Remove" onClick={() => this.removeTexture()}/>
  277. }
  278. </LineContainerComponent>
  279. <GenericPropertyTabComponent globalState={this.props.globalState} block={this.props.block}/>
  280. </div>
  281. );
  282. }
  283. }