addAnimation.tsx 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429
  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. }
  205. let alreadyAnimatedProperty = (this.props
  206. .entity as IAnimatable).animations?.find(
  207. (anim) => anim.targetProperty === this.state.animationTargetProperty,
  208. this
  209. );
  210. let alreadyAnimationName = (this.props
  211. .entity as IAnimatable).animations?.find(
  212. (anim) => anim.name === this.state.animationName,
  213. this
  214. );
  215. if (alreadyAnimatedProperty) {
  216. this.props.setNotificationMessage(
  217. `The property "${this.state.animationTargetProperty}" already has an animation`
  218. );
  219. } else if (alreadyAnimationName) {
  220. this.props.setNotificationMessage(
  221. `There is already an animation with the name: "${this.state.animationName}"`
  222. );
  223. } else {
  224. let animation = new Animation(
  225. this.state.animationName,
  226. this.state.animationTargetProperty,
  227. this.props.fps,
  228. animationDataType
  229. );
  230. // Start with two keyframes
  231. var keys: IAnimationKey[] = [];
  232. keys.push({
  233. frame: 0,
  234. value: startValue,
  235. outTangent: outTangent,
  236. });
  237. animation.setKeys(keys);
  238. if (this.props.entity.animations) {
  239. const store = this.props.entity.animations;
  240. const updatedCollection = [
  241. ...this.props.entity.animations,
  242. animation,
  243. ];
  244. this.raiseOnPropertyChanged(updatedCollection, store);
  245. this.props.entity.animations = updatedCollection;
  246. this.props.addedNewAnimation();
  247. //Cleaning form fields
  248. this.setState({
  249. animationName: '',
  250. animationTargetPath: '',
  251. animationType: Animation.ANIMATIONTYPE_FLOAT,
  252. loopMode: Animation.ANIMATIONLOOPMODE_CYCLE,
  253. animationTargetProperty: '',
  254. });
  255. }
  256. }
  257. } else {
  258. this.props.setNotificationMessage(
  259. `The property "${
  260. this.state.animationTargetProperty
  261. }" is not a "${this.getTypeAsString(this.state.animationType)}" type`
  262. );
  263. }
  264. } else {
  265. this.props.setNotificationMessage(
  266. `You need to provide a name and target property.`
  267. );
  268. }
  269. }
  270. raiseOnPropertyChanged(newValue: Animation[], previousValue: Animation[]) {
  271. if (!this.props.onPropertyChangedObservable) {
  272. return;
  273. }
  274. this.props.onPropertyChangedObservable.notifyObservers({
  275. object: this.props.entity,
  276. property: 'animations',
  277. value: newValue,
  278. initialValue: previousValue,
  279. });
  280. }
  281. raiseOnPropertyUpdated(
  282. newValue: string | number | undefined,
  283. previousValue: string | number,
  284. property: string
  285. ) {
  286. if (!this.props.onPropertyChangedObservable) {
  287. return;
  288. }
  289. this.props.onPropertyChangedObservable.notifyObservers({
  290. object: this.props.selectedToUpdate,
  291. property: property,
  292. value: newValue,
  293. initialValue: previousValue,
  294. });
  295. }
  296. handleNameChange(event: React.ChangeEvent<HTMLInputElement>) {
  297. event.preventDefault();
  298. this.setState({ animationName: event.target.value.trim() });
  299. }
  300. handlePathChange(event: React.ChangeEvent<HTMLInputElement>) {
  301. event.preventDefault();
  302. this.setState({ animationTargetPath: event.target.value.trim() });
  303. }
  304. handleTypeChange(event: React.ChangeEvent<HTMLSelectElement>) {
  305. event.preventDefault();
  306. this.setState({ animationType: parseInt(event.target.value) });
  307. }
  308. handlePropertyChange(event: React.ChangeEvent<HTMLInputElement>) {
  309. event.preventDefault();
  310. this.setState({ animationTargetProperty: event.target.value });
  311. }
  312. handleLoopModeChange(event: React.ChangeEvent<HTMLSelectElement>) {
  313. event.preventDefault();
  314. this.setState({ loopMode: parseInt(event.target.value) });
  315. }
  316. render() {
  317. return (
  318. <div
  319. className='new-animation'
  320. style={{ display: this.props.isOpen ? 'block' : 'none' }}
  321. >
  322. <div className='sub-content'>
  323. {this.state.isUpdating ? null : (
  324. <div className='label-input'>
  325. <label>Target Path</label>
  326. <input
  327. type='text'
  328. value={this.state.animationTargetPath}
  329. onChange={(e) => this.handlePathChange(e)}
  330. disabled
  331. ></input>
  332. </div>
  333. )}
  334. <div className='label-input'>
  335. <label>Display Name</label>
  336. <input
  337. type='text'
  338. value={this.state.animationName}
  339. onChange={(e) => this.handleNameChange(e)}
  340. ></input>
  341. </div>
  342. <div className='label-input'>
  343. <label>Property</label>
  344. <input
  345. type='text'
  346. value={this.state.animationTargetProperty}
  347. onChange={(e) => this.handlePropertyChange(e)}
  348. ></input>
  349. </div>
  350. {this.state.isUpdating ? null : (
  351. <div className='label-input'>
  352. <label>Type</label>
  353. <select
  354. onChange={(e) => this.handleTypeChange(e)}
  355. value={this.state.animationType}
  356. >
  357. <option value={Animation.ANIMATIONTYPE_FLOAT}>Float</option>
  358. <option value={Animation.ANIMATIONTYPE_VECTOR3}>Vector3</option>
  359. <option value={Animation.ANIMATIONTYPE_VECTOR2}>Vector2</option>
  360. <option value={Animation.ANIMATIONTYPE_QUATERNION}>
  361. Quaternion
  362. </option>
  363. <option value={Animation.ANIMATIONTYPE_COLOR3}>Color3</option>
  364. <option value={Animation.ANIMATIONTYPE_COLOR4}>Color4</option>
  365. <option value={Animation.ANIMATIONTYPE_SIZE}>Size</option>
  366. </select>
  367. </div>
  368. )}
  369. <div className='label-input'>
  370. <label>Loop Mode</label>
  371. <select
  372. onChange={(e) => this.handleLoopModeChange(e)}
  373. value={this.state.loopMode}
  374. >
  375. <option value={Animation.ANIMATIONLOOPMODE_CYCLE}>Cycle</option>
  376. <option value={Animation.ANIMATIONLOOPMODE_RELATIVE}>
  377. Relative
  378. </option>
  379. <option value={Animation.ANIMATIONLOOPMODE_CONSTANT}>
  380. Constant
  381. </option>
  382. </select>
  383. </div>
  384. <div className='confirm-buttons'>
  385. <ButtonLineComponent
  386. label={this.state.isUpdating ? 'Update' : 'Create'}
  387. onClick={
  388. this.state.isUpdating
  389. ? () => this.updateAnimation()
  390. : () => this.addAnimation()
  391. }
  392. />
  393. </div>
  394. </div>
  395. </div>
  396. );
  397. }
  398. }