rendererComponent.tsx 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324
  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. import { WebGPUEngine } from "babylonjs";
  9. require("../scss/rendering.scss");
  10. interface IRenderingComponentProps {
  11. globalState: GlobalState;
  12. }
  13. export class RenderingComponent extends React.Component<IRenderingComponentProps> {
  14. private _engine: Nullable<Engine>;
  15. private _scene: Nullable<Scene>;
  16. private _canvasRef: React.RefObject<HTMLCanvasElement>;
  17. private _downloadManager: DownloadManager;
  18. private _unityToolkitWasLoaded = false;
  19. public constructor(props: IRenderingComponentProps) {
  20. super(props);
  21. this._canvasRef = React.createRef();
  22. // Create the global handleException
  23. (window as any).handleException = (e: Error) => {
  24. console.error(e);
  25. this.props.globalState.onErrorObservable.notifyObservers(e);
  26. };
  27. this.props.globalState.onRunRequiredObservable.add(() => {
  28. this._compileAndRunAsync();
  29. });
  30. this._downloadManager = new DownloadManager(this.props.globalState);
  31. this.props.globalState.onDownloadRequiredObservable.add(() => {
  32. if (!this._engine) {
  33. return;
  34. }
  35. this._downloadManager.download(this._engine);
  36. });
  37. this.props.globalState.onInspectorRequiredObservable.add((state) => {
  38. if (!this._scene) {
  39. return;
  40. }
  41. if (state) {
  42. this._scene.debugLayer.show({
  43. embedMode: true,
  44. });
  45. } else {
  46. this._scene.debugLayer.hide();
  47. }
  48. this.props.globalState.inspectorIsOpened = state;
  49. });
  50. this.props.globalState.onFullcreenRequiredObservable.add(() => {
  51. this._engine?.switchFullscreen(false);
  52. });
  53. if (this.props.globalState.runtimeMode !== RuntimeMode.Editor) {
  54. this.props.globalState.onCodeLoaded.add((code) => {
  55. this.props.globalState.currentCode = code;
  56. this.props.globalState.onRunRequiredObservable.notifyObservers();
  57. });
  58. }
  59. window.addEventListener("resize", () => {
  60. if (!this._engine) {
  61. return;
  62. }
  63. this._engine.resize();
  64. });
  65. }
  66. private async _loadScriptAsync(url: string): Promise<void> {
  67. return new Promise((resolve, reject) => {
  68. let script = document.createElement('script');
  69. script.src = url;
  70. script.onload = () => {
  71. resolve();
  72. };
  73. document.head.appendChild(script);
  74. });
  75. }
  76. private async _compileAndRunAsync() {
  77. this.props.globalState.onDisplayWaitRingObservable.notifyObservers(false);
  78. this.props.globalState.onErrorObservable.notifyObservers(null);
  79. const displayInspector = this._scene?.debugLayer.isVisible();
  80. let useWebGPU = location.href.indexOf("webgpu") !== -1 && WebGPUEngine.IsSupported;
  81. let forceWebGL1 = false;
  82. const configuredEngine = Utilities.ReadStringFromStore("engineVersion", "WebGL2");
  83. switch (configuredEngine) {
  84. case "WebGPU":
  85. useWebGPU = true;
  86. break;
  87. case "WebGL":
  88. forceWebGL1 = true;
  89. break;
  90. }
  91. if (this._engine) {
  92. try {
  93. this._engine.dispose();
  94. } catch (ex) {
  95. // just ignore
  96. }
  97. this._engine = null;
  98. }
  99. try {
  100. let globalObject = window as any;
  101. let canvas = this._canvasRef.current!;
  102. globalObject.canvas = canvas;
  103. if (useWebGPU) {
  104. globalObject.createDefaultEngine = async function() {
  105. var engine = new BABYLON.WebGPUEngine(canvas);
  106. await engine.initAsync();
  107. return engine;
  108. }
  109. } else {
  110. globalObject.createDefaultEngine = function () {
  111. return new Engine(canvas, true, {
  112. disableWebGL2Support: forceWebGL1,
  113. preserveDrawingBuffer: true,
  114. stencil: true,
  115. });
  116. };
  117. }
  118. let zipVariables = "var engine = null;\r\nvar scene = null;\r\nvar sceneToRender = null;\r\n";
  119. let defaultEngineZip = `var createDefaultEngine = function() { return new BABYLON.Engine(canvas, true, { preserveDrawingBuffer: true, stencil: true, disableWebGL2Support: ${forceWebGL1}}); }`;
  120. if (useWebGPU) {
  121. defaultEngineZip = `var createDefaultEngine = async function() {
  122. var engine = new BABYLON.WebGPUEngine(canvas);
  123. await engine.initAsync();
  124. return engine;
  125. }`;
  126. }
  127. let code = await this.props.globalState.getCompiledCode();
  128. if (!code) {
  129. return;
  130. }
  131. // Check for Unity Toolkit
  132. if ((location.href.indexOf("UnityToolkit") !== -1 || Utilities.ReadBoolFromStore("unity-toolkit", false)) && !this._unityToolkitWasLoaded) {
  133. await this._loadScriptAsync("/libs/babylon.manager.js");
  134. this._unityToolkitWasLoaded = true;
  135. }
  136. let createEngineFunction = "createDefaultEngine";
  137. let createSceneFunction = "";
  138. let checkCamera = true;
  139. let checkSceneCount = true;
  140. if (code.indexOf("createEngine") !== -1) {
  141. createEngineFunction = "createEngine";
  142. }
  143. // Check for different typos
  144. if (code.indexOf("delayCreateScene") !== -1) {
  145. // delayCreateScene
  146. createSceneFunction = "delayCreateScene";
  147. checkCamera = false;
  148. } else if (code.indexOf("createScene") !== -1) {
  149. // createScene
  150. createSceneFunction = "createScene";
  151. } else if (code.indexOf("CreateScene") !== -1) {
  152. // CreateScene
  153. createSceneFunction = "CreateScene";
  154. } else if (code.indexOf("createscene") !== -1) {
  155. // createscene
  156. createSceneFunction = "createscene";
  157. }
  158. if (!createSceneFunction) {
  159. this.props.globalState.onErrorObservable.notifyObservers({
  160. message: "You must provide a function named createScene.",
  161. });
  162. return;
  163. } else {
  164. code += `
  165. var engine;
  166. var scene;
  167. initFunction = async function() {
  168. var asyncEngineCreation = async function() {
  169. try {
  170. return ${createEngineFunction}();
  171. } catch(e) {
  172. console.log("the available createEngine function failed. Creating the default engine instead");
  173. return createDefaultEngine();
  174. }
  175. }
  176. engine = await asyncEngineCreation();`;
  177. code += "\r\nif (!engine) throw 'engine should not be null.';";
  178. if (this.props.globalState.language === "JS") {
  179. code += "\r\n" + "scene = " + createSceneFunction + "();";
  180. } else {
  181. var startCar = code.search("var " + createSceneFunction);
  182. code = code.substr(0, startCar) + code.substr(startCar + 4);
  183. code += "\n" + "scene = " + createSceneFunction + "();";
  184. }
  185. code += `}`;
  186. // Execute the code
  187. Utilities.FastEval(code);
  188. await globalObject.initFunction();
  189. this._engine = globalObject.engine;
  190. if (!this._engine) {
  191. this.props.globalState.onErrorObservable.notifyObservers({
  192. message: "createEngine function must return an engine.",
  193. });
  194. return;
  195. }
  196. if (!globalObject.scene) {
  197. this.props.globalState.onErrorObservable.notifyObservers({
  198. message: createSceneFunction + " function must return a scene.",
  199. });
  200. return;
  201. }
  202. let sceneToRenderCode = "sceneToRender = scene";
  203. // if scene returns a promise avoid checks
  204. if (globalObject.scene.then) {
  205. checkCamera = false;
  206. checkSceneCount = false;
  207. sceneToRenderCode = "scene.then(returnedScene => { sceneToRender = returnedScene; });\r\n";
  208. }
  209. let createEngineZip = createEngineFunction === "createEngine" ? zipVariables : zipVariables + defaultEngineZip;
  210. this.props.globalState.zipCode = createEngineZip + ";\r\n" + code + ";\r\n" + sceneToRenderCode;
  211. }
  212. if (globalObject.scene.then) {
  213. globalObject.scene.then((s: Scene) => {
  214. this._scene = s;
  215. globalObject.scene = this._scene;
  216. });
  217. } else {
  218. this._scene = globalObject.scene as Scene;
  219. }
  220. this._engine.runRenderLoop(() => {
  221. if (!this._scene || !this._engine) {
  222. return;
  223. }
  224. if (this.props.globalState.runtimeMode === RuntimeMode.Editor && window.innerWidth > this.props.globalState.MobileSizeTrigger) {
  225. if (canvas.width !== canvas.clientWidth || canvas.height !== canvas.clientHeight) {
  226. this._engine.resize();
  227. }
  228. }
  229. if (this._scene.activeCamera || this._scene.activeCameras && this._scene.activeCameras.length > 0) {
  230. this._scene.render();
  231. }
  232. // Update FPS if camera is not a webxr camera
  233. if (!(this._scene.activeCamera && this._scene.activeCamera.getClassName && this._scene.activeCamera.getClassName() === "WebXRCamera")) {
  234. if (this.props.globalState.runtimeMode !== RuntimeMode.Full) {
  235. this.props.globalState.fpsElement.innerHTML = this._engine.getFps().toFixed() + " fps";
  236. }
  237. }
  238. });
  239. if (checkSceneCount && this._engine.scenes.length === 0) {
  240. this.props.globalState.onErrorObservable.notifyObservers({
  241. message: "You must at least create a scene.",
  242. });
  243. return;
  244. }
  245. if (this._engine.scenes[0] && displayInspector) {
  246. this.props.globalState.onInspectorRequiredObservable.notifyObservers(true);
  247. }
  248. if (checkCamera && this._engine.scenes[0].activeCamera == null) {
  249. this.props.globalState.onErrorObservable.notifyObservers({
  250. message: "You must at least create a camera.",
  251. });
  252. return;
  253. } else if (globalObject.scene.then) {
  254. globalObject.scene.then(() => {
  255. if (this._engine!.scenes[0] && displayInspector) {
  256. this.props.globalState.onInspectorRequiredObservable.notifyObservers(true);
  257. }
  258. });
  259. } else {
  260. this._engine.scenes[0].executeWhenReady(() => {
  261. this.props.globalState.onRunExecutedObservable.notifyObservers();
  262. });
  263. }
  264. } catch (err) {
  265. this.props.globalState.onErrorObservable.notifyObservers(err);
  266. }
  267. }
  268. public render() {
  269. return <canvas id="renderCanvas" ref={this._canvasRef}></canvas>;
  270. }
  271. }