nartc / angular-three

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

[BUG] NgtCannonConstraintController handling of ngtPhysicConeTwistConstraint #86

Closed IRobot1 closed 2 years ago

IRobot1 commented 2 years ago

I'm converting the cannon-es ragdoll example

  <ng-container ngtPhysicConeTwistConstraint [options]="neckjoint">
    <ngt-mesh ngtPhysicSphere [getPhysicProps]="getHeadProps">
      <ngt-sphere-geometry [args]="[headRadius]"></ngt-sphere-geometry>
    </ngt-mesh>

    <ngt-mesh ngtPhysicBox [getPhysicProps]="getUpperBodyProps">
      <ngt-box-geometry [args]="upperBodyArgs"></ngt-box-geometry>
    </ngt-mesh>
  </ng-container>

  <ng-container ngtPhysicConeTwistConstraint [options]="elbowJoint">
    <ngt-mesh ngtPhysicBox [getPhysicProps]="getUpperLeftArmProps">
      <ngt-box-geometry [args]="upperArmArgs"></ngt-box-geometry>
    </ngt-mesh>

    <ngt-mesh ngtPhysicBox [getPhysicProps]="getLowerLeftArmProps">
      <ngt-box-geometry [args]="lowerArmArgs"></ngt-box-geometry>
    </ngt-mesh>
  </ng-container>

  <ng-container ngtPhysicConeTwistConstraint [options]="elbowJoint">
    <ngt-mesh ngtPhysicBox [getPhysicProps]="getUpperRightArmProps">
      <ngt-box-geometry [args]="upperArmArgs"></ngt-box-geometry>
    </ngt-mesh>

    <ngt-mesh ngtPhysicBox [getPhysicProps]="getLowerRightArmProps">
      <ngt-box-geometry [args]="lowerArmArgs"></ngt-box-geometry>
    </ngt-mesh>
  </ng-container>

  <ngt-mesh ngtPhysicBox [getPhysicProps]="getPelvisProps">
    <ngt-box-geometry [args]="pelvisArgs"></ngt-box-geometry>
  </ngt-mesh>

  <ng-container ngtPhysicConeTwistConstraint [options]="kneeJoint">
    <ngt-mesh ngtPhysicBox [getPhysicProps]="getUpperLeftLegProps">
      <ngt-box-geometry [args]="upperLegArgs"></ngt-box-geometry>
    </ngt-mesh>

    <ngt-mesh ngtPhysicBox [getPhysicProps]="getLowerLeftLegProps">
      <ngt-box-geometry [args]="lowerLegArgs"></ngt-box-geometry>
    </ngt-mesh>
  </ng-container>

  <ng-container ngtPhysicConeTwistConstraint [options]="kneeJoint">
    <ngt-mesh ngtPhysicBox [getPhysicProps]="getUpperRightLegProps">
      <ngt-box-geometry [args]="upperLegArgs"></ngt-box-geometry>
    </ngt-mesh>

    <ngt-mesh ngtPhysicBox [getPhysicProps]="getLowerRightLegProps">
      <ngt-box-geometry [args]="lowerLegArgs"></ngt-box-geometry>
    </ngt-mesh>
  </ng-container>

The example connects multiple meshes together using a cone twist constraint

       // Spine
        const spineJoint = new CANNON.ConeTwistConstraint(pelvis, upperBody, {
          pivotA: new CANNON.Vec3(0, 0, pelvisLength / 2),
          pivotB: new CANNON.Vec3(0, 0, -upperBodyLength / 2),
          axisA: CANNON.Vec3.UNIT_Z,
          axisB: CANNON.Vec3.UNIT_Z,
          angle,
          twistAngle,
        })

The current ngtPhysicConeTwistConstraint requires both meshes to be in the same ng-container. However, given how things are connected in this example, its no possible using the currently solution.

Here's what I am able to complete so far. The ragdoll on the left shows arms, legs and head connected, but not connected to the body parts. The ragdoll on the right is static showing how the parts should be connected.

image

Partially working code is here. Pick ragdoll

