Viewcube scene in typescript not working

Hi guys,

I’m new to babylon and was attempting to create my first scene with a viewcube. I was using this demo as inspiration. Voxel Builder. It seems I have the majority of the code correct. However, the viewcube scene is not in the lower right corner as it was in the demo. I am assuming this is because the activecamera.alpha and activecamera.beta are not the same way. The properties alpha, and beta were not available inside my code for some reason (difference between typescript and js). I’m not quite sure what problem is.

this is my index.ts

import { Engine } from "@babylonjs/core/Engines/engine";
import { cc_scene } from "./classes/cc_scene";
import { cc_scene_viewcube } from "./classes/cc_scene_viewcube";

const canvas = document.querySelectorAll('canvas')[0];

const engine = new Engine(canvas, true, {}, false);
engine.disablePerformanceMonitorInBackground = true;
engine.enableOfflineSupport = false;
engine.doNotHandleContextLost = true;
//engine.useHighPrecisionFloats = false;

let viewport = new cc_scene( engine, canvas );
let viewcube = new cc_scene_viewcube( engine, canvas );

const ViewportScene = viewport.create_scene();
const ViewcubeScene = viewcube.create_scene();

engine.runRenderLoop( function(){
    ViewportScene.render();
    ViewcubeScene.render();
    ViewcubeScene.activeCamera = ViewportScene.activeCamera;
});

This is cc_scene.ts

import { Engine } from "@babylonjs/core/Engines/engine";
import { Scene } from "@babylonjs/core/scene";
import { ArcRotateCamera, GroundBuilder } from "@babylonjs/core";
import { HemisphericLight } from "@babylonjs/core";
import { DirectionalLight } from "@babylonjs/core";
import { ShadowGenerator } from "@babylonjs/core";
import { Mesh } from "@babylonjs/core";
import { MeshBuilder } from "@babylonjs/core";
import { GridMaterial } from "@babylonjs/materials";
import { ShadowOnlyMaterial } from "@babylonjs/materials";
import { Vector3 } from "@babylonjs/core";
import { Color3 } from "@babylonjs/core";
import { Color4 } from "@babylonjs/core";
import { RenderTargetTexture } from "@babylonjs/core";
import { cc_scene_viewcube } from "./cc_scene_viewcube";

export class cc_scene 
{
    private _canvas: HTMLCanvasElement;
    private _engine: Engine;
    private _scene: Scene;
    private _camera: ArcRotateCamera;
    private _light_ambient: HemisphericLight;
    private _light_directional: DirectionalLight;
    private _shadowEmitter: ShadowGenerator;
    private _ground: Mesh;
    private _ground_material: GridMaterial;

    constructor(engine: Engine, canvas: HTMLCanvasElement ) 
    {
        this._canvas = canvas;
        this._engine = engine;
        this._scene = this.setSceneProperties();
        this._camera = this.setArcCamera();
        this._light_ambient = this.CreateAmbientLight();
        this._light_directional = this.CreateSpotlight();
        this._shadowEmitter = this.CreateShadowEmitter();
        this._ground_material = this.CreateGroundMaterial();
        this._ground = this.CreateGroundMesh();
    }

    public create_scene(): Scene 
    {
        return this._scene;
    }

    public getEngine(): Engine {
        return this._engine;
    }

    // public getCanvas(): HTMLCanvasElement {
    //     return this._canvas;
    // }

    private setSceneProperties(): Scene {
        let SceneSetting = new Scene(this._engine);
        SceneSetting.clearColor = Color4.FromHexString('#D618B6');
        //SceneSetting.clearColor = new Color4(0,0,0,0);
        SceneSetting.autoClear = false;
        SceneSetting.autoClearDepthAndStencil = false;
        SceneSetting.blockMaterialDirtyMechanism = true;
        return SceneSetting;
    }

    private setArcCamera(): ArcRotateCamera {
        let ArcCamera = new ArcRotateCamera("Camera", 0, 0, 10, Vector3.Zero(), this._scene);
        ArcCamera.attachControl(this._canvas, true);
        ArcCamera.setPosition(new Vector3(20, 15, 20));
        ArcCamera.setTarget(new Vector3(0, 2, 0));
        ArcCamera.panningSensibility = 300;
        ArcCamera.wheelPrecision = 10;
        ArcCamera.lowerRadiusLimit = 3;
        ArcCamera.upperRadiusLimit = 200;
        //ArcCamera.allowUpsideDown = false;
        return ArcCamera;
    }

    private setLightPositionByAngle(light: DirectionalLight, angle: number, distance: number, height: number): DirectionalLight {
        const x = Math.cos(angle * Math.PI / 180) * distance;
        const y = height;
        const z = Math.sin(angle * Math.PI / 180) * distance;
        const pos = new Vector3(x, y, z);
        light.position = pos; // our primary shadow light
        light.setDirectionToTarget(Vector3.Zero());
        return light;
    }

