renderingZone.tsx 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286
  1. import * as React from "react";
  2. import { GlobalState } from "../globalState";
  3. import { Engine } from "babylonjs/Engines/engine";
  4. import { SceneLoader } from "babylonjs/Loading/sceneLoader";
  5. import { GLTFFileLoader } from "babylonjs-loaders/glTF/glTFFileLoader";
  6. import { Scene } from "babylonjs/scene";
  7. import { Vector3 } from "babylonjs/Maths/math.vector";
  8. import { ArcRotateCamera } from "babylonjs/Cameras/arcRotateCamera";
  9. import { FramingBehavior } from "babylonjs/Behaviors/Cameras/framingBehavior";
  10. import { EnvironmentTools } from "../tools/environmentTools";
  11. import { Tools } from "babylonjs/Misc/tools";
  12. import { FilesInput } from "babylonjs/Misc/filesInput";
  13. import { Animation } from "babylonjs/Animations/animation";
  14. require("../scss/renderingZone.scss");
  15. interface IRenderingZoneProps {
  16. globalState: GlobalState;
  17. assetUrl?: string;
  18. cameraPosition?: Vector3;
  19. expanded: boolean;
  20. }
  21. export class RenderingZone extends React.Component<IRenderingZoneProps> {
  22. private _currentPluginName: string;
  23. private _engine: Engine;
  24. private _scene: Scene;
  25. private _canvas: HTMLCanvasElement;
  26. public constructor(props: IRenderingZoneProps) {
  27. super(props);
  28. }
  29. initEngine() {
  30. this._canvas = document.getElementById("renderCanvas") as HTMLCanvasElement;
  31. this._engine = new Engine(this._canvas, true, { premultipliedAlpha: false, preserveDrawingBuffer: true });
  32. this._engine.loadingUIBackgroundColor = "#2A2342";
  33. // Resize
  34. window.addEventListener("resize", () => {
  35. this._engine.resize();
  36. });
  37. this.loadAsset();
  38. // File inputs
  39. let filesInput = new FilesInput(
  40. this._engine,
  41. null,
  42. (sceneFile: File, scene: Scene) => {
  43. this._scene = scene;
  44. this.onSceneLoaded(sceneFile.name);
  45. },
  46. null,
  47. null,
  48. null,
  49. () => {
  50. Tools.ClearLogCache();
  51. if (this._scene) {
  52. this.props.globalState.isDebugLayerEnabled = this.props.globalState.currentScene.debugLayer.isVisible();
  53. if (this.props.globalState.isDebugLayerEnabled) {
  54. this._scene.debugLayer.hide();
  55. }
  56. }
  57. },
  58. null,
  59. (file, scene, message) => {
  60. this.props.globalState.onError.notifyObservers({ message: message });
  61. }
  62. );
  63. filesInput.onProcessFileCallback = (file, name, extension) => {
  64. if (filesInput.filesToLoad && filesInput.filesToLoad.length === 1 && extension) {
  65. if (extension.toLowerCase() === "dds" || extension.toLowerCase() === "env" || extension.toLowerCase() === "hdr") {
  66. FilesInput.FilesToLoad[name] = file;
  67. EnvironmentTools.SkyboxPath = "file:" + (file as any).correctName;
  68. return false;
  69. }
  70. }
  71. return true;
  72. };
  73. filesInput.monitorElementForDragNDrop(this._canvas);
  74. this.props.globalState.filesInput = filesInput;
  75. window.addEventListener("keydown", (event) => {
  76. // Press R to reload
  77. if (event.keyCode === 82 && event.target && (event.target as HTMLElement).nodeName !== "INPUT" && this._scene) {
  78. if (this.props.assetUrl) {
  79. this.loadAssetFromUrl();
  80. } else {
  81. filesInput.reload();
  82. }
  83. }
  84. });
  85. }
  86. prepareCamera() {
  87. let camera: ArcRotateCamera;
  88. // Attach camera to canvas inputs
  89. if (!this._scene.activeCamera || this._scene.lights.length === 0) {
  90. this._scene.createDefaultCamera(true);
  91. camera = this._scene.activeCamera! as ArcRotateCamera;
  92. if (this.props.cameraPosition) {
  93. camera.setPosition(this.props.cameraPosition);
  94. } else {
  95. if (this._currentPluginName === "gltf") {
  96. // glTF assets use a +Z forward convention while the default camera faces +Z. Rotate the camera to look at the front of the asset.
  97. camera.alpha += Math.PI;
  98. }
  99. // Enable camera's behaviors
  100. camera.useFramingBehavior = true;
  101. var framingBehavior = camera.getBehaviorByName("Framing") as FramingBehavior;
  102. framingBehavior.framingTime = 0;
  103. framingBehavior.elevationReturnTime = -1;
  104. if (this._scene.meshes.length) {
  105. camera.lowerRadiusLimit = null;
  106. var worldExtends = this._scene.getWorldExtends(function (mesh) {
  107. return mesh.isVisible && mesh.isEnabled();
  108. });
  109. framingBehavior.zoomOnBoundingInfo(worldExtends.min, worldExtends.max);
  110. }
  111. }
  112. camera.pinchPrecision = 200 / camera.radius;
  113. camera.upperRadiusLimit = 5 * camera.radius;
  114. camera.wheelDeltaPercentage = 0.01;
  115. camera.pinchDeltaPercentage = 0.01;
  116. }
  117. this._scene.activeCamera!.attachControl();
  118. }
  119. handleErrors() {
  120. // In case of error during loading, meshes will be empty and clearColor is set to red
  121. if (this._scene.meshes.length === 0 && this._scene.clearColor.r === 1 && this._scene.clearColor.g === 0 && this._scene.clearColor.b === 0) {
  122. this._canvas.style.opacity = "0";
  123. this.props.globalState.onError.notifyObservers({ scene: this._scene, message: "No mesh found in your scene" });
  124. } else {
  125. if (Tools.errorsCount > 0) {
  126. this.props.globalState.onError.notifyObservers({ scene: this._scene, message: "Scene loaded but several errors were found" });
  127. }
  128. // this._canvas.style.opacity = "1";
  129. let camera = this._scene.activeCamera! as ArcRotateCamera;
  130. if (camera.keysUp) {
  131. camera.keysUp.push(90); // Z
  132. camera.keysUp.push(87); // W
  133. camera.keysDown.push(83); // S
  134. camera.keysLeft.push(65); // A
  135. camera.keysLeft.push(81); // Q
  136. camera.keysRight.push(69); // E
  137. camera.keysRight.push(68); // D
  138. }
  139. }
  140. }
  141. prepareLighting() {
  142. if (this._currentPluginName === "gltf") {
  143. if (!this._scene.environmentTexture) {
  144. this._scene.environmentTexture = EnvironmentTools.LoadSkyboxPathTexture(this._scene);
  145. }
  146. if (this._scene.environmentTexture) {
  147. this._scene.createDefaultSkybox(this._scene.environmentTexture, true, (this._scene.activeCamera!.maxZ - this._scene.activeCamera!.minZ) / 2, 0.3, false);
  148. }
  149. } else {
  150. var pbrPresent = false;
  151. for (var i = 0; i < this._scene.materials.length; i++) {
  152. if (this._scene.materials[i].transparencyMode !== undefined) {
  153. pbrPresent = true;
  154. break;
  155. }
  156. }
  157. if (pbrPresent) {
  158. if (!this._scene.environmentTexture) {
  159. this._scene.environmentTexture = EnvironmentTools.LoadSkyboxPathTexture(this._scene);
  160. }
  161. } else {
  162. this._scene.createDefaultLight();
  163. }
  164. }
  165. }
  166. onSceneLoaded(filename: string) {
  167. this._engine.clearInternalTexturesCache();
  168. this._scene.skipFrustumClipping = true;
  169. this.props.globalState.onSceneLoaded.notifyObservers({ scene: this._scene, filename: filename });
  170. this.prepareCamera();
  171. this.prepareLighting();
  172. this.handleErrors();
  173. if (this.props.globalState.isDebugLayerEnabled) {
  174. this.props.globalState.showDebugLayer();
  175. }
  176. }
  177. loadAssetFromUrl() {
  178. let assetUrl = this.props.assetUrl!;
  179. let rootUrl = Tools.GetFolderPath(assetUrl);
  180. let fileName = Tools.GetFilename(assetUrl);
  181. SceneLoader.LoadAsync(rootUrl, fileName, this._engine)
  182. .then((scene) => {
  183. if (this._scene) {
  184. this._scene.dispose();
  185. }
  186. this._scene = scene;
  187. this.onSceneLoaded(fileName);
  188. scene.whenReadyAsync().then(() => {
  189. this._engine.runRenderLoop(() => {
  190. scene.render();
  191. });
  192. });
  193. })
  194. .catch((reason) => {
  195. this.props.globalState.onError.notifyObservers({ message: reason.message });
  196. //TODO sceneError({ name: fileName }, null, reason.message || reason);
  197. });
  198. }
  199. loadAsset() {
  200. if (this.props.assetUrl) {
  201. this.loadAssetFromUrl();
  202. return;
  203. }
  204. }
  205. componentDidMount() {
  206. if (!Engine.isSupported()) {
  207. return;
  208. }
  209. Engine.ShadersRepository = "/src/Shaders/";
  210. // This is really important to tell Babylon.js to use decomposeLerp and matrix interpolation
  211. Animation.AllowMatricesInterpolation = true;
  212. // Setting up some GLTF values
  213. GLTFFileLoader.IncrementalLoading = false;
  214. SceneLoader.OnPluginActivatedObservable.add((plugin) => {
  215. this._currentPluginName = plugin.name;
  216. if (this._currentPluginName === "gltf") {
  217. (plugin as GLTFFileLoader).onValidatedObservable.add((results) => {
  218. if (results.issues.numErrors > 0) {
  219. this.props.globalState.showDebugLayer();
  220. }
  221. });
  222. }
  223. });
  224. this.initEngine();
  225. }
  226. shouldComponentUpdate(nextProps: IRenderingZoneProps) {
  227. if (nextProps.expanded !== this.props.expanded) {
  228. setTimeout(() => this._engine.resize());
  229. return true;
  230. }
  231. return false;
  232. }
  233. public render() {
  234. return (
  235. <div id="canvasZone" className={this.props.expanded ? "expanded" : ""}>
  236. <canvas id="renderCanvas" touch-action="none" onContextMenu={(evt) => evt.preventDefault()}></canvas>
  237. </div>
  238. );
  239. }
  240. }