IRobot1 commented 2 years ago

One solution might be where the ngtPhysicConeTwistConstraint supports bodyAuuid and bodyB.uuid inputs that allow the two physics body to be declared, that might work. For example

    <ngt-mesh #head="ngtPhysicSphere" ngtPhysicSphere [getPhysicProps]="getHeadProps">
      <ngt-sphere-geometry [args]="[headRadius]"></ngt-sphere-geometry>
    </ngt-mesh>

    <ngt-mesh #upperBody="ngtPhysicBox" ngtPhysicBox [getPhysicProps]="getUpperBodyProps">
      <ngt-box-geometry [args]="upperBodyArgs"></ngt-box-geometry>
    </ngt-mesh>

 <ng-container ngtPhysicConeTwistConstraint [options]="neckjoint" [bodyA]="head" [bodyB]="upperBody">
  </ng-container>

Given this, perhaps using a dedicated ngt-conetwist-constraint element would make more sense than using ng-container.

  <ngt-conetwist-constraint [bodyA]="head" [bodyB]="upperBody" [options]="neckjoint">  
  </ngt-conetwist-constraint>
nartc commented 2 years ago

Thanks for exploring these! I'll try the ragdoll example from React Three tonight to see if we need to adjust the API of constraints somehow to make it work better in Angular. Once again, extremely appreciate your efforts!

nartc commented 2 years ago