    private CreateEngine(): Engine {
        var ccEngine: Engine = new Engine(this._canvas, true);
        ccEngine.disablePerformanceMonitorInBackground = true;
        ccEngine.enableOfflineSupport = false;
        ccEngine.doNotHandleContextLost = true;
        return ccEngine;
    }

    private CreateAmbientLight(): HemisphericLight {
        let AmbientLight = new HemisphericLight("ambient", new Vector3(0, 1, 0), this._scene);
        AmbientLight.diffuse = new Color3(0.5, 0.5, 0.5);
        AmbientLight.specular = new Color3(0, 0, 0);
        AmbientLight.groundColor = new Color3(0.4, 0.4, 0.4);
        AmbientLight.intensity = 0.3; 
        return AmbientLight;
    }

    private CreateSpotlight(): DirectionalLight {
        let DirectLight = new DirectionalLight("light", new Vector3(0, -1, 0), this._scene);
        DirectLight = this.setLightPositionByAngle(DirectLight, 120, 50, 100);
        DirectLight.autoUpdateExtends = true;
        DirectLight.diffuse = new Color3(1, 1, 1);
        DirectLight.intensity = 1;
        return DirectLight;
    }

    private CreateShadowEmitter(): ShadowGenerator {
        let Emitter = new ShadowGenerator(512, this._light_directional);
        Emitter.getShadowMap()!.refreshRate = RenderTargetTexture.REFRESHRATE_RENDER_ONCE;
        Emitter.useExponentialShadowMap = false;
        Emitter.usePercentageCloserFiltering = true;
        Emitter.setDarkness(0.6);
        return Emitter;
    }

    private CreateGroundMaterial(): GridMaterial {
        let Material = new GridMaterial("grid", this._scene);
        Material.gridRatio = 0.5;
        Material.mainColor = new Color3(0.6, 0.6, 0.6);
        Material.lineColor = new Color3(0.7, 0.7, 0.7);
        Material.opacity = 0.3;
        Material.backFaceCulling = false;
        Material.freeze();
        return Material;
    }

    private CreateGroundMesh(): Mesh {
        let Ground = MeshBuilder.CreateGround("grid", { width: 100, height: 100 }, this._scene);
        Ground.material = this._ground_material;
        Ground.position.y = -0.5;
        Ground.isPickable = false;
        Ground.doNotSyncBoundingInfo = true;
        Ground.convertToUnIndexedMesh();
        Ground.freezeWorldMatrix();
        Ground.freezeNormals();
        return Ground;
    }

};

and here is cc_scene_viewcube.ts

import { Engine } from "@babylonjs/core/Engines/engine";
import { Scene } from "@babylonjs/core/scene";
import { float, HemisphericLight, int } from "@babylonjs/core";
import { Vector3 } from "@babylonjs/core";
import { Color3 } from "@babylonjs/core";
import { ArcRotateCamera } from "@babylonjs/core";
import { Viewport } from "@babylonjs/core";
import { MeshBuilder } from "@babylonjs/core";
import { NormalMaterial } from "@babylonjs/materials";

export class cc_scene_viewcube {
    private _engine: Engine;
    private _scene: Scene;
    private _light_ambient: HemisphericLight;
    private _canvas: HTMLCanvasElement;
    private _camera: ArcRotateCamera;
    

    constructor( engine: Engine, canvas: HTMLCanvasElement ) 
    {
        this._canvas = canvas;
        this._engine = engine;
        this._scene = this.setSceneProperties();
        this._light_ambient = this.CreateAmbientLight();        
        this._camera = this.setArcCamera();
    }

    public create_scene(): Scene {
        const vc_front = MeshBuilder.CreateBox("vc_front", { height: 34.5, width: 0.75, depth: 23.125 }, this._scene );
        vc_front.material = new NormalMaterial("viewcube", this._scene );
        vc_front.position.x = 0
        vc_front.position.y = 0

        //const vc_plane = MeshBuilder.CreatePlane("plane1", {}, this._scene);
        //vc_plane.material = new NormalMaterial("viewcube", this._scene);

        return this._scene;
    }

    private setSceneProperties(): Scene {
        let ViewCubeScene = new Scene(this._engine)
        ViewCubeScene.autoClear = false;
        ViewCubeScene.autoClearDepthAndStencil = false;
        ViewCubeScene.blockMaterialDirtyMechanism = true;
        return ViewCubeScene;
    }

    private CreateAmbientLight(): HemisphericLight {
        let AmbientLight = new HemisphericLight("ambient", new Vector3(0, 1, 0), this._scene);
        AmbientLight.diffuse = new Color3(1, 1, 1);
        AmbientLight.groundColor = new Color3(0.5, 0.5, 0.5);
        AmbientLight.intensity = 3.0;
        return AmbientLight;
    }

