0

There seems to be something that I don't understand in this code here. It keeps on throwing an error, although all seems correct to me. Possibly someone can spot it...

I know the class is quite long but I thought it would be better to insert the whole code. It crashed inside createScene() method when I try to add my 3D model to the scene with this.scene.add(object) although the scene has been initialized at the top of this method with this.scene = new THREE.Scene() and I also have tried initializing it on top of the class with an empty object but that doesn't help.

To be honest quite new to angular and no idea where to look for the error. The error reads Cannot read property 'scene' of undefined.

The strangest thing is that if this line is commented out the same lines are used to add light in create light method and it seems to be fine. The obj 3D model loads fine as well and doesn't go into error brackets so its not that I guess.

Would be really grateful if someone could spot the error. Thanks.

        import { AfterContentInit, Component, ElementRef, HostListener, OnInit, ViewChild } from '@angular/core';
        import * as THREE from 'three-full';

        @Component({
          selector: 'app-home',
          templateUrl: './home.component.html',
          styleUrls: ['./home.component.css']
        })
        export class HomeComponent implements  AfterContentInit {  

        private renderer: THREE.WebGLRenderer;
        private camera: THREE.PerspectiveCamera;
        private cameraTarget: THREE.Vector3;
        public scene: THREE.Scene = {make: null};
        public fieldOfView: number = 60;
        public nearClippingPane: number = 1;
        public farClippingPane: number = 1100;
        public controls: THREE.OrbitControls;

        @ViewChild('canvas')
        private canvasRef: ElementRef;

        constructor() {
                this.render = this.render.bind(this);
                this.onModelLoadingCompleted = this.onModelLoadingCompleted.bind(this);
        }


        ngAfterContentInit() {
          this.createScene();
          this.createLight();
          this.createCamera();
          this.startRendering();
          this.addControls();
        }


        private get canvas(): HTMLCanvasElement {
            return this.canvasRef.nativeElement;
        }

        private createScene() {
            this.scene = new THREE.Scene();
            var objLoader = new THREE.OBJLoader();
            var myMan: any = {};

            objLoader.load(
                // resource URL
                '/assets/man.obj',
                // called when resource is loaded
                function ( object ) {
                myMan = object;
                this.scene.add(object);
                },
                // called when loading is in progresses
                function ( xhr ) {

                console.log( ( xhr.loaded / xhr.total * 100 ) + '% loaded' );

                },
                // called when loading has errors
                function ( error ) {

                console.log( 'An error happened' );


                }


              );


        }

        public render() {
            this.renderer.render(this.scene, this.camera);
        }

        private onModelLoadingCompleted(collada) {
            var modelScene = collada.scene;
            this.scene.add(modelScene);
            this.render();
        }

        private createLight() {
            var light = new THREE.PointLight(0xffffff, 1, 1000);
            light.position.set(0, 0, 100);
            this.scene.add(light);

            var light = new THREE.PointLight(0xffffff, 1, 1000);
            light.position.set(0, 0, -100);
            this.scene.add(light);
        }

        private createCamera() {
          let aspectRatio = this.getAspectRatio();
          this.camera = new THREE.PerspectiveCamera(
              this.fieldOfView,
              aspectRatio,
              this.nearClippingPane,
              this.farClippingPane
          );

          // Set position and look at
          this.camera.position.x = 10;
          this.camera.position.y = 10;
          this.camera.position.z = 100;
        }

        private getAspectRatio(): number {
          let height = this.canvas.clientHeight;
          if (height === 0) {
              return 0;
          }
          return this.canvas.clientWidth / this.canvas.clientHeight;
        }

        private startRendering() {
          this.renderer = new THREE.WebGLRenderer({
              canvas: this.canvas,
              antialias: true
          });
          this.renderer.setPixelRatio(devicePixelRatio);
          this.renderer.setSize(this.canvas.clientWidth, this.canvas.clientHeight);

          this.renderer.shadowMap.enabled = true;
          this.renderer.shadowMap.type = THREE.PCFSoftShadowMap;
          this.renderer.setClearColor(0xffffff, 1);
          this.renderer.autoClear = true;

          let component: HomeComponent = this;

          (function render() {
              //requestAnimationFrame(render);
              component.render();
          }());
        }

        public addControls() {
          this.controls = new THREE.OrbitControls(this.camera);
          this.controls.rotateSpeed = 1.0;
          this.controls.zoomSpeed = 1.2;
          this.controls.addEventListener('change', this.render);

        }

        public onMouseDown(event: MouseEvent) {
          console.log("onMouseDown");
                event.preventDefault();

                // Example of mesh selection/pick:
                var raycaster = new THREE.Raycaster();
                var mouse = new THREE.Vector2();
                mouse.x = (event.clientX / this.renderer.domElement.clientWidth) * 2 - 1;
                mouse.y = - (event.clientY / this.renderer.domElement.clientHeight) * 2 + 1;
                raycaster.setFromCamera(mouse, this.camera);

                var obj: THREE.Object3D[] = [];
                this.findAllObjects(obj, this.scene);
                var intersects = raycaster.intersectObjects(obj);
                console.log("Scene has " + obj.length + " objects");
                console.log(intersects.length + " intersected objects found")
                intersects.forEach((i) => {
                    console.log(i.object); // do what you want to do with object
                });
        }

        private findAllObjects(pred: THREE.Object3D[], parent: THREE.Object3D) {
          // NOTE: Better to keep separate array of selected objects
          if (parent.children.length > 0) {
              parent.children.forEach((i) => {
                  pred.push(i);
                  this.findAllObjects(pred, i);                
              });
          }
        }

        public onMouseUp(event: MouseEvent) {
          console.log('Mouse Up');
        }

        @HostListener('window:resize', ['$event'])
            public onResize(event: Event) {
                this.canvas.style.width = "100%";
                this.canvas.style.height = "100%";
                console.log("onResize: " + this.canvas.clientWidth + ", " + this.canvas.clientHeight);

                this.camera.aspect = this.getAspectRatio();
                this.camera.updateProjectionMatrix();
                this.renderer.setSize(this.canvas.clientWidth, this.canvas.clientHeight);
                this.render();
            }

            @HostListener('document:keypress', ['$event'])
            public onKeyPress(event: KeyboardEvent) {
                console.log("onKeyPress: " + event.key);
            }



        }
