textureCanvasManager.ts 26 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689
  1. import { Engine } from 'babylonjs/Engines/engine';
  2. import { Scene } from 'babylonjs/scene';
  3. import { Vector3, Vector2 } from 'babylonjs/Maths/math.vector';
  4. import { Color4, Color3 } from 'babylonjs/Maths/math.color';
  5. import { FreeCamera } from 'babylonjs/Cameras/freeCamera';
  6. import { Nullable } from 'babylonjs/types'
  7. import { PlaneBuilder } from 'babylonjs/Meshes/Builders/planeBuilder';
  8. import { Mesh } from 'babylonjs/Meshes/mesh';
  9. import { Camera } from 'babylonjs/Cameras/camera';
  10. import { BaseTexture } from 'babylonjs/Materials/Textures/baseTexture';
  11. import { HtmlElementTexture } from 'babylonjs/Materials/Textures/htmlElementTexture';
  12. import { InternalTexture } from 'babylonjs/Materials/Textures/internalTexture';
  13. import { Texture } from 'babylonjs/Materials/Textures/texture';
  14. import { RawCubeTexture } from 'babylonjs/Materials/Textures/rawCubeTexture';
  15. import { CubeTexture } from 'babylonjs/Materials/Textures/cubeTexture';
  16. import { ShaderMaterial } from 'babylonjs/Materials/shaderMaterial';
  17. import { StandardMaterial } from 'babylonjs/Materials/standardMaterial';
  18. import { ISize } from 'babylonjs/Maths/math.size';
  19. import { Tools } from 'babylonjs/Misc/tools';
  20. import { PointerEventTypes, PointerInfo } from 'babylonjs/Events/pointerEvents';
  21. import { KeyboardEventTypes } from 'babylonjs/Events/keyboardEvents';
  22. import { TextureHelper } from '../../../../../../textureHelper';
  23. import { ITool } from './toolBar';
  24. import { IChannel } from './channelsBar';
  25. import { IMetadata } from './textureEditorComponent';
  26. import { canvasShader } from './canvasShader';
  27. export interface IPixelData {
  28. x? : number;
  29. y? : number;
  30. r? : number;
  31. g? : number;
  32. b? : number;
  33. a? : number;
  34. }
  35. export class TextureCanvasManager {
  36. private _engine: Engine;
  37. private _scene: Scene;
  38. private _camera: FreeCamera;
  39. private _cameraPos: Vector2;
  40. private _scale : number;
  41. private _isPanning : boolean = false;
  42. private _mouseX : number;
  43. private _mouseY : number;
  44. private _UICanvas : HTMLCanvasElement;
  45. private _size : ISize;
  46. /** The canvas we paint onto using the canvas API */
  47. private _2DCanvas : HTMLCanvasElement;
  48. /** The canvas we apply post processes to */
  49. private _3DCanvas : HTMLCanvasElement;
  50. /** The canvas which handles channel filtering */
  51. private _channelsTexture : HtmlElementTexture;
  52. private _3DEngine : Engine;
  53. private _3DPlane : Mesh;
  54. private _3DCanvasTexture : HtmlElementTexture;
  55. private _3DScene : Scene;
  56. private _channels : IChannel[] = [];
  57. private _face : number = 0;
  58. private _mipLevel : number = 0;
  59. /** The texture from the original engine that we invoked the editor on */
  60. private _originalTexture: BaseTexture;
  61. /** This is a hidden texture which is only responsible for holding the actual texture memory in the original engine */
  62. private _target : HtmlElementTexture | RawCubeTexture;
  63. /** The internal texture representation of the original texture */
  64. private _originalInternalTexture : Nullable<InternalTexture> = null;
  65. /** Keeps track of whether we have modified the texture */
  66. private _didEdit : boolean = false;
  67. private _plane : Mesh;
  68. private _planeMaterial : ShaderMaterial;
  69. /** Tracks which keys are currently pressed */
  70. private _keyMap : any = {};
  71. private readonly ZOOM_MOUSE_SPEED : number = 0.001;
  72. private readonly ZOOM_KEYBOARD_SPEED : number = 0.4;
  73. private readonly ZOOM_IN_KEY : string = '+';
  74. private readonly ZOOM_OUT_KEY : string = '-';
  75. private readonly PAN_SPEED : number = 0.003;
  76. private readonly PAN_MOUSE_BUTTON : number = 1; // MMB
  77. private readonly MIN_SCALE : number = 0.01;
  78. private readonly GRID_SCALE : number = 0.047;
  79. private readonly MAX_SCALE : number = 10;
  80. private readonly SELECT_ALL_KEY = 'KeyA';
  81. private readonly SAVE_KEY ='KeyS';
  82. private readonly RESET_KEY = 'KeyR';
  83. private readonly DESELECT_KEY = 'Escape'
  84. /** The number of milliseconds between texture updates */
  85. private readonly PUSH_FREQUENCY = 32;
  86. private _tool : Nullable<ITool>;
  87. private _setPixelData : (pixelData : IPixelData) => void;
  88. private _setMipLevel: (mipLevel: number) => void;
  89. private _window : Window;
  90. private _metadata : IMetadata;
  91. private _editing3D : boolean = false;
  92. private _onUpdate : () => void;
  93. private _setMetadata : (metadata: any) => void;
  94. private _imageData : Uint8Array | Uint8ClampedArray;
  95. private _canPush : boolean = true;
  96. private _shouldPush : boolean = false;
  97. private _paintCanvas: HTMLCanvasElement;
  98. public constructor(
  99. texture: BaseTexture,
  100. window: Window,
  101. canvasUI: HTMLCanvasElement,
  102. canvas2D: HTMLCanvasElement,
  103. canvas3D: HTMLCanvasElement,
  104. setPixelData: (pixelData : IPixelData) => void,
  105. metadata: IMetadata,
  106. onUpdate: () => void,
  107. setMetadata: (metadata: any) => void,
  108. setMipLevel: (level: number) => void
  109. ) {
  110. this._window = window;
  111. this._UICanvas = canvasUI;
  112. this._2DCanvas = canvas2D;
  113. this._3DCanvas = canvas3D;
  114. this._paintCanvas = document.createElement('canvas');
  115. this._setPixelData = setPixelData;
  116. this._metadata = metadata;
  117. this._onUpdate = onUpdate;
  118. this._setMetadata = setMetadata;
  119. this._setMipLevel = setMipLevel;
  120. this._size = texture.getSize();
  121. this._originalTexture = texture;
  122. this._originalInternalTexture = this._originalTexture._texture;
  123. this._engine = new Engine(this._UICanvas, true);
  124. this._scene = new Scene(this._engine, {virtual: true});
  125. this._scene.clearColor = new Color4(0.11, 0.11, 0.11, 1.0);
  126. this._camera = new FreeCamera('camera', new Vector3(0, 0, -1), this._scene);
  127. this._camera.mode = Camera.ORTHOGRAPHIC_CAMERA;
  128. this._cameraPos = new Vector2();
  129. this._channelsTexture = new HtmlElementTexture('ct', this._2DCanvas, {engine: this._engine, scene: null, samplingMode: Texture.NEAREST_SAMPLINGMODE, generateMipMaps: true});
  130. this._3DEngine = new Engine(this._3DCanvas);
  131. this._3DScene = new Scene(this._3DEngine, {virtual: true});
  132. this._3DScene.clearColor = new Color4(0,0,0,0);
  133. this._3DCanvasTexture = new HtmlElementTexture('canvas', this._2DCanvas, {engine: this._3DEngine, scene: this._3DScene});
  134. this._3DCanvasTexture.hasAlpha = true;
  135. const cam = new FreeCamera('camera', new Vector3(0,0,-1), this._3DScene);
  136. cam.mode = Camera.ORTHOGRAPHIC_CAMERA;
  137. [cam.orthoBottom, cam.orthoLeft, cam.orthoTop, cam.orthoRight] = [-0.5, -0.5, 0.5, 0.5];
  138. this._3DPlane = PlaneBuilder.CreatePlane('texture', {width: 1, height: 1}, this._3DScene);
  139. this._3DPlane.hasVertexAlpha = true;
  140. const mat = new StandardMaterial('material', this._3DScene);
  141. mat.diffuseTexture = this._3DCanvasTexture;
  142. mat.useAlphaFromDiffuseTexture = true;
  143. mat.disableLighting = true;
  144. mat.emissiveColor = Color3.White();
  145. this._3DPlane.material = mat;
  146. this._planeMaterial = new ShaderMaterial(
  147. 'canvasShader',
  148. this._scene,
  149. canvasShader.path,
  150. canvasShader.options
  151. );
  152. this.grabOriginalTexture();
  153. this._planeMaterial.setTexture('textureSampler', this._channelsTexture);
  154. this._planeMaterial.setFloat('r', 1.0);
  155. this._planeMaterial.setFloat('g', 1.0);
  156. this._planeMaterial.setFloat('b', 1.0);
  157. this._planeMaterial.setFloat('a', 1.0);
  158. this._planeMaterial.setInt('x1', -1);
  159. this._planeMaterial.setInt('y1', -1);
  160. this._planeMaterial.setInt('x2', -1);
  161. this._planeMaterial.setInt('y2', -1);
  162. this._planeMaterial.setInt('w', this._size.width);
  163. this._planeMaterial.setInt('h', this._size.height);
  164. this._planeMaterial.setInt('time', 0);
  165. this._planeMaterial.setFloat('showGrid', 0.0);
  166. this._plane.material = this._planeMaterial;
  167. this._window.addEventListener('keydown', evt => {
  168. this._keyMap[evt.code] = true;
  169. if (evt.code === this.SELECT_ALL_KEY && evt.ctrlKey) {
  170. this._setMetadata({
  171. select: {
  172. x1: 0,
  173. y1: 0,
  174. x2: this._size.width,
  175. y2: this._size.height
  176. }
  177. });
  178. evt.preventDefault();
  179. }
  180. if (evt.code === this.SAVE_KEY && evt.ctrlKey) {
  181. this.saveTexture();
  182. evt.preventDefault();
  183. }
  184. if (evt.code === this.RESET_KEY && evt.ctrlKey) {
  185. this.reset();
  186. evt.preventDefault();
  187. }
  188. if (evt.code === this.DESELECT_KEY) {
  189. this._setMetadata({
  190. select: {
  191. x1: -1,
  192. y1: -1,
  193. x2: -1,
  194. y2: -1
  195. }
  196. })
  197. }
  198. });
  199. this._window.addEventListener('keyup', evt => {
  200. this._keyMap[evt.code] = false;
  201. });
  202. this._engine.runRenderLoop(() => {
  203. this._engine.resize();
  204. this._scene.render();
  205. this._planeMaterial.setInt('time', new Date().getTime());
  206. });
  207. this._scale = 1.5 / Math.max(this._size.width, this._size.height);
  208. this._isPanning = false;
  209. this._scene.onBeforeRenderObservable.add(() => {
  210. this._scale = Math.min(Math.max(this._scale, this.MIN_SCALE / Math.log2(Math.min(this._size.width, this._size.height))), this.MAX_SCALE);
  211. if (this._scale > this.GRID_SCALE) {
  212. this._planeMaterial.setFloat('showGrid', 1.0);
  213. } else {
  214. this._planeMaterial.setFloat('showGrid', 0.0);
  215. }
  216. const ratio = this._UICanvas?.width / this._UICanvas?.height;
  217. const {x,y} = this._cameraPos;
  218. this._camera.orthoBottom = y - 1 / this._scale;
  219. this._camera.orthoTop = y + 1 / this._scale;
  220. this._camera.orthoLeft = x - ratio / this._scale;
  221. this._camera.orthoRight = x + ratio / this._scale;
  222. })
  223. this._scene.onPointerObservable.add((pointerInfo) => {
  224. switch (pointerInfo.type) {
  225. case PointerEventTypes.POINTERWHEEL:
  226. const event = pointerInfo.event as MouseWheelEvent;
  227. this._scale -= (event.deltaY * this.ZOOM_MOUSE_SPEED * this._scale);
  228. break;
  229. case PointerEventTypes.POINTERDOWN:
  230. if (pointerInfo.event.button === this.PAN_MOUSE_BUTTON) {
  231. this._isPanning = true;
  232. this._mouseX = pointerInfo.event.x;
  233. this._mouseY = pointerInfo.event.y;
  234. pointerInfo.event.preventDefault();
  235. }
  236. break;
  237. case PointerEventTypes.POINTERUP:
  238. if (pointerInfo.event.button === this.PAN_MOUSE_BUTTON) {
  239. this._isPanning = false;
  240. }
  241. break;
  242. case PointerEventTypes.POINTERMOVE:
  243. if (this._isPanning) {
  244. this._cameraPos.x -= (pointerInfo.event.x - this._mouseX) * this.PAN_SPEED / this._scale;
  245. this._cameraPos.y += (pointerInfo.event.y - this._mouseY) * this.PAN_SPEED / this._scale;
  246. this._mouseX = pointerInfo.event.x;
  247. this._mouseY = pointerInfo.event.y;
  248. }
  249. if (pointerInfo.pickInfo?.hit) {
  250. const pos = this.getMouseCoordinates(pointerInfo);
  251. const idx = (pos.x + pos.y * this._size.width) * 4;
  252. this._setPixelData({x: pos.x, y: pos.y, r:this._imageData[idx], g:this._imageData[idx + 1], b:this._imageData[idx + 2], a:this._imageData[idx + 3]});
  253. } else {
  254. this._setPixelData({});
  255. }
  256. break;
  257. }
  258. })
  259. this._scene.onKeyboardObservable.add((kbInfo) => {
  260. switch(kbInfo.type) {
  261. case KeyboardEventTypes.KEYDOWN:
  262. this._keyMap[kbInfo.event.key] = true;
  263. switch (kbInfo.event.key) {
  264. case this.ZOOM_IN_KEY:
  265. this._scale += this.ZOOM_KEYBOARD_SPEED * this._scale;
  266. break;
  267. case this.ZOOM_OUT_KEY:
  268. this._scale -= this.ZOOM_KEYBOARD_SPEED * this._scale;
  269. break;
  270. }
  271. break;
  272. case KeyboardEventTypes.KEYUP:
  273. this._keyMap[kbInfo.event.key] = false;
  274. break;
  275. }
  276. });
  277. }
  278. public async updateTexture() {
  279. if (this._mipLevel !== 0) await this._setMipLevel(0);
  280. this._didEdit = true;
  281. const element = this._editing3D ? this._3DCanvas : this._2DCanvas;
  282. if (this._editing3D) {
  283. this._3DCanvasTexture.update();
  284. this._3DScene.render();
  285. }
  286. if (this._originalTexture.isCube) {
  287. // TODO: fix cube map editing
  288. } else {
  289. if (!this._target) {
  290. this._target = new HtmlElementTexture(
  291. "editor",
  292. element,
  293. {
  294. engine: this._originalTexture.getScene()?.getEngine()!,
  295. scene: null,
  296. samplingMode: (this._originalTexture as Texture).samplingMode,
  297. generateMipMaps: this._originalInternalTexture?.generateMipMaps
  298. }
  299. );
  300. } else {
  301. (this._target as HtmlElementTexture).element = element;
  302. }
  303. this.pushTexture();
  304. }
  305. this._originalTexture._texture = this._target._texture;
  306. this._channelsTexture.element = element;
  307. this.updateDisplay();
  308. this._onUpdate();
  309. }
  310. private pushTexture() {
  311. if (this._canPush) {
  312. (this._target as HtmlElementTexture).update((this._originalTexture as Texture).invertY);
  313. this._target._texture?.updateSize(this._size.width, this._size.height);
  314. if (this._editing3D) {
  315. this._imageData = this._3DEngine.readPixels(0, 0, this._size.width, this._size.height);
  316. } else {
  317. this._imageData = this._2DCanvas.getContext('2d')!.getImageData(0, 0, this._size.width, this._size.height).data;
  318. }
  319. this._canPush = false;
  320. this._shouldPush = false;
  321. setTimeout(() => {
  322. this._canPush = true;
  323. if (this._shouldPush) {
  324. this.pushTexture();
  325. }
  326. }, this.PUSH_FREQUENCY);
  327. } else {
  328. this._shouldPush = true;
  329. }
  330. }
  331. public async startPainting() : Promise<CanvasRenderingContext2D> {
  332. if (this._mipLevel != 0) await this._setMipLevel(0);
  333. let x = 0, y = 0, w = this._size.width, h = this._size.height;
  334. if (this._metadata.select.x1 != -1) {
  335. x = this._metadata.select.x1;
  336. y = this._metadata.select.y1;
  337. w = this._metadata.select.x2 - this._metadata.select.x1;
  338. h = this._metadata.select.y2 - this._metadata.select.y1;
  339. }
  340. this._paintCanvas.width = w;
  341. this._paintCanvas.height = h;
  342. const ctx = this._paintCanvas.getContext('2d')!;
  343. ctx.imageSmoothingEnabled = false;
  344. ctx.drawImage(this._2DCanvas, x, y, w, h, 0, 0, w, h);
  345. return ctx;
  346. }
  347. public updatePainting() {
  348. let x = 0, y = 0, w = this._size.width, h = this._size.height;
  349. if (this._metadata.select.x1 != -1) {
  350. x = this._metadata.select.x1;
  351. y = this._metadata.select.y1;
  352. w = this._metadata.select.x2 - this._metadata.select.x1;
  353. h = this._metadata.select.y2 - this._metadata.select.y1;
  354. }
  355. let editingAllChannels = true;
  356. this._channels.forEach(channel => {
  357. if (!channel.editable) editingAllChannels = false;
  358. })
  359. let oldData : Uint8ClampedArray;
  360. if (!editingAllChannels) {
  361. oldData = this._2DCanvas.getContext('2d')!.getImageData(x, y, w, h).data;
  362. }
  363. const ctx = this._paintCanvas.getContext('2d')!;
  364. const ctx2D = this.canvas2D.getContext('2d')!;
  365. ctx2D.globalAlpha = 1.0;
  366. ctx2D.globalCompositeOperation = 'destination-out';
  367. ctx2D.fillStyle = 'white';
  368. ctx2D.fillRect(x,y,w,h);
  369. ctx2D.imageSmoothingEnabled = false;
  370. // If we're not editing all channels, we must process the pixel data
  371. if (!editingAllChannels) {
  372. const newData = ctx.getImageData(0, 0, w, h);
  373. const nd = newData.data;
  374. this._channels.forEach((channel, index) => {
  375. if (!channel.editable) {
  376. for(let i = index; i < w * h * 4; i += 4) {
  377. nd[i] = oldData[i];
  378. }
  379. }
  380. });
  381. ctx2D.globalCompositeOperation = 'source-over';
  382. ctx2D.globalAlpha = 1.0;
  383. ctx2D.putImageData(newData, x, y);
  384. } else {
  385. ctx2D.globalCompositeOperation = 'source-over';
  386. ctx2D.globalAlpha = 1.0;
  387. // We want to use drawImage wherever possible since it is much faster than putImageData
  388. ctx2D.drawImage(ctx.canvas, x, y);
  389. }
  390. this.updateTexture();
  391. }
  392. public stopPainting() : void {
  393. this._paintCanvas.getContext('2d')!.clearRect(0, 0, this._paintCanvas.width, this._paintCanvas.height);
  394. }
  395. private updateDisplay() {
  396. this._3DScene.render();
  397. this._channelsTexture.update(true);
  398. }
  399. public set channels(channels: IChannel[]) {
  400. // Determine if we need to re-render the texture. This is an expensive operation, so we should only do it if channel visibility has changed.
  401. let needsRender = false;
  402. if (channels.length !== this._channels.length) {
  403. needsRender = true;
  404. }
  405. else {
  406. channels.forEach(
  407. (channel,index) => {
  408. if (channel.visible !== this._channels[index].visible) {
  409. needsRender = true;
  410. this._planeMaterial.setFloat(channel.id.toLowerCase(), channel.visible ? 1.0 : 0.0);
  411. }
  412. }
  413. );
  414. }
  415. this._channels = channels;
  416. if (needsRender) {
  417. this.updateDisplay();
  418. }
  419. }
  420. public paintPixelsOnCanvas(pixelData : Uint8Array, canvas: HTMLCanvasElement) {
  421. const ctx = canvas.getContext('2d')!;
  422. const imgData = ctx.createImageData(canvas.width, canvas.height);
  423. imgData.data.set(pixelData);
  424. ctx.putImageData(imgData, 0, 0);
  425. }
  426. public async grabOriginalTexture() {
  427. // Grab image data from original texture and paint it onto the context of a DynamicTexture
  428. this.setSize(this._originalTexture.getSize());
  429. const data = await TextureHelper.GetTextureDataAsync(
  430. this._originalTexture,
  431. this._size.width,
  432. this._size.height,
  433. this._face,
  434. {R:true, G:true, B:true, A:true},
  435. undefined,
  436. this._mipLevel
  437. );
  438. this._imageData = data;
  439. this.paintPixelsOnCanvas(data, this._2DCanvas);
  440. this._3DCanvasTexture.update();
  441. this.updateDisplay();
  442. return data;
  443. }
  444. public getMouseCoordinates(pointerInfo : PointerInfo) {
  445. if (pointerInfo.pickInfo?.hit) {
  446. const x = Math.floor(pointerInfo.pickInfo.getTextureCoordinates()!.x * this._size.width);
  447. const y = Math.floor((1 - pointerInfo.pickInfo.getTextureCoordinates()!.y) * this._size.height);
  448. return new Vector2(x,y);
  449. } else {
  450. return new Vector2();
  451. }
  452. }
  453. public get scene() {
  454. return this._scene;
  455. }
  456. public get canvas2D() {
  457. return this._2DCanvas;
  458. }
  459. public get size() {
  460. return this._size;
  461. }
  462. public set tool(tool: Nullable<ITool>) {
  463. if (this._tool) {
  464. this._tool.instance.cleanup();
  465. }
  466. this._tool = tool;
  467. if (this._tool) {
  468. this._tool.instance.setup();
  469. if (this._editing3D && !this._tool.is3D) {
  470. this._editing3D = false;
  471. this._2DCanvas.getContext('2d')?.drawImage(this._3DCanvas, 0, 0);
  472. }
  473. else if (!this._editing3D && this._tool.is3D) {
  474. this._editing3D = true;
  475. this.updateTexture();
  476. }
  477. }
  478. }
  479. public get tool() {
  480. return this._tool;
  481. }
  482. public set face(face: number) {
  483. if (this._face !== face) {
  484. this._face = face;
  485. this.grabOriginalTexture();
  486. this.updateDisplay();
  487. }
  488. }
  489. public set mipLevel(mipLevel : number) {
  490. if (this._mipLevel === mipLevel) return;
  491. this._mipLevel = mipLevel;
  492. this.grabOriginalTexture();
  493. }
  494. /** Returns the 3D scene used for postprocesses */
  495. public get scene3D() {
  496. return this._3DScene;
  497. }
  498. public set metadata(metadata: IMetadata) {
  499. this._metadata = metadata;
  500. const {x1,y1,x2,y2} = metadata.select;
  501. this._planeMaterial.setInt('x1', x1);
  502. this._planeMaterial.setInt('y1', y1);
  503. this._planeMaterial.setInt('x2', x2);
  504. this._planeMaterial.setInt('y2', y2);
  505. }
  506. private makePlane() {
  507. if (this._plane) this._plane.dispose();
  508. this._plane = PlaneBuilder.CreatePlane("plane", {width: this._size.width, height: this._size.height}, this._scene);
  509. this._plane.enableEdgesRendering();
  510. this._plane.edgesWidth = 4.0;
  511. this._plane.edgesColor = new Color4(1,1,1,1);
  512. this._plane.enablePointerMoveEvents = true;
  513. this._plane.material = this._planeMaterial;
  514. }
  515. public reset() : void {
  516. if (this._tool && this._tool.instance.onReset) {
  517. this._tool.instance.onReset();
  518. }
  519. this._originalTexture._texture = this._originalInternalTexture;
  520. this.grabOriginalTexture();
  521. this.makePlane();
  522. this._didEdit = false;
  523. this._onUpdate();
  524. }
  525. public async resize(newSize : ISize) {
  526. const data = await TextureHelper.GetTextureDataAsync(this._originalTexture, newSize.width, newSize.height, this._face, {R: true,G: true,B: true,A: true});
  527. this.setSize(newSize);
  528. this.paintPixelsOnCanvas(data, this._2DCanvas);
  529. this.updateTexture();
  530. this._didEdit = true;
  531. }
  532. public setSize(size: ISize) {
  533. const oldSize = this._size;
  534. this._size = size;
  535. this._2DCanvas.width = this._size.width;
  536. this._2DCanvas.height = this._size.height;
  537. this._3DCanvas.width = this._size.width;
  538. this._3DCanvas.height = this._size.height;
  539. this._planeMaterial.setInt('w', this._size.width);
  540. this._planeMaterial.setInt('h', this._size.height);
  541. if (oldSize.width != size.width || oldSize.height != size.height) {
  542. this._cameraPos.x = 0;
  543. this._cameraPos.y = 0;
  544. this._scale = 1.5 / Math.max(this._size.width, this._size.height);
  545. }
  546. this.makePlane();
  547. }
  548. public upload(file : File) {
  549. Tools.ReadFile(file, (data) => {
  550. var blob = new Blob([data], { type: "octet/stream" });
  551. let extension: string | undefined = undefined;
  552. if (file.name.toLowerCase().indexOf(".dds") > 0) {
  553. extension = ".dds";
  554. } else if (file.name.toLowerCase().indexOf(".env") > 0) {
  555. extension = ".env";
  556. }
  557. var reader = new FileReader();
  558. reader.readAsDataURL(blob);
  559. reader.onloadend = () => {
  560. let base64data = reader.result as string;
  561. if (extension === '.dds' || extension === '.env') {
  562. (this._originalTexture as CubeTexture).updateURL(base64data, extension, () => this.grabOriginalTexture());
  563. } else {
  564. const texture = new Texture(
  565. base64data,
  566. this._scene,
  567. this._originalTexture.noMipmap,
  568. false,
  569. Texture.NEAREST_SAMPLINGMODE,
  570. () => {
  571. TextureHelper.GetTextureDataAsync(texture, texture.getSize().width, texture.getSize().height, 0, {R: true, G: true, B: true, A: true})
  572. .then(async (pixels) => {
  573. if (this._tool && this._tool.instance.onReset) {
  574. this._tool.instance.onReset();
  575. }
  576. texture.dispose();
  577. this.setSize(texture.getSize());
  578. this.paintPixelsOnCanvas(pixels, this._2DCanvas);
  579. await this.updateTexture();
  580. this._setMipLevel(0);
  581. });
  582. }
  583. );
  584. }
  585. };
  586. }, undefined, true);
  587. }
  588. public saveTexture() {
  589. const canvas = this._editing3D ? this._3DCanvas : this._2DCanvas;
  590. Tools.ToBlob(canvas, (blob) => {
  591. Tools.Download(blob!, this._originalTexture.name);
  592. });
  593. }
  594. public dispose() {
  595. if (this._didEdit) {
  596. this._originalInternalTexture?.dispose();
  597. }
  598. if (this._tool) {
  599. this._tool.instance.cleanup();
  600. }
  601. this._paintCanvas.parentNode?.removeChild(this._paintCanvas);
  602. this._3DPlane.dispose();
  603. this._3DCanvasTexture.dispose();
  604. this._3DScene.dispose();
  605. this._3DEngine.dispose();
  606. this._plane.dispose();
  607. this._channelsTexture.dispose();
  608. this._planeMaterial.dispose();
  609. this._camera.dispose();
  610. this._scene.dispose();
  611. this._engine.dispose();
  612. }
  613. }