@IRobot1 The React Three version (https://cannon.pmnd.rs/) recursively references the parent context to grab the parent Object3D. I think we can do the same for Angular (so no *ngFor). I'll explore more today if I have time.

IRobot1 commented 2 years ago

I tried creating a wrapper component that was recursive. It was passed the array of things to connect and the current index. Consider a component call 'conetwist-link'

<ng-container ngtXXXConstraint>
<ngt-mesh ngtPhysicXXX>
</ngt-mesh>
<conetwist-link *ngIf="index < constraints.length-1" [constraints]="constraints" [index]="index+1"></conetwist-link>
</ng-container>

I wasn't successful. The outer container (bodyA) doesn't see the mesh in the nested container (bodyB).

Without a good example, this will never be obvious on how to implement.

I still think declaring the constraints separate has merit. It would be easy to use with ngFor. ngIf would allow the constraint to be removed (arm to fall off or chain to split).

IRobot1 commented 2 years ago

I think that letting the developer decide how the constraints are applied is more flexible than doing it automatically.

One use case would be a link of objects connected with constraints, but having the first connected to the last. For having multiple chains connected to a central point (hanging lights).

nartc commented 2 years ago

@IRobot1 I agree. How would the public API look like?

IRobot1 commented 2 years ago

You mean something like this? @Input() bodyA: NgtPhysicBase - some type of base class @Input() bodyB: NgtPhysicBase - some type of base class @Input() options: Record<string, unknown>

api() : { methods like you have now } There could either be separate ngt-|type|-constraint elements or type could be an @Input. The problem with having Input is that it would support changing type which would not be good.

nartc commented 2 years ago

@IRobot1 After hours of pain last night trying out Ragdoll with Angular Three, I think the current implementation that use Directives aren't enough. I'll switch to Service-based Physic to see if it improves things.

@Component({
   template: `
      <ngt-mesh [objectRef]="boxRef"></ngt-mesh>
   `
})
export class SomeComponent {
   boxRef = this.physicBody.useBox(/* anything that box accepts */)

   constructor(private physicBody: NgtPhysicBody) {}
}

Something like this. But this requires me to change the underlying objects' wrappers to use ref instead (you can think of ref as I can make a Ref here and pass it around, updating to the Ref will make sure ALL the places that use the Ref get the changes)

IRobot1 commented 2 years ago

Sounds good. Sorry to cause a re-design. Take your time.
When you're ready, I can quickly convert the examples to your new design to help you valid all is good.

nartc commented 2 years ago

@IRobot1 I got the ref to work with Bodies.

import { NgtPhysicsModule } from '@angular-three/cannon';
import { NgtPhysicBody } from '@angular-three/cannon/bodies';
import { NgtCannonDebugModule } from '@angular-three/cannon/debug';
import {
    NgtCanvasModule,
    NgtColorPipeModule,
    NgtEuler,
    NgtTriple,
    NgtVector3,
} from '@angular-three/core';
import {
    NgtColorAttributeModule,
    NgtVector2AttributeModule,
} from '@angular-three/core/attributes';
import {
    NgtBoxGeometryModule,
    NgtPlaneGeometryModule,
} from '@angular-three/core/geometries';
import {
    NgtAmbientLightModule,
    NgtDirectionalLightModule,
} from '@angular-three/core/lights';
import {
    NgtMeshLambertMaterialModule,
    NgtShadowMaterialModule,
} from '@angular-three/core/materials';
import { NgtMeshModule } from '@angular-three/core/meshes';
import {
    ChangeDetectionStrategy,
    Component,
    Input,
    NgModule,
} from '@angular/core';

@Component({
    selector: 'sandbox-physic-cubes',
    template: `
        <ngt-canvas
            initialLog
            shadows
            [dpr]="[1, 2]"
            [gl]="{ alpha: false }"
            [camera]="{ position: [-1, 5, 5], fov: 45 }"
        >
            <ngt-color attach="background" color="lightblue"></ngt-color>
            <ngt-ambient-light></ngt-ambient-light>
            <ngt-directional-light [position]="10" castShadow>
                <ngt-vector2
                    [attach]="['shadow', 'mapSize']"
                    [vector2]="2048"
                ></ngt-vector2>
            </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>
    `,
    changeDetection: ChangeDetectionStrategy.OnPush,
})
export class SandboxPhysicCubesComponent {}

@Component({
    selector: 'sandbox-plane',
    template: `
        <!--                      👇 -->
        <ngt-mesh receiveShadow [ref]="planeRef.ref">
            <ngt-plane-geometry [args]="[1000, 1000]"></ngt-plane-geometry>
            <ngt-shadow-material
                color="#171717"
                [transparent]="true"
                [opacity]="0.4"
            ></ngt-shadow-material>
        </ngt-mesh>
    `,
    changeDetection: ChangeDetectionStrategy.OnPush,
    providers: [NgtPhysicBody],
})
export class SandboxPlaneComponent {
    @Input() position?: NgtVector3;
    rotation = [-Math.PI / 2, 0, 0] as NgtEuler;

    // 👇
    planeRef = this.physicBody.usePlane(() => ({
        args: [1000, 1000],
        rotation: this.rotation as NgtTriple,
        position: this.position as NgtTriple,
    }));

    constructor(private physicBody: NgtPhysicBody) {}
}

@Component({
    selector: 'sandbox-cube',
    template: `
        <!--                                 👇 -->
        <ngt-mesh receiveShadow castShadow [ref]="boxRef.ref">
            <ngt-box-geometry></ngt-box-geometry>
            <ngt-mesh-lambert-material
                color="hotpink"
            ></ngt-mesh-lambert-material>
        </ngt-mesh>
    `,
    changeDetection: ChangeDetectionStrategy.OnPush,
    providers: [NgtPhysicBody],
})
export class SandboxCubeComponent {
    @Input() position?: NgtVector3;
    rotation = [0.4, 0.2, 0.5] as NgtEuler;

    // 👇  
    boxRef = this.physicBody.useBox(() => ({
        mass: 1,
        position: this.position as NgtTriple,
        rotation: this.rotation as NgtTriple,
    }));

    constructor(private physicBody: NgtPhysicBody) {}
}

@NgModule({
    declarations: [
        SandboxPhysicCubesComponent,
        SandboxPlaneComponent,
        SandboxCubeComponent,
    ],
    exports: [SandboxPhysicCubesComponent, SandboxPlaneComponent],
    imports: [
        NgtCanvasModule,
        NgtColorAttributeModule,
        NgtAmbientLightModule,
        NgtDirectionalLightModule,
        NgtVector2AttributeModule,
        NgtPhysicsModule,
        NgtMeshModule,
        NgtPlaneGeometryModule,
        NgtShadowMaterialModule,
        NgtBoxGeometryModule,
        NgtMeshLambertMaterialModule,
        NgtCannonDebugModule,
        NgtColorPipeModule,
    ],
})
export class SandboxPhysicCubesModule {}
nartc commented 2 years ago

@IRobot1 good news. Got Constraint to work https://www.youtube.com/watch?v=fab5itCc-2A

The code is quite long and it's not available on Github yet (It's on my local branch with some breaking changes which I'll be releasing as the next major version for Angular Three)

import {
    ConeTwistConstraintOpts,
    NgtPhysicsModule,
} from '@angular-three/cannon';
import {
    NgtPhysicBody,
    NgtPhysicsBodyPublicApi,
} from '@angular-three/cannon/bodies';
import {
    NgtConstraintReturn,
    NgtPhysicConstraint,
} from '@angular-three/cannon/constraints';
import {
    NgtCanvasModule,
    NgtObject,
    NgtRenderState,
    NgtTriple,
    NgtVector3,
    Ref,
} from '@angular-three/core';
import {
    NgtColorAttributeModule,
    NgtFogAttributeModule,
    NgtVector3AttributeModule,
} from '@angular-three/core/attributes';
import {
    NgtBoxGeometryModule,
    NgtPlaneGeometryModule,
    NgtSphereGeometryModule,
} from '@angular-three/core/geometries';
import { NgtGroupModule } from '@angular-three/core/group';
import {
    NgtAmbientLightModule,
    NgtPointLightModule,
} from '@angular-three/core/lights';
import {
    NgtMeshBasicMaterialModule,
    NgtMeshStandardMaterialModule,
} from '@angular-three/core/materials';
import { NgtMeshModule } from '@angular-three/core/meshes';
import { CommonModule } from '@angular/common';
import {
    ChangeDetectionStrategy,
    Component,
    ContentChild,
    Directive,
    Input,
    NgModule,
    OnInit,
    Optional,
    Self,
    SkipSelf,
    TemplateRef,
} from '@angular/core';
import { takeUntil } from 'rxjs';
import * as THREE from 'three';
import { createRagdoll, ShapeConfig, ShapeName } from './monday-morning.config';

const { joints, shapes } = createRagdoll(4.8, Math.PI / 16, Math.PI / 16, 0);

const double = ([x, y, z]: Readonly<NgtTriple>): NgtTriple => [
    x * 2,
    y * 2,
    z * 2,
];

const cursor = new Ref<THREE.Object3D>();

@Component({
    selector: 'sandbox-monday-morning',
    template: `
        <ngt-canvas
            [camera]="{ far: 100, near: 1, position: [-25, 20, 25], zoom: 25 }"
            orthographic
            shadows
            style="cursor: none"
            initialLog
        >
            <ngt-color attach="background" color="#171720"></ngt-color>
            <ngt-fog attach="fog" [fog]="['#171720', 20, 70]"></ngt-fog>

            <ngt-ambient-light [intensity]="0.2"></ngt-ambient-light>
            <ngt-point-light
                [position]="[-10, -10, -10]"
                color="red"
                [intensity]="1.5"
            ></ngt-point-light>

            <ngt-physics
                [iterations]="15"
                [gravity]="[0, -200, 0]"
                [allowSleep]="false"
            >
                <sandbox-cursor></sandbox-cursor>
                <sandbox-ragdoll [position]="[0, 0, 0]"></sandbox-ragdoll>
                <sandbox-plane></sandbox-plane>
            </ngt-physics>

            <!--            <Physics iterations={15} gravity={[0, -200, 0]} allowSleep={false}>-->
            <!--                <Cursor />-->
            <!--                <Ragdoll position={[0, 0, 0]} />-->
            <!--                <Plane position={[0, -5, 0]} rotation={[-Math.PI / 2, 0, 0]} />-->
            <!--                <Chair />-->
            <!--                <Table />-->
            <!--                <Lamp />-->
            <!--            </Physics>-->
        </ngt-canvas>
    `,
    changeDetection: ChangeDetectionStrategy.OnPush,
})
export class SandboxMondayMorningComponent {}

@Component({
    selector: 'sandbox-cursor',
    template: `
        <ngt-mesh
            [ref]="sphereRef.ref"
            (beforeRender)="onCursorBeforeRender($event.state)"
        >
            <ngt-sphere-geometry [args]="[0.5, 32, 32]"></ngt-sphere-geometry>
            <ngt-mesh-basic-material
                [fog]="false"
                [depthTest]="false"
                [transparent]="true"
                [opacity]="0.5"
            ></ngt-mesh-basic-material>
        </ngt-mesh>
    `,
    changeDetection: ChangeDetectionStrategy.OnPush,
    providers: [NgtPhysicBody],
})
export class SandboxCursorComponent {
    sphereRef = this.physicBody.useSphere(
        () => ({
            args: [0.5],
            position: [0, 0, 10000],
            type: 'Static',
        }),
        cursor
    );

    constructor(private physicBody: NgtPhysicBody) {}

    onCursorBeforeRender({
        pointer,
        viewport: { width, height },
    }: NgtRenderState) {
        const x = pointer.x * width;
        const y = (pointer.y * height) / 1.9 + -x / 3.5;
        this.sphereRef.api.position.set(x / 1.4, y, 0);
    }
}

@Directive({
    selector: '[sandboxDragConstraint]',
    providers: [NgtPhysicConstraint],
})
export class SandboxDragConstraintDirective implements OnInit {
    private constraint!: NgtConstraintReturn<'PointToPoint'>;

    constructor(
        private physicConstraint: NgtPhysicConstraint,
        @Self()
        private object: NgtObject
    ) {
        object.pointerdown
            .pipe(takeUntil(object.destroy$))
            .subscribe((event: any) => {
                event.stopPropagation();
                event.target.setPointerCapture(event.pointerId);
                this.constraint.api.enable();
            });
        object.pointerup.pipe(takeUntil(object.destroy$)).subscribe(() => {
            this.constraint.api.disable();
        });
    }

    ngOnInit() {
        this.constraint = this.physicConstraint.usePointToPointConstraint(
            cursor,
            this.object.instance as unknown as Ref<THREE.Object3D>,
            { pivotA: [0, 0, 0], pivotB: [0, 0, 0] }
        );
        this.constraint.api.disable();
    }
}

@Component({
    selector: 'sandbox-box[name]',
    template: `
        <ng-container *ngIf="boxRef">
            <ngt-mesh
                castShadow
                receiveShadow
                sandboxDragConstraint
                [ref]="boxRef.ref"
                [name]="name"
                [position]="position"
            >
                <ngt-vector3 attach="scale" [vector3]="scale"></ngt-vector3>
                <ngt-box-geometry [args]="args"></ngt-box-geometry>
                <ngt-mesh-standard-material
                    [color]="shape.color"
                    [opacity]="opacity"
                    [transparent]="transparent"
                ></ngt-mesh-standard-material>
                <ng-container
                    *ngIf="renderTemplate && name !== 'upperBody'"
                    [ngTemplateOutlet]="renderTemplate"
                    [ngTemplateOutletContext]="{ scale, parent: boxRef.ref }"
                ></ng-container>
            </ngt-mesh>
            <ng-container
                *ngIf="childTemplate"
                [ngTemplateOutlet]="childTemplate"
            ></ng-container>
            <ng-content></ng-content>
        </ng-container>
    `,
    changeDetection: ChangeDetectionStrategy.OnPush,
    providers: [NgtPhysicBody, NgtPhysicConstraint],
})
export class SandboxBoxComponent implements OnInit {
    @Input() position?: NgtVector3;
    @Input() args: ConstructorParameters<typeof THREE.BoxGeometry> = [1, 1, 1];
    @Input() opacity = 1;
    @Input() transparent = false;

    @Input() name!: ShapeName;
    @Input() config: ConeTwistConstraintOpts = {};

    @ContentChild('child', { read: TemplateRef })
    childTemplate?: TemplateRef<unknown>;
    @ContentChild('render', { read: TemplateRef })
    renderTemplate?: TemplateRef<unknown>;

    shape!: ShapeConfig;
    scale!: NgtTriple;
    boxRef!: { ref: Ref<THREE.Object3D>; api: NgtPhysicsBodyPublicApi };

    constructor(
        @Optional()
        @SkipSelf()
        private parentBox: SandboxBoxComponent,
        private physicBody: NgtPhysicBody,
        private physicConstraint: NgtPhysicConstraint
    ) {}

    ngOnInit() {
        this.shape = shapes[this.name];
        this.scale = double(this.shape.args);
        this.boxRef = this.physicBody.useBox(() => ({
            args: [...this.shape.args],
            linearDamping: 0.99,
            mass: this.shape.mass,
            position: [...this.shape.position],
        }));
        if (this.parentBox) {
            this.physicConstraint.useConeTwistConstraint(
                this.boxRef.ref,
                this.parentBox.boxRef.ref,
                this.config
            );
        }
    }
}

@Component({
    selector: 'sandbox-ragdoll',
    template: `
        <sandbox-box [position]="position" name="upperBody">
            <ng-template #child>
                <sandbox-box
                    name="head"
                    [position]="position"
                    [config]="joints['neckJoint']"
                >
                    <ng-template #render let-scale="scale" let-parent="parent">
                        <ngt-group
                            (beforeRender)="onEyesBeforeRender($event)"
                            [appendTo]="parent"
                        >
                            <ngt-mesh
                                castShadow
                                receiveShadow
                                [position]="[-0.3, 0.1, 0.5]"
                            >
                                <ngt-vector3
                                    attach="scale"
                                    [vector3]="scale"
                                ></ngt-vector3>
                                <ngt-box-geometry
                                    [args]="[0.3, 0.01, 0.1]"
                                ></ngt-box-geometry>
                                <ngt-mesh-standard-material
                                    color="black"
                                    [opacity]="0.8"
                                    [transparent]="true"
                                ></ngt-mesh-standard-material>
                            </ngt-mesh>
                            <ngt-mesh
                                castShadow
                                receiveShadow
                                [position]="[0.3, 0.1, 0.5]"
                            >
                                <ngt-vector3
                                    attach="scale"
                                    [vector3]="scale"
                                ></ngt-vector3>
                                <ngt-box-geometry
                                    [args]="[0.3, 0.01, 0.1]"
                                ></ngt-box-geometry>
                                <ngt-mesh-standard-material
                                    color="black"
                                    [opacity]="0.8"
                                    [transparent]="true"
                                ></ngt-mesh-standard-material>
                            </ngt-mesh>
                        </ngt-group>
                        <ngt-mesh
                            castShadow
                            receiveShadow
                            [position]="[0, -0.2, 0.5]"
                            (beforeRender)="onMouthBeforeRender($event)"
                            [appendTo]="parent"
                        >
                            <ngt-vector3
                                attach="scale"
                                [vector3]="scale"
                            ></ngt-vector3>
                            <ngt-box-geometry
                                [args]="[0.3, 0.05, 0.1]"
                            ></ngt-box-geometry>
                            <ngt-mesh-standard-material
                                color="black"
                                [opacity]="0.8"
                                [transparent]="true"
                            ></ngt-mesh-standard-material>
                        </ngt-mesh>
                    </ng-template>
                </sandbox-box>

                <sandbox-box
                    name="upperLeftArm"
                    [config]="joints['leftShoulder']"
                    [position]="position"
                >
                    <ng-template #child>
                        <sandbox-box
                            [position]="position"
                            name="lowerLeftArm"
                            [config]="joints['leftElbowJoint']"
                        ></sandbox-box>
                    </ng-template>
                </sandbox-box>

                <sandbox-box
                    name="upperRightArm"
                    [config]="joints['rightShoulder']"
                    [position]="position"
                >
                    <ng-template #child>
                        <sandbox-box
                            [position]="position"
                            name="lowerRightArm"
                            [config]="joints['rightElbowJoint']"
                        ></sandbox-box>
                    </ng-template>
                </sandbox-box>

                <sandbox-box
                    name="pelvis"
                    [config]="joints['spineJoint']"
                    [position]="position"
                >
                    <ng-template #child>
                        <sandbox-box
                            name="upperLeftLeg"
                            [config]="joints['leftHipJoint']"
                            [position]="position"
                        >
                            <ng-template #child>
                                <sandbox-box
                                    name="lowerLeftLeg"
                                    [config]="joints['leftKneeJoint']"
                                    [position]="position"
                                ></sandbox-box>
                            </ng-template>
                        </sandbox-box>

                        <sandbox-box
                            name="upperRightLeg"
                            [config]="joints['rightHipJoint']"
                            [position]="position"
                        >
                            <ng-template #child>
                                <sandbox-box
                                    name="lowerRightLeg"
                                    [config]="joints['rightKneeJoint']"
                                    [position]="position"
                                ></sandbox-box>
                            </ng-template>
                        </sandbox-box>
                    </ng-template>
                </sandbox-box>
            </ng-template>
        </sandbox-box>
    `,
    changeDetection: ChangeDetectionStrategy.OnPush,
})
export class SandboxRagdollComponent {
    @Input() position?: NgtVector3;
    readonly joints = joints;

    onEyesBeforeRender({
        object,
        state: { clock },
    }: {
        state: NgtRenderState;
        object: THREE.Group;
    }) {
        object.position.y = Math.sin(clock.getElapsedTime()) * 0.06;
    }

    onMouthBeforeRender({
        object,
        state: { clock },
    }: {
        state: NgtRenderState;
        object: THREE.Mesh;
    }) {
        object.scale.y = (1 + Math.sin(clock.getElapsedTime())) * 1.5;
    }
}

@Component({
    selector: 'sandbox-plane',
    template: `
        <ngt-mesh receiveShadow [ref]="planeRef.ref">
            <ngt-plane-geometry [args]="[1000, 1000]"></ngt-plane-geometry>
            <ngt-mesh-standard-material
                color="#171720"
            ></ngt-mesh-standard-material>
        </ngt-mesh>
    `,
    changeDetection: ChangeDetectionStrategy.OnPush,
    providers: [NgtPhysicBody],
})
export class SandboxPlaneComponent {
    planeRef = this.physicBody.usePlane(() => ({
        args: [1000, 1000],
        position: [0, -5, 0],
        rotation: [-Math.PI / 2, 0, 0],
    }));

    constructor(private physicBody: NgtPhysicBody) {}
}

@NgModule({
    declarations: [
        SandboxMondayMorningComponent,
        SandboxCursorComponent,
        SandboxBoxComponent,
        SandboxRagdollComponent,
        SandboxPlaneComponent,
        SandboxDragConstraintDirective,
    ],
    exports: [SandboxMondayMorningComponent, SandboxBoxComponent],
    imports: [
        NgtMeshModule,
        NgtBoxGeometryModule,
        NgtMeshStandardMaterialModule,
        CommonModule,
        NgtGroupModule,
        NgtVector3AttributeModule,
        NgtPlaneGeometryModule,
        NgtCanvasModule,
        NgtColorAttributeModule,
        NgtFogAttributeModule,
        NgtAmbientLightModule,
        NgtPointLightModule,
        NgtPhysicsModule,
        NgtSphereGeometryModule,
        NgtMeshBasicMaterialModule,
    ],
})
export class SandboxMondayMorningModule {}
IRobot1 commented 2 years ago

Nice work. That approach looks much cleaner and more flexible.

nartc commented 2 years ago

@IRobot1 Thanks. If you'd like, I can try publishing beta version so you can play with the new API. Currently, I can't because soba and postprocessing are broken w/ the breaking changes :D

IRobot1 commented 2 years ago

I'd be happy to give it a try

IRobot1 commented 2 years ago

Fixed in version 5 when its released. See constraints example