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