addAnimation.tsx 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432
  1. import * as React from 'react';
  2. import { ButtonLineComponent } from '../../../lines/buttonLineComponent';
  3. import { Observable } from 'babylonjs/Misc/observable';
  4. import { PropertyChangedEvent } from '../../../../../components/propertyChangedEvent';
  5. import { Animation } from 'babylonjs/Animations/animation';
  6. import { Vector2, Vector3, Quaternion } from 'babylonjs/Maths/math.vector';
  7. import { Size } from 'babylonjs/Maths/math.size';
  8. import { Color3, Color4 } from 'babylonjs/Maths/math.color';
  9. import { IAnimatable } from 'babylonjs/Animations/animatable.interface';
  10. import { IAnimationKey } from 'babylonjs/Animations/animationKey';
  11. interface IAddAnimationProps {
  12. isOpen: boolean;
  13. close: () => void;
  14. entity: IAnimatable;
  15. onPropertyChangedObservable?: Observable<PropertyChangedEvent>;
  16. setNotificationMessage: (message: string) => void;
  17. finishedUpdate: () => void;
  18. addedNewAnimation: () => void;
  19. fps: number;
  20. selectedToUpdate?: Animation | undefined;
  21. }
  22. export class AddAnimation extends React.Component<
  23. IAddAnimationProps,
  24. {
  25. animationName: string;
  26. animationTargetProperty: string;
  27. animationType: number;
  28. loopMode: number;
  29. animationTargetPath: string;
  30. isUpdating: boolean;
  31. }
  32. > {
  33. constructor(props: IAddAnimationProps) {
  34. super(props);
  35. this.state = this.setInitialState(this.props.selectedToUpdate);
  36. }
  37. setInitialState(editingAnimation?: Animation) {
  38. return {
  39. animationName: editingAnimation ? editingAnimation.name : '',
  40. animationTargetPath: '',
  41. animationType: editingAnimation
  42. ? editingAnimation.dataType
  43. : Animation.ANIMATIONTYPE_FLOAT,
  44. loopMode: editingAnimation
  45. ? editingAnimation.loopMode ?? Animation.ANIMATIONLOOPMODE_CYCLE
  46. : Animation.ANIMATIONLOOPMODE_CYCLE,
  47. animationTargetProperty: editingAnimation
  48. ? editingAnimation.targetProperty
  49. : '',
  50. isUpdating: editingAnimation ? true : false,
  51. };
  52. }
  53. componentWillReceiveProps(nextProps: IAddAnimationProps) {
  54. if (
  55. nextProps.selectedToUpdate !== undefined &&
  56. nextProps.selectedToUpdate !== this.props.selectedToUpdate
  57. ) {
  58. this.setState(this.setInitialState(nextProps.selectedToUpdate));
  59. } else {
  60. if (nextProps.isOpen === true && nextProps.isOpen !== this.props.isOpen)
  61. this.setState(this.setInitialState());
  62. }
  63. }
  64. updateAnimation() {
  65. if (this.props.selectedToUpdate !== undefined) {
  66. const oldNameValue = this.props.selectedToUpdate.name;
  67. this.props.selectedToUpdate.name = this.state.animationName;
  68. this.raiseOnPropertyUpdated(
  69. oldNameValue,
  70. this.state.animationName,
  71. 'name'
  72. );
  73. const oldLoopModeValue = this.props.selectedToUpdate.loopMode;
  74. this.props.selectedToUpdate.loopMode = this.state.loopMode;
  75. this.raiseOnPropertyUpdated(
  76. oldLoopModeValue,
  77. this.state.loopMode,
  78. 'loopMode'
  79. );
  80. const oldTargetPropertyValue = this.props.selectedToUpdate.targetProperty;
  81. this.props.selectedToUpdate.targetProperty = this.state.animationTargetProperty;
  82. this.raiseOnPropertyUpdated(
  83. oldTargetPropertyValue,
  84. this.state.animationTargetProperty,
  85. 'targetProperty'
  86. );
  87. this.props.finishedUpdate();
  88. }
  89. }
  90. getTypeAsString(type: number) {
  91. switch (type) {
  92. case Animation.ANIMATIONTYPE_FLOAT:
  93. return 'Float';
  94. case Animation.ANIMATIONTYPE_QUATERNION:
  95. return 'Quaternion';
  96. case Animation.ANIMATIONTYPE_VECTOR3:
  97. return 'Vector3';
  98. case Animation.ANIMATIONTYPE_VECTOR2:
  99. return 'Vector2';
  100. case Animation.ANIMATIONTYPE_SIZE:
  101. return 'Size';
  102. case Animation.ANIMATIONTYPE_COLOR3:
  103. return 'Color3';
  104. case Animation.ANIMATIONTYPE_COLOR4:
  105. return 'Color4';
  106. default:
  107. return 'Float';
  108. }
  109. }
  110. addAnimation() {
  111. if (
  112. this.state.animationName != '' &&
  113. this.state.animationTargetProperty != ''
  114. ) {
  115. let matchTypeTargetProperty = this.state.animationTargetProperty.split(
  116. '.'
  117. );
  118. let animationDataType = this.state.animationType;
  119. let matched = false;
  120. if (matchTypeTargetProperty.length === 1) {
  121. let match = (this.props.entity as any)[matchTypeTargetProperty[0]];
  122. if (match) {
  123. switch (match.constructor.name) {
  124. case 'Vector2':
  125. animationDataType === Animation.ANIMATIONTYPE_VECTOR2
  126. ? (matched = true)
  127. : (matched = false);
  128. break;
  129. case 'Vector3':
  130. animationDataType === Animation.ANIMATIONTYPE_VECTOR3
  131. ? (matched = true)
  132. : (matched = false);
  133. break;
  134. case 'Quaternion':
  135. animationDataType === Animation.ANIMATIONTYPE_QUATERNION
  136. ? (matched = true)
  137. : (matched = false);
  138. break;
  139. case 'Color3':
  140. animationDataType === Animation.ANIMATIONTYPE_COLOR3
  141. ? (matched = true)
  142. : (matched = false);
  143. break;
  144. case 'Color4':
  145. animationDataType === Animation.ANIMATIONTYPE_COLOR4
  146. ? (matched = true)
  147. : (matched = false);
  148. break;
  149. case 'Size':
  150. animationDataType === Animation.ANIMATIONTYPE_SIZE
  151. ? (matched = true)
  152. : (matched = false);
  153. break;
  154. }
  155. } else {
  156. this.props.setNotificationMessage(
  157. `The selected entity doesn't have a ${matchTypeTargetProperty[0]} property`
  158. );
  159. }
  160. } else if (matchTypeTargetProperty.length > 1) {
  161. let matchProp = (this.props.entity as any)[matchTypeTargetProperty[0]];
  162. if (matchProp) {
  163. let match = matchProp[matchTypeTargetProperty[1]];
  164. if (typeof match === 'number') {
  165. animationDataType === Animation.ANIMATIONTYPE_FLOAT
  166. ? (matched = true)
  167. : (matched = false);
  168. }
  169. }
  170. }
  171. if (matched) {
  172. let startValue;
  173. let outTangent;
  174. // Default start and end values for new animations
  175. switch (animationDataType) {
  176. case Animation.ANIMATIONTYPE_FLOAT:
  177. startValue = 1;
  178. outTangent = 0;
  179. break;
  180. case Animation.ANIMATIONTYPE_VECTOR2:
  181. startValue = new Vector2(1, 1);
  182. outTangent = Vector2.Zero();
  183. break;
  184. case Animation.ANIMATIONTYPE_VECTOR3:
  185. startValue = new Vector3(1, 1, 1);
  186. outTangent = Vector3.Zero();
  187. break;
  188. case Animation.ANIMATIONTYPE_QUATERNION:
  189. startValue = new Quaternion(1, 1, 1, 1);
  190. outTangent = Quaternion.Zero();
  191. break;
  192. case Animation.ANIMATIONTYPE_COLOR3:
  193. startValue = new Color3(1, 1, 1);
  194. outTangent = new Color3(0, 0, 0);
  195. break;
  196. case Animation.ANIMATIONTYPE_COLOR4:
  197. startValue = new Color4(1, 1, 1, 1);
  198. outTangent = new Color4(0, 0, 0, 0);
  199. break;
  200. case Animation.ANIMATIONTYPE_SIZE:
  201. startValue = new Size(1, 1);
  202. outTangent = Size.Zero();
  203. break;
  204. default:
  205. console.log('not recognized');
  206. break;
  207. }
  208. let alreadyAnimatedProperty = (this.props
  209. .entity as IAnimatable).animations?.find(
  210. (anim) => anim.targetProperty === this.state.animationTargetProperty,
  211. this
  212. );
  213. let alreadyAnimationName = (this.props
  214. .entity as IAnimatable).animations?.find(
  215. (anim) => anim.name === this.state.animationName,
  216. this
  217. );
  218. if (alreadyAnimatedProperty) {
  219. this.props.setNotificationMessage(
  220. `The property "${this.state.animationTargetProperty}" already has an animation`
  221. );
  222. } else if (alreadyAnimationName) {
  223. this.props.setNotificationMessage(
  224. `There is already an animation with the name: "${this.state.animationName}"`
  225. );
  226. } else {
  227. let animation = new Animation(
  228. this.state.animationName,
  229. this.state.animationTargetProperty,
  230. this.props.fps,
  231. animationDataType
  232. );
  233. // Start with two keyframes
  234. var keys: IAnimationKey[] = [];
  235. keys.push({
  236. frame: 0,
  237. value: startValue,
  238. outTangent: outTangent,
  239. });
  240. animation.setKeys(keys);
  241. if (this.props.entity.animations) {
  242. const store = this.props.entity.animations;
  243. const updatedCollection = [
  244. ...this.props.entity.animations,
  245. animation,
  246. ];
  247. this.raiseOnPropertyChanged(updatedCollection, store);
  248. this.props.entity.animations = updatedCollection;
  249. this.props.addedNewAnimation();
  250. //Cleaning form fields
  251. this.setState({
  252. animationName: '',
  253. animationTargetPath: '',
  254. animationType: Animation.ANIMATIONTYPE_FLOAT,
  255. loopMode: Animation.ANIMATIONLOOPMODE_CYCLE,
  256. animationTargetProperty: '',
  257. });
  258. }
  259. }
  260. } else {
  261. this.props.setNotificationMessage(
  262. `The property "${
  263. this.state.animationTargetProperty
  264. }" is not a "${this.getTypeAsString(this.state.animationType)}" type`
  265. );
  266. }
  267. } else {
  268. this.props.setNotificationMessage(
  269. `You need to provide a name and target property.`
  270. );
  271. }
  272. }
  273. raiseOnPropertyChanged(newValue: Animation[], previousValue: Animation[]) {
  274. if (!this.props.onPropertyChangedObservable) {
  275. return;
  276. }
  277. this.props.onPropertyChangedObservable.notifyObservers({
  278. object: this.props.entity,
  279. property: 'animations',
  280. value: newValue,
  281. initialValue: previousValue,
  282. });
  283. }
  284. raiseOnPropertyUpdated(
  285. newValue: string | number | undefined,
  286. previousValue: string | number,
  287. property: string
  288. ) {
  289. if (!this.props.onPropertyChangedObservable) {
  290. return;
  291. }
  292. this.props.onPropertyChangedObservable.notifyObservers({
  293. object: this.props.selectedToUpdate,
  294. property: property,
  295. value: newValue,
  296. initialValue: previousValue,
  297. });
  298. }
  299. handleNameChange(event: React.ChangeEvent<HTMLInputElement>) {
  300. event.preventDefault();
  301. this.setState({ animationName: event.target.value.trim() });
  302. }
  303. handlePathChange(event: React.ChangeEvent<HTMLInputElement>) {
  304. event.preventDefault();
  305. this.setState({ animationTargetPath: event.target.value.trim() });
  306. }
  307. handleTypeChange(event: React.ChangeEvent<HTMLSelectElement>) {
  308. event.preventDefault();
  309. this.setState({ animationType: parseInt(event.target.value) });
  310. }
  311. handlePropertyChange(event: React.ChangeEvent<HTMLInputElement>) {
  312. event.preventDefault();
  313. this.setState({ animationTargetProperty: event.target.value });
  314. }
  315. handleLoopModeChange(event: React.ChangeEvent<HTMLSelectElement>) {
  316. event.preventDefault();
  317. this.setState({ loopMode: parseInt(event.target.value) });
  318. }
  319. render() {
  320. return (
  321. <div
  322. className='new-animation'
  323. style={{ display: this.props.isOpen ? 'block' : 'none' }}
  324. >
  325. <div className='sub-content'>
  326. {this.state.isUpdating ? null : (
  327. <div className='label-input'>
  328. <label>Target Path</label>
  329. <input
  330. type='text'
  331. value={this.state.animationTargetPath}
  332. onChange={(e) => this.handlePathChange(e)}
  333. disabled
  334. ></input>
  335. </div>
  336. )}
  337. <div className='label-input'>
  338. <label>Display Name</label>
  339. <input
  340. type='text'
  341. value={this.state.animationName}
  342. onChange={(e) => this.handleNameChange(e)}
  343. ></input>
  344. </div>
  345. <div className='label-input'>
  346. <label>Property</label>
  347. <input
  348. type='text'
  349. value={this.state.animationTargetProperty}
  350. onChange={(e) => this.handlePropertyChange(e)}
  351. ></input>
  352. </div>
  353. {this.state.isUpdating ? null : (
  354. <div className='label-input'>
  355. <label>Type</label>
  356. <select
  357. onChange={(e) => this.handleTypeChange(e)}
  358. value={this.state.animationType}
  359. >
  360. <option value={Animation.ANIMATIONTYPE_FLOAT}>Float</option>
  361. <option value={Animation.ANIMATIONTYPE_VECTOR3}>Vector3</option>
  362. <option value={Animation.ANIMATIONTYPE_VECTOR2}>Vector2</option>
  363. <option value={Animation.ANIMATIONTYPE_QUATERNION}>
  364. Quaternion
  365. </option>
  366. <option value={Animation.ANIMATIONTYPE_COLOR3}>Color3</option>
  367. <option value={Animation.ANIMATIONTYPE_COLOR4}>Color4</option>
  368. <option value={Animation.ANIMATIONTYPE_SIZE}>Size</option>
  369. </select>
  370. </div>
  371. )}
  372. <div className='label-input'>
  373. <label>Loop Mode</label>
  374. <select
  375. onChange={(e) => this.handleLoopModeChange(e)}
  376. value={this.state.loopMode}
  377. >
  378. <option value={Animation.ANIMATIONLOOPMODE_CYCLE}>Cycle</option>
  379. <option value={Animation.ANIMATIONLOOPMODE_RELATIVE}>
  380. Relative
  381. </option>
  382. <option value={Animation.ANIMATIONLOOPMODE_CONSTANT}>
  383. Constant
  384. </option>
  385. </select>
  386. </div>
  387. <div className='confirm-buttons'>
  388. <ButtonLineComponent
  389. label={this.state.isUpdating ? 'Update' : 'Create'}
  390. onClick={
  391. this.state.isUpdating
  392. ? () => this.updateAnimation()
  393. : () => this.addAnimation()
  394. }
  395. />
  396. </div>
  397. </div>
  398. </div>
  399. );
  400. }
  401. }