nartc / angular-three

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

[v5] Issues/Breaking Changes/Documentation requests #96

Closed nartc closed 2 years ago

nartc commented 2 years ago

This is an issue used to track all Issues/regressions/breaking changes and documentation requests to v5. You can track the latest progress on documentations here: https://github.com/nartc/angular-three/tree/v5/libs/documentations/docs

In case you miss it, 5.0.0-beta.4 has been released so you can try it out and find any issues you run into. I'm in the process of writing documentations for v5.

Also, @angular-three/soba is missing a lot of functionalities right now as the core has changed. I will continue to fill them as I move along with the documentations.

cc @IRobot1

IRobot1 commented 2 years ago

Document the second and third arguments for physicBody.useXXX methods

nartc commented 2 years ago

https://v5--angular-three.netlify.app/ WIP docs is live here

IRobot1 commented 2 years ago

I think the note at top of this page should also indicate some knowledge of threejs is needed too.

IRobot1 commented 2 years ago

I think ComponentStore needs an more complete example of its use in a component. A link to an working example of its use would be good enough

IRobot1 commented 2 years ago

A common problem I run into is the need to access the camera from a component to use in a raycast.

Show how a component gets access to the camera for the following example

   // Project the mouse onto the movement plane
   const hitPoint = this.getHitPoint(event.clientX, event.clientY, this.movementPlane, this.camera)

  private getHitPoint(clientX: number, clientY: number, mesh: Mesh, camera: Camera): Vector3 | undefined {
    // Get 3D point form the client x y
    const mouse = new Vector2()
    mouse.x = (clientX / window.innerWidth) * 2 - 1
    mouse.y = -((clientY / window.innerHeight) * 2 - 1)

    const raycaster = new Raycaster();
    // Get the picking ray from the point
    raycaster.setFromCamera(mouse, camera)

    // Find out if there's a hit
    const hits = raycaster.intersectObject(mesh)

    // Return the closest hit or undefined
    return hits.length > 0 ? hits[0].point : undefined
  }
nartc commented 2 years ago

@IRobot1 I have updated Component Store documentation https://v5--angular-three.netlify.app/docs/core/component-store

Is Store documentation not clear on how to access the NgtStore state, which contains the default Camera?

Polyterative commented 2 years ago

Wow, the new documentation is starting to look fantastic! Nice work!

For me, the pain point now is to understand where to define post-processing steps and how to generally post-process stuff. Would really appreciate even a basic example

nartc commented 2 years ago

@Polyterative thank you. Yes, there will be examples on postprocessing stuffs. It's just a lot of work getting there :D I'm fleshing out some Cannon stuffs so @IRobot1 can move on with porting cannon-es examples over to Angular Three which is HUGE for the community

nartc commented 2 years ago

@IRobot1 latest beta (beta.11) has the cannon package consolidated

// before
import { NgtPhysicBody } from '@angular-three/cannon/bodies';

// after
import { NgtPhysicBody } from '@angular-three/cannon';

I also added: Spring, Raycast, and RaycastVehicle

IRobot1 commented 2 years ago

Thanks. I'll give it a try.

BTW, I agree the new documentation is an easier read.

IRobot1 commented 2 years ago

NgtConstraintReturn was renamed to NgtPhysicConstraintReturn

IRobot1 commented 2 years ago

NgtPhysicSpring api missing applyForce method

nartc commented 2 years ago

@IRobot1 https://github.com/pmndrs/use-cannon/blob/master/packages/cannon-worker-api/src/worker/operations/add-spring.ts#L31 seems like Cannon Worker API calls applyForce() automatically and it does not expose Spring#applyForce (only Body#applyForce is exposed)

IRobot1 commented 2 years ago

Is Store documentation not clear on how to access the NgtStore state, which contains the default Camera?

Store is not clear to me yet. It doesn't actually show how its injected/created within a component. All examples refer to "this.store", but where is this.store being added?

ComponentStore is also confusing. I'm not a react developer and never used NgRx store. It assumes I am so implies a lot. Its not clear why/when I need to extend from NgtComponentStore or SomeBaseClass.

So far, I've written lots of examples of ng3 components and never needed to use this. I tried injecting NgtStore into a component where I want to access camera, but its value is always undefined.

  constructor(
    private store: NgtStore,
  ) {
    console.warn(this.store.get((s) => s.camera)); // undefined
  }

I tried this approach too without luck.

@Component({
  providers: [NgtComponentStore ],
})
export class MousePickExample {
  constructor(
    private store: NgtComponentStore ,
  ) {
    console.warn(this.store.get((s) => s.camera)); // undefined
  }
}

What am I missing?

Here's the code where I want to use the store to access the camera instead of passing it as in input.

nartc commented 2 years ago