gman
  • 100,619
  • 31
  • 269
  • 393
Yuri Zolotarev
  • 721
  • 9
  • 23
  • Your `THREE.Scene` instance is initialised in `createScene`. Is it possible that one of the other methods relying on `this.scene` is getting called before `createScene`? Adding some logging prior to each use of `this.scene` might help to determine. – Alex Peters Sep 21 '18 at 14:27
  • Well, createScene() is the first one that gets called in ngAfterContentInit() and if I actually take out the this.scene.add(object) from inside of it the rest of the methods run fine... It almost looks as if this.scene = new THREE.Scene() doesn't have enough time to initialize at this point which sounds a bit stange too. – Yuri Zolotarev Sep 21 '18 at 14:33
  • Your initialisation of `this.scene` to `{ make: null }` at declaration time looks a little problematic to me. Does much change if you remove that initialisation? – Alex Peters Sep 21 '18 at 14:38
  • Oh I actually tried with and without it and also just with and empty {} so nothing hapens here. I think it must be something to do with the fact of it being inside a function because anything that I put inside there comes out as undefined for some reason... – Yuri Zolotarev Sep 21 '18 at 14:41
  • For example if I put body: any = {}; at the top globaly and then this.body = object; body comes out as undefined. – Yuri Zolotarev Sep 21 '18 at 14:42
  • I wonder if it's to do with your `function () { ... }` definitions inside `createScene`—specifically the use of `this` inside them. What happens if you `console.log(this)` inside any of those functions? – Alex Peters Sep 21 '18 at 14:47
  • 1
    It was to do with 'this' pointing at a different instance of an object. Issue solved, thank you. – Yuri Zolotarev Sep 21 '18 at 14:48

1 Answers1

0

There has to be a pointer to this before the function start in java. How to access the correct `this` inside a callback?

private createScene() {
this.scene = new THREE.Scene();
var objLoader = new THREE.OBJLoader();
var self = this;   //ADDED THIS BEFORE FUNCTION START

objLoader.load(
    // resource URL
    '/assets/man.obj',
    // called when resource is loaded
    function ( object ) {

        self.onBodyLoadingCompleted(object);   //CAN REFERENCE THE CORRECT OBJECT NOW

    },
    // called when loading is in progresses
    function ( xhr ) {

    console.log( ( xhr.loaded / xhr.total * 100 ) + '% loaded' );

    },
    // called when loading has errors
    function ( error ) {

    console.log( 'An error happened' );


    }


  );

}

Yuri Zolotarev
  • 721
  • 9
  • 23