All user data for FoundryVTT. Includes worlds, systems, modules, and any asset in the "foundryuserdata" directory. Does NOT include the FoundryVTT installation itself.
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

699 lines
23 KiB

  1. import { FILTERS } from '../../applications/overlayConfig.js';
  2. import { evaluateComparator, getTokenEffects } from '../hooks/effectMappingHooks.js';
  3. import {
  4. registerOverlayRefreshHook,
  5. unregisterOverlayRefreshHooks,
  6. } from '../hooks/overlayHooks.js';
  7. import { DEFAULT_OVERLAY_CONFIG } from '../models.js';
  8. import { interpolateColor, removeMarkedOverlays } from '../token/overlay.js';
  9. import { executeMacro, toggleCEEffect, toggleTMFXPreset, tv_executeScript } from '../utils.js';
  10. class OutlineFilter extends OutlineOverlayFilter {
  11. /** @inheritdoc */
  12. static createFragmentShader() {
  13. return `
  14. varying vec2 vTextureCoord;
  15. varying vec2 vFilterCoord;
  16. uniform sampler2D uSampler;
  17. uniform vec2 thickness;
  18. uniform vec4 outlineColor;
  19. uniform vec4 filterClamp;
  20. uniform float alphaThreshold;
  21. uniform float time;
  22. uniform bool knockout;
  23. uniform bool wave;
  24. ${this.CONSTANTS}
  25. ${this.WAVE()}
  26. void main(void) {
  27. float dist = distance(vFilterCoord, vec2(0.5)) * 2.0;
  28. vec4 ownColor = texture2D(uSampler, vTextureCoord);
  29. vec4 wColor = wave ? outlineColor *
  30. wcos(0.0, 1.0, dist * 75.0,
  31. -time * 0.01 + 3.0 * dot(vec4(1.0), ownColor))
  32. * 0.33 * (1.0 - dist) : vec4(0.0);
  33. float texAlpha = smoothstep(alphaThreshold, 1.0, ownColor.a);
  34. vec4 curColor;
  35. float maxAlpha = 0.;
  36. vec2 displaced;
  37. for ( float angle = 0.0; angle <= TWOPI; angle += ${this.#quality.toFixed(7)} ) {
  38. displaced.x = vTextureCoord.x + thickness.x * cos(angle);
  39. displaced.y = vTextureCoord.y + thickness.y * sin(angle);
  40. curColor = texture2D(uSampler, clamp(displaced, filterClamp.xy, filterClamp.zw));
  41. curColor.a = clamp((curColor.a - 0.6) * 2.5, 0.0, 1.0);
  42. maxAlpha = max(maxAlpha, curColor.a);
  43. }
  44. float resultAlpha = max(maxAlpha, texAlpha);
  45. vec3 result = (ownColor.rgb + outlineColor.rgb * (1.0 - texAlpha)) * resultAlpha;
  46. gl_FragColor = vec4((ownColor.rgb + outlineColor.rgb * (1. - ownColor.a)) * resultAlpha, resultAlpha);
  47. }
  48. `;
  49. }
  50. static get #quality() {
  51. switch (canvas.performance.mode) {
  52. case CONST.CANVAS_PERFORMANCE_MODES.LOW:
  53. return (Math.PI * 2) / 10;
  54. case CONST.CANVAS_PERFORMANCE_MODES.MED:
  55. return (Math.PI * 2) / 20;
  56. default:
  57. return (Math.PI * 2) / 30;
  58. }
  59. }
  60. }
  61. export class TVASprite extends TokenMesh {
  62. constructor(pTexture, token, config) {
  63. super(token);
  64. if (pTexture.shapes) pTexture.shapes = this.addChild(pTexture.shapes);
  65. this.pseudoTexture = pTexture;
  66. this.texture = pTexture.texture;
  67. //this.setTexture(pTexture, { refresh: false });
  68. this.ready = false;
  69. this.overlaySort = 0;
  70. this.overlayConfig = mergeObject(DEFAULT_OVERLAY_CONFIG, config, { inplace: false });
  71. // linkDimensions has been converted to linkDimensionsX and linkDimensionsY
  72. // Make sure we're using the latest fields
  73. // 20/07/2023
  74. if (!('linkDimensionsX' in this.overlayConfig) && this.overlayConfig.linkDimensions) {
  75. this.overlayConfig.linkDimensionsX = true;
  76. this.overlayConfig.linkDimensionsY = true;
  77. }
  78. this._registerHooks(this.overlayConfig);
  79. this._tvaPlay().then(() => this.refresh());
  80. // Workaround needed for v11 visible property
  81. Object.defineProperty(this, 'visible', {
  82. get: this._customVisible,
  83. set: function () {},
  84. configurable: true,
  85. });
  86. this.enableInteractivity(this.overlayConfig);
  87. }
  88. enableInteractivity() {
  89. if (this.mouseInteractionManager && !this.overlayConfig.interactivity?.length) {
  90. this.removeAllListeners();
  91. this.mouseInteractionManager = null;
  92. this.cursor = null;
  93. return;
  94. } else if (this.mouseInteractionManager || !this.overlayConfig.interactivity?.length) return;
  95. if (canvas.primary.eventMode === 'none') {
  96. canvas.primary.eventMode = 'passive';
  97. }
  98. this.eventMode = 'static';
  99. this.cursor = 'pointer';
  100. const token = this.object;
  101. const sprite = this;
  102. const runInteraction = function (event, listener) {
  103. sprite.overlayConfig.interactivity.forEach((i) => {
  104. if (i.listener === listener) {
  105. event.preventDefault();
  106. event.stopPropagation();
  107. if (i.script) tv_executeScript(i.script, { token });
  108. if (i.macro) executeMacro(i.macro, token);
  109. if (i.ceEffect) toggleCEEffect(token, i.ceEffect);
  110. if (i.tmfxPreset) toggleTMFXPreset(token, i.tmfxPreset);
  111. }
  112. });
  113. };
  114. const permissions = {
  115. hoverIn: () => true,
  116. hoverOut: () => true,
  117. clickLeft: () => true,
  118. clickLeft2: () => true,
  119. clickRight: () => true,
  120. clickRight2: () => true,
  121. dragStart: () => false,
  122. };
  123. const callbacks = {
  124. hoverIn: (event) => runInteraction(event, 'hoverIn'),
  125. hoverOut: (event) => runInteraction(event, 'hoverOut'),
  126. clickLeft: (event) => runInteraction(event, 'clickLeft'),
  127. clickLeft2: (event) => runInteraction(event, 'clickLeft2'),
  128. clickRight: (event) => runInteraction(event, 'clickRight'),
  129. clickRight2: (event) => runInteraction(event, 'clickRight2'),
  130. dragLeftStart: null,
  131. dragLeftMove: null,
  132. dragLeftDrop: null,
  133. dragLeftCancel: null,
  134. dragRightStart: null,
  135. dragRightMove: null,
  136. dragRightDrop: null,
  137. dragRightCancel: null,
  138. longPress: null,
  139. };
  140. const options = { target: null };
  141. // Create the interaction manager
  142. const mgr = new MouseInteractionManager(this, canvas.stage, permissions, callbacks, options);
  143. this.mouseInteractionManager = mgr.activate();
  144. }
  145. _customVisible() {
  146. const ov = this.overlayConfig;
  147. if (!this.ready || !(this.object.visible || ov.alwaysVisible)) return false;
  148. if (ov.limitedToOwner && !this.object.owner) return false;
  149. if (ov.limitedUsers?.length && !ov.limitedUsers.includes(game.user.id)) return false;
  150. if (ov.limitOnEffect || ov.limitOnProperty) {
  151. const speaker = ChatMessage.getSpeaker();
  152. let token = canvas.ready ? canvas.tokens.get(speaker.token) : null;
  153. if (!token) return false;
  154. if (ov.limitOnEffect) {
  155. if (!getTokenEffects(token).includes(ov.limitOnEffect)) return false;
  156. }
  157. if (ov.limitOnProperty) {
  158. if (!evaluateComparator(token.document, ov.limitOnProperty)) return false;
  159. }
  160. }
  161. if (ov.limitOnHover || ov.limitOnControl || ov.limitOnHighlight) {
  162. let visible = false;
  163. if (
  164. ov.limitOnHover &&
  165. canvas.controls.ruler._state === Ruler.STATES.INACTIVE &&
  166. this.object.hover
  167. )
  168. visible = true;
  169. if (ov.limitOnControl && this.object.controlled) visible = true;
  170. if (ov.limitOnHighlight && (canvas.tokens.highlightObjects ?? canvas.tokens._highlight))
  171. visible = true;
  172. return visible;
  173. }
  174. return true;
  175. }
  176. // Overlays have the same sort order as the parent
  177. get sort() {
  178. let sort = this.object.document.sort || 0;
  179. if (this.overlayConfig.top) return sort + 1000;
  180. else if (this.overlayConfig.bottom) return sort - 1000;
  181. return sort;
  182. }
  183. get _lastSortedIndex() {
  184. return (this.object.mesh._lastSortedIndex || 0) + this.overlaySort;
  185. }
  186. set _lastSortedIndex(val) {}
  187. async _tvaPlay() {
  188. // Ensure playback state for video
  189. const source = foundry.utils.getProperty(this.texture, 'baseTexture.resource.source');
  190. if (source && source.tagName === 'VIDEO') {
  191. // Detach video from others
  192. const s = source.cloneNode();
  193. if (this.overlayConfig.playOnce) {
  194. s.onended = () => {
  195. this.alpha = 0;
  196. this.tvaVideoEnded = true;
  197. };
  198. }
  199. await new Promise((resolve) => (s.oncanplay = resolve));
  200. this.texture = PIXI.Texture.from(s, { resourceOptions: { autoPlay: false } });
  201. const options = {
  202. loop: this.overlayConfig.loop && !this.overlayConfig.playOnce,
  203. volume: 0,
  204. offset: 0,
  205. playing: true,
  206. };
  207. game.video.play(s, options);
  208. }
  209. }
  210. addChildAuto(...children) {
  211. if (this.pseudoTexture?.shapes) {
  212. return this.pseudoTexture.shapes.addChild(...children);
  213. } else {
  214. return this.addChild(...children);
  215. }
  216. }
  217. setTexture(pTexture, { preview = false, refresh = true, configuration = null } = {}) {
  218. // Text preview handling
  219. if (preview) {
  220. this._swapChildren(pTexture);
  221. if (this.originalTexture) this._destroyTexture();
  222. else {
  223. this.originalTexture = this.pseudoTexture;
  224. if (this.originalTexture.shapes) this.removeChild(this.originalTexture.shapes);
  225. }
  226. this.pseudoTexture = pTexture;
  227. this.texture = pTexture.texture;
  228. if (pTexture.shapes) pTexture.shapes = this.addChild(pTexture.shapes);
  229. } else if (this.originalTexture) {
  230. this._swapChildren(this.originalTexture);
  231. this._destroyTexture();
  232. this.pseudoTexture = this.originalTexture;
  233. this.texture = this.originalTexture.texture;
  234. if (this.originalTexture.shapes)
  235. this.pseudoTexture.shapes = this.addChild(this.originalTexture.shapes);
  236. delete this.originalTexture;
  237. } else {
  238. this._swapChildren(pTexture);
  239. this._destroyTexture();
  240. this.pseudoTexture = pTexture;
  241. this.texture = pTexture.texture;
  242. if (pTexture.shapes) this.pseudoTexture.shapes = this.addChild(pTexture.shapes);
  243. }
  244. if (refresh) this.refresh(configuration, { fullRefresh: !preview });
  245. }
  246. refresh(configuration, { preview = false, fullRefresh = true, previewTexture = null } = {}) {
  247. if (!this.overlayConfig || !this.texture) return;
  248. // Text preview handling
  249. if (previewTexture || this.originalTexture) {
  250. this.setTexture(previewTexture, { preview: Boolean(previewTexture), refresh: false });
  251. }
  252. // Register/Unregister hooks that should refresh this overlay
  253. if (configuration) {
  254. this._registerHooks(configuration);
  255. }
  256. const config = mergeObject(this.overlayConfig, configuration ?? {}, { inplace: !preview });
  257. this.enableInteractivity(config);
  258. if (fullRefresh) {
  259. const source = foundry.utils.getProperty(this.texture, 'baseTexture.resource.source');
  260. if (source && source.tagName === 'VIDEO') {
  261. if (!source.loop && config.loop) {
  262. game.video.play(source);
  263. } else if (source.loop && !config.loop) {
  264. game.video.stop(source);
  265. }
  266. source.loop = config.loop;
  267. }
  268. }
  269. const shapes = this.pseudoTexture.shapes;
  270. // Scale the image using the same logic as the token
  271. const dimensions = shapes ?? this.texture;
  272. if (config.linkScale && !config.parentID) {
  273. const scale = this.scale;
  274. const aspect = dimensions.width / dimensions.height;
  275. if (aspect >= 1) {
  276. scale.x = (this.object.w * this.object.document.texture.scaleX) / dimensions.width;
  277. scale.y = Number(scale.x);
  278. } else {
  279. scale.y = (this.object.h * this.object.document.texture.scaleY) / dimensions.height;
  280. scale.x = Number(scale.y);
  281. }
  282. } else if (config.linkStageScale) {
  283. this.scale.x = 1 / canvas.stage.scale.x;
  284. this.scale.y = 1 / canvas.stage.scale.y;
  285. } else if (config.linkDimensionsX || config.linkDimensionsY) {
  286. if (config.linkDimensionsX) {
  287. this.scale.x = this.object.document.width;
  288. }
  289. if (config.linkDimensionsY) {
  290. this.scale.y = this.object.document.height;
  291. }
  292. } else {
  293. this.scale.x = config.width ? config.width / dimensions.width : 1;
  294. this.scale.y = config.height ? config.height / dimensions.height : 1;
  295. }
  296. // Adjust scale according to config
  297. this.scale.x = this.scale.x * config.scaleX;
  298. this.scale.y = this.scale.y * config.scaleY;
  299. // Check if mirroring should be inherited from the token and if so apply it
  300. if (config.linkMirror && !config.parentID) {
  301. this.scale.x = Math.abs(this.scale.x) * (this.object.document.texture.scaleX < 0 ? -1 : 1);
  302. this.scale.y = Math.abs(this.scale.y) * (this.object.document.texture.scaleY < 0 ? -1 : 1);
  303. }
  304. if (this.anchor) {
  305. if (!config.anchor) this.anchor.set(0.5, 0.5);
  306. else this.anchor.set(config.anchor.x, config.anchor.y);
  307. }
  308. let xOff = 0;
  309. let yOff = 0;
  310. if (shapes) {
  311. shapes.position.x = -this.anchor.x * shapes.width;
  312. shapes.position.y = -this.anchor.y * shapes.height;
  313. if (config.animation.relative) {
  314. this.pivot.set(0, 0);
  315. shapes.pivot.set(
  316. (0.5 - this.anchor.x) * shapes.width,
  317. (0.5 - this.anchor.y) * shapes.height
  318. );
  319. xOff = shapes.pivot.x * this.scale.x;
  320. yOff = shapes.pivot.y * this.scale.y;
  321. }
  322. } else if (config.animation.relative) {
  323. xOff = (0.5 - this.anchor.x) * this.width;
  324. yOff = (0.5 - this.anchor.y) * this.height;
  325. this.pivot.set(
  326. (0.5 - this.anchor.x) * this.texture.width,
  327. (0.5 - this.anchor.y) * this.texture.height
  328. );
  329. }
  330. // Position
  331. if (config.parentID) {
  332. const anchor = this.parent.anchor ?? { x: 0, y: 0 };
  333. const pWidth = this.parent.width / this.parent.scale.x;
  334. const pHeight = this.parent.height / this.parent.scale.y;
  335. this.position.set(
  336. -config.offsetX * pWidth - anchor.x * pWidth + pWidth / 2,
  337. -config.offsetY * pHeight - anchor.y * pHeight + pHeight / 2
  338. );
  339. } else {
  340. if (config.animation.relative) {
  341. this.position.set(
  342. this.object.document.x + this.object.w / 2 + -config.offsetX * this.object.w + xOff,
  343. this.object.document.y + this.object.h / 2 + -config.offsetY * this.object.h + yOff
  344. );
  345. } else {
  346. this.position.set(
  347. this.object.document.x + this.object.w / 2,
  348. this.object.document.y + this.object.h / 2
  349. );
  350. this.pivot.set(
  351. (config.offsetX * this.object.w) / this.scale.x,
  352. (config.offsetY * this.object.h) / this.scale.y
  353. );
  354. }
  355. }
  356. // Set alpha but only if playOnce is disabled and the video hasn't
  357. // finished playing yet. Otherwise we want to keep alpha as 0 to keep the video hidden
  358. if (!this.tvaVideoEnded) {
  359. this.alpha = config.linkOpacity ? this.object.document.alpha : config.alpha;
  360. }
  361. // Angle in degrees
  362. if (fullRefresh) {
  363. if (config.linkRotation) this.angle = this.object.document.rotation + config.angle;
  364. else this.angle = config.angle;
  365. } else if (!config.animation.rotate) {
  366. if (config.linkRotation) this.angle = this.object.document.rotation + config.angle;
  367. }
  368. // Apply color tinting
  369. const tint = config.inheritTint
  370. ? this.object.document.texture.tint
  371. : interpolateColor(config.tint, config.interpolateColor, true);
  372. this.tint = tint ? Color.from(tint) : 0xffffff;
  373. if (fullRefresh) {
  374. if (config.animation.rotate) {
  375. this.animate(config);
  376. } else {
  377. this.stopAnimation();
  378. }
  379. }
  380. // Apply filters
  381. if (fullRefresh) this._applyFilters(config);
  382. //if (fullRefresh) this.filters = this._getFilters(config);
  383. if (preview && this.children) {
  384. this.children.forEach((ch) => {
  385. if (ch instanceof TVASprite) ch.refresh(null, { preview: true });
  386. });
  387. }
  388. this.ready = true;
  389. }
  390. _activateTicker() {
  391. this._deactivateTicker();
  392. canvas.app.ticker.add(this.updatePosition, this, PIXI.UPDATE_PRIORITY.HIGH);
  393. }
  394. _deactivateTicker() {
  395. canvas.app.ticker.remove(this.updatePosition, this);
  396. }
  397. updatePosition() {
  398. let coord = canvas.canvasCoordinatesFromClient({
  399. x: window.innerWidth / 2 + this.overlayConfig.offsetX * window.innerWidth,
  400. y: window.innerHeight / 2 + this.overlayConfig.offsetY * window.innerHeight,
  401. });
  402. this.position.set(coord.x, coord.y);
  403. }
  404. async _applyFilters(config) {
  405. const filterName = config.filter;
  406. const FilterClass = PIXI.filters[filterName];
  407. const options = mergeObject(FILTERS[filterName]?.defaultValues || {}, config.filterOptions);
  408. let filter;
  409. if (FilterClass) {
  410. if (FILTERS[filterName]?.argType === 'args') {
  411. let args = [];
  412. const controls = FILTERS[filterName]?.controls;
  413. if (controls) {
  414. controls.forEach((c) => args.push(options[c.name]));
  415. }
  416. filter = new FilterClass(...args);
  417. } else if (FILTERS[filterName]?.argType === 'options') {
  418. filter = new FilterClass(options);
  419. } else {
  420. filter = new FilterClass();
  421. }
  422. } else if (filterName === 'OutlineOverlayFilter') {
  423. filter = OutlineFilter.create(options);
  424. filter.thickness = options.trueThickness ?? 1;
  425. filter.animate = options.animate ?? false;
  426. } else if (filterName === 'Token Magic FX') {
  427. this.filters = await constructTMFXFilters(options.params || [], this);
  428. return;
  429. }
  430. if (filter) {
  431. this.filters = [filter];
  432. } else {
  433. this.filters = [];
  434. }
  435. }
  436. async stopAnimation() {
  437. if (this.animationName) {
  438. CanvasAnimation.terminateAnimation(this.animationName);
  439. }
  440. }
  441. async animate(config) {
  442. if (!this.animationName) this.animationName = this.object.sourceId + '.' + randomID(5);
  443. let newAngle = this.angle + (config.animation.clockwise ? 360 : -360);
  444. const rotate = [{ parent: this, attribute: 'angle', to: newAngle }];
  445. const completed = await CanvasAnimation.animate(rotate, {
  446. duration: config.animation.duration,
  447. name: this.animationName,
  448. });
  449. if (completed) {
  450. this.animate(config);
  451. }
  452. }
  453. _registerHooks(configuration) {
  454. if (configuration.linkStageScale) registerOverlayRefreshHook(this, 'canvasPan');
  455. else unregisterOverlayRefreshHooks(this, 'canvasPan');
  456. }
  457. _swapChildren(to) {
  458. const from = this.pseudoTexture;
  459. if (from.shapes) {
  460. this.removeChild(this.pseudoTexture.shapes);
  461. const children = from.shapes.removeChildren();
  462. if (to?.shapes) children.forEach((c) => to.shapes.addChild(c)?.refresh());
  463. else children.forEach((c) => this.addChild(c)?.refresh());
  464. } else if (to?.shapes) {
  465. const children = this.removeChildren();
  466. children.forEach((c) => to.shapes.addChild(c)?.refresh());
  467. }
  468. }
  469. _destroyTexture() {
  470. if (this.texture.textLabel || this.texture.destroyable) {
  471. this.texture.destroy(true);
  472. }
  473. if (this.pseudoTexture?.shapes) {
  474. this.removeChild(this.pseudoTexture.shapes);
  475. this.pseudoTexture.shapes.destroy();
  476. }
  477. }
  478. destroy() {
  479. this.stopAnimation();
  480. unregisterOverlayRefreshHooks(this);
  481. if (this.children) {
  482. for (const ch of this.children) {
  483. if (ch instanceof TVASprite) ch.tvaRemove = true;
  484. }
  485. removeMarkedOverlays(this.object);
  486. if (this.pseudoTexture.shapes) {
  487. this.pseudoTexture.shapes.children.forEach((c) => c.destroy());
  488. this.removeChild(this.pseudoTexture.shapes)?.destroy();
  489. // this.pseudoTexture.shapes.destroy();
  490. }
  491. }
  492. if (this.texture.textLabel || this.texture.destroyable) {
  493. return super.destroy(true);
  494. } else if (this.texture?.baseTexture.resource?.source?.tagName === 'VIDEO') {
  495. this.texture.baseTexture.destroy();
  496. }
  497. super.destroy();
  498. }
  499. // Foundry BUG Fix
  500. calculateTrimmedVertices() {
  501. return PIXI.Sprite.prototype.calculateTrimmedVertices.call(this);
  502. }
  503. }
  504. async function constructTMFXFilters(paramsArray, sprite) {
  505. if (typeof TokenMagic === 'undefined') return [];
  506. try {
  507. paramsArray = eval(paramsArray);
  508. } catch (e) {
  509. return [];
  510. }
  511. if (!Array.isArray(paramsArray)) {
  512. paramsArray = TokenMagic.getPreset(paramsArray);
  513. }
  514. if (!(paramsArray instanceof Array && paramsArray.length > 0)) return [];
  515. let filters = [];
  516. for (const params of paramsArray) {
  517. if (
  518. !params.hasOwnProperty('filterType') ||
  519. !TMFXFilterTypes.hasOwnProperty(params.filterType)
  520. ) {
  521. // one invalid ? all rejected.
  522. return [];
  523. }
  524. if (!params.hasOwnProperty('rank')) {
  525. params.rank = 5000;
  526. }
  527. if (!params.hasOwnProperty('filterId') || params.filterId == null) {
  528. params.filterId = randomID();
  529. }
  530. if (!params.hasOwnProperty('enabled') || !(typeof params.enabled === 'boolean')) {
  531. params.enabled = true;
  532. }
  533. params.filterInternalId = randomID();
  534. const gms = game.users.filter((user) => user.isGM);
  535. params.filterOwner = gms.length ? gms[0].id : game.data.userId;
  536. // params.placeableType = placeable._TMFXgetPlaceableType();
  537. params.updateId = randomID();
  538. const filterClass = await getTMFXFilter(params.filterType);
  539. if (filterClass) {
  540. filterClass.prototype.assignPlaceable = function () {
  541. this.targetPlaceable = sprite.object;
  542. this.placeableImg = sprite;
  543. };
  544. filterClass.prototype._TMFXsetAnimeFlag = async function () {};
  545. const filter = new filterClass(params);
  546. if (filter) {
  547. // Patch fixes
  548. filter.placeableImg = sprite;
  549. filter.targetPlaceable = sprite.object;
  550. // end of fixes
  551. filters.unshift(filter);
  552. }
  553. }
  554. }
  555. return filters;
  556. }
  557. async function getTMFXFilter(id) {
  558. if (id in TMFXFilterTypes) {
  559. if (id in LOADED_TMFXFilters) return LOADED_TMFXFilters[id];
  560. else {
  561. try {
  562. const className = TMFXFilterTypes[id];
  563. let fxModule = await import(`../../../tokenmagic/fx/filters/${className}.js`);
  564. if (fxModule && fxModule[className]) {
  565. LOADED_TMFXFilters[id] = fxModule[className];
  566. return fxModule[className];
  567. }
  568. } catch (e) {}
  569. }
  570. }
  571. return null;
  572. }
  573. const LOADED_TMFXFilters = {};
  574. const TMFXFilterTypes = {
  575. adjustment: 'FilterAdjustment',
  576. distortion: 'FilterDistortion',
  577. oldfilm: 'FilterOldFilm',
  578. glow: 'FilterGlow',
  579. outline: 'FilterOutline',
  580. bevel: 'FilterBevel',
  581. xbloom: 'FilterXBloom',
  582. shadow: 'FilterDropShadow',
  583. twist: 'FilterTwist',
  584. zoomblur: 'FilterZoomBlur',
  585. blur: 'FilterBlur',
  586. bulgepinch: 'FilterBulgePinch',
  587. zapshadow: 'FilterRemoveShadow',
  588. ray: 'FilterRays',
  589. fog: 'FilterFog',
  590. xfog: 'FilterXFog',
  591. electric: 'FilterElectric',
  592. wave: 'FilterWaves',
  593. shockwave: 'FilterShockwave',
  594. fire: 'FilterFire',
  595. fumes: 'FilterFumes',
  596. smoke: 'FilterSmoke',
  597. flood: 'FilterFlood',
  598. images: 'FilterMirrorImages',
  599. field: 'FilterForceField',
  600. xray: 'FilterXRays',
  601. liquid: 'FilterLiquid',
  602. xglow: 'FilterGleamingGlow',
  603. pixel: 'FilterPixelate',
  604. web: 'FilterSpiderWeb',
  605. ripples: 'FilterSolarRipples',
  606. globes: 'FilterGlobes',
  607. transform: 'FilterTransform',
  608. splash: 'FilterSplash',
  609. polymorph: 'FilterPolymorph',
  610. xfire: 'FilterXFire',
  611. sprite: 'FilterSprite',
  612. replaceColor: 'FilterReplaceColor',
  613. ddTint: 'FilterDDTint',
  614. };