@IRobot1 You've got a point there. I am very familiar with NgRx ComponentStore hence I subconsciously write docs with a lot of assumptions. My apologies! I'll think of how to make it clearer for developers who aren't experienced with NgRx.

Here's a PR that shows how to use NgtStore and NgtComponentStore https://github.com/IRobot1/ng3-cannon-template/pull/1

You can read through my comments on the PR to understand the changes better

nartc commented 2 years ago

@IRobot1 updated v5 doc with Cannon API sections.

This page https://v5--angular-three.netlify.app/docs/cannon/body explains the 2nd and 3rd arguments of NgtPhysicBody.use***

IRobot1 commented 2 years ago

One limitation when using ng3/cannon is there's no quick way to make a mesh a static physics object. Currently have to

<ngt-mesh [ref]="leftSphere.ref" castShadow>
  <ngt-sphere-geometry></ngt-sphere-geometry>
  <ngt-mesh-standard-material></ngt-mesh-standard-material>
</ngt-mesh>
  leftSphere = this.physicBody.useSphere(() => ({
    mass: 0,
    position: [0, 0, 4]
  }));

Would be nice if there was a simple short-cut, where the correct body type was implicitly created with mass 0

<ngt-mesh static [position]="[0, 0, 4]" castShadow>
  <ngt-sphere-geometry></ngt-sphere-geometry>
  <ngt-mesh-standard-material></ngt-mesh-standard-material>
</ngt-mesh>
or
<ngt-mesh staticSphere [position]="[0, 0, 4]" castShadow>
  <ngt-sphere-geometry></ngt-sphere-geometry>
  <ngt-mesh-standard-material></ngt-mesh-standard-material>
</ngt-mesh>
nartc commented 2 years ago

Wouldn't it be to use type: 'Static'?

IRobot1 commented 2 years ago

Yes. I think mass of 0 implies static. I've never explicitly set body type to Static.

IRobot1 commented 2 years ago

I've been struggling with the terminology of effect and tapEffect. To me, better names would be monitor and task. Is there a way to create aliases for these?

IRobot1 commented 2 years ago

For [canvas input], for the camera property it mentions you can provide your own THREE.Camera. I was hoping something like this was possible

<ngt-canvas [camera]="camera">
    <ngt-camera #camera [args]="{ position: [16, 4, 0], fov: 65, near: 1, far: 100}"></ngt-camera>

Regardless, I was hoping there could be a way to set lookat location without having to write code.

IRobot1 commented 2 years ago

I noticed this recent change with camera settings. If rotation is missing, it defaults to lookat origin. Unfortunately, setting rotation is not the same as looking at something.

It would be nice if camera options explicitly supported lookat position. That way, it could track/follow an object in the scene. Of course, setting lookat and rotation might not be clear.

IRobot1 commented 2 years ago

For spring documentation, you can copy working example code from here

IRobot1 commented 2 years ago

Refer to the following physics trigger example. View the browser console messages to see the event value when bodies collide. Here's the corresponding code

{
  body: sn {uuid: '51694ff7-c2e6-4625-ad11-e244d98ab40c', name: 'sphere', type: 'Mesh', parent: Vd, children: Array(0), …}
  collisionFilters: {bodyFilterGroup: 1, bodyFilterMask: -1, targetFilterGroup: 1, targetFilterMask: -1}
  contact: {bi: sn, bj: sn, contactNormal: Array(3), contactPoint: Array(3), id: 0, …}
  op: "event"
  target: sn {uuid: '4e2972df-30d0-4673-861f-cd4f85bcd87f', name: 'trigger', type: 'Mesh', parent: Vd, children: Array(0), …}
  type: "collide"
}

The physics body onCollideBegin and onCollideEnd event references the mesh that collided. However, I haven't figure out how to use this to get a reference to the body. When something collides, I want to have the option to create a constraint between to two bodies.

IRobot1 commented 2 years ago

Its possible to search to V4 documentation. However, the current V5 documentation is missing the capability

IRobot1 commented 2 years ago

In your pull request for the physics mousepick example, you mention this

    // anything physics related, we might want to run outside of Angular zone to prevent Change Detection ticks
    this.zone.runOutsideAngular(() => {
    });

I think the document needs to be a topic that covers this and when/why change detection should be avoided.
Perhaps some best practices when using ngIf and ngFor too

nartc commented 2 years ago

@IRobot1

  1. You would use the two Refs to create the constraint instead of the bodies right? The surface API of angular-three/cannon does not work with Bodies directly but the Ref of the object. Or am I missing something?
  2. Yes, the new docs needs a bit of a setup to have Doc Search.
  3. That's a good point. I have plans to create a Performance section. Just need to work on some examples on codesandbox. Will definitely mention NgZone there. Thanks.
IRobot1 commented 2 years ago
  1. You would use the two Refs to create the constraint instead of the bodies right? The surface API of angular-three/cannon does not work with Bodies directly but the Ref of the object. Or am I missing something?

I would have expected the ColledBeginEvent to be like the following since its bodies interacting, not meshes

export declare type CollideBeginEvent = {
    body: NgtPhysicsBodyReturn;
    op: 'event';
    target: NgtPhysicsBodyReturn;
    type: 'collideBegin';
};

In the example below, when the sphere enters the box trigger, the sphere becomes connected to the particle via the constratint

  sphere = this.physicBody.useSphere(() => ({
    mass: 1,
  }));

  particle = this.physicBody.useParticle(() => ({
    mass: 0,
  }));

  box = this.physicBody.useBox(() => ({
    isTrigger: true,
    onCollideBegin: (event) => {
      const constraint = this.physicConstraint.useDistanceConstraint(this.particle.ref, new Ref(event.body), {
        distance:0
      });
    },
  }));

However, the new Ref(event.body) is not the same as sphere.ref. As a result, I'm not able to access sphere.api. Any thoughts on how this could work? Can CollideBeginEvent be changed to pass NgtPhysicsBodyReturn?

Without this, I would need to maintain a dictionary/map of uuid to NgtPhysicsBodyReturn

Hope this makes sense.

nartc commented 2 years ago

@IRobot1 why can't you use this.sphere.ref?

IRobot1 commented 2 years ago

Imagine my hand is the box trigger and there's many physics bodies in the room that my hand can overlap.
Closing my hand, creates the constraint and causes the body to follow my hand until I release it. I won't know which body given the current info in the event. Its surpising that the CollideBeginEvent refers to meshes and not bodies.
Is there an internal map of body ids to bodies?

IRobot1 commented 2 years ago

I created the following drop in wrapper for NgtPhysicBody. It creates a map of ref uuid for all the bodies created. It includes an extra method getBody that allows the uuid in the CollideBeginEvent to lookup the body in order to create the constraint

  cubeProps = this.physicBody.useBox(() => ({
    isTrigger: true,
    onCollideBegin: (event: CollideBeginEvent) => {
      const body = this.physicBody.getBody(event.body.uuid);
      if (body) {
        const constraint = this.physicConstraint.useDistanceConstraint(this.particle.ref, body.ref, {
          distance: 0
        });
        this.particle.api.position.set(0, 0, -5)
      }
    },

  }));
  constructor(
    private physicBody: PhysicsBodyRegistry,
    private physicConstraint: NgtPhysicConstraint,
  ) {
  }
import { Injectable } from "@angular/core";

import { Ref } from "@angular-three/core";

import { BoxProps, CompoundBodyProps, ConvexPolyhedronProps, CylinderProps, GetByIndex, HeightfieldProps, NgtPhysicBody, NgtPhysicBodyReturn, ParticleProps, PlaneProps, SphereProps, TrimeshProps } from "@angular-three/cannon";

@Injectable()
export class PhysicsBodyRegistry {
  private uuidbodymap = new Map<string, NgtPhysicBodyReturn>([]);

  getBody(uuid: string): NgtPhysicBodyReturn | undefined {
    return this.uuidbodymap.get(uuid);
  }

  removeBody(uuid: string): boolean {
    return this.uuidbodymap.delete(uuid);
  }

  constructor(
    private physicBody: NgtPhysicBody,
  ) { }

  private register(body: NgtPhysicBodyReturn): NgtPhysicBodyReturn {
    const s = body.ref.subscribe(next => {
      if (next != null) {
        this.uuidbodymap.set(next.uuid, body);
        s.unsubscribe();
      }
    });
    return body;
  }

  public useBox(fn: GetByIndex<BoxProps>, useOnTemplate?: boolean, ref?: Ref<THREE.Object3D>): NgtPhysicBodyReturn {
    return this.register(
      this.physicBody.useBox(fn, useOnTemplate, ref)
    );
  }

  public useCompoundBody(fn: GetByIndex<CompoundBodyProps>, useOnTemplate?: boolean, ref?: Ref<THREE.Object3D>): NgtPhysicBodyReturn {
    return this.register(
      this.physicBody.useCompoundBody(fn, useOnTemplate, ref)
    );
  }

  useConvexPolyhedron(fn: GetByIndex<ConvexPolyhedronProps>, useOnTemplate?: boolean, ref?: Ref<THREE.Object3D>): NgtPhysicBodyReturn {
    return this.register(
      this.physicBody.useConvexPolyhedron(fn, useOnTemplate, ref)
    );
  }
  useCylinder(fn: GetByIndex<CylinderProps>, useOnTemplate?: boolean, ref?: Ref<THREE.Object3D>): NgtPhysicBodyReturn {
    return this.register(
      this.physicBody.useCylinder(fn, useOnTemplate, ref)
    );
  }
  useHeightfield(fn: GetByIndex<HeightfieldProps>, useOnTemplate?: boolean, ref?: Ref<THREE.Object3D>): NgtPhysicBodyReturn {
    return this.register(
      this.physicBody.useHeightfield(fn, useOnTemplate, ref)
    );
  }
  useParticle(fn: GetByIndex<ParticleProps>, useOnTemplate?: boolean, ref?: Ref<THREE.Object3D>): NgtPhysicBodyReturn {
    return this.register(
      this.physicBody.useParticle(fn, useOnTemplate, ref)
    );
  }
  usePlane(fn: GetByIndex<PlaneProps>, useOnTemplate?: boolean, ref?: Ref<THREE.Object3D>): NgtPhysicBodyReturn {
    return this.register(
      this.physicBody.usePlane(fn, useOnTemplate, ref)
    );
  }
  useSphere(fn: GetByIndex<SphereProps>, useOnTemplate?: boolean, ref?: Ref<THREE.Object3D>): NgtPhysicBodyReturn {
    return this.register(
      this.physicBody.useSphere(fn, useOnTemplate, ref)
    );
  }
  useTrimesh(fn: GetByIndex<TrimeshProps>, useOnTemplate?: boolean, ref?: Ref<THREE.Object3D>): NgtPhysicBodyReturn {
    return this.register(
      this.physicBody.useTrimesh(fn, useOnTemplate, ref)
    );
  }

}
IRobot1 commented 2 years ago

After creating a constraint, how to I delete (not disable) it at runtime?

        const constraint = this.physicConstraint.useDistanceConstraint(body.ref, lastBody.ref, {
          distance: distance,
          maxMultiplier: this.strength, // higher multiplier makes links stronger
        });
nartc commented 2 years ago

@IRobot1 I thought the returned “api” had a method to remove the constraint. It didn’t. Released beta-16 to add remove() to constraint api, spring api, and raycastvehicle api

nartc commented 2 years ago

v5 is now parity with the main branch, as in all components have been reworked. I'll focus on documentations next then will probably promote v5 to latest version this weekend or next week.

IRobot1 commented 2 years ago

Are you planning on updating to r140 before releasing V5?

IRobot1 commented 2 years ago

Can you update the soba sky story example to expose the properties supported by sky component? I think this would better demonstrate the capabilities of this component.

nartc commented 2 years ago

@IRobot1

  1. Really depends on if @types/three releases the new type early enough
  2. I can add docs to Sky for you but all Soba's story documentations cannot be a blocker for v5 release. It's a lot of work to document all Soba
nartc commented 2 years ago

@IRobot1 More on NgtComponentStore, effect, and tapEffect

The "Effect" term has been used in the frontend space for quite a while now and it means "to handle side-effect". Side-effect can be varied: fetching an API (in THREE, it can mean using any of the Loader), watching for changes on Inputs to update an object, registering a callback to the animation loop etc...

I will write documentations on what is considered an Effect in Angular Three and will link to the NgRx (inspiration) documentations as needed. The name cannot be changed because to counter your argument, there also might be people who ARE familiar with NgRx. Changing the terms would be confusing for them.

IRobot1 commented 2 years ago

Add support for capsule geometry which was added in r139

nartc commented 2 years ago

@IRobot1 added. I'll probably release v5 tonight or tomorrow.

IRobot1 commented 2 years ago

The documentation for cannon API should mention how to use ngt-cannon-debug.

Also, recall a limitation that cannon doesn't work as expected when the mesh/body is contained in a parent that has position or rotation set. However, I can't find where this is mentioned.

<ngt-group [rotation]="[0, 1.57, 0]" [position]="[-2, 0, 0]">
  <ngt-mesh [name]="'trigger'" [ref]="cubeProps.ref" [scale]="cubescale">
    <ngt-box-geometry></ngt-box-geometry>
    <ngt-mesh-standard-material color='white'></ngt-mesh-standard-material>
  </ngt-mesh>
</ngt-group>

In the following image, the solid box is what's rendered in the scene, but the wireframe is how cannon thinks its positioned

image

nartc commented 2 years ago

@IRobot1 Thank you for reporting that. However, documentations is something I'd expect the community to help out. Naturally, the child object takes over the parent's object orientations if the child object does not specify its orientation. In this case, the mesh takes over the values from the Group. To workaround (I am guessing), is to pass the group's position/rotation to useBox.

IRobot1 commented 2 years ago

The work around is to put inside a compound body.
I'll submit a separate issue about how the current way to do this could be improved.

nartc commented 2 years ago

V5 has been released so I'll close this issue. If anyone has any suggestions for documentations, we can track em in a separate one