renderingZone.tsx 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285
  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(this._engine, null,
  40. (sceneFile: File, scene: Scene) => {
  41. this._scene = scene;
  42. this.onSceneLoaded(sceneFile.name);
  43. },
  44. null, null, null,
  45. () => {
  46. Tools.ClearLogCache();
  47. if (this._scene) {
  48. this.props.globalState.isDebugLayerEnabled = this.props.globalState.currentScene.debugLayer.isVisible();
  49. if (this.props.globalState.isDebugLayerEnabled) {
  50. this._scene.debugLayer.hide();
  51. }
  52. }
  53. }, null, (file, scene, message) => {
  54. this.props.globalState.onError.notifyObservers({ message : message});
  55. });
  56. filesInput.onProcessFileCallback = (file, name, extension) => {
  57. if (filesInput.filesToLoad && filesInput.filesToLoad.length === 1 && extension) {
  58. if (extension.toLowerCase() === "dds" ||
  59. extension.toLowerCase() === "env" ||
  60. extension.toLowerCase() === "hdr") {
  61. FilesInput.FilesToLoad[name] = file;
  62. EnvironmentTools.SkyboxPath = "file:" + (file as any).correctName;
  63. return false;
  64. }
  65. }
  66. return true;
  67. };
  68. filesInput.monitorElementForDragNDrop(this._canvas);
  69. this.props.globalState.filesInput = filesInput;
  70. window.addEventListener("keydown", (event) => {
  71. // Press R to reload
  72. if (event.keyCode === 82 && event.target && (event.target as HTMLElement).nodeName !== "INPUT" && this._scene) {
  73. if (this.props.assetUrl) {
  74. this.loadAssetFromUrl();
  75. }
  76. else {
  77. filesInput.reload();
  78. }
  79. }
  80. });
  81. }
  82. prepareCamera() {
  83. let camera: ArcRotateCamera;
  84. // Attach camera to canvas inputs
  85. if (!this._scene.activeCamera || this._scene.lights.length === 0) {
  86. this._scene.createDefaultCamera(true);
  87. camera = this._scene.activeCamera! as ArcRotateCamera;
  88. if (this.props.cameraPosition) {
  89. camera.setPosition(this.props.cameraPosition);
  90. }
  91. else {
  92. if (this._currentPluginName === "gltf") {
  93. // glTF assets use a +Z forward convention while the default camera faces +Z. Rotate the camera to look at the front of the asset.
  94. camera.alpha += Math.PI;
  95. }
  96. // Enable camera's behaviors
  97. camera.useFramingBehavior = true;
  98. var framingBehavior = camera.getBehaviorByName("Framing") as FramingBehavior;
  99. framingBehavior.framingTime = 0;
  100. framingBehavior.elevationReturnTime = -1;
  101. if (this._scene.meshes.length) {
  102. camera.lowerRadiusLimit = null;
  103. var worldExtends = this._scene.getWorldExtends(function (mesh) {
  104. return mesh.isVisible && mesh.isEnabled();
  105. });
  106. framingBehavior.zoomOnBoundingInfo(worldExtends.min, worldExtends.max);
  107. }
  108. }
  109. camera.pinchPrecision = 200 / camera.radius;
  110. camera.upperRadiusLimit = 5 * camera.radius;
  111. camera.wheelDeltaPercentage = 0.01;
  112. camera.pinchDeltaPercentage = 0.01;
  113. }
  114. this._scene.activeCamera!.attachControl(this._canvas);
  115. }
  116. handleErrors() {
  117. // In case of error during loading, meshes will be empty and clearColor is set to red
  118. if (this._scene.meshes.length === 0 && this._scene.clearColor.r === 1 && this._scene.clearColor.g === 0 && this._scene.clearColor.b === 0) {
  119. this._canvas.style.opacity = "0";
  120. this.props.globalState.onError.notifyObservers({scene: this._scene, message: "No mesh found in your scene"});
  121. }
  122. else {
  123. if (Tools.errorsCount > 0) {
  124. this.props.globalState.onError.notifyObservers({scene: this._scene, message: "Scene loaded but several errors were found"});
  125. }
  126. // this._canvas.style.opacity = "1";
  127. let camera = this._scene.activeCamera! as ArcRotateCamera;
  128. if (camera.keysUp) {
  129. camera.keysUp.push(90); // Z
  130. camera.keysUp.push(87); // W
  131. camera.keysDown.push(83); // S
  132. camera.keysLeft.push(65); // A
  133. camera.keysLeft.push(81); // Q
  134. camera.keysRight.push(69); // E
  135. camera.keysRight.push(68); // D
  136. }
  137. }
  138. }
  139. prepareLighting() {
  140. if (this._currentPluginName === "gltf") {
  141. if (!this._scene.environmentTexture) {
  142. this._scene.environmentTexture = EnvironmentTools.LoadSkyboxPathTexture(this._scene);
  143. }
  144. if (this._scene.environmentTexture) {
  145. this._scene.createDefaultSkybox(this._scene.environmentTexture, true, (this._scene.activeCamera!.maxZ - this._scene.activeCamera!.minZ) / 2, 0.3, false);
  146. }
  147. }
  148. else {
  149. var pbrPresent = false;
  150. for (var i = 0; i < this._scene.materials.length; i++) {
  151. if (this._scene.materials[i].transparencyMode !== undefined) {
  152. pbrPresent = true;
  153. break;
  154. }
  155. }
  156. if (pbrPresent) {
  157. if (!this._scene.environmentTexture) {
  158. this._scene.environmentTexture = EnvironmentTools.LoadSkyboxPathTexture(this._scene);
  159. }
  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).then((scene) => {
  182. if (this._scene) {
  183. this._scene.dispose();
  184. }
  185. this._scene = scene;
  186. this.onSceneLoaded(fileName);
  187. scene.whenReadyAsync().then(() => {
  188. this._engine.runRenderLoop(() => {
  189. scene.render();
  190. });
  191. });
  192. }).catch((reason) => {
  193. this.props.globalState.onError.notifyObservers({ message : reason.message});
  194. //TODO sceneError({ name: fileName }, null, reason.message || reason);
  195. });
  196. }
  197. loadAsset() {
  198. if (this.props.assetUrl) {
  199. this.loadAssetFromUrl();
  200. return;
  201. }
  202. }
  203. componentDidMount() {
  204. if (!Engine.isSupported()) {
  205. return;
  206. }
  207. Engine.ShadersRepository = "/src/Shaders/";
  208. // This is really important to tell Babylon.js to use decomposeLerp and matrix interpolation
  209. Animation.AllowMatricesInterpolation = true;
  210. // Setting up some GLTF values
  211. GLTFFileLoader.IncrementalLoading = false;
  212. SceneLoader.OnPluginActivatedObservable.add((plugin) =>{
  213. this._currentPluginName = plugin.name;
  214. if (this._currentPluginName === "gltf") {
  215. (plugin as GLTFFileLoader).onValidatedObservable.add((results) =>{
  216. if (results.issues.numErrors > 0) {
  217. this.props.globalState.showDebugLayer();
  218. }
  219. });
  220. }
  221. });
  222. this.initEngine();
  223. }
  224. shouldComponentUpdate(nextProps: IRenderingZoneProps) {
  225. if (nextProps.expanded !== this.props.expanded) {
  226. setTimeout(() => this._engine.resize());
  227. return true;
  228. }
  229. return false;
  230. }
  231. public render() {
  232. return (
  233. <div id="canvasZone" className={this.props.expanded ? "expanded" : ""}>
  234. <canvas id="renderCanvas" touch-action="none"
  235. onContextMenu={evt => evt.preventDefault()}></canvas>
  236. </div>
  237. )
  238. }
  239. }