animationCurveEditorComponent.tsx 53 KB

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