rendererComponent.tsx 13 KB

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