animationCurveEditorComponent.tsx 50 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456145714581459146014611462146314641465146614671468146914701471147214731474147514761477147814791480148114821483148414851486148714881489149014911492149314941495149614971498149915001501150215031504150515061507150815091510151115121513151415151516151715181519152015211522152315241525152615271528152915301531153215331534153515361537153815391540154115421543154415451546154715481549155015511552155315541555155615571558155915601561156215631564156515661567156815691570157115721573157415751576157715781579158015811582158315841585158615871588158915901591159215931594159515961597159815991600160116021603160416051606160716081609161016111612161316141615161616171618161916201621162216231624162516261627162816291630163116321633163416351636163716381639164016411642164316441645164616471648164916501651165216531654165516561657165816591660166116621663166416651666166716681669167016711672167316741675
  1. import * as React from 'react';
  2. import { Animation } from 'babylonjs/Animations/animation';
  3. import { Vector2, Vector3, Quaternion } from 'babylonjs/Maths/math.vector';
  4. import { Color3, Color4 } from 'babylonjs/Maths/math.color';
  5. import { Size } from 'babylonjs/Maths/math.size';
  6. import { EasingFunction } from 'babylonjs/Animations/easing';
  7. import { IAnimationKey } from 'babylonjs/Animations/animationKey';
  8. import { IKeyframeSvgPoint } from './keyframeSvgPoint';
  9. import { SvgDraggableArea } from './svgDraggableArea';
  10. import { Timeline } from './timeline';
  11. import { Notification } from './notification';
  12. import { GraphActionsBar } from './graphActionsBar';
  13. import { Scene } from 'babylonjs/scene';
  14. import { IAnimatable } from 'babylonjs/Animations/animatable.interface';
  15. import { Animatable } from 'babylonjs/Animations/animatable';
  16. import { TargetedAnimation } from 'babylonjs/Animations/animationGroup';
  17. import { EditorControls } from './editorControls';
  18. import { SelectedCoordinate } from './animationListTree';
  19. import { LockObject } from '../lockObject';
  20. import { GlobalState } from '../../../../globalState';
  21. import { Nullable } from 'babylonjs/types';
  22. import { Observer } from 'babylonjs/Misc/observable';
  23. require('./curveEditor.scss');
  24. interface IAnimationCurveEditorComponentProps {
  25. close: (event: any) => void;
  26. playOrPause?: () => void;
  27. scene: Scene;
  28. entity: IAnimatable | TargetedAnimation;
  29. lockObject: LockObject;
  30. globalState: GlobalState;
  31. }
  32. interface ICanvasAxis {
  33. value: number;
  34. label: number;
  35. }
  36. interface ICurveData {
  37. pathData: string;
  38. pathLength: number;
  39. domCurve: React.RefObject<SVGPathElement>;
  40. color: string;
  41. id: string;
  42. }
  43. export class AnimationCurveEditorComponent extends React.Component<
  44. IAnimationCurveEditorComponentProps,
  45. {
  46. isOpen: boolean;
  47. selected: Animation | null;
  48. svgKeyframes: IKeyframeSvgPoint[] | undefined;
  49. currentFrame: number;
  50. currentValue: number;
  51. frameAxisLength: ICanvasAxis[];
  52. valueAxisLength: ICanvasAxis[];
  53. isFlatTangentMode: boolean;
  54. isTangentMode: boolean;
  55. isBrokenMode: boolean;
  56. lerpMode: boolean;
  57. scale: number;
  58. playheadOffset: number;
  59. notification: string;
  60. currentPoint: SVGPoint | undefined;
  61. playheadPos: number;
  62. isPlaying: boolean;
  63. selectedPathData: ICurveData[] | undefined;
  64. selectedCoordinate: number;
  65. animationLimit: number;
  66. fps: number;
  67. }
  68. > {
  69. private _snippetUrl = 'https://snippet.babylonjs.com';
  70. // Height scale *Review this functionaliy
  71. private _heightScale: number = 100;
  72. // Canvas Length *Review this functionality
  73. readonly _entityName: string;
  74. readonly _canvasLength: number = 20;
  75. private _svgKeyframes: IKeyframeSvgPoint[] = [];
  76. private _isPlaying: boolean = false;
  77. private _graphCanvas: React.RefObject<HTMLDivElement>;
  78. //private _selectedCurve: React.RefObject<SVGPathElement>;
  79. private _svgCanvas: React.RefObject<SvgDraggableArea>;
  80. private _isTargetedAnimation: boolean;
  81. private _pixelFrameUnit: number;
  82. private _onBeforeRenderObserver: Nullable<Observer<Scene>>;
  83. private _mainAnimatable: Nullable<Animatable>;
  84. constructor(props: IAnimationCurveEditorComponentProps) {
  85. super(props);
  86. this._entityName = (this.props.entity as any).id;
  87. // Review is we really need this refs
  88. this._graphCanvas = React.createRef();
  89. //this._selectedCurve = React.createRef();
  90. this._svgCanvas = React.createRef();
  91. this._pixelFrameUnit = 10;
  92. let initialSelection;
  93. let initialPathData;
  94. let initialLerpMode;
  95. if (this.props.entity instanceof TargetedAnimation) {
  96. this._isTargetedAnimation = true;
  97. initialSelection = this.props.entity.animation;
  98. initialLerpMode =
  99. initialSelection !== undefined
  100. ? this.analizeAnimationForLerp(initialSelection)
  101. : false;
  102. initialPathData =
  103. initialSelection !== undefined
  104. ? this.getPathData(initialSelection)
  105. : undefined;
  106. } else {
  107. this._isTargetedAnimation = false;
  108. let hasAnimations =
  109. this.props.entity.animations !== undefined ||
  110. this.props.entity.animations !== null
  111. ? this.props.entity.animations
  112. : false;
  113. initialSelection =
  114. hasAnimations !== false ? hasAnimations && hasAnimations[0] : null;
  115. initialLerpMode =
  116. initialSelection !== undefined
  117. ? this.analizeAnimationForLerp(
  118. this.props.entity.animations && initialSelection
  119. )
  120. : false;
  121. initialPathData = initialSelection && this.getPathData(initialSelection);
  122. initialPathData =
  123. initialPathData === null || initialPathData === undefined
  124. ? undefined
  125. : initialPathData;
  126. }
  127. // will update this until we have a top scroll/zoom feature
  128. let valueInd = [2, 1.8, 1.6, 1.4, 1.2, 1, 0.8, 0.6, 0.4, 0.2, 0];
  129. this.state = {
  130. selected: initialSelection,
  131. isOpen: true,
  132. svgKeyframes: this._svgKeyframes,
  133. currentFrame: 0,
  134. currentValue: 1,
  135. isFlatTangentMode: false,
  136. isTangentMode: false,
  137. isBrokenMode: false,
  138. lerpMode: initialLerpMode,
  139. playheadOffset: this._graphCanvas.current
  140. ? this._graphCanvas.current.children[0].clientWidth /
  141. (this._canvasLength * 10)
  142. : 0,
  143. frameAxisLength: new Array(this._canvasLength).fill(0).map((s, i) => {
  144. return { value: i * 10, label: i };
  145. }),
  146. valueAxisLength: new Array(10).fill(0).map((s, i) => {
  147. return { value: i * 10, label: valueInd[i] };
  148. }),
  149. notification: '',
  150. currentPoint: undefined,
  151. scale: 1,
  152. playheadPos: 0,
  153. isPlaying: this.isAnimationPlaying(),
  154. selectedPathData: initialPathData,
  155. selectedCoordinate: 0,
  156. animationLimit: 120,
  157. fps: 60,
  158. };
  159. }
  160. componentDidMount() {
  161. setTimeout(() => this.resetPlayheadOffset(), 500);
  162. }
  163. /**
  164. * Notifications
  165. * To add notification we set the state and clear to make the notification bar hide.
  166. */
  167. clearNotification() {
  168. this.setState({ notification: '' });
  169. }
  170. /**
  171. * Zoom and Scroll
  172. * This section handles zoom and scroll
  173. * of the graph area.
  174. */
  175. zoom(e: React.WheelEvent<HTMLDivElement>) {
  176. e.nativeEvent.stopImmediatePropagation();
  177. let scaleX = 1;
  178. if (Math.sign(e.deltaY) === -1) {
  179. scaleX = this.state.scale - 0.01;
  180. } else {
  181. scaleX = this.state.scale + 0.01;
  182. }
  183. this.setState({ scale: scaleX }, this.setAxesLength);
  184. }
  185. setAxesLength() {
  186. let length = 20;
  187. let newlength = Math.round(this._canvasLength * this.state.scale);
  188. if (!isNaN(newlength) || newlength !== undefined) {
  189. length = newlength;
  190. }
  191. let highestFrame = 100;
  192. if (this.state.selected !== null && this.state.selected !== undefined) {
  193. highestFrame = this.state.selected.getHighestFrame();
  194. }
  195. if (length < (highestFrame * 2) / 10) {
  196. length = Math.round((highestFrame * 2) / 10);
  197. }
  198. let valueLines = Math.round((this.state.scale * this._heightScale) / 10);
  199. let newFrameLength = new Array(length).fill(0).map((s, i) => {
  200. return { value: i * 10, label: i };
  201. });
  202. let newValueLength = new Array(valueLines).fill(0).map((s, i) => {
  203. return { value: i * 10, label: this.getValueLabel(i * 10) };
  204. });
  205. this.setState({
  206. frameAxisLength: newFrameLength,
  207. valueAxisLength: newValueLength,
  208. });
  209. this.resetPlayheadOffset();
  210. }
  211. getValueLabel(i: number) {
  212. // Need to update this when Y axis grows
  213. let label = 0;
  214. if (i === 0) {
  215. label = 2;
  216. }
  217. if (i === 50) {
  218. label = 1;
  219. } else {
  220. label = (100 - i * 2) * 0.01 + 1;
  221. }
  222. return label;
  223. }
  224. resetPlayheadOffset() {
  225. if (this._graphCanvas && this._graphCanvas.current) {
  226. this.setState({
  227. playheadOffset:
  228. this._graphCanvas.current.children[0].clientWidth /
  229. (this._canvasLength * 10 * this.state.scale),
  230. });
  231. }
  232. }
  233. /**
  234. * Keyframe Manipulation
  235. * This section handles events from SvgDraggableArea.
  236. */
  237. selectKeyframe(id: string, multiselect: boolean) {
  238. let selectedKeyFrame = this.state.svgKeyframes?.find((kf) => kf.id === id)
  239. ?.selected;
  240. if (!multiselect) {
  241. this.deselectKeyframes();
  242. }
  243. let updatedKeyframes = this.state.svgKeyframes?.map((kf) => {
  244. if (kf.id === id) {
  245. kf.selected = !selectedKeyFrame;
  246. }
  247. return kf;
  248. });
  249. this.setState({ svgKeyframes: updatedKeyframes });
  250. }
  251. selectedControlPoint(type: string, id: string) {
  252. let updatedKeyframes = this.state.svgKeyframes?.map((kf) => {
  253. if (kf.id === id) {
  254. this.setState({ isFlatTangentMode: false });
  255. if (type === 'left') {
  256. kf.isLeftActive = !kf.isLeftActive;
  257. kf.isRightActive = false;
  258. }
  259. if (type === 'right') {
  260. kf.isRightActive = !kf.isRightActive;
  261. kf.isLeftActive = false;
  262. }
  263. }
  264. return kf;
  265. });
  266. this.setState({ svgKeyframes: updatedKeyframes });
  267. }
  268. deselectKeyframes() {
  269. let updatedKeyframes = this.state.svgKeyframes?.map((kf) => {
  270. kf.isLeftActive = false;
  271. kf.isRightActive = false;
  272. kf.selected = false;
  273. return kf;
  274. });
  275. this.setState({ svgKeyframes: updatedKeyframes });
  276. }
  277. updateValuePerCoordinate(
  278. dataType: number,
  279. value: number | Vector2 | Vector3 | Color3 | Color4 | Size | Quaternion,
  280. newValue: number,
  281. coordinate?: number
  282. ) {
  283. if (dataType === Animation.ANIMATIONTYPE_FLOAT) {
  284. value = newValue;
  285. }
  286. if (dataType === Animation.ANIMATIONTYPE_VECTOR2) {
  287. switch (coordinate) {
  288. case SelectedCoordinate.x:
  289. (value as Vector2).x = newValue;
  290. break;
  291. case SelectedCoordinate.y:
  292. (value as Vector2).y = newValue;
  293. break;
  294. }
  295. }
  296. if (dataType === Animation.ANIMATIONTYPE_VECTOR3) {
  297. switch (coordinate) {
  298. case SelectedCoordinate.x:
  299. (value as Vector3).x = newValue;
  300. break;
  301. case SelectedCoordinate.y:
  302. (value as Vector3).y = newValue;
  303. break;
  304. case SelectedCoordinate.z:
  305. (value as Vector3).z = newValue;
  306. break;
  307. }
  308. }
  309. if (dataType === Animation.ANIMATIONTYPE_QUATERNION) {
  310. switch (coordinate) {
  311. case SelectedCoordinate.x:
  312. (value as Quaternion).x = newValue;
  313. break;
  314. case SelectedCoordinate.y:
  315. (value as Quaternion).y = newValue;
  316. break;
  317. case SelectedCoordinate.z:
  318. (value as Quaternion).z = newValue;
  319. break;
  320. case SelectedCoordinate.w:
  321. (value as Quaternion).w = newValue;
  322. break;
  323. }
  324. }
  325. if (dataType === Animation.ANIMATIONTYPE_COLOR3) {
  326. switch (coordinate) {
  327. case SelectedCoordinate.r:
  328. (value as Color3).r = newValue;
  329. break;
  330. case SelectedCoordinate.g:
  331. (value as Color3).g = newValue;
  332. break;
  333. case SelectedCoordinate.b:
  334. (value as Color3).b = newValue;
  335. break;
  336. }
  337. }
  338. if (dataType === Animation.ANIMATIONTYPE_COLOR4) {
  339. switch (coordinate) {
  340. case SelectedCoordinate.r:
  341. (value as Color4).r = newValue;
  342. break;
  343. case SelectedCoordinate.g:
  344. (value as Color4).g = newValue;
  345. break;
  346. case SelectedCoordinate.b:
  347. (value as Color4).b = newValue;
  348. break;
  349. case SelectedCoordinate.a:
  350. (value as Color4).a = newValue;
  351. break;
  352. }
  353. }
  354. if (dataType === Animation.ANIMATIONTYPE_SIZE) {
  355. switch (coordinate) {
  356. case SelectedCoordinate.width:
  357. (value as Size).width = newValue;
  358. break;
  359. case SelectedCoordinate.g:
  360. (value as Size).height = newValue;
  361. break;
  362. }
  363. }
  364. return value;
  365. }
  366. renderPoints(updatedSvgKeyFrame: IKeyframeSvgPoint, id: string) {
  367. let animation = this.state.selected as Animation;
  368. // Bug: After play/stop we get an extra keyframe at 0
  369. let index = parseInt(id.split('_')[3]);
  370. let coordinate = parseInt(id.split('_')[2]);
  371. let keys = [...animation.getKeys()];
  372. let newFrame = 0;
  373. if (updatedSvgKeyFrame.keyframePoint.x !== 0) {
  374. if (
  375. updatedSvgKeyFrame.keyframePoint.x > 0 &&
  376. updatedSvgKeyFrame.keyframePoint.x < 1
  377. ) {
  378. newFrame = 1;
  379. } else {
  380. newFrame = Math.round(
  381. updatedSvgKeyFrame.keyframePoint.x / this._pixelFrameUnit
  382. );
  383. }
  384. }
  385. keys[index].frame = newFrame; // This value comes as percentage/frame/time
  386. // Calculate value for Vector3...
  387. let updatedValue =
  388. ((this._heightScale - updatedSvgKeyFrame.keyframePoint.y) /
  389. this._heightScale) *
  390. 2; // this value comes inverted svg from 0 = 100 to 100 = 0
  391. keys[index].value = this.updateValuePerCoordinate(
  392. animation.dataType,
  393. keys[index].value,
  394. updatedValue,
  395. coordinate
  396. );
  397. if (updatedSvgKeyFrame.isLeftActive) {
  398. if (updatedSvgKeyFrame.leftControlPoint !== null) {
  399. // Rotate
  400. let newValue =
  401. ((this._heightScale - updatedSvgKeyFrame.leftControlPoint.y) /
  402. this._heightScale) *
  403. 2;
  404. let keyframeValue =
  405. ((this._heightScale - updatedSvgKeyFrame.keyframePoint.y) /
  406. this._heightScale) *
  407. 2;
  408. let updatedValue = keyframeValue - newValue;
  409. keys[index].inTangent = this.updateValuePerCoordinate(
  410. animation.dataType,
  411. keys[index].inTangent,
  412. updatedValue,
  413. coordinate
  414. );
  415. if (!this.state.isBrokenMode) {
  416. // Right control point if exists
  417. if (updatedSvgKeyFrame.rightControlPoint !== null) {
  418. // Sets opposite value
  419. keys[index].outTangent = keys[index].inTangent * -1;
  420. }
  421. }
  422. }
  423. }
  424. if (updatedSvgKeyFrame.isRightActive) {
  425. if (updatedSvgKeyFrame.rightControlPoint !== null) {
  426. // Rotate
  427. let newValue =
  428. ((this._heightScale - updatedSvgKeyFrame.rightControlPoint.y) /
  429. this._heightScale) *
  430. 2;
  431. let keyframeValue =
  432. ((this._heightScale - updatedSvgKeyFrame.keyframePoint.y) /
  433. this._heightScale) *
  434. 2;
  435. let updatedValue = keyframeValue - newValue;
  436. keys[index].outTangent = this.updateValuePerCoordinate(
  437. animation.dataType,
  438. keys[index].outTangent,
  439. updatedValue,
  440. coordinate
  441. );
  442. if (!this.state.isBrokenMode) {
  443. if (updatedSvgKeyFrame.leftControlPoint !== null) {
  444. // Sets opposite value
  445. keys[index].inTangent = keys[index].outTangent * -1;
  446. }
  447. }
  448. }
  449. }
  450. animation.setKeys(keys);
  451. this.selectAnimation(animation, coordinate);
  452. }
  453. /**
  454. * Actions
  455. * This section handles events from GraphActionsBar.
  456. */
  457. handleFrameChange(event: React.ChangeEvent<HTMLInputElement>) {
  458. event.preventDefault();
  459. this.changeCurrentFrame(parseInt(event.target.value));
  460. }
  461. handleValueChange(event: React.ChangeEvent<HTMLInputElement>) {
  462. event.preventDefault();
  463. this.setState({ currentValue: parseFloat(event.target.value) }, () => {
  464. if (this.state.selected !== null) {
  465. let animation = this.state.selected;
  466. let keys = animation.getKeys();
  467. let isKeyframe = keys.find((k) => k.frame === this.state.currentFrame);
  468. if (isKeyframe) {
  469. let updatedKeys = keys.map((k) => {
  470. if (k.frame === this.state.currentFrame) {
  471. k.value = this.state.currentValue;
  472. }
  473. return k;
  474. });
  475. this.state.selected.setKeys(updatedKeys);
  476. this.selectAnimation(animation);
  477. }
  478. }
  479. });
  480. }
  481. setFlatTangent() {
  482. if (this.state.selected !== null) {
  483. let animation = this.state.selected;
  484. this.setState({ isFlatTangentMode: !this.state.isFlatTangentMode }, () =>
  485. this.selectAnimation(animation)
  486. );
  487. }
  488. }
  489. // Use this for Bezier curve mode
  490. setTangentMode() {
  491. if (this.state.selected !== null) {
  492. let animation = this.state.selected;
  493. this.setState({ isTangentMode: !this.state.isTangentMode }, () =>
  494. this.selectAnimation(animation)
  495. );
  496. }
  497. }
  498. setBrokenMode() {
  499. if (this.state.selected !== null) {
  500. let animation = this.state.selected;
  501. this.setState({ isBrokenMode: !this.state.isBrokenMode }, () =>
  502. this.selectAnimation(animation)
  503. );
  504. }
  505. }
  506. setLerpMode() {
  507. if (this.state.selected !== null) {
  508. let animation = this.state.selected;
  509. this.setState({ lerpMode: !this.state.lerpMode }, () =>
  510. this.selectAnimation(animation)
  511. );
  512. }
  513. }
  514. addKeyframeClick() {
  515. if (this.state.selected !== null) {
  516. let currentAnimation = this.state.selected;
  517. if (currentAnimation.dataType === Animation.ANIMATIONTYPE_FLOAT) {
  518. let keys = currentAnimation.getKeys();
  519. let x = this.state.currentFrame;
  520. let y = this.state.currentValue;
  521. keys.push({ frame: x, value: y, inTangent: 0, outTangent: 0 });
  522. keys.sort((a, b) => a.frame - b.frame);
  523. currentAnimation.setKeys(keys);
  524. this.selectAnimation(currentAnimation);
  525. }
  526. }
  527. }
  528. removeKeyframeClick() {
  529. if (this.state.selected !== null) {
  530. let currentAnimation = this.state.selected;
  531. if (currentAnimation.dataType === Animation.ANIMATIONTYPE_FLOAT) {
  532. let keys = currentAnimation.getKeys();
  533. let x = this.state.currentFrame;
  534. let filteredKeys = keys.filter((kf) => kf.frame !== x);
  535. currentAnimation.setKeys(filteredKeys);
  536. this.selectAnimation(currentAnimation);
  537. }
  538. }
  539. }
  540. removeKeyframes(points: IKeyframeSvgPoint[]) {
  541. if (this.state.selected !== null) {
  542. let currentAnimation = this.state.selected;
  543. const indexesToRemove = points.map((p) => {
  544. return {
  545. index: parseInt(p.id.split('_')[3]),
  546. coordinate: parseInt(p.id.split('_')[2]),
  547. };
  548. });
  549. if (currentAnimation.dataType === Animation.ANIMATIONTYPE_FLOAT) {
  550. let keys = currentAnimation.getKeys();
  551. let filteredKeys = keys.filter((_, i) => {
  552. return !indexesToRemove.find((x) => x.index === i);
  553. });
  554. currentAnimation.setKeys(filteredKeys);
  555. this.deselectKeyframes();
  556. this.selectAnimation(currentAnimation);
  557. }
  558. }
  559. }
  560. addKeyFrame(event: React.MouseEvent<SVGSVGElement>) {
  561. event.preventDefault();
  562. if (this.state.selected !== null) {
  563. var svg = event.target as SVGSVGElement;
  564. var pt = svg.createSVGPoint();
  565. pt.x = event.clientX;
  566. pt.y = event.clientY;
  567. var inverse = svg.getScreenCTM()?.inverse();
  568. var cursorpt = pt.matrixTransform(inverse);
  569. var currentAnimation = this.state.selected;
  570. var keys = currentAnimation.getKeys();
  571. var height = 100;
  572. var middle = height / 2;
  573. var keyValue;
  574. if (cursorpt.y < middle) {
  575. keyValue = 1 + (100 / cursorpt.y) * 0.1;
  576. }
  577. if (cursorpt.y > middle) {
  578. keyValue = 1 - (100 / cursorpt.y) * 0.1;
  579. }
  580. keys.push({ frame: cursorpt.x, value: keyValue });
  581. currentAnimation.setKeys(keys);
  582. this.selectAnimation(currentAnimation);
  583. }
  584. }
  585. /**
  586. * Curve Rendering Functions
  587. * This section handles how to render curves.
  588. */
  589. linearInterpolation(
  590. keyframes: IAnimationKey[],
  591. data: string,
  592. middle: number
  593. ): string {
  594. keyframes.forEach((key, i) => {
  595. // identify type of value and split...
  596. var point = new Vector2(0, 0);
  597. point.x = key.frame;
  598. point.y = this._heightScale - key.value * middle;
  599. this.setKeyframePointLinear(point, i);
  600. if (i !== 0) {
  601. data += ` L${point.x} ${point.y}`;
  602. }
  603. });
  604. return data;
  605. }
  606. setKeyframePointLinear(point: Vector2, index: number) {
  607. // here set the ID to a unique id
  608. let svgKeyframe = {
  609. keyframePoint: point,
  610. rightControlPoint: null,
  611. leftControlPoint: null,
  612. id: index.toString(),
  613. selected: false,
  614. isLeftActive: false,
  615. isRightActive: false,
  616. };
  617. this._svgKeyframes.push(svgKeyframe);
  618. }
  619. flatTangents(keyframes: IAnimationKey[], dataType: number) {
  620. // Checks if Flat Tangent is active (tangents are set to zero)
  621. let flattened;
  622. if (this.state && this.state.isFlatTangentMode) {
  623. flattened = keyframes.map((kf) => {
  624. if (kf.inTangent !== undefined) {
  625. kf.inTangent = this.returnZero(dataType);
  626. }
  627. if (kf.outTangent !== undefined) {
  628. kf.outTangent = this.returnZero(dataType);
  629. }
  630. return kf;
  631. });
  632. } else {
  633. flattened = keyframes;
  634. }
  635. return flattened;
  636. }
  637. returnZero(dataType: number) {
  638. let type;
  639. switch (dataType) {
  640. case Animation.ANIMATIONTYPE_FLOAT:
  641. type = 0;
  642. break;
  643. case Animation.ANIMATIONTYPE_VECTOR3:
  644. type = Vector3.Zero();
  645. break;
  646. case Animation.ANIMATIONTYPE_VECTOR2:
  647. type = Vector2.Zero();
  648. break;
  649. case Animation.ANIMATIONTYPE_QUATERNION:
  650. type = Quaternion.Zero();
  651. break;
  652. case Animation.ANIMATIONTYPE_COLOR3:
  653. type = new Color3(0, 0, 0);
  654. break;
  655. case Animation.ANIMATIONTYPE_COLOR4:
  656. type = new Color4(0, 0, 0, 0);
  657. break;
  658. case Animation.ANIMATIONTYPE_SIZE:
  659. type = new Size(0, 0);
  660. break;
  661. }
  662. return type;
  663. }
  664. getValueAsArray(
  665. valueType: number,
  666. value: number | Vector2 | Vector3 | Color3 | Color4 | Size | Quaternion
  667. ) {
  668. let valueAsArray: number[] = [];
  669. switch (valueType) {
  670. case Animation.ANIMATIONTYPE_FLOAT:
  671. valueAsArray = [value as number];
  672. break;
  673. case Animation.ANIMATIONTYPE_VECTOR3:
  674. valueAsArray = (value as Vector3).asArray();
  675. break;
  676. case Animation.ANIMATIONTYPE_VECTOR2:
  677. valueAsArray = (value as Vector2).asArray();
  678. break;
  679. case Animation.ANIMATIONTYPE_QUATERNION:
  680. valueAsArray = (value as Quaternion).asArray();
  681. break;
  682. case Animation.ANIMATIONTYPE_COLOR3:
  683. valueAsArray = (value as Color3).asArray();
  684. break;
  685. case Animation.ANIMATIONTYPE_COLOR4:
  686. valueAsArray = (value as Color4).asArray();
  687. break;
  688. case Animation.ANIMATIONTYPE_SIZE:
  689. valueAsArray = [(value as Size).width, (value as Size).height];
  690. break;
  691. }
  692. return valueAsArray;
  693. }
  694. getPathData(animation: Animation | null) {
  695. if (animation === null) {
  696. return undefined;
  697. }
  698. var keyframes = animation.getKeys();
  699. if (keyframes === undefined) {
  700. return undefined;
  701. } else {
  702. const {
  703. easingMode,
  704. easingType,
  705. usesTangents,
  706. valueType,
  707. highestFrame,
  708. name,
  709. targetProperty,
  710. } = this.getAnimationData(animation);
  711. keyframes = this.flatTangents(keyframes, valueType);
  712. const startKey = keyframes[0];
  713. let middle = this._heightScale / 2;
  714. let collection: ICurveData[] = [];
  715. const colors = ['red', 'green', 'blue', 'white', '#7a4ece'];
  716. const startValue = this.getValueAsArray(valueType, startKey.value);
  717. for (var d = 0; d < startValue.length; d++) {
  718. const id = `${name}_${targetProperty}_${d}`;
  719. const curveColor =
  720. valueType === Animation.ANIMATIONTYPE_FLOAT ? colors[4] : colors[d];
  721. // START OF LINE/CURVE
  722. let data: string | undefined = `M${
  723. startKey.frame * this._pixelFrameUnit
  724. }, ${this._heightScale - startValue[d] * middle}`; //
  725. if (this.state && this.state.lerpMode) {
  726. data = this.linearInterpolation(keyframes, data, middle);
  727. } else {
  728. if (usesTangents) {
  729. data = this.curvePathWithTangents(
  730. keyframes,
  731. data,
  732. middle,
  733. valueType,
  734. d,
  735. id
  736. );
  737. } else {
  738. if (easingType !== undefined && easingMode !== undefined) {
  739. let easingFunction = animation.getEasingFunction();
  740. data = this.curvePath(
  741. keyframes,
  742. data,
  743. middle,
  744. easingFunction as EasingFunction
  745. );
  746. } else {
  747. if (this.state !== undefined) {
  748. let emptyTangents = keyframes.map((kf, i) => {
  749. if (i === 0) {
  750. kf.outTangent = this.returnZero(valueType);
  751. } else if (i === keyframes.length - 1) {
  752. kf.inTangent = this.returnZero(valueType);
  753. } else {
  754. kf.inTangent = this.returnZero(valueType);
  755. kf.outTangent = this.returnZero(valueType);
  756. }
  757. return kf;
  758. });
  759. data = this.curvePathWithTangents(
  760. emptyTangents,
  761. data,
  762. middle,
  763. valueType,
  764. d,
  765. id
  766. );
  767. } else {
  768. data = this.linearInterpolation(keyframes, data, middle);
  769. }
  770. }
  771. }
  772. }
  773. collection.push({
  774. pathData: data,
  775. pathLength: highestFrame,
  776. domCurve: React.createRef(),
  777. color: curveColor,
  778. id: id,
  779. });
  780. }
  781. return collection;
  782. }
  783. }
  784. getAnimationData(animation: Animation) {
  785. // General Props
  786. let loopMode = animation.loopMode;
  787. let name = animation.name;
  788. let blendingSpeed = animation.blendingSpeed;
  789. let targetProperty = animation.targetProperty;
  790. let targetPropertyPath = animation.targetPropertyPath;
  791. let framesPerSecond = animation.framePerSecond;
  792. let highestFrame = animation.getHighestFrame();
  793. //let serialized = animation.serialize();
  794. let usesTangents =
  795. animation
  796. .getKeys()
  797. .find(
  798. (kf) =>
  799. kf.hasOwnProperty('inTangent') || kf.hasOwnProperty('outTangent')
  800. ) !== undefined
  801. ? true
  802. : false;
  803. let valueType = animation.dataType;
  804. // easing properties
  805. let easingType, easingMode;
  806. let easingFunction: EasingFunction = animation.getEasingFunction() as EasingFunction;
  807. if (easingFunction === undefined) {
  808. easingType = undefined;
  809. easingMode = undefined;
  810. } else {
  811. easingType = easingFunction.constructor.name;
  812. easingMode = easingFunction.getEasingMode();
  813. }
  814. return {
  815. loopMode,
  816. name,
  817. blendingSpeed,
  818. targetPropertyPath,
  819. targetProperty,
  820. framesPerSecond,
  821. highestFrame,
  822. usesTangents,
  823. easingType,
  824. easingMode,
  825. valueType,
  826. };
  827. }
  828. curvePathWithTangents(
  829. keyframes: IAnimationKey[],
  830. data: string,
  831. middle: number,
  832. type: number,
  833. coordinate: number,
  834. animationName: string
  835. ) {
  836. keyframes.forEach((key, i) => {
  837. // Create a unique id for curve
  838. const curveId = animationName + '_' + i;
  839. // identify type of value and split...
  840. const keyframe_valueAsArray = this.getValueAsArray(type, key.value)[
  841. coordinate
  842. ];
  843. let svgKeyframe;
  844. let outTangent;
  845. let inTangent;
  846. let defaultWeight = 5;
  847. let defaultTangent: number | null = null;
  848. if (i !== 0 || i !== keyframes.length - 1) {
  849. defaultTangent = 0;
  850. }
  851. var inT =
  852. key.inTangent === undefined
  853. ? defaultTangent
  854. : this.getValueAsArray(type, key.inTangent)[coordinate];
  855. var outT =
  856. key.outTangent === undefined
  857. ? defaultTangent
  858. : this.getValueAsArray(type, key.outTangent)[coordinate];
  859. let y = this._heightScale - keyframe_valueAsArray * middle;
  860. let nextKeyframe = keyframes[i + 1];
  861. let prevKeyframe = keyframes[i - 1];
  862. if (nextKeyframe !== undefined) {
  863. let distance = keyframes[i + 1].frame - key.frame;
  864. defaultWeight = distance * 0.33;
  865. }
  866. if (prevKeyframe !== undefined) {
  867. let distance = key.frame - keyframes[i - 1].frame;
  868. defaultWeight = distance * 0.33;
  869. }
  870. if (inT !== null) {
  871. let valueIn = y * inT + y;
  872. inTangent = new Vector2(
  873. key.frame * this._pixelFrameUnit - defaultWeight,
  874. valueIn
  875. );
  876. } else {
  877. inTangent = null;
  878. }
  879. if (outT !== null) {
  880. let valueOut = y * outT + y;
  881. outTangent = new Vector2(
  882. key.frame * this._pixelFrameUnit + defaultWeight,
  883. valueOut
  884. );
  885. } else {
  886. outTangent = null;
  887. }
  888. if (i === 0) {
  889. svgKeyframe = {
  890. keyframePoint: new Vector2(
  891. key.frame * this._pixelFrameUnit,
  892. this._heightScale - keyframe_valueAsArray * middle
  893. ),
  894. rightControlPoint: outTangent,
  895. leftControlPoint: null,
  896. id: curveId,
  897. selected: false,
  898. isLeftActive: false,
  899. isRightActive: false,
  900. };
  901. if (outTangent !== null) {
  902. data += ` C${outTangent.x} ${outTangent.y} `;
  903. }
  904. } else {
  905. svgKeyframe = {
  906. keyframePoint: new Vector2(
  907. key.frame * this._pixelFrameUnit,
  908. this._heightScale - keyframe_valueAsArray * middle
  909. ),
  910. rightControlPoint: outTangent,
  911. leftControlPoint: inTangent,
  912. id: curveId,
  913. selected: false,
  914. isLeftActive: false,
  915. isRightActive: false,
  916. };
  917. if (outTangent !== null && inTangent !== null) {
  918. data += ` ${inTangent.x} ${inTangent.y} ${svgKeyframe.keyframePoint.x} ${svgKeyframe.keyframePoint.y} C${outTangent.x} ${outTangent.y} `;
  919. } else if (inTangent !== null) {
  920. data += ` ${inTangent.x} ${inTangent.y} ${svgKeyframe.keyframePoint.x} ${svgKeyframe.keyframePoint.y} `;
  921. }
  922. }
  923. if (this.state) {
  924. let prev = this.state.svgKeyframes?.find((kf) => kf.id === curveId);
  925. if (prev) {
  926. svgKeyframe.isLeftActive = prev?.isLeftActive;
  927. svgKeyframe.isRightActive = prev?.isRightActive;
  928. svgKeyframe.selected = prev?.selected;
  929. }
  930. }
  931. this._svgKeyframes.push(svgKeyframe);
  932. }, this);
  933. return data;
  934. }
  935. curvePath(
  936. keyframes: IAnimationKey[],
  937. data: string,
  938. middle: number,
  939. easingFunction: EasingFunction
  940. ) {
  941. // This will get 1/4 and 3/4 of points in eased curve
  942. const u = 0.25;
  943. const v = 0.75;
  944. keyframes.forEach((key, i) => {
  945. // identify type of value and split...
  946. // Gets previous initial point of curve segment
  947. var pointA = new Vector2(0, 0);
  948. if (i === 0) {
  949. pointA.x = key.frame;
  950. pointA.y = this._heightScale - key.value * middle;
  951. this.setKeyframePoint([pointA], i, keyframes.length);
  952. } else {
  953. pointA.x = keyframes[i - 1].frame;
  954. pointA.y = this._heightScale - keyframes[i - 1].value * middle;
  955. // Gets the end point of this curve segment
  956. var pointB = new Vector2(
  957. key.frame,
  958. this._heightScale - key.value * middle
  959. );
  960. // Get easing value of percentage to get the bezier control points below
  961. let du = easingFunction.easeInCore(u); // What to do here, when user edits the curve? Option 1: Modify the curve with the new control points as BezierEaseCurve(x,y,z,w)
  962. let dv = easingFunction.easeInCore(v); // Option 2: Create a easeInCore function and adapt it with the new control points values... needs more revision.
  963. // Direction of curve up/down
  964. let yInt25 = 0;
  965. if (pointB.y > pointA.y) {
  966. // if pointB.y > pointA.y = goes down
  967. yInt25 = (pointB.y - pointA.y) * du + pointA.y;
  968. } else if (pointB.y < pointA.y) {
  969. // if pointB.y < pointA.y = goes up
  970. yInt25 = pointA.y - (pointA.y - pointB.y) * du;
  971. }
  972. let yInt75 = 0;
  973. if (pointB.y > pointA.y) {
  974. yInt75 = (pointB.y - pointA.y) * dv + pointA.y;
  975. } else if (pointB.y < pointA.y) {
  976. yInt75 = pointA.y - (pointA.y - pointB.y) * dv;
  977. }
  978. // Intermediate points in curve
  979. let intermediatePoint25 = new Vector2(
  980. (pointB.x - pointA.x) * u + pointA.x,
  981. yInt25
  982. );
  983. let intermediatePoint75 = new Vector2(
  984. (pointB.x - pointA.x) * v + pointA.x,
  985. yInt75
  986. );
  987. // Gets the four control points of bezier curve
  988. let controlPoints = this.interpolateControlPoints(
  989. pointA,
  990. intermediatePoint25,
  991. u,
  992. intermediatePoint75,
  993. v,
  994. pointB
  995. );
  996. if (controlPoints === undefined) {
  997. console.log('error getting bezier control points');
  998. } else {
  999. this.setKeyframePoint(controlPoints, i, keyframes.length);
  1000. data += ` C${controlPoints[1].x} ${controlPoints[1].y} ${controlPoints[2].x} ${controlPoints[2].y} ${controlPoints[3].x} ${controlPoints[3].y}`;
  1001. }
  1002. }
  1003. });
  1004. return data;
  1005. }
  1006. setKeyframePoint(
  1007. controlPoints: Vector2[],
  1008. index: number,
  1009. keyframesCount: number
  1010. ) {
  1011. let svgKeyframe;
  1012. if (index === 0) {
  1013. svgKeyframe = {
  1014. keyframePoint: controlPoints[0],
  1015. rightControlPoint: null,
  1016. leftControlPoint: null,
  1017. id: index.toString(),
  1018. selected: false,
  1019. isLeftActive: false,
  1020. isRightActive: false,
  1021. };
  1022. } else {
  1023. this._svgKeyframes[index - 1].rightControlPoint = controlPoints[1];
  1024. svgKeyframe = {
  1025. keyframePoint: controlPoints[3],
  1026. rightControlPoint: null,
  1027. leftControlPoint: controlPoints[2],
  1028. id: index.toString(),
  1029. selected: false,
  1030. isLeftActive: false,
  1031. isRightActive: false,
  1032. };
  1033. }
  1034. this._svgKeyframes.push(svgKeyframe);
  1035. }
  1036. interpolateControlPoints(
  1037. p0: Vector2,
  1038. p1: Vector2,
  1039. u: number,
  1040. p2: Vector2,
  1041. v: number,
  1042. p3: Vector2
  1043. ): Vector2[] | undefined {
  1044. let a = 0.0;
  1045. let b = 0.0;
  1046. let c = 0.0;
  1047. let d = 0.0;
  1048. let det = 0.0;
  1049. let q1: Vector2 = new Vector2();
  1050. let q2: Vector2 = new Vector2();
  1051. let controlA: Vector2 = p0;
  1052. let controlB: Vector2 = new Vector2();
  1053. let controlC: Vector2 = new Vector2();
  1054. let controlD: Vector2 = p3;
  1055. if (u <= 0.0 || u >= 1.0 || v <= 0.0 || v >= 1.0 || u >= v) {
  1056. return undefined;
  1057. }
  1058. a = 3 * (1 - u) * (1 - u) * u;
  1059. b = 3 * (1 - u) * u * u;
  1060. c = 3 * (1 - v) * (1 - v) * v;
  1061. d = 3 * (1 - v) * v * v;
  1062. det = a * d - b * c;
  1063. if (det == 0.0) return undefined;
  1064. q1.x = p1.x - ((1 - u) * (1 - u) * (1 - u) * p0.x + u * u * u * p3.x);
  1065. q1.y = p1.y - ((1 - u) * (1 - u) * (1 - u) * p0.y + u * u * u * p3.y);
  1066. q2.x = p2.x - ((1 - v) * (1 - v) * (1 - v) * p0.x + v * v * v * p3.x);
  1067. q2.y = p2.y - ((1 - v) * (1 - v) * (1 - v) * p0.y + v * v * v * p3.y);
  1068. controlB.x = (d * q1.x - b * q2.x) / det;
  1069. controlB.y = (d * q1.y - b * q2.y) / det;
  1070. controlC.x = (-c * q1.x + a * q2.x) / det;
  1071. controlC.y = (-c * q1.y + a * q2.y) / det;
  1072. return [controlA, controlB, controlC, controlD];
  1073. }
  1074. deselectAnimation() {
  1075. this.setState({
  1076. selected: null,
  1077. svgKeyframes: [],
  1078. selectedPathData: [],
  1079. selectedCoordinate: 0,
  1080. });
  1081. }
  1082. /**
  1083. * Core functions
  1084. * This section handles main Curve Editor Functions.
  1085. */
  1086. selectAnimation(animation: Animation, coordinate?: SelectedCoordinate) {
  1087. this._svgKeyframes = [];
  1088. let updatedPath;
  1089. let filteredSvgKeys;
  1090. let selectedCurve = 0;
  1091. if (coordinate === undefined) {
  1092. this.playStopAnimation();
  1093. updatedPath = this.getPathData(animation);
  1094. if (updatedPath === undefined) {
  1095. console.log('no keyframes in this animation');
  1096. }
  1097. } else {
  1098. let curves = this.getPathData(animation);
  1099. if (curves === undefined) {
  1100. console.log('no keyframes in this animation');
  1101. }
  1102. updatedPath = [];
  1103. filteredSvgKeys = this._svgKeyframes?.filter((curve) => {
  1104. let id = parseInt(curve.id.split('_')[2]);
  1105. if (id === coordinate) {
  1106. return true;
  1107. } else {
  1108. return false;
  1109. }
  1110. });
  1111. curves?.map((curve) => {
  1112. let id = parseInt(curve.id.split('_')[2]);
  1113. if (id === coordinate) {
  1114. updatedPath.push(curve);
  1115. }
  1116. });
  1117. selectedCurve = coordinate;
  1118. }
  1119. // check for empty svgKeyframes, lastframe, selected
  1120. this.setState({
  1121. selected: animation,
  1122. svgKeyframes:
  1123. coordinate !== undefined ? filteredSvgKeys : this._svgKeyframes,
  1124. selectedPathData: updatedPath,
  1125. selectedCoordinate: selectedCurve,
  1126. });
  1127. }
  1128. isAnimationPlaying() {
  1129. let target = this.props.entity;
  1130. if (this.props.entity instanceof TargetedAnimation) {
  1131. target = this.props.entity.target;
  1132. }
  1133. return this.props.scene.getAllAnimatablesByTarget(target).length > 0;
  1134. }
  1135. playStopAnimation() {
  1136. let target = this.props.entity;
  1137. if (this.props.entity instanceof TargetedAnimation) {
  1138. target = this.props.entity.target;
  1139. }
  1140. this._isPlaying =
  1141. this.props.scene.getAllAnimatablesByTarget(target).length > 0;
  1142. if (this._isPlaying) {
  1143. this.props.playOrPause && this.props.playOrPause();
  1144. return true;
  1145. } else {
  1146. this._isPlaying = false;
  1147. return false;
  1148. }
  1149. }
  1150. analizeAnimationForLerp(animation: Animation | null) {
  1151. if (animation !== null) {
  1152. const { easingMode, easingType, usesTangents } = this.getAnimationData(
  1153. animation
  1154. );
  1155. if (
  1156. easingType === undefined &&
  1157. easingMode === undefined &&
  1158. !usesTangents
  1159. ) {
  1160. return true;
  1161. } else {
  1162. return false;
  1163. }
  1164. } else {
  1165. return false;
  1166. }
  1167. }
  1168. /**
  1169. * Timeline
  1170. * This section controls the timeline.
  1171. */
  1172. changeCurrentFrame(frame: number) {
  1173. let currentValue;
  1174. if (this.state.selectedPathData) {
  1175. let selectedCurve = this.state.selectedPathData[
  1176. this.state.selectedCoordinate
  1177. ].domCurve.current;
  1178. if (selectedCurve) {
  1179. var curveLength = selectedCurve.getTotalLength();
  1180. let frameValue = (frame * curveLength) / 100;
  1181. let currentP = selectedCurve.getPointAtLength(frameValue);
  1182. let middle = this._heightScale / 2;
  1183. let offset =
  1184. (currentP?.y * this._heightScale - this._heightScale ** 2 / 2) /
  1185. middle /
  1186. this._heightScale;
  1187. let unit = Math.sign(offset);
  1188. currentValue = unit === -1 ? Math.abs(offset + unit) : unit - offset;
  1189. this.setState({
  1190. currentFrame: frame,
  1191. currentValue: currentValue,
  1192. currentPoint: currentP,
  1193. });
  1194. }
  1195. }
  1196. }
  1197. changeAnimationLimit(limit: number) {
  1198. this.setState({
  1199. animationLimit: limit,
  1200. });
  1201. }
  1202. updateFrameInKeyFrame(frame: number, index: number) {
  1203. if (this.state && this.state.selected) {
  1204. let animation = this.state.selected;
  1205. let keys = [...animation.getKeys()];
  1206. keys[index].frame = frame;
  1207. animation.setKeys(keys);
  1208. this.selectAnimation(animation);
  1209. }
  1210. }
  1211. playPause(direction: number) {
  1212. this.registerObs();
  1213. if (this.state.selected) {
  1214. let target = this.props.entity;
  1215. if (this.props.entity instanceof TargetedAnimation) {
  1216. target = this.props.entity.target;
  1217. }
  1218. if (this.state.isPlaying) {
  1219. this.props.scene.stopAnimation(target);
  1220. this.setState({ isPlaying: false });
  1221. this._isPlaying = false;
  1222. this.forceUpdate();
  1223. } else {
  1224. let keys = this.state.selected.getKeys();
  1225. let firstFrame = keys[0].frame;
  1226. let LastFrame = keys[keys.length - 1].frame;
  1227. if (direction === 1) {
  1228. this._mainAnimatable = this.props.scene.beginAnimation(
  1229. target,
  1230. firstFrame,
  1231. LastFrame,
  1232. true
  1233. );
  1234. }
  1235. if (direction === -1) {
  1236. this._mainAnimatable = this.props.scene.beginAnimation(
  1237. target,
  1238. LastFrame,
  1239. firstFrame,
  1240. true
  1241. );
  1242. }
  1243. this._isPlaying = true;
  1244. this.setState({ isPlaying: true });
  1245. this.forceUpdate();
  1246. }
  1247. }
  1248. }
  1249. moveFrameTo(e: React.MouseEvent<SVGRectElement, MouseEvent>) {
  1250. var svg = e.currentTarget as SVGRectElement;
  1251. var CTM = svg.getScreenCTM();
  1252. let position;
  1253. if (CTM) {
  1254. position = new Vector2(
  1255. (e.clientX - CTM.e) / CTM.a,
  1256. (e.clientY - CTM.f) / CTM.d
  1257. );
  1258. let selectedFrame = Math.round(position.x / this._pixelFrameUnit);
  1259. this.setState({ currentFrame: selectedFrame });
  1260. }
  1261. }
  1262. registerObs() {
  1263. this._onBeforeRenderObserver = this.props.scene.onBeforeRenderObservable.add(
  1264. () => {
  1265. if (!this._isPlaying || !this._mainAnimatable) {
  1266. return;
  1267. }
  1268. this.setState({
  1269. currentFrame: Math.round(this._mainAnimatable.masterFrame),
  1270. });
  1271. }
  1272. );
  1273. }
  1274. componentWillUnmount() {
  1275. if (this._onBeforeRenderObserver) {
  1276. this.props.scene.onBeforeRenderObservable.remove(
  1277. this._onBeforeRenderObserver
  1278. );
  1279. this._onBeforeRenderObserver = null;
  1280. }
  1281. }
  1282. isCurrentFrame(frame: number) {
  1283. return this.state.currentFrame === frame;
  1284. }
  1285. render() {
  1286. return (
  1287. <div id='animation-curve-editor'>
  1288. <Notification
  1289. message={this.state.notification}
  1290. open={this.state.notification !== '' ? true : false}
  1291. close={() => this.clearNotification()}
  1292. />
  1293. <GraphActionsBar
  1294. enabled={
  1295. this.state.selected === null || this.state.selected === undefined
  1296. ? false
  1297. : true
  1298. }
  1299. title={this._entityName}
  1300. close={this.props.close}
  1301. currentValue={this.state.currentValue}
  1302. currentFrame={this.state.currentFrame}
  1303. handleFrameChange={(e) => this.handleFrameChange(e)}
  1304. handleValueChange={(e) => this.handleValueChange(e)}
  1305. addKeyframe={() => this.addKeyframeClick()}
  1306. removeKeyframe={() => this.removeKeyframeClick()}
  1307. brokenMode={this.state.isBrokenMode}
  1308. brokeTangents={() => this.setBrokenMode()}
  1309. lerpMode={this.state.lerpMode}
  1310. setLerpMode={() => this.setLerpMode()}
  1311. flatTangent={() => this.setFlatTangent()}
  1312. />
  1313. <div className='content'>
  1314. <div className='row'>
  1315. <EditorControls
  1316. deselectAnimation={() => this.deselectAnimation()}
  1317. selectAnimation={(
  1318. animation: Animation,
  1319. axis?: SelectedCoordinate
  1320. ) => this.selectAnimation(animation, axis)}
  1321. isTargetedAnimation={this._isTargetedAnimation}
  1322. entity={this.props.entity}
  1323. selected={this.state.selected}
  1324. lockObject={this.props.lockObject}
  1325. setNotificationMessage={(message: string) => {
  1326. this.setState({ notification: message });
  1327. }}
  1328. globalState={this.props.globalState}
  1329. snippetServer={this._snippetUrl}
  1330. setFps={(fps: number) => {
  1331. this.setState({ fps: fps });
  1332. }}
  1333. />
  1334. <div
  1335. ref={this._graphCanvas}
  1336. className='graph-chart'
  1337. onWheel={(e) => this.zoom(e)}
  1338. >
  1339. {this.state.svgKeyframes && (
  1340. <SvgDraggableArea
  1341. ref={this._svgCanvas}
  1342. selectKeyframe={(id: string, multiselect: boolean) =>
  1343. this.selectKeyframe(id, multiselect)
  1344. }
  1345. viewBoxScale={this.state.frameAxisLength.length}
  1346. scale={this.state.scale}
  1347. keyframeSvgPoints={this.state.svgKeyframes}
  1348. removeSelectedKeyframes={(points: IKeyframeSvgPoint[]) =>
  1349. this.removeKeyframes(points)
  1350. }
  1351. selectedControlPoint={(type: string, id: string) =>
  1352. this.selectedControlPoint(type, id)
  1353. }
  1354. deselectKeyframes={() => this.deselectKeyframes()}
  1355. updatePosition={(
  1356. updatedSvgKeyFrame: IKeyframeSvgPoint,
  1357. id: string
  1358. ) => this.renderPoints(updatedSvgKeyFrame, id)}
  1359. >
  1360. {/* Multiple Curves */}
  1361. {this.state.selectedPathData?.map((curve, i) => (
  1362. <path
  1363. key={i}
  1364. ref={curve.domCurve}
  1365. pathLength={curve.pathLength}
  1366. id='curve'
  1367. d={curve.pathData}
  1368. style={{
  1369. stroke: curve.color,
  1370. fill: 'none',
  1371. strokeWidth: '0.5',
  1372. }}
  1373. ></path>
  1374. ))}
  1375. <svg>
  1376. <rect
  1377. x='-4%'
  1378. y='0%'
  1379. width='4%'
  1380. height='101%'
  1381. fill='#222'
  1382. ></rect>
  1383. </svg>
  1384. {this.state.valueAxisLength.map((f, i) => {
  1385. return (
  1386. <svg key={i}>
  1387. <text
  1388. x='-4'
  1389. y={f.value}
  1390. dx='0'
  1391. dy='1'
  1392. style={{ fontSize: `${0.2 * this.state.scale}em` }}
  1393. >
  1394. {f.label.toFixed(1)}
  1395. </text>
  1396. <line x1='0' y1={f.value} x2='105%' y2={f.value}></line>
  1397. </svg>
  1398. );
  1399. })}
  1400. <rect
  1401. onClick={(e) => this.moveFrameTo(e)}
  1402. x='0%'
  1403. y='91%'
  1404. width='105%'
  1405. height='10%'
  1406. fill='#222'
  1407. style={{ cursor: 'pointer' }}
  1408. ></rect>
  1409. {this.state.frameAxisLength.map((f, i) => (
  1410. <svg key={i} x='0' y='96%'>
  1411. <text
  1412. x={f.value}
  1413. y='0'
  1414. dx='2px'
  1415. style={{ fontSize: `${0.17 * this.state.scale}em` }}
  1416. >
  1417. {f.label}
  1418. </text>
  1419. <line x1={f.value} y1='0' x2={f.value} y2='5%'></line>
  1420. {f.value % this.state.fps === 0 && f.value !== 0 ? (
  1421. <line
  1422. x1={f.value}
  1423. y1='-100'
  1424. x2={f.value}
  1425. y2='5%'
  1426. ></line>
  1427. ) : null}
  1428. {this.isCurrentFrame(f.label) ? (
  1429. <svg>
  1430. <line
  1431. x1={f.value}
  1432. y1='0'
  1433. x2={f.value}
  1434. y2='-100%'
  1435. style={{
  1436. stroke: 'white',
  1437. strokeWidth: 0.4,
  1438. }}
  1439. />
  1440. <svg x={f.value} y='-1'>
  1441. <circle
  1442. className='svg-playhead'
  1443. cx='0'
  1444. cy='0'
  1445. r='2%'
  1446. fill='white'
  1447. />
  1448. <text
  1449. x='-0.6%'
  1450. y='1%'
  1451. style={{
  1452. fontSize: `${0.17 * this.state.scale}em`,
  1453. }}
  1454. >
  1455. {f.label}
  1456. </text>
  1457. </svg>
  1458. </svg>
  1459. ) : null}
  1460. </svg>
  1461. ))}
  1462. </SvgDraggableArea>
  1463. )}
  1464. </div>
  1465. </div>
  1466. <div className='row-bottom'>
  1467. <Timeline
  1468. currentFrame={this.state.currentFrame}
  1469. playPause={(direction: number) => this.playPause(direction)}
  1470. isPlaying={this.state.isPlaying}
  1471. dragKeyframe={(frame: number, index: number) =>
  1472. this.updateFrameInKeyFrame(frame, index)
  1473. }
  1474. onCurrentFrameChange={(frame: number) =>
  1475. this.changeCurrentFrame(frame)
  1476. }
  1477. onAnimationLimitChange={(limit: number) =>
  1478. this.changeAnimationLimit(limit)
  1479. }
  1480. animationLimit={this.state.animationLimit}
  1481. keyframes={this.state.selected && this.state.selected.getKeys()}
  1482. selected={this.state.selected && this.state.selected.getKeys()[0]}
  1483. fps={this.state.fps}
  1484. ></Timeline>
  1485. </div>
  1486. </div>
  1487. </div>
  1488. );
  1489. }
  1490. }