addAnimation.tsx 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285
  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 { Color3, Color4 } from "babylonjs/Maths/math.color";
  8. import { IAnimatable } from "babylonjs/Animations/animatable.interface";
  9. import { IAnimationKey } from "babylonjs/Animations/animationKey";
  10. interface IAddAnimationProps {
  11. isOpen: boolean;
  12. close: () => void;
  13. entity: IAnimatable;
  14. onPropertyChangedObservable?: Observable<PropertyChangedEvent>;
  15. setNotificationMessage: (message: string) => void;
  16. finishedUpdate: () => void;
  17. addedNewAnimation: (animation: Animation) => void;
  18. fps: number;
  19. selectedToUpdate?: Animation | undefined;
  20. }
  21. /**
  22. * Controls the creation of a new animation
  23. */
  24. export class AddAnimation extends React.Component<
  25. IAddAnimationProps,
  26. {
  27. animationName: string;
  28. animationTargetProperty: string;
  29. animationType: number;
  30. loopMode: number;
  31. animationTargetPath: string;
  32. isUpdating: boolean;
  33. }
  34. > {
  35. constructor(props: IAddAnimationProps) {
  36. super(props);
  37. this.state = this.setInitialState(this.props.selectedToUpdate);
  38. }
  39. setInitialState(editingAnimation?: Animation) {
  40. return {
  41. animationName: editingAnimation ? editingAnimation.name : "",
  42. animationTargetPath: "",
  43. animationType: editingAnimation ? editingAnimation.dataType : Animation.ANIMATIONTYPE_FLOAT,
  44. loopMode: editingAnimation ? editingAnimation.loopMode ?? Animation.ANIMATIONLOOPMODE_CYCLE : Animation.ANIMATIONLOOPMODE_CYCLE,
  45. animationTargetProperty: editingAnimation ? editingAnimation.targetProperty : "",
  46. isUpdating: editingAnimation ? true : false,
  47. };
  48. }
  49. componentDidUpdate(prevProps: IAddAnimationProps, prevState: any) {
  50. if (this.props.selectedToUpdate !== undefined && this.props.selectedToUpdate !== prevProps.selectedToUpdate) {
  51. this.setState(this.setInitialState(this.props.selectedToUpdate));
  52. } else {
  53. if (this.props.isOpen === true && this.props.isOpen !== prevProps.isOpen) {
  54. this.setState(this.setInitialState());
  55. }
  56. }
  57. }
  58. updateAnimation = () => {
  59. if (this.props.selectedToUpdate !== undefined) {
  60. const oldNameValue = this.props.selectedToUpdate.name;
  61. this.props.selectedToUpdate.name = this.state.animationName;
  62. this.raiseOnPropertyUpdated(oldNameValue, this.state.animationName, "name");
  63. const oldLoopModeValue = this.props.selectedToUpdate.loopMode;
  64. this.props.selectedToUpdate.loopMode = this.state.loopMode;
  65. this.raiseOnPropertyUpdated(oldLoopModeValue, this.state.loopMode, "loopMode");
  66. const oldTargetPropertyValue = this.props.selectedToUpdate.targetProperty;
  67. this.props.selectedToUpdate.targetProperty = this.state.animationTargetProperty;
  68. this.raiseOnPropertyUpdated(oldTargetPropertyValue, this.state.animationTargetProperty, "targetProperty");
  69. this.props.finishedUpdate();
  70. }
  71. };
  72. getTypeAsString(type: number) {
  73. switch (type) {
  74. case Animation.ANIMATIONTYPE_FLOAT:
  75. return "Float";
  76. case Animation.ANIMATIONTYPE_QUATERNION:
  77. return "Quaternion";
  78. case Animation.ANIMATIONTYPE_VECTOR3:
  79. return "Vector3";
  80. case Animation.ANIMATIONTYPE_VECTOR2:
  81. return "Vector2";
  82. case Animation.ANIMATIONTYPE_SIZE:
  83. return "Size";
  84. case Animation.ANIMATIONTYPE_COLOR3:
  85. return "Color3";
  86. case Animation.ANIMATIONTYPE_COLOR4:
  87. return "Color4";
  88. default:
  89. return "Float";
  90. }
  91. }
  92. addAnimation = () => {
  93. if (this.state.animationName != "" && this.state.animationTargetProperty != "") {
  94. let matchTypeTargetProperty = this.state.animationTargetProperty.split(".");
  95. let animationDataType = this.state.animationType;
  96. let matched = false;
  97. if (matchTypeTargetProperty.length === 1) {
  98. let match = (this.props.entity as any)[matchTypeTargetProperty[0]];
  99. if (match) {
  100. switch (match.constructor.name) {
  101. case "Vector2":
  102. matched = animationDataType === Animation.ANIMATIONTYPE_VECTOR2;
  103. break;
  104. case "Vector3":
  105. matched = animationDataType === Animation.ANIMATIONTYPE_VECTOR3;
  106. break;
  107. case "Quaternion":
  108. matched = animationDataType === Animation.ANIMATIONTYPE_QUATERNION;
  109. break;
  110. case "Color3":
  111. matched = animationDataType === Animation.ANIMATIONTYPE_COLOR3;
  112. break;
  113. case "Color4":
  114. matched = animationDataType === Animation.ANIMATIONTYPE_COLOR4;
  115. break;
  116. case "Size":
  117. matched = animationDataType === Animation.ANIMATIONTYPE_SIZE;
  118. break;
  119. }
  120. } else {
  121. this.props.setNotificationMessage(`The selected entity doesn't have a ${matchTypeTargetProperty[0]} property`);
  122. }
  123. } else if (matchTypeTargetProperty.length > 1) {
  124. let matchProp = (this.props.entity as any)[matchTypeTargetProperty[0]];
  125. if (matchProp) {
  126. let match = matchProp[matchTypeTargetProperty[1]];
  127. if (typeof match === "number") {
  128. animationDataType === Animation.ANIMATIONTYPE_FLOAT ? (matched = true) : (matched = false);
  129. }
  130. }
  131. }
  132. if (matched) {
  133. let alreadyAnimatedProperty = (this.props.entity as IAnimatable).animations?.find((anim) => anim.targetProperty === this.state.animationTargetProperty, this);
  134. let alreadyAnimationName = (this.props.entity as IAnimatable).animations?.find((anim) => anim.name === this.state.animationName, this);
  135. if (alreadyAnimatedProperty) {
  136. this.props.setNotificationMessage(`The property "${this.state.animationTargetProperty}" already has an animation`);
  137. } else if (alreadyAnimationName) {
  138. this.props.setNotificationMessage(`There is already an animation with the name: "${this.state.animationName}"`);
  139. } else {
  140. let animation = new Animation(this.state.animationName, this.state.animationTargetProperty, this.props.fps, animationDataType);
  141. // Start with two keyframes
  142. var keys: IAnimationKey[] = [];
  143. animation.setKeys(keys);
  144. if (this.props.entity.animations) {
  145. const store = this.props.entity.animations;
  146. const updatedCollection = [...this.props.entity.animations, animation];
  147. this.raiseOnPropertyChanged(updatedCollection, store);
  148. this.props.entity.animations = updatedCollection;
  149. this.props.addedNewAnimation(animation);
  150. //Cleaning form fields
  151. this.setState({
  152. animationName: "",
  153. animationTargetPath: "",
  154. animationType: animationDataType,
  155. loopMode: this.state.loopMode,
  156. animationTargetProperty: "",
  157. });
  158. }
  159. }
  160. } else {
  161. this.props.setNotificationMessage(`The property "${this.state.animationTargetProperty}" is not a "${this.getTypeAsString(this.state.animationType)}" type`);
  162. }
  163. } else {
  164. this.props.setNotificationMessage(`You need to provide a name and target property.`);
  165. }
  166. };
  167. raiseOnPropertyChanged(newValue: Animation[], previousValue: Animation[]) {
  168. if (!this.props.onPropertyChangedObservable) {
  169. return;
  170. }
  171. this.props.onPropertyChangedObservable.notifyObservers({
  172. object: this.props.entity,
  173. property: "animations",
  174. value: newValue,
  175. initialValue: previousValue,
  176. });
  177. }
  178. raiseOnPropertyUpdated(newValue: string | number | undefined, previousValue: string | number, property: string) {
  179. if (!this.props.onPropertyChangedObservable) {
  180. return;
  181. }
  182. this.props.onPropertyChangedObservable.notifyObservers({
  183. object: this.props.selectedToUpdate,
  184. property: property,
  185. value: newValue,
  186. initialValue: previousValue,
  187. });
  188. }
  189. handlePathChange = (event: React.ChangeEvent<HTMLInputElement>) => {
  190. event.preventDefault();
  191. this.setState({ animationTargetPath: event.target.value.trim() });
  192. };
  193. handleNameChange = (event: React.ChangeEvent<HTMLInputElement>) => {
  194. event.preventDefault();
  195. this.setState({ animationName: event.target.value.trim() });
  196. };
  197. handleTypeChange = (event: React.ChangeEvent<HTMLSelectElement>) => {
  198. event.preventDefault();
  199. this.setState({ animationType: parseInt(event.target.value) });
  200. };
  201. handlePropertyChange = (event: React.ChangeEvent<HTMLInputElement>) => {
  202. event.preventDefault();
  203. this.setState({ animationTargetProperty: event.target.value });
  204. };
  205. handleLoopModeChange = (event: React.ChangeEvent<HTMLSelectElement>) => {
  206. event.preventDefault();
  207. this.setState({ loopMode: parseInt(event.target.value) });
  208. };
  209. render() {
  210. const confirmLabel = this.state.isUpdating ? "Update" : "Create";
  211. const confirmHandleOnClick = this.state.isUpdating ? this.updateAnimation : this.addAnimation;
  212. return (
  213. <div className="new-animation" style={{ display: this.props.isOpen ? "block" : "none" }}>
  214. <div className="sub-content">
  215. <div className="label-input">
  216. <label>Display Name</label>
  217. <input type="text" value={this.state.animationName} onChange={this.handleNameChange}></input>
  218. </div>
  219. {this.state.isUpdating ? null : (
  220. <div className="label-input">
  221. <label>Property</label>
  222. <input type="text" value={this.state.animationTargetProperty} onChange={this.handlePropertyChange}></input>
  223. </div>
  224. )}
  225. {this.state.isUpdating ? null : (
  226. <div className="label-input">
  227. <label>Type</label>
  228. <select onChange={this.handleTypeChange} value={this.state.animationType}>
  229. {/* <option value={Animation.ANIMATIONTYPE_COLOR3}>Color3</option>
  230. <option value={Animation.ANIMATIONTYPE_COLOR4}>Color4</option> */}
  231. <option value={Animation.ANIMATIONTYPE_FLOAT}>Float</option>
  232. {/* <option value={Animation.ANIMATIONTYPE_VECTOR3}>Vector3</option>
  233. <option value={Animation.ANIMATIONTYPE_VECTOR2}>Vector2</option>
  234. <option value={Animation.ANIMATIONTYPE_QUATERNION}>
  235. Quaternion
  236. </option> */}
  237. </select>
  238. </div>
  239. )}
  240. <div className="label-input">
  241. <label>Loop Mode</label>
  242. <select onChange={this.handleLoopModeChange} value={this.state.loopMode}>
  243. <option value={Animation.ANIMATIONLOOPMODE_CYCLE}>Cycle</option>
  244. <option value={Animation.ANIMATIONLOOPMODE_RELATIVE}>Relative</option>
  245. <option value={Animation.ANIMATIONLOOPMODE_CONSTANT}>Constant</option>
  246. </select>
  247. </div>
  248. <div className="confirm-buttons">
  249. <ButtonLineComponent label={confirmLabel} onClick={confirmHandleOnClick} />
  250. {this.props.entity.animations?.length !== 0 ? <ButtonLineComponent label={"Cancel"} onClick={this.props.close} /> : null}
  251. </div>
  252. </div>
  253. </div>
  254. );
  255. }
  256. }