rendererComponent.tsx 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269
  1. import * as React from "react";
  2. import { GlobalState, RuntimeMode } from "../globalState";
  3. import { Engine } from "babylonjs/Engines/engine";
  4. import { Nullable } from "babylonjs/types";
  5. import { Scene } from "babylonjs/scene";
  6. import { Utilities } from "../tools/utilities";
  7. import { DownloadManager } from "../tools/downloadManager";
  8. require("../scss/rendering.scss");
  9. interface IRenderingComponentProps {
  10. globalState: GlobalState;
  11. }
  12. export class RenderingComponent extends React.Component<IRenderingComponentProps> {
  13. private _engine: Nullable<Engine>;
  14. private _scene: Nullable<Scene>;
  15. private _canvasRef: React.RefObject<HTMLCanvasElement>;
  16. private _downloadManager: DownloadManager;
  17. public constructor(props: IRenderingComponentProps) {
  18. super(props);
  19. this._canvasRef = React.createRef();
  20. // Create the global handleException
  21. (window as any).handleException = (e: Error) => {
  22. console.error(e);
  23. this.props.globalState.onErrorObservable.notifyObservers(e);
  24. };
  25. this.props.globalState.onRunRequiredObservable.add(() => {
  26. this._compileAndRunAsync();
  27. });
  28. this._downloadManager = new DownloadManager(this.props.globalState);
  29. this.props.globalState.onDownloadRequiredObservable.add(() => {
  30. if (!this._engine) {
  31. return;
  32. }
  33. this._downloadManager.download(this._engine);
  34. });
  35. this.props.globalState.onInspectorRequiredObservable.add((state) => {
  36. if (!this._scene) {
  37. return;
  38. }
  39. if (state) {
  40. this._scene.debugLayer.show({
  41. embedMode: true,
  42. });
  43. } else {
  44. this._scene.debugLayer.hide();
  45. }
  46. this.props.globalState.inspectorIsOpened = state;
  47. });
  48. this.props.globalState.onFullcreenRequiredObservable.add(() => {
  49. this._engine?.switchFullscreen(false);
  50. });
  51. if (this.props.globalState.runtimeMode !== RuntimeMode.Editor) {
  52. this.props.globalState.onCodeLoaded.add((code) => {
  53. this.props.globalState.currentCode = code;
  54. this.props.globalState.onRunRequiredObservable.notifyObservers();
  55. });
  56. }
  57. window.addEventListener("resize", () => {
  58. if (!this._engine) {
  59. return;
  60. }
  61. this._engine.resize();
  62. });
  63. }
  64. private async _compileAndRunAsync() {
  65. this.props.globalState.onDisplayWaitRingObservable.notifyObservers(false);
  66. this.props.globalState.onErrorObservable.notifyObservers(null);
  67. const displayInspector = this._scene?.debugLayer.isVisible();
  68. if (this._engine) {
  69. try {
  70. this._engine.dispose();
  71. } catch (ex) {
  72. // just ignore
  73. }
  74. this._engine = null;
  75. }
  76. try {
  77. let globalObject = window as any;
  78. let canvas = this._canvasRef.current!;
  79. globalObject.canvas = canvas;
  80. globalObject.createDefaultEngine = function () {
  81. return new Engine(canvas, true, {
  82. preserveDrawingBuffer: true,
  83. stencil: true,
  84. });
  85. };
  86. let zipVariables = "var engine = null;\r\nvar scene = null;\r\nvar sceneToRender = null;\r\n";
  87. let defaultEngineZip = "var createDefaultEngine = function() { return new BABYLON.Engine(canvas, true, { preserveDrawingBuffer: true, stencil: true }); }";
  88. let code = await this.props.globalState.getCompiledCode();
  89. if (!code) {
  90. return;
  91. }
  92. let createEngineFunction = "createDefaultEngine";
  93. let createSceneFunction = "";
  94. let checkCamera = true;
  95. let checkSceneCount = true;
  96. if (code.indexOf("createEngine") !== -1) {
  97. createEngineFunction = "createEngine";
  98. }
  99. // Check for different typos
  100. if (code.indexOf("delayCreateScene") !== -1) {
  101. // delayCreateScene
  102. createSceneFunction = "delayCreateScene";
  103. checkCamera = false;
  104. } else if (code.indexOf("createScene") !== -1) {
  105. // createScene
  106. createSceneFunction = "createScene";
  107. } else if (code.indexOf("CreateScene") !== -1) {
  108. // CreateScene
  109. createSceneFunction = "CreateScene";
  110. } else if (code.indexOf("createscene") !== -1) {
  111. // createscene
  112. createSceneFunction = "createscene";
  113. }
  114. if (!createSceneFunction) {
  115. this._engine = globalObject.createDefaultEngine() as Engine;
  116. this._scene = new Scene(this._engine);
  117. globalObject.engine = this._engine;
  118. globalObject.scene = this._scene;
  119. let runScript: any = null;
  120. Utilities.FastEval("runScript = function(scene, canvas) {" + code + "}");
  121. runScript(this._scene, canvas);
  122. this.props.globalState.zipCode = zipVariables + defaultEngineZip + "var engine = createDefaultEngine();" + ";\r\nvar scene = new BABYLON.Scene(engine);\r\n\r\n" + code;
  123. } else {
  124. code += `
  125. var engine;
  126. try {
  127. engine = ${createEngineFunction}();
  128. } catch(e) {
  129. console.log("the available createEngine function failed. Creating the default engine instead");
  130. engine = createDefaultEngine();
  131. }`;
  132. code += "\r\nif (!engine) throw 'engine should not be null.';";
  133. if (this.props.globalState.language === "JS") {
  134. code += "\r\n" + "scene = " + createSceneFunction + "();";
  135. } else {
  136. var startCar = code.search("var " + createSceneFunction);
  137. code = code.substr(0, startCar) + code.substr(startCar + 4);
  138. code += "\n" + "scene = " + createSceneFunction + "();";
  139. }
  140. // Execute the code
  141. Utilities.FastEval(code);
  142. this._engine = globalObject.engine;
  143. if (!this._engine) {
  144. this.props.globalState.onErrorObservable.notifyObservers({
  145. message: "createEngine function must return an engine.",
  146. });
  147. return;
  148. }
  149. if (!globalObject.scene) {
  150. this.props.globalState.onErrorObservable.notifyObservers({
  151. message: createSceneFunction + " function must return a scene.",
  152. });
  153. return;
  154. }
  155. let sceneToRenderCode = "sceneToRender = scene";
  156. // if scene returns a promise avoid checks
  157. if (globalObject.scene.then) {
  158. checkCamera = false;
  159. checkSceneCount = false;
  160. sceneToRenderCode = "scene.then(returnedScene => { sceneToRender = returnedScene; });\r\n";
  161. }
  162. let createEngineZip = createEngineFunction === "createEngine" ? zipVariables : zipVariables + defaultEngineZip;
  163. this.props.globalState.zipCode = createEngineZip + ";\r\n" + code + ";\r\n" + sceneToRenderCode;
  164. }
  165. if (globalObject.scene.then) {
  166. globalObject.scene.then((s: Scene) => {
  167. this._scene = s;
  168. globalObject.scene = this._scene;
  169. });
  170. } else {
  171. this._scene = globalObject.scene as Scene;
  172. }
  173. this._engine.runRenderLoop(() => {
  174. if (!this._scene || !this._engine) {
  175. return;
  176. }
  177. if (this.props.globalState.runtimeMode === RuntimeMode.Editor && window.innerWidth > this.props.globalState.MobileSizeTrigger) {
  178. if (canvas.width !== canvas.clientWidth || canvas.height !== canvas.clientHeight) {
  179. this._engine.resize();
  180. }
  181. }
  182. if (this._scene.activeCamera || this._scene.activeCameras.length > 0) {
  183. this._scene.render();
  184. }
  185. // Update FPS if camera is not a webxr camera
  186. if (!(this._scene.activeCamera && this._scene.activeCamera.getClassName && this._scene.activeCamera.getClassName() === "WebXRCamera")) {
  187. if (this.props.globalState.runtimeMode !== RuntimeMode.Full) {
  188. this.props.globalState.fpsElement.innerHTML = this._engine.getFps().toFixed() + " fps";
  189. }
  190. }
  191. });
  192. if (checkSceneCount && this._engine.scenes.length === 0) {
  193. this.props.globalState.onErrorObservable.notifyObservers({
  194. message: "You must at least create a scene.",
  195. });
  196. return;
  197. }
  198. if (this._engine.scenes[0] && displayInspector) {
  199. this.props.globalState.onInspectorRequiredObservable.notifyObservers(true);
  200. }
  201. if (checkCamera && this._engine.scenes[0].activeCamera == null) {
  202. this.props.globalState.onErrorObservable.notifyObservers({
  203. message: "You must at least create a camera.",
  204. });
  205. return;
  206. } else if (globalObject.scene.then) {
  207. globalObject.scene.then(() => {
  208. if (this._engine!.scenes[0] && displayInspector) {
  209. this.props.globalState.onInspectorRequiredObservable.notifyObservers(true);
  210. }
  211. });
  212. } else {
  213. this._engine.scenes[0].executeWhenReady(function () {});
  214. }
  215. } catch (err) {
  216. this.props.globalState.onErrorObservable.notifyObservers(err);
  217. }
  218. }
  219. public render() {
  220. return <canvas id="renderCanvas" ref={this._canvasRef}></canvas>;
  221. }
  222. }