    private setArcCamera(): ArcRotateCamera {
        let ArcCamera = new ArcRotateCamera("camera", 0, 0, 10, Vector3.Zero(), this._scene);
        ArcCamera.viewport = this.updateViewport(100, 100, 10, 10);
        ArcCamera.radius = 3;
        return ArcCamera;
    }

    private updateViewport(w: float, h: float, bottom: float, right: float): Viewport {
        const width = this._canvas.width;
        const height = this._canvas.height;
        return new Viewport(1 - (w + right) / width, 1 - (bottom + height) / height, w / width, h / height);
    }
};

and finally here is my index.ejs

<!DOCTYPE html>
<html>
<head>
    <meta http-equiv="Content-Type" content="text/html" charset="utf-8"/>
    <title>Cabinet Commerce</title>
    <!-- <link rel="stylesheet" type="text/css" href="http://localhost:8080/core/viewport.css" > -->
    <style>
    * {
  margin: 0;
  padding: 0;
}

html,
body {
  overscroll-behavior-y: contain;
  user-select: none;
}

/* prevent pull-down refresh and text selection on chrome mobile */
body {
  font-family: sans-serif;
  font-size: 11px;
  /* color: #cccccc; */
  /* background-color: blueviolet; */
  cursor: default;
}

a {
  color: slategray;
  text-decoration: none;
}

a:hover {
  color: orange;
  text-decoration: none;
}

canvas {
  z-index: 0;
  position: absolute;
  width: 100%;
  height: 100%;
  outline: none;
  background: radial-gradient(
    circle,
    rgb(81, 90, 109) 0%,
    rgb(49, 53, 68) 100%
  );
}
    </style>
</head>
<body>
    <canvas></canvas>
</body>
</html>

Thank you in advance for any help

EDIT: I’ve tried adding this data to the playground but its giving me errors while the intellisense does not show anything wrong.

Here’s the playground
https://playground.babylonjs.com/#BXVZCG#4

Hi!

It would be very helpful if we actually saw a reproduction of the problem. The playground would be great, but if it is not possible for some reason, any other way for people to debug the issue will be a great help.

I’ve never done that before in the playground. I can give it a shot. Will my imports work?

1 Like

I attempted to add to the playground. Here’s the url https://playground.babylonjs.com/#BXVZCG#4

1 Like

Thank you so much for that :slight_smile:

Could you make the playground work? render the issue? Your playground has a few errors that prevent the code from actually running. I assume this is not the issue you are facing, but something related to rendering.

I wish I could make it work. As I said before, I have never used the playground. So, I do not understand why those errors are there. When I type BABYLON.DIRECTIONALLIGHT it comes up no problem inside the playground, but then it gives an error saying it cannot find DIRECTIONALIGHT

seems you are missing BABYLON.

image

1 Like

Thank you. I couldn’t see that one. I fixed it. Now it says this.

r.initFunction is not a function

1 Like

Hey there, I fixed a few issues and got it running by removing the export keyword from the classes and returning the scene from CreateScene(). Also it was smearing, so I commented out the part that disabled auto-clearing on line 32. :slightly_smiling_face:
https://playground.babylonjs.com/#BXVZCG#6

Awesome. thank you for that. So, now we can see my issue at least. The box that is showing should be in the lower right corner like that demo link I posted Voxel Builder

I also updated my code with your edits

1 Like

So, this is the default behavior. The position of a box is the center of it. If you want it to be “on the ground” you need to set its y position to be half of its height.
What you can do afterwards is bake this transformation to “reset” the position. Another solution would be to use a pivot matrix to provide the center position of this box:
GM Viewcube | Babylon.js Playground (babylonjs.com) (line 20)

I am not sure why you have 2 different scenes and why you render both of them at the same time. This might cause sync issues between the two cameras. It is, however, technically viable (as you already found out :-))

1 Like

Click the link to that voxel builder above. You can see the lower right corner there is a viewcube that adjusts when you orbit the main scene. That is what I am trying to accomplish

I essentially copied the code from that sample but put it into typescript

If you take a look at that voxel builder demo. There were the following 2 lines of code for this viewcube.

sceneAxisView.activeCamera.alpha = scene.activeCamera.alpha;
sceneAxisView.activeCamera.beta = scene.activeCamera.beta;

If I try to do the same thing in my code, the .alpha and the .beta are invalid. I need to know how to achieve those 2 lines of code in typescript. That should solve my problem i think

https://playground.babylonjs.com/#BXVZCG#10

1 Like

After casting activeCamera from type Camera to type ArcRotateCamera, then you can access the ArcRotateCamera’s properties like you want. Also, commenting out the line that sets activeCamera got the mesh to show up at the bottom right corner, at least. PS I noticed that you already created the scenes so returned the main viewport scene from Playground.CreateScene. :slightly_smiling_face:

https://playground.babylonjs.com/#BXVZCG#12