nartc / angular-three

🧊 THREE.js integration for Angular 🧊
https://angular-three.netlify.app/
MIT License
306 stars 26 forks source link

[V5 beta] ERROR Error: Uncaught (in promise): NullInjectorError: R3InjectorError(AppModule)[NgtStore -> NgtStore -> NgtStore]: NullInjectorError: No provider for NgtStore! #94

Closed IRobot1 closed 2 years ago

IRobot1 commented 2 years ago

As soon as I add NgtPhysicBody to a component, I get the NullInjectorError. Feels like I'm missing an import module, but not sure which.

@Component({
  templateUrl: './bounce.component.html',
  providers: [NgtPhysicBody],
})
export class BounceComponent {
  constructor(private physicBody: NgtPhysicBody) { }
}
nartc commented 2 years ago

This component needs to be inside of

IRobot1 commented 2 years ago

Looks like your comment was truncated

nartc commented 2 years ago

The component needs to be inside of ngt-physics which needs to be inside of ngt-canvas.

IRobot1 commented 2 years ago

It is. All of my components where working with V4. Here's the full code for the bounce example.

<div style="height:100vh">
  <ngt-canvas [camera]="{ position: [10, 2, 0], fov: 45, near:1, far:100 }"
              [scene]="{ background: 'gray' | color }">
    <ngt-directional-light [args]="['white' | color, 1.75]"
                           [position]="[20, 20, 20]" [castShadow]="true">
    </ngt-directional-light>

    <ngt-physics>
      <ngt-cannon-debug [disabled]="true" [scale]="1.1" color="red">

        <ngt-mesh [ref]="sphere1Props.ref" [castShadow]="true">
          <ngt-sphere-geometry></ngt-sphere-geometry>
          <ngt-mesh-standard-material [parameters]="{ color: 'red' | color }"></ngt-mesh-standard-material>
        </ngt-mesh>

        <ngt-mesh [ref]="sphere2Props.ref" [castShadow]="true">
          <ngt-sphere-geometry></ngt-sphere-geometry>
          <ngt-mesh-standard-material [parameters]="{ color: 'white' | color }"></ngt-mesh-standard-material>
        </ngt-mesh>

        <ngt-mesh [ref]="sphere3Props.ref" [castShadow]="true">
          <ngt-sphere-geometry></ngt-sphere-geometry>
          <ngt-mesh-standard-material [parameters]="{ color: 'blue' | color }"></ngt-mesh-standard-material>
        </ngt-mesh>

        <floor></floor>
      </ngt-cannon-debug>
    </ngt-physics>

    <ngt-soba-orbit-controls></ngt-soba-orbit-controls>
    <ngt-stats></ngt-stats>
  </ngt-canvas>
</div>
import { Component } from "@angular/core";

import { NgtPhysicBody } from "@angular-three/cannon/bodies";
import { NgtTriple } from "@angular-three/core";

@Component({
  templateUrl: './bounce.component.html',
  providers: [NgtPhysicBody],
})
export class BounceComponent {

  sphere1Props = this.physicBody.useSphere(() => ({
    mass: 1,
    position: [-3, 5, 3] as NgtTriple,
    material: { restitution: 0.4 },
  }));

  sphere2Props = this.physicBody.useSphere(() => ({
    mass: 1,
    position: [-3, 5, 0] as NgtTriple,
    material: { restitution: 0.6 },
  }));

  sphere3Props = this.physicBody.useSphere(() => ({
    mass: 1,
    position: [-3, 5, -3] as NgtTriple,
    material: { restitution: 0.8 },
  }));

  constructor(private physicBody: NgtPhysicBody) { }
}
IRobot1 commented 2 years ago

I added the following to AppModule, now the error changes to NullInjectorError: R3InjectorError(AppModule)[NgtStore -> ElementRef -> ElementRef -> ElementRef]

  providers: [NgtStore],

I don't think this is the right solution though.

IRobot1 commented 2 years ago

I've pushed the full cannon examples project to a v5 branch. Hopefully, it will repeat for you.

nartc commented 2 years ago

I'm outside right now. can you see if you can replicate the error on the physic-cubes example in apps/sandbox on the v5 branch?

IRobot1 commented 2 years ago

What's the command to run this standbox server?

IRobot1 commented 2 years ago

Figure it out. nx serve standbox

nartc commented 2 years ago

I think I see the problem. Your BounceComponent also includes the ngt-canvas which makes BounceComponent a parent (host) of NgtCanvas, not children.

Why don't you just bring the three spheres into the Bounce component instead of the whole canvas?

IRobot1 commented 2 years ago

This is the whole example page. This worked fine in V4.

Sounds like if I want to pull NgtPhysicBody into a component, that component can't include ngt-canvas

IRobot1 commented 2 years ago

As soon as I add those two lines to SandboxPhysicCubesComponent component, it starts failing in the same way.

@Component({
  selector: 'sandbox-physic-cubes',
  template: `
<div style="height:100vh">
        <ngt-canvas
            [dpr]="[1, 2]"
            [gl]="{ alpha: false }"
            [camera]="{ position: [-1, 5, 5], fov: 45 }">

            <ngt-ambient-light></ngt-ambient-light>
            <ngt-directional-light [position]="10" [castShadow]="true">
            </ngt-directional-light>

            <ngt-physics>
                <sandbox-plane [position]="[0, -2.5, 0]"></sandbox-plane>
                <sandbox-cube [position]="[0.1, 5, 0]"></sandbox-cube>
                <sandbox-cube [position]="[0, 10, -1]"></sandbox-cube>
                <sandbox-cube [position]="[0, 20, -2]"></sandbox-cube>
            </ngt-physics>
        </ngt-canvas>
</div>`,
  changeDetection: ChangeDetectionStrategy.OnPush,
  providers: [NgtPhysicBody],
})
export class SandboxPhysicCubesComponent {
  constructor(private physicBody: NgtPhysicBody) { }
}

Can you confirm this is the path forward for V5?
Does this only apply for objects with physics enabled?

nartc commented 2 years ago

Correct. That's how DI works. In v4, the physic body directives get attached on the mesh which are children of the Canvas. In v5, the PhysicBody service needs to be a child of Canvas. Your wrapper component wraps the Canvas which makes your component the Canvas's parent.

IRobot1 commented 2 years ago

Ok, I'll split all my examples into two components.

Hopefully, that subtly can be captured in your updated documentation.

Once I have everything migrated, I'll make some V4 to V5 migration notes based on my own experience.

nartc commented 2 years ago

Please do! And definitely lemme know any feedback at all as we can make changes now if needed:

In v5, all objects that NGT wraps THREE are Instances (NgtInstance) and Instances have the concept of attaching which allows one instance to attach itself on the parent instance based on an array of paths. Eg: attach="background" -> parent.background, [attach]="['shadow', 'mapSize']" -> parent.shadow.mapSize etc...

<!-- instead of doing this, which is fine, but requires some memory: scene Input, color pipe etc... -->
<ngt-canvas [scene]="{ background: 'blue' | color }"></ngt-canvas>

<!-- you can do this -->
<ngt-canvas>
   <!-- here, you say: I have this Color instance that I want to attach on the scene.background -->
   <!-- it is easy to remember and most importantly, it has its own lifecycle as an Angular Component -->
   <!-- you can ngIf it, or make it a reusable component -->
   <ngt-color attach="background" color="blue"></ngt-color>
</ngt-canvas>

In v5, most (if not all) Inputs that are boolean can accept a BooleanInput

- <ngt-mesh [castShadow]="true" [receiveShadow]="true"></ngt-mesh>
+ <ngt-mesh castShadow receiveShadow></ngt-mesh>
nartc commented 2 years ago

@IRobot1 here's my list so far https://github.com/nartc/angular-three/blob/v5/libs/documentations/docs/getting-started/migrate-to-v5.mdx

IRobot1 commented 2 years ago

Can you give an example of how to use instanced mesh with physics? Its not clear to me how ref is applied to each instance.

Previously the following code worked, but now throws lots of errors in the tick method

  count = 400;

  private boxes: Array<NgtTriple> = []

  constructor(private physicBody: NgtPhysicBody) {
    for (let i = 0; i < this.count; i++) {
      // start with random positions
      this.boxes.push([
        (Math.random() * 2 - 1) * 5,
        Math.random() * 10,
        (Math.random() * 2 - 1) * 5
      ])
    }
  }

  cubeProps = this.physicBody.useBox((index: number) => ({
    mass: 1,
    position: this.boxes[index],
    args: [1, 1, 1],
  }));

  ready(inst: InstancedMesh) {
    for (let i = 0; i < this.count; i++) {
      inst.setColorAt(i, new Color().setHex(Math.random() * 0xffffff));
    }
  }

  tick() {
    const index = Math.floor(Math.random() * this.count)
    this.cubeProps.api.at(index).position.set(0, 5 + Math.random() * 15, 0);
  }
<ngt-instanced-mesh #inst (ready)="ready(inst.instance.value)" [count]="count"
                    (animateReady)="tick()"
                    [ref]="cubeProps.ref" castShadow>
  <ngt-box-geometry></ngt-box-geometry>
  <ngt-mesh-standard-material></ngt-mesh-standard-material>
</ngt-instanced-mesh>

Uncaught TypeError: Cannot read properties of undefined (reading 'position')

Looks like at(index) no longer returns a valid object

IRobot1 commented 2 years ago

Also, some documentation on the second and third parameters of the useXXX methods.

nartc commented 2 years ago

@IRobot1 There's a bug with InstancedMesh. I just released beta.2 that contains the fix. I also added the Kinematic Cube example to the sandbox. Please take a look

IRobot1 commented 2 years ago

I confirmed instance mesh is now fixed with beta 2.

nartc commented 2 years ago

@IRobot1 https://github.com/nartc/angular-three/issues/96 can you chime in here all the findings that you've found so far?