123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361 |
- import * as React from "react";
- import { GlobalState } from "../globalState";
- import { Engine } from "babylonjs/Engines/engine";
- import { SceneLoader } from "babylonjs/Loading/sceneLoader";
- import { GLTFFileLoader } from "babylonjs-loaders/glTF/glTFFileLoader";
- import { Scene } from "babylonjs/scene";
- import { Vector3 } from "babylonjs/Maths/math.vector";
- import { ArcRotateCamera } from "babylonjs/Cameras/arcRotateCamera";
- import { FramingBehavior } from "babylonjs/Behaviors/Cameras/framingBehavior";
- import { EnvironmentTools } from "../tools/environmentTools";
- import { Tools } from "babylonjs/Misc/tools";
- import { FilesInput } from "babylonjs/Misc/filesInput";
- import { Animation } from "babylonjs/Animations/animation";
- import { PBRBaseMaterial, PBRMaterial, StringTools, Texture } from "babylonjs";
- import { Mesh } from "babylonjs/Meshes/mesh";
- require("../scss/renderingZone.scss");
- interface IRenderingZoneProps {
- globalState: GlobalState;
- assetUrl?: string;
- cameraPosition?: Vector3;
- expanded: boolean;
- }
- function isTextureAsset(name: string): boolean {
- return (
- StringTools.EndsWith(name, ".ktx") ||
- StringTools.EndsWith(name, ".ktx2") ||
- StringTools.EndsWith(name, ".png") ||
- StringTools.EndsWith(name, ".jpg") ||
- StringTools.EndsWith(name, ".jpeg")
- );
- }
- export class RenderingZone extends React.Component<IRenderingZoneProps> {
- private _currentPluginName?: string;
- private _engine: Engine;
- private _scene: Scene;
- private _canvas: HTMLCanvasElement;
- public constructor(props: IRenderingZoneProps) {
- super(props);
- }
- initEngine() {
- this._canvas = document.getElementById("renderCanvas") as HTMLCanvasElement;
- this._engine = new Engine(this._canvas, true, { premultipliedAlpha: false, preserveDrawingBuffer: true });
- this._engine.loadingUIBackgroundColor = "#2A2342";
- // Resize
- window.addEventListener("resize", () => {
- this._engine.resize();
- });
- this.loadAsset();
- // File inputs
- let filesInput = new FilesInput(
- this._engine,
- null,
- (sceneFile: File, scene: Scene) => {
- this._scene = scene;
- this.onSceneLoaded(sceneFile.name);
- },
- null,
- null,
- null,
- () => {
- Tools.ClearLogCache();
- if (this._scene) {
- this.props.globalState.isDebugLayerEnabled = this.props.globalState.currentScene.debugLayer.isVisible();
- if (this.props.globalState.isDebugLayerEnabled) {
- this._scene.debugLayer.hide();
- }
- }
- },
- null,
- (file, scene, message) => {
- this.props.globalState.onError.notifyObservers({ message: message });
- }
- );
- filesInput.onProcessFileCallback = (file, name, extension, setSceneFileToLoad) => {
- if (filesInput.filesToLoad && filesInput.filesToLoad.length === 1 && extension) {
- switch (extension.toLowerCase()) {
- case "dds":
- case "env":
- case "hdr": {
- FilesInput.FilesToLoad[name] = file;
- EnvironmentTools.SkyboxPath = "file:" + (file as any).correctName;
- return false;
- }
- default: {
- if (isTextureAsset(name)) {
- setSceneFileToLoad(file);
- }
- break;
- }
- }
- }
- return true;
- };
- filesInput.loadAsync = (sceneFile, onProgress) => {
- const filesToLoad = filesInput.filesToLoad;
- if (filesToLoad.length === 1) {
- const fileName = (filesToLoad[0] as any).correctName;
- if (isTextureAsset(fileName)) {
- return Promise.resolve(this.loadTextureAsset(`file:${fileName}`));
- }
- }
- return SceneLoader.LoadAsync("file:", sceneFile, this._engine, onProgress);
- };
- filesInput.monitorElementForDragNDrop(this._canvas);
- this.props.globalState.filesInput = filesInput;
- window.addEventListener("keydown", (event) => {
- // Press R to reload
- if (event.keyCode === 82 && event.target && (event.target as HTMLElement).nodeName !== "INPUT" && this._scene) {
- if (this.props.assetUrl) {
- this.loadAssetFromUrl();
- } else {
- filesInput.reload();
- }
- }
- });
- }
- prepareCamera() {
- let camera: ArcRotateCamera;
- // Attach camera to canvas inputs
- if (!this._scene.activeCamera || this._scene.lights.length === 0) {
- this._scene.createDefaultCamera(true);
- camera = this._scene.activeCamera! as ArcRotateCamera;
- if (this.props.cameraPosition) {
- camera.setPosition(this.props.cameraPosition);
- } else {
- if (this._currentPluginName === "gltf") {
- // glTF assets use a +Z forward convention while the default camera faces +Z. Rotate the camera to look at the front of the asset.
- camera.alpha += Math.PI;
- }
- // Enable camera's behaviors
- camera.useFramingBehavior = true;
- var framingBehavior = camera.getBehaviorByName("Framing") as FramingBehavior;
- framingBehavior.framingTime = 0;
- framingBehavior.elevationReturnTime = -1;
- if (this._scene.meshes.length) {
- camera.lowerRadiusLimit = null;
- var worldExtends = this._scene.getWorldExtends(function (mesh) {
- return mesh.isVisible && mesh.isEnabled();
- });
- framingBehavior.zoomOnBoundingInfo(worldExtends.min, worldExtends.max);
- }
- }
- camera.pinchPrecision = 200 / camera.radius;
- camera.upperRadiusLimit = 5 * camera.radius;
- camera.wheelDeltaPercentage = 0.01;
- camera.pinchDeltaPercentage = 0.01;
- }
- this._scene.activeCamera!.attachControl();
- }
- handleErrors() {
- // In case of error during loading, meshes will be empty and clearColor is set to red
- if (this._scene.meshes.length === 0 && this._scene.clearColor.r === 1 && this._scene.clearColor.g === 0 && this._scene.clearColor.b === 0) {
- this._canvas.style.opacity = "0";
- this.props.globalState.onError.notifyObservers({ scene: this._scene, message: "No mesh found in your scene" });
- } else {
- if (Tools.errorsCount > 0) {
- this.props.globalState.onError.notifyObservers({ scene: this._scene, message: "Scene loaded but several errors were found" });
- }
- // this._canvas.style.opacity = "1";
- let camera = this._scene.activeCamera! as ArcRotateCamera;
- if (camera.keysUp) {
- camera.keysUp.push(90); // Z
- camera.keysUp.push(87); // W
- camera.keysDown.push(83); // S
- camera.keysLeft.push(65); // A
- camera.keysLeft.push(81); // Q
- camera.keysRight.push(69); // E
- camera.keysRight.push(68); // D
- }
- }
- }
- prepareLighting() {
- if (this._currentPluginName === "gltf") {
- if (!this._scene.environmentTexture) {
- this._scene.environmentTexture = EnvironmentTools.LoadSkyboxPathTexture(this._scene);
- }
- if (this._scene.environmentTexture) {
- this._scene.createDefaultSkybox(this._scene.environmentTexture, true, (this._scene.activeCamera!.maxZ - this._scene.activeCamera!.minZ) / 2, 0.3, false);
- }
- } else {
- var pbrPresent = false;
- for (const material of this._scene.materials) {
- if (material instanceof PBRBaseMaterial) {
- pbrPresent = true;
- break;
- }
- }
- if (pbrPresent) {
- if (!this._scene.environmentTexture) {
- this._scene.environmentTexture = EnvironmentTools.LoadSkyboxPathTexture(this._scene);
- }
- } else {
- this._scene.createDefaultLight();
- }
- }
- }
- onSceneLoaded(filename: string) {
- this._engine.clearInternalTexturesCache();
- this._scene.skipFrustumClipping = true;
- this.props.globalState.onSceneLoaded.notifyObservers({ scene: this._scene, filename: filename });
- this.prepareCamera();
- this.prepareLighting();
- this.handleErrors();
- if (this.props.globalState.isDebugLayerEnabled) {
- this.props.globalState.showDebugLayer();
- }
- delete this._currentPluginName;
- }
- loadTextureAsset(url: string): Scene {
- const scene = new Scene(this._engine);
- const plane = Mesh.CreatePlane("plane", 1, scene);
- const texture = new Texture(url, scene, undefined, undefined, Texture.NEAREST_LINEAR, () => {
- const size = texture.getBaseSize();
- if (size.width > size.height) {
- plane.scaling.y = size.height / size.width;
- } else {
- plane.scaling.x = size.width / size.height;
- }
- texture.gammaSpace = true;
- texture.hasAlpha = true;
- texture.wrapU = Texture.CLAMP_ADDRESSMODE;
- texture.wrapV = Texture.CLAMP_ADDRESSMODE;
- scene.debugLayer.show();
- scene.debugLayer.select(texture, "PREVIEW");
- }, (message, exception) => {
- this.props.globalState.onError.notifyObservers({ scene: scene, message: message || exception.message || "Failed to load texture" });
- });
- const material = new PBRMaterial("unlit", scene);
- material.unlit = true;
- material.albedoTexture = texture;
- material.alphaMode = PBRMaterial.PBRMATERIAL_ALPHABLEND;
- plane.material = material;
- return scene;
- }
- loadAssetFromUrl() {
- const assetUrl = this.props.assetUrl!;
- const rootUrl = Tools.GetFolderPath(assetUrl);
- const fileName = Tools.GetFilename(assetUrl);
- const promise = isTextureAsset(assetUrl)
- ? Promise.resolve(this.loadTextureAsset(assetUrl))
- : SceneLoader.LoadAsync(rootUrl, fileName, this._engine);
- promise
- .then((scene) => {
- if (this._scene) {
- this._scene.dispose();
- }
- this._scene = scene;
- this.onSceneLoaded(fileName);
- scene.whenReadyAsync().then(() => {
- this._engine.runRenderLoop(() => {
- scene.render();
- });
- });
- })
- .catch((reason) => {
- this.props.globalState.onError.notifyObservers({ message: reason.message });
- });
- }
- loadAsset() {
- if (this.props.assetUrl) {
- this.loadAssetFromUrl();
- return;
- }
- }
- componentDidMount() {
- if (!Engine.isSupported()) {
- return;
- }
- Engine.ShadersRepository = "/src/Shaders/";
- // This is really important to tell Babylon.js to use decomposeLerp and matrix interpolation
- Animation.AllowMatricesInterpolation = true;
- // Setting up some GLTF values
- GLTFFileLoader.IncrementalLoading = false;
- SceneLoader.OnPluginActivatedObservable.add((plugin) => {
- this._currentPluginName = plugin.name;
- if (this._currentPluginName === "gltf") {
- (plugin as GLTFFileLoader).onValidatedObservable.add((results) => {
- if (results.issues.numErrors > 0) {
- this.props.globalState.showDebugLayer();
- }
- });
- }
- });
- this.initEngine();
- }
- shouldComponentUpdate(nextProps: IRenderingZoneProps) {
- if (nextProps.expanded !== this.props.expanded) {
- setTimeout(() => this._engine.resize());
- return true;
- }
- return false;
- }
- public render() {
- return (
- <div id="canvasZone" className={this.props.expanded ? "expanded" : ""}>
- <canvas id="renderCanvas" touch-action="none" onContextMenu={(evt) => evt.preventDefault()}></canvas>
- </div>
- );
- }
- }
|