animationPropertyGridComponent.tsx 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350
  1. import * as React from 'react';
  2. import { Observable, Observer } from 'babylonjs/Misc/observable';
  3. import { Scene } from 'babylonjs/scene';
  4. import { PropertyChangedEvent } from '../../../../propertyChangedEvent';
  5. import { ButtonLineComponent } from '../../../lines/buttonLineComponent';
  6. import { LineContainerComponent } from '../../../lineContainerComponent';
  7. import { SliderLineComponent } from '../../../lines/sliderLineComponent';
  8. import { LockObject } from '../lockObject';
  9. import { GlobalState } from '../../../../globalState';
  10. import { Animation } from 'babylonjs/Animations/animation';
  11. import { Animatable } from 'babylonjs/Animations/animatable';
  12. import { AnimationPropertiesOverride } from 'babylonjs/Animations/animationPropertiesOverride';
  13. import { AnimationRange } from 'babylonjs/Animations/animationRange';
  14. import { CheckBoxLineComponent } from '../../../lines/checkBoxLineComponent';
  15. import { Nullable } from 'babylonjs/types';
  16. import { FloatLineComponent } from '../../../lines/floatLineComponent';
  17. import { TextLineComponent } from '../../../lines/textLineComponent';
  18. import { IAnimatable } from 'babylonjs/Animations/animatable.interface';
  19. import { AnimationCurveEditorComponent } from '../animations/animationCurveEditorComponent';
  20. import { PopupComponent } from '../animations/popupComponent';
  21. interface IAnimationGridComponentProps {
  22. globalState: GlobalState;
  23. animatable: IAnimatable;
  24. scene: Scene;
  25. lockObject: LockObject;
  26. onPropertyChangedObservable?: Observable<PropertyChangedEvent>;
  27. }
  28. export class AnimationGridComponent extends React.Component<
  29. IAnimationGridComponentProps,
  30. { currentFrame: number }
  31. > {
  32. private _animations: Nullable<Animation[]> = null;
  33. private _ranges: AnimationRange[];
  34. private _mainAnimatable: Nullable<Animatable>;
  35. private _onBeforeRenderObserver: Nullable<Observer<Scene>>;
  36. private _isPlaying = false;
  37. private timelineRef: React.RefObject<SliderLineComponent>;
  38. private _isCurveEditorOpen = false;
  39. private _animationControl = {
  40. from: 0,
  41. to: 0,
  42. loop: false,
  43. };
  44. constructor(props: IAnimationGridComponentProps) {
  45. super(props);
  46. this.state = { currentFrame: 0 };
  47. const animatableAsAny = this.props.animatable as any;
  48. this._ranges = animatableAsAny.getAnimationRanges
  49. ? animatableAsAny.getAnimationRanges()
  50. : [];
  51. if (animatableAsAny.getAnimatables) {
  52. const animatables = animatableAsAny.getAnimatables();
  53. this._animations = new Array<Animation>();
  54. animatables.forEach((animatable: IAnimatable) => {
  55. if (animatable.animations) {
  56. this._animations!.push(...animatable.animations);
  57. }
  58. });
  59. if (animatableAsAny.animations) {
  60. this._animations!.push(...animatableAsAny.animations);
  61. }
  62. // Extract from and to
  63. if (this._animations && this._animations.length) {
  64. this._animations.forEach((animation) => {
  65. let keys = animation.getKeys();
  66. if (keys && keys.length > 0) {
  67. if (keys[0].frame < this._animationControl.from) {
  68. this._animationControl.from = keys[0].frame;
  69. }
  70. const lastKeyIndex = keys.length - 1;
  71. if (keys[lastKeyIndex].frame > this._animationControl.to) {
  72. this._animationControl.to = keys[lastKeyIndex].frame;
  73. }
  74. }
  75. });
  76. }
  77. }
  78. this.timelineRef = React.createRef();
  79. }
  80. playOrPause() {
  81. const animatable = this.props.animatable;
  82. this._isPlaying =
  83. this.props.scene.getAllAnimatablesByTarget(animatable).length > 0;
  84. if (this._isPlaying) {
  85. this.props.scene.stopAnimation(this.props.animatable);
  86. this._mainAnimatable = null;
  87. } else {
  88. this._mainAnimatable = this.props.scene.beginAnimation(
  89. this.props.animatable,
  90. this._animationControl.from,
  91. this._animationControl.to,
  92. this._animationControl.loop
  93. );
  94. }
  95. this.forceUpdate();
  96. }
  97. componentDidMount() {
  98. this._onBeforeRenderObserver = this.props.scene.onBeforeRenderObservable.add(
  99. () => {
  100. if (!this._isPlaying || !this._mainAnimatable) {
  101. return;
  102. }
  103. this.setState({ currentFrame: this._mainAnimatable.masterFrame });
  104. }
  105. );
  106. }
  107. componentWillUnmount() {
  108. if (this._onBeforeRenderObserver) {
  109. this.props.scene.onBeforeRenderObservable.remove(
  110. this._onBeforeRenderObserver
  111. );
  112. this._onBeforeRenderObserver = null;
  113. }
  114. }
  115. onCurrentFrameChange(value: number) {
  116. if (!this._mainAnimatable) {
  117. return;
  118. }
  119. this._mainAnimatable.goToFrame(value);
  120. this.setState({ currentFrame: value });
  121. }
  122. onChangeFromOrTo() {
  123. this.playOrPause();
  124. if (this._isPlaying) {
  125. this.playOrPause();
  126. }
  127. }
  128. onOpenAnimationCurveEditor() {
  129. this._isCurveEditorOpen = true;
  130. }
  131. onCloseAnimationCurveEditor(window: Window | null) {
  132. this._isCurveEditorOpen = false;
  133. if (window === null) {
  134. console.log('Window already closed');
  135. } else {
  136. window.close();
  137. }
  138. }
  139. render() {
  140. const animatable = this.props.animatable;
  141. const animatableAsAny = this.props.animatable as any;
  142. let animatablesForTarget = this.props.scene.getAllAnimatablesByTarget(
  143. animatable
  144. );
  145. this._isPlaying = animatablesForTarget.length > 0;
  146. if (this._isPlaying && !this._mainAnimatable) {
  147. this._mainAnimatable = animatablesForTarget[0];
  148. if (this._mainAnimatable) {
  149. this._animationControl.from = this._mainAnimatable.fromFrame;
  150. this._animationControl.to = this._mainAnimatable.toFrame;
  151. this._animationControl.loop = this._mainAnimatable.loopAnimation;
  152. }
  153. }
  154. let animations = animatable.animations;
  155. return (
  156. <div>
  157. {this._ranges.length > 0 && (
  158. <LineContainerComponent
  159. globalState={this.props.globalState}
  160. title='ANIMATION RANGES'
  161. >
  162. {this._ranges.map((range, i) => {
  163. return (
  164. <ButtonLineComponent
  165. key={range.name + i}
  166. label={range.name}
  167. onClick={() => {
  168. this._mainAnimatable = null;
  169. this.props.scene.beginAnimation(
  170. animatable,
  171. range.from,
  172. range.to,
  173. true
  174. );
  175. }}
  176. />
  177. );
  178. })}
  179. </LineContainerComponent>
  180. )}
  181. {animations && (
  182. <>
  183. <LineContainerComponent
  184. globalState={this.props.globalState}
  185. title='ANIMATIONS'
  186. >
  187. <TextLineComponent
  188. label='Count'
  189. value={animations.length.toString()}
  190. />
  191. <ButtonLineComponent
  192. label='Edit'
  193. onClick={() => this.onOpenAnimationCurveEditor()}
  194. />
  195. {animations.map((anim, i) => {
  196. return (
  197. <TextLineComponent
  198. key={anim.targetProperty + i}
  199. label={'#' + i + ' >'}
  200. value={anim.targetProperty}
  201. />
  202. );
  203. })}
  204. {this._isCurveEditorOpen && (
  205. <PopupComponent
  206. id='curve-editor'
  207. title='Curve Animation Editor'
  208. size={{ width: 1024, height: 490 }}
  209. onOpen={(window: Window) => {}}
  210. onClose={(window: Window) =>
  211. this.onCloseAnimationCurveEditor(window)
  212. }
  213. >
  214. <AnimationCurveEditorComponent
  215. scene={this.props.scene}
  216. entity={animatableAsAny}
  217. close={(event) =>
  218. this.onCloseAnimationCurveEditor(event.view)
  219. }
  220. lockObject={this.props.lockObject}
  221. playOrPause={() => this.playOrPause()}
  222. />
  223. </PopupComponent>
  224. )}
  225. </LineContainerComponent>
  226. {animations.length > 0 && (
  227. <LineContainerComponent
  228. globalState={this.props.globalState}
  229. title='ANIMATION GENERAL CONTROL'
  230. >
  231. <FloatLineComponent
  232. lockObject={this.props.lockObject}
  233. isInteger={true}
  234. label='From'
  235. target={this._animationControl}
  236. propertyName='from'
  237. onChange={() => this.onChangeFromOrTo()}
  238. />
  239. <FloatLineComponent
  240. lockObject={this.props.lockObject}
  241. isInteger={true}
  242. label='To'
  243. target={this._animationControl}
  244. propertyName='to'
  245. onChange={() => this.onChangeFromOrTo()}
  246. />
  247. <CheckBoxLineComponent
  248. label='Loop'
  249. onSelect={(value) => (this._animationControl.loop = value)}
  250. isSelected={() => this._animationControl.loop}
  251. />
  252. {this._isPlaying && (
  253. <SliderLineComponent
  254. ref={this.timelineRef}
  255. label='Current frame'
  256. minimum={this._animationControl.from}
  257. maximum={this._animationControl.to}
  258. step={
  259. (this._animationControl.to -
  260. this._animationControl.from) /
  261. 1000.0
  262. }
  263. directValue={this.state.currentFrame}
  264. onInput={(value) => this.onCurrentFrameChange(value)}
  265. />
  266. )}
  267. <ButtonLineComponent
  268. label={this._isPlaying ? 'Stop' : 'Play'}
  269. onClick={() => this.playOrPause()}
  270. />
  271. {(this._ranges.length > 0 ||
  272. (this._animations && this._animations.length > 0)) && (
  273. <>
  274. <CheckBoxLineComponent
  275. label='Enable override'
  276. onSelect={(value) => {
  277. if (value) {
  278. animatableAsAny.animationPropertiesOverride = new AnimationPropertiesOverride();
  279. animatableAsAny.animationPropertiesOverride.blendingSpeed = 0.05;
  280. } else {
  281. animatableAsAny.animationPropertiesOverride = null;
  282. }
  283. this.forceUpdate();
  284. }}
  285. isSelected={() =>
  286. animatableAsAny.animationPropertiesOverride != null
  287. }
  288. onValueChanged={() => this.forceUpdate()}
  289. />
  290. {animatableAsAny.animationPropertiesOverride != null && (
  291. <div>
  292. <CheckBoxLineComponent
  293. label='Enable blending'
  294. target={animatableAsAny.animationPropertiesOverride}
  295. propertyName='enableBlending'
  296. onPropertyChangedObservable={
  297. this.props.onPropertyChangedObservable
  298. }
  299. />
  300. <SliderLineComponent
  301. label='Blending speed'
  302. target={animatableAsAny.animationPropertiesOverride}
  303. propertyName='blendingSpeed'
  304. minimum={0}
  305. maximum={0.1}
  306. step={0.01}
  307. onPropertyChangedObservable={
  308. this.props.onPropertyChangedObservable
  309. }
  310. />
  311. </div>
  312. )}
  313. </>
  314. )}
  315. </LineContainerComponent>
  316. )}
  317. </>
  318. )}
  319. </div>
  320. );
  321. }
  322. }