diff --git a/docssrc/pages/index.mdx b/docssrc/pages/index.mdx index e8cc11c..5e684e5 100644 --- a/docssrc/pages/index.mdx +++ b/docssrc/pages/index.mdx @@ -16,7 +16,7 @@ import { HomePage } from "vocs/components"; [KAPLAY](https://kaplayjs.com/). - + Get started diff --git a/src/examples/8-Ball.ts b/src/examples/8-Ball.ts index 463225a..5480ba7 100644 --- a/src/examples/8-Ball.ts +++ b/src/examples/8-Ball.ts @@ -54,8 +54,8 @@ const eightBallScene = (k: KAPLANCKCtx) => () => { const ballRadius = 1; const pocketRadius = 1.6; - const width = k.p2u(k.width() - 200); - const height = k.p2u(k.height() - 200); + const width = k.p2m(k.width() - 200); + const height = k.p2m(k.height() - 200); const railH = [ new Vec2(pocketRadius, height * 0.5), @@ -81,18 +81,12 @@ const eightBallScene = (k: KAPLANCKCtx) => () => { const railFixDef = { friction: 0.1, restitution: 0.9, - userData: { - tag: "rail", - }, - }; - const pocketFixDef = { - userData: { tag: "pocket" }, }; + const pocketFixDef = {}; const ballFixDef: KPFixtureDef = { friction: 0.1, restitution: 0.99, density: 1, - userData: { tag: "ball" }, }; const ballBodyDef: KPBodyDef = { type: "dynamic", diff --git a/src/examples/AddPair.ts b/src/examples/AddPair.ts index 5e8ed66..106cba4 100644 --- a/src/examples/AddPair.ts +++ b/src/examples/AddPair.ts @@ -48,7 +48,7 @@ const addPairScene = (k: KAPLANCKCtx) => () => { k.kpFixture({ density: 1 }), ]); - box.body?.setLinearVelocity({ x: 100, y: 0 }); + box.setLinearVelocity({ x: 100, y: 0 }); addScenesButtons(k, scene); }; diff --git a/src/examples/ApplyForce.ts b/src/examples/ApplyForce.ts index dfad97e..bdb1406 100644 --- a/src/examples/ApplyForce.ts +++ b/src/examples/ApplyForce.ts @@ -21,7 +21,7 @@ * SOFTWARE. */ -import { FrictionJoint, Transform, Vec2 } from "planck"; +import { Transform, Vec2 } from "planck"; import { KPFixtureDef } from "../lib"; import { addScenesButtons, type KAPLANCKCtx } from "../shared"; @@ -86,14 +86,14 @@ const applyForceScene = (k: KAPLANCKCtx) => () => { const xf1 = new Transform(); const xf2 = new Transform(); - xf1.q.set(0.3524 * Math.PI); + xf1.q.set(-0.3524 * Math.PI); xf1.p.set(xf1.q.getXAxis()); - xf2.q.set(-0.3524 * Math.PI); + xf2.q.set(0.3524 * Math.PI); xf2.p.set(Vec2.neg(xf2.q.getXAxis())); const jet = worldContainer.add([ k.kpPos(k.kpCenter().add({ x: 0, y: 18 })), - k.kpRotate(), + k.kpRotate(Math.PI), k.kpBody({ type: "dynamic", angularDamping: 2, @@ -107,22 +107,26 @@ const applyForceScene = (k: KAPLANCKCtx) => () => { vertices: [ new Vec2(-1.0, 0.0), new Vec2(1.0, 0.0), - new Vec2(0.0, 0.5), + new Vec2(0.0, -0.5), ].map((v) => Transform.mul(xf1, v)), draw: true, + fill: false, }), k.kpFixture({ density: 2 }), + k.outline(1, new k.Color(255, 255, 255)), ]); jet.add([ k.kpPolygonShape({ vertices: [ new Vec2(-1.0, 0.0), new Vec2(1.0, 0.0), - new Vec2(0.0, 0.5), + new Vec2(0.0, -0.5), ].map((v) => Transform.mul(xf2, v)), draw: true, + fill: false, }), k.kpFixture({ density: 2 }), + k.outline(1, new k.Color(255, 255, 255)), ]); if (!ground.body) return; @@ -136,9 +140,10 @@ const applyForceScene = (k: KAPLANCKCtx) => () => { const box = worldContainer.add([ k.kpPos(k.kpCenter().add({ x: 0, y: 15 - 1.54 * i })), k.kpRotate(), - k.kpBoxShape({ width: 1, height: 1, draw: true }), + k.kpBoxShape({ width: 1, height: 1, draw: true, fill: false }), k.kpBody({ type: "dynamic" }), k.kpFixture(boxFixtureDef), + k.outline(1, new k.Color(255, 255, 255)), ]); if (!box.body) return; @@ -148,19 +153,21 @@ const applyForceScene = (k: KAPLANCKCtx) => () => { const mass = box.body.getMass(); const radius = Math.sqrt((2 * I) / mass); - // TODO: create a component so that the joint can be drawn/visualized on debug mode - worldContainer.world.createJoint( - new FrictionJoint( + worldContainer.add([ + k.kpFrictionJoint( { collideConnected: true, maxForce: mass * gravity, maxTorque: mass * radius * gravity, + localAnchorA: Vec2.zero(), + localAnchorB: Vec2.zero(), + draw: true, }, - ground.body, - box.body, - Vec2.zero(), + ground, + box, + worldContainer, ), - ); + ]); } scene.onUpdate(() => { @@ -173,8 +180,8 @@ const applyForceScene = (k: KAPLANCKCtx) => () => { if (k.isKeyDown("up")) { if (!jet.body) return; - const f = jet.body.getWorldVector(new Vec2(0, -1)); - const p = jet.body.getWorldPoint(new Vec2(0, 2)); + const f = jet.body.getWorldVector(new Vec2(0, 1)); + const p = jet.body.getWorldPoint(new Vec2(0, -2)); jet.body?.applyLinearImpulse(f, p, true); } diff --git a/src/examples/MotorJoint.ts b/src/examples/MotorJoint.ts new file mode 100644 index 0000000..d6cbf1b --- /dev/null +++ b/src/examples/MotorJoint.ts @@ -0,0 +1,88 @@ +/* + * MIT License + * Copyright (c) 2019 Erin Catto + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +import { Vec2 } from "planck"; +import { addScenesButtons, type KAPLANCKCtx } from "../shared"; + +const motorJointScene = (k: KAPLANCKCtx) => () => { + const scene = k.add([]); + + const worldContainer = scene.add([k.kpWorld(new Vec2(0, 10))]); + + const ground = worldContainer.add([ + k.kpPos(k.kpCenter()), + k.kpRotate(), + k.kpBody(), + k.kpEdgeShape({ + v1: new Vec2(-20, 0), + v2: new Vec2(20, 0), + draw: true, + }), + k.kpFixture(), + ]); + + const box = worldContainer.add([ + k.kpPos(k.kpCenter().add({ x: 0, y: -8 })), + k.kpRotate(), + k.kpBody({ type: "dynamic" }), + k.kpBoxShape({ + width: 4, + height: 1, + draw: true, + fill: false, + }), + k.kpFixture({ + friction: 0.6, + density: 2, + }), + k.outline(1, new k.Color(255, 255, 255)), + ]); + + const joint = worldContainer.add([ + k.kpMotorJoint( + { + maxForce: 1000, + maxTorque: 1000, + draw: true, + }, + ground, + box, + worldContainer, + ), + ]); + + let time = 0; + + worldContainer.onUpdate(() => { + time += k.dt(); + + joint.setLinearOffset( + new Vec2(6 * Math.sin(2 * time), -8 + 4 * Math.sin(time)), + ); + joint.setAngularOffset(4 * time); + }); + + addScenesButtons(k, scene); +}; + +export default motorJointScene; diff --git a/src/examples/Tumbler.ts b/src/examples/Tumbler.ts index 8472f91..4c6797e 100644 --- a/src/examples/Tumbler.ts +++ b/src/examples/Tumbler.ts @@ -1,4 +1,27 @@ -import { RevoluteJoint, Vec2 } from "planck"; +/* + * MIT License + * Copyright (c) 2019 Erin Catto + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +import { Vec2 } from "planck"; import { addScenesButtons, type KAPLANCKCtx } from "../shared"; const tumblerScene = (k: KAPLANCKCtx) => () => { @@ -99,19 +122,21 @@ const tumblerScene = (k: KAPLANCKCtx) => () => { k.outline(1, new k.Color(200, 200, 200)), ]); - // TODO: create a component so that the joint can be drawn/visualized on debug mode - worldContainer.world.createJoint( - new RevoluteJoint( + worldContainer.add([ + k.kpRevoluteJoint( { - motorSpeed: 0.08 * Math.PI, + motorSpeed: 0.08 * -Math.PI, maxMotorTorque: 1e8, enableMotor: true, + localAnchorA: k.kpCenter(), + localAnchorB: Vec2.zero(), + referenceAngle: 0, }, - ground.body, - container.body, - k.kpCenter(), + ground, + container, + worldContainer, ), - ); + ]); let count = 200; diff --git a/src/examples/Web.ts b/src/examples/Web.ts new file mode 100644 index 0000000..5a3592d --- /dev/null +++ b/src/examples/Web.ts @@ -0,0 +1,209 @@ +/* + * MIT License + * Copyright (c) 2019 Erin Catto + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +import type { GameObj } from "kaplay"; +import { Vec2 } from "planck"; +import type { + KPBodyComp, + KPDistanceJointComp, + KPPosComp, + KPRotateComp, +} from "../lib"; +import { addScenesButtons, type KAPLANCKCtx } from "../shared"; + +const webScene = (k: KAPLANCKCtx) => () => { + const scene = k.add([]); + + const worldContainer = scene.add([k.kpWorld(new Vec2(0, 10))]); + + const ground = worldContainer.add([ + k.kpPos(k.kpCenter()), + k.kpRotate(), + k.kpBody(), + k.kpEdgeShape({ + v1: new Vec2(-40, 0), + v2: new Vec2(40, 0), + draw: true, + }), + k.kpFixture(), + ]); + + const bodies: GameObj>[] = []; + let joints: GameObj[] = []; + + const bodyPositions = [ + { x: -5, y: -5 }, + { x: 5, y: -5 }, + { x: 5, y: -15 }, + { x: -5, y: -15 }, + ]; + + for (const pos of bodyPositions) { + bodies.push( + worldContainer.add([ + k.kpPos(k.kpCenter().add(pos)), + k.kpRotate(), + k.kpBody({ type: "dynamic" }), + k.kpBoxShape({ width: 1, height: 1, draw: true }), + k.kpFixture({ density: 5 }), + ]), + ); + } + + joints.push( + worldContainer.add([ + k.kpDistanceJoint( + { + localAnchorA: new Vec2(-10, 0), + localAnchorB: new Vec2(-0.5, 0.5), + draw: true, + }, + ground, + bodies[0], + worldContainer, + ), + ]), + ); + joints.push( + worldContainer.add([ + k.kpDistanceJoint( + { + localAnchorA: new Vec2(10, 0), + localAnchorB: new Vec2(0.5, 0.5), + draw: true, + }, + ground, + bodies[1], + worldContainer, + ), + ]), + ); + joints.push( + worldContainer.add([ + k.kpDistanceJoint( + { + localAnchorA: new Vec2(10, -20), + localAnchorB: new Vec2(0.5, -0.5), + draw: true, + }, + ground, + bodies[2], + worldContainer, + ), + ]), + ); + joints.push( + worldContainer.add([ + k.kpDistanceJoint( + { + localAnchorA: new Vec2(-10, -20), + localAnchorB: new Vec2(-0.5, -0.5), + draw: true, + }, + ground, + bodies[3], + worldContainer, + ), + ]), + ); + + joints.push( + worldContainer.add([ + k.kpDistanceJoint( + { + localAnchorA: new Vec2(0.5, 0), + localAnchorB: new Vec2(-0.5, 0), + draw: true, + }, + bodies[0], + bodies[1], + worldContainer, + ), + ]), + ); + joints.push( + worldContainer.add([ + k.kpDistanceJoint( + { + localAnchorA: new Vec2(0, -0.5), + localAnchorB: new Vec2(0, 0.5), + draw: true, + }, + bodies[1], + bodies[2], + worldContainer, + ), + ]), + ); + joints.push( + worldContainer.add([ + k.kpDistanceJoint( + { + localAnchorA: new Vec2(-0.5, 0), + localAnchorB: new Vec2(0.5, 0), + draw: true, + }, + bodies[2], + bodies[3], + worldContainer, + ), + ]), + ); + joints.push( + worldContainer.add([ + k.kpDistanceJoint( + { + localAnchorA: new Vec2(0, 0.5), + localAnchorB: new Vec2(0, -0.5), + draw: true, + }, + bodies[3], + bodies[0], + worldContainer, + ), + ]), + ); + + scene.onKeyRelease("x", () => { + if (bodies.length) { + worldContainer.addToDestroyList(bodies.pop()!); + } + }); + scene.onKeyRelease("z", () => { + if (joints.length) { + worldContainer.addToDestroyList(joints.pop()!); + } + }); + + worldContainer.onRemoveJoint((joint) => { + if (joints.length) { + joints = joints.filter((j) => j !== joint); + + joint.destroy(); + } + }); + + addScenesButtons(k, scene); +}; + +export default webScene; diff --git a/src/lib/components/Body.ts b/src/lib/components/Body.ts index 05ad32b..73640d3 100644 --- a/src/lib/components/Body.ts +++ b/src/lib/components/Body.ts @@ -23,6 +23,7 @@ import { type Vec2Value, } from "planck"; +import { KPUserData } from "../types"; import { getWorldContainerFromGameObj, getWorldFromGameObj, @@ -33,10 +34,6 @@ import type { KPRotateComp } from "./Rotate"; export type KPBodyDef = Omit; -export interface KPBodyUserData { - gameObj: GameObj; -} - export interface KPBodyComp extends Comp { /** * The physics body associated with the game object. @@ -441,10 +438,10 @@ export default function body( checkContact(this: BodyCompThis, other: GameObj) { for (let edge = this.body.getContactList(); edge; edge = edge.next) { const gameObjA = ( - edge.contact.getFixtureA().getBody().getUserData() as KPBodyUserData + edge.contact.getFixtureA().getUserData() as KPUserData ).gameObj; const gameObjB = ( - edge.contact.getFixtureB().getBody().getUserData() as KPBodyUserData + edge.contact.getFixtureB().getUserData() as KPUserData ).gameObj; if ( @@ -683,12 +680,8 @@ function handleCollisionEvent( tag: Tag | null, cb: ActionCB, ) { - const gameObjA = ( - contact.getFixtureA().getBody().getUserData() as KPBodyUserData - ).gameObj; - const gameObjB = ( - contact.getFixtureB().getBody().getUserData() as KPBodyUserData - ).gameObj; + const gameObjA = (contact.getFixtureA().getUserData() as KPUserData).gameObj; + const gameObjB = (contact.getFixtureB().getUserData() as KPUserData).gameObj; let thatGameObj = gameObjA; diff --git a/src/lib/components/BoxShape.ts b/src/lib/components/BoxShape.ts index d7940d0..582dabb 100644 --- a/src/lib/components/BoxShape.ts +++ b/src/lib/components/BoxShape.ts @@ -2,7 +2,7 @@ import type { GameObj, KAPLAYCtx } from "kaplay"; import { type KPShapeComp, type KPShapeOpt } from "./Shape"; import { BoxShape, type Vec2Value } from "planck"; -import { getRenderProps, p2kVec2, u2p } from "../utils"; +import { getRenderProps, m2p, p2kVec2 } from "../utils"; export interface KPBoxShapeComp extends KPShapeComp { shape: BoxShape; @@ -72,8 +72,8 @@ export default function boxShape( if (!opt.draw) return; const renderingProps = getRenderProps(this); - const width = u2p(opt.width); - const height = u2p(opt.height); + const width = m2p(opt.width); + const height = m2p(opt.height); const pos = k.vec2(p2kVec2(k, this.shape.m_centroid)); k.drawRect({ diff --git a/src/lib/components/CircleShape.ts b/src/lib/components/CircleShape.ts index d41a6e7..57bbda1 100644 --- a/src/lib/components/CircleShape.ts +++ b/src/lib/components/CircleShape.ts @@ -2,7 +2,7 @@ import type { GameObj, KAPLAYCtx } from "kaplay"; import { type KPShapeComp, type KPShapeOpt } from "./Shape"; import { CircleShape, Vec2, type Vec2Value } from "planck"; -import { getRenderProps, p2kVec2, u2p } from "../utils"; +import { getRenderProps, m2p, p2kVec2 } from "../utils"; export interface KPCircleShapeComp extends KPShapeComp { shape: CircleShape; @@ -44,7 +44,7 @@ export default function circleShape( k.drawCircle({ ...renderingProps, pos: p2kVec2(k, this.shape.getCenter()), - radius: u2p(this.shape.getRadius()), + radius: m2p(this.shape.getRadius()), fill: opt?.fill, }); }, diff --git a/src/lib/components/DistanceJoint.ts b/src/lib/components/DistanceJoint.ts new file mode 100644 index 0000000..a1b617f --- /dev/null +++ b/src/lib/components/DistanceJoint.ts @@ -0,0 +1,112 @@ +import type { GameObj, KAPLAYCtx } from "kaplay"; +import { DistanceJoint, DistanceJointDef, Vec2 } from "planck"; + +import { getWorldFromGameObj } from "../utils"; +import type { KPBodyComp } from "./Body"; +import type { KPJointComp } from "./Joint"; +import joint from "./Joint"; +import type { KPPosComp } from "./Position"; +import type { KPRotateComp } from "./Rotate"; +import type { KPWorldComp } from "./World"; + +export interface KPDistanceJointDef + extends Omit { + draw?: boolean; +} + +export interface KPDistanceJointComp extends KPJointComp { + joint: DistanceJoint; + + getDampingRatio(): number; + getFrequency(): number; + getLength(): number; + getLocalAnchorA(): Vec2; + getLocalAnchorB(): Vec2; + setDampingRatio(damping_ratio: number): void; + setFrequency(frequency: number): void; + setLength(length: number): void; +} + +type DistanceJointCompThis = GameObj; + +export default function distanceJoint( + k: KAPLAYCtx, + def: KPDistanceJointDef, + gameObjA: GameObj, + gameObjB: GameObj, + worldContainer?: GameObj, +): KPDistanceJointComp { + let _joint: DistanceJoint | null = null; + + return { + ...joint(k, gameObjA, gameObjB, def.draw), + + id: "kpDistanceJoint", + + get joint() { + if (!_joint) { + throw new Error("kpDistanceJoint not initialized"); + } + + return _joint; + }, + + getDampingRatio() { + return this.joint.getDampingRatio(); + }, + getFrequency() { + return this.joint.getFrequency(); + }, + getLength() { + return this.joint.getLength(); + }, + getLocalAnchorA() { + return this.joint.getLocalAnchorA(); + }, + getLocalAnchorB() { + return this.joint.getLocalAnchorB(); + }, + setDampingRatio(damping_ratio: number) { + this.joint.setDampingRatio(damping_ratio); + }, + setFrequency(frequency: number) { + this.joint.setFrequency(frequency); + }, + setLength(length: number) { + this.joint.setLength(length); + }, + + add(this: DistanceJointCompThis) { + const world = worldContainer + ? worldContainer.world + : getWorldFromGameObj(this); + + if (!world) { + throw new Error( + "kpDistanceJoint requires to be a descendant of kpWorld", + ); + } + + const userData = def?.userData ?? {}; + + _joint = world.createJoint( + new DistanceJoint({ + ...def, + bodyA: gameObjA.body, + bodyB: gameObjB.body, + userData: { + ...userData, + gameObj: this, + }, + }), + ); + }, + destroy() { + const world = this.joint.getBodyA().getWorld(); + + world.destroyJoint(this.joint); + + _joint = null; + }, + }; +} diff --git a/src/lib/components/Fixture.ts b/src/lib/components/Fixture.ts index 9691280..fa04019 100644 --- a/src/lib/components/Fixture.ts +++ b/src/lib/components/Fixture.ts @@ -1,7 +1,8 @@ import type { Comp, GameObj } from "kaplay"; import type { Fixture, FixtureDef } from "planck"; -import type { KPBodyComp, KPBodyUserData } from "./Body"; +import type { KPUserData } from "../types"; +import type { KPBodyComp } from "./Body"; import type { KPShapeComp } from "./Shape"; export type KPFixtureDef = Omit; @@ -79,15 +80,21 @@ export default function fixture(def?: KPFixtureDef): KPFixtureComp { throw new Error("A body is required"); } + const userData = def?.userData ?? {}; + _fixture = gameObjWithKPBodyComp.body.createFixture({ ...def, shape: this.shape, + userData: { + ...userData, + gameObj: this, + }, }); this.fixture.shouldCollide = (that: Fixture) => { if (gameObjWithKPBodyComp.collisionIgnore.length === 0) return true; - const userData = that.getBody().getUserData() as KPBodyUserData; + const userData = that.getUserData() as KPUserData; const thatGameObj = userData.gameObj; diff --git a/src/lib/components/FrictionJoint.ts b/src/lib/components/FrictionJoint.ts new file mode 100644 index 0000000..b71bd0a --- /dev/null +++ b/src/lib/components/FrictionJoint.ts @@ -0,0 +1,102 @@ +import type { GameObj, KAPLAYCtx } from "kaplay"; +import { FrictionJoint, type FrictionJointDef, type Vec2 } from "planck"; +import { getWorldFromGameObj } from "../utils"; +import type { KPBodyComp } from "./Body"; +import type { KPJointComp } from "./Joint"; +import joint from "./Joint"; +import type { KPPosComp } from "./Position"; +import type { KPRotateComp } from "./Rotate"; +import type { KPWorldComp } from "./World"; + +export interface KPFrictionJointDef + extends Omit { + draw?: boolean; +} + +export interface KPFrictionJointComp extends KPJointComp { + joint: FrictionJoint; + + getLocalAnchorA(): Vec2; + getLocalAnchorB(): Vec2; + getMaxForce(): number; + getMaxTorque(): number; + setMaxForce(force: number): void; + setMaxTorque(torque: number): void; +} + +type FrictionJointCompThis = GameObj; + +export default function frictionJoint( + k: KAPLAYCtx, + def: KPFrictionJointDef, + gameObjA: GameObj, + gameObjB: GameObj, + worldContainer?: GameObj, +): KPFrictionJointComp { + let _joint: FrictionJoint | null = null; + + return { + ...joint(k, gameObjA, gameObjB, def.draw), + + id: "kpFrictionJoint", + + get joint() { + if (!_joint) { + throw new Error("kpFrictionJoint not initialized"); + } + + return _joint; + }, + getLocalAnchorA() { + return this.joint.getLocalAnchorA(); + }, + getLocalAnchorB() { + return this.joint.getLocalAnchorB(); + }, + getMaxForce() { + return this.joint.getMaxForce(); + }, + getMaxTorque() { + return this.joint.getMaxTorque(); + }, + setMaxForce(force: number) { + this.joint.setMaxForce(force); + }, + setMaxTorque(torque: number) { + this.joint.setMaxTorque(torque); + }, + + add(this: FrictionJointCompThis) { + const world = worldContainer + ? worldContainer.world + : getWorldFromGameObj(this); + + if (!world) { + throw new Error( + "kpFrictionJoint requires to be a descendant of kpWorld", + ); + } + + const userData = def?.userData ?? {}; + + _joint = world.createJoint( + new FrictionJoint({ + ...def, + bodyA: gameObjA.body, + bodyB: gameObjB.body, + userData: { + ...userData, + gameObj: this, + }, + }), + ); + }, + destroy() { + const world = this.joint.getBodyA().getWorld(); + + world.destroyJoint(this.joint); + + _joint = null; + }, + }; +} diff --git a/src/lib/components/GearJoint.ts b/src/lib/components/GearJoint.ts new file mode 100644 index 0000000..54495e7 --- /dev/null +++ b/src/lib/components/GearJoint.ts @@ -0,0 +1,98 @@ +import type { GameObj, KAPLAYCtx } from "kaplay"; +import { type GearJointDef, GearJoint } from "planck"; +import { getWorldFromGameObj } from "../utils"; +import type { KPBodyComp } from "./Body"; +import joint, { type KPJointComp } from "./Joint"; +import type { KPPosComp } from "./Position"; +import type { KPPrismaticJointComp } from "./PrismaticJoint"; +import type { KPRevoluteJointComp } from "./RevoluteJoint"; +import type { KPRotateComp } from "./Rotate"; +import type { KPWorldComp } from "./World"; + +export interface KPGearJointDef + extends Omit { + draw?: boolean; +} + +export interface KPGearJointComp extends KPJointComp { + joint: GearJoint; + + getJoint1(): GameObj | GameObj; + getJoint2(): GameObj | GameObj; + getRatio(): number; + setRatio(ratio: number): void; +} + +type GearJointCompThis = GameObj; + +export default function gearJoint( + k: KAPLAYCtx, + def: KPGearJointDef, + gameObjA: GameObj, + gameObjB: GameObj, + jointGameObj1: GameObj | GameObj, + jointGameObj2: GameObj | GameObj, + worldContainer?: GameObj, +): KPGearJointComp { + let _joint: GearJoint | null = null; + + return { + ...joint(k, gameObjA, gameObjB, def.draw), + + id: "kpGearJoint", + + get joint() { + if (!_joint) { + throw new Error("kpGearJoint not initialized"); + } + + return _joint; + }, + + getJoint1() { + return jointGameObj1; + }, + getJoint2() { + return jointGameObj2; + }, + getRatio() { + return this.joint.getRatio(); + }, + setRatio(ratio: number) { + this.joint.setRatio(ratio); + }, + + add(this: GearJointCompThis) { + const world = worldContainer + ? worldContainer.world + : getWorldFromGameObj(this); + + if (!world) { + throw new Error("kpGearJoint requires to be a descendant of kpWorld"); + } + + const userData = def?.userData ?? {}; + + _joint = world.createJoint( + new GearJoint({ + ...def, + bodyA: gameObjA.body, + bodyB: gameObjB.body, + joint1: jointGameObj1.joint, + joint2: jointGameObj2.joint, + userData: { + ...userData, + gameObj: this, + }, + }), + ); + }, + destroy() { + const world = this.joint.getBodyA().getWorld(); + + world.destroyJoint(this.joint); + + _joint = null; + }, + }; +} diff --git a/src/lib/components/Joint.ts b/src/lib/components/Joint.ts new file mode 100644 index 0000000..836a48e --- /dev/null +++ b/src/lib/components/Joint.ts @@ -0,0 +1,82 @@ +import type { Comp, GameObj, KAPLAYCtx } from "kaplay"; +import { type Joint, Vec2, type Vec2Value } from "planck"; + +import { p2kVec2 } from "../utils"; +import type { KPBodyComp } from "./Body"; +import type { KPPosComp } from "./Position"; +import type { KPRotateComp } from "./Rotate"; + +export interface KPJointComp extends Comp { + joint: Joint; + + getAnchorA(): Vec2; + getAnchorB(): Vec2; + getCollideConnected(): boolean; + getGameObjA(): GameObj; + getGameObjB(): GameObj; + isActive(): boolean; + shiftOrigin(new_origin: Vec2Value): void; +} + +type JointCompThis = GameObj; + +export default function joint( + k: KAPLAYCtx, + gameObjA: GameObj, + gameObjB: GameObj, + draw = false, +): KPJointComp { + const regularColor = new k.Color(255, 255, 255); + const inspectColor = new k.Color(0, 0, 255); + + return { + // Should be replaced with the actual joint implementation + id: "kpJoint", + + get joint() { + // Should be implemented by the actual joint implementation + return {} as Joint; + }, + + getAnchorA() { + return this.joint.getAnchorA(); + }, + getAnchorB() { + return this.joint.getAnchorB(); + }, + getCollideConnected() { + return this.joint.getCollideConnected(); + }, + getGameObjA() { + return gameObjA; + }, + getGameObjB() { + return gameObjB; + }, + isActive() { + return this.joint.isActive(); + }, + shiftOrigin(new_origin: Vec2Value) { + this.joint.shiftOrigin(new_origin); + }, + + draw() { + if (!draw) return; + + k.drawLine({ + p1: p2kVec2(k, new Vec2(this.getAnchorA())), + p2: p2kVec2(k, new Vec2(this.getAnchorB())), + width: 1, + color: regularColor, + }); + }, + drawInspect(this: JointCompThis) { + k.drawLine({ + p1: p2kVec2(k, new Vec2(this.getAnchorA())), + p2: p2kVec2(k, new Vec2(this.getAnchorB())), + width: 4, + color: inspectColor, + }); + }, + }; +} diff --git a/src/lib/components/MotorJoint.ts b/src/lib/components/MotorJoint.ts new file mode 100644 index 0000000..6af72d0 --- /dev/null +++ b/src/lib/components/MotorJoint.ts @@ -0,0 +1,121 @@ +import type { GameObj, KAPLAYCtx } from "kaplay"; +import { + MotorJoint, + type MotorJointDef, + type Vec2, + type Vec2Value, +} from "planck"; +import { getWorldFromGameObj } from "../utils"; +import type { KPBodyComp } from "./Body"; +import joint, { KPJointComp } from "./Joint"; +import type { KPPosComp } from "./Position"; +import type { KPRotateComp } from "./Rotate"; +import type { KPWorldComp } from "./World"; + +export interface KPMotorJointDef + extends Omit { + draw?: boolean; +} + +export interface KPMotorJointComp extends KPJointComp { + joint: MotorJoint; + + getAngularOffset(): number; + getCorrectionFactor(): number; + getLinearOffset(): Vec2; + getMaxForce(): number; + getMaxTorque(): number; + setAngularOffset(angularOffset: number): void; + setCorrectionFactor(factor: number): void; + setLinearOffset(linearOffset: Vec2Value): void; + setMaxForce(force: number): void; + setMaxTorque(torque: number): void; +} + +type MotorJointCompThis = GameObj; + +export default function motorJoint( + k: KAPLAYCtx, + def: KPMotorJointDef, + gameObjA: GameObj, + gameObjB: GameObj, + worldContainer?: GameObj, +): KPMotorJointComp { + let _joint: MotorJoint | null = null; + + return { + ...joint(k, gameObjA, gameObjB, def.draw), + + id: "kpMotorJoint", + + get joint() { + if (!_joint) { + throw new Error("kpMotorJoint not initialized"); + } + + return _joint; + }, + + getAngularOffset() { + return this.joint.getAngularOffset(); + }, + getCorrectionFactor() { + return this.joint.getCorrectionFactor(); + }, + getLinearOffset() { + return this.joint.getLinearOffset(); + }, + getMaxForce() { + return this.joint.getMaxForce(); + }, + getMaxTorque() { + return this.joint.getMaxTorque(); + }, + setAngularOffset(angularOffset: number) { + this.joint.setAngularOffset(angularOffset); + }, + setCorrectionFactor(factor: number) { + this.joint.setCorrectionFactor(factor); + }, + setLinearOffset(linearOffset: Vec2Value) { + this.joint.setLinearOffset(linearOffset); + }, + setMaxForce(force: number) { + this.joint.setMaxForce(force); + }, + setMaxTorque(torque: number) { + this.joint.setMaxTorque(torque); + }, + + add(this: MotorJointCompThis) { + const world = worldContainer + ? worldContainer.world + : getWorldFromGameObj(this); + + if (!world) { + throw new Error("kpMotorJoint requires to be a descendant of kpWorld"); + } + + const userData = def?.userData ?? {}; + + _joint = world.createJoint( + new MotorJoint({ + ...def, + bodyA: gameObjA.body, + bodyB: gameObjB.body, + userData: { + ...userData, + gameObj: this, + }, + }), + ); + }, + destroy() { + const world = this.joint.getBodyA().getWorld(); + + world.destroyJoint(this.joint); + + _joint = null; + }, + }; +} diff --git a/src/lib/components/MouseJoint.ts b/src/lib/components/MouseJoint.ts new file mode 100644 index 0000000..0b68cae --- /dev/null +++ b/src/lib/components/MouseJoint.ts @@ -0,0 +1,114 @@ +import type { GameObj, KAPLAYCtx } from "kaplay"; +import { + MouseJoint, + type MouseJointDef, + type Vec2, + type Vec2Value, +} from "planck"; +import { getWorldFromGameObj } from "../utils"; +import type { KPBodyComp } from "./Body"; +import type { KPJointComp } from "./Joint"; +import joint from "./Joint"; +import type { KPPosComp } from "./Position"; +import type { KPRotateComp } from "./Rotate"; +import type { KPWorldComp } from "./World"; + +export interface KPMouseJointDef + extends Omit { + draw?: boolean; +} + +export interface KPMouseJointComp extends KPJointComp { + joint: MouseJoint; + + getDampingRatio(): number; + getFrequency(): number; + getMaxForce(): number; + getTarget(): Vec2; + setDampingRatio(dampingRatio: number): void; + setFrequency(frequency: number): void; + setMaxForce(maxForce: number): void; + setTarget(target: Vec2Value): void; +} + +type MouseJointCompThis = GameObj; + +export default function mouseJoint( + k: KAPLAYCtx, + def: KPMouseJointDef, + gameObjA: GameObj, + gameObjB: GameObj, + worldContainer?: GameObj, +): KPMouseJointComp { + let _joint: MouseJoint | null = null; + + return { + ...joint(k, gameObjA, gameObjB, def.draw), + + id: "kpMouseJoint", + + get joint() { + if (!_joint) { + throw new Error("kpMouseJoint not initialized"); + } + + return _joint; + }, + + getDampingRatio(): number { + return this.joint.getDampingRatio(); + }, + getFrequency() { + return this.joint.getFrequency(); + }, + getMaxForce() { + return this.joint.getMaxForce(); + }, + getTarget() { + return this.joint.getTarget(); + }, + setDampingRatio(dampingRatio: number) { + this.joint.setDampingRatio(dampingRatio); + }, + setFrequency(frequency: number) { + this.joint.setFrequency(frequency); + }, + setMaxForce(maxForce: number) { + this.joint.setMaxForce(maxForce); + }, + setTarget(target: Vec2) { + this.joint.setTarget(target); + }, + + add(this: MouseJointCompThis) { + const world = worldContainer + ? worldContainer.world + : getWorldFromGameObj(this); + + if (!world) { + throw new Error("kpMouseJoint requires to be a descendant of kpWorld"); + } + + const userData = def?.userData ?? {}; + + _joint = world.createJoint( + new MouseJoint({ + ...def, + bodyA: gameObjA.body, + bodyB: gameObjB.body, + userData: { + ...userData, + gameObj: this, + }, + }), + ); + }, + destroy() { + const world = this.joint.getBodyA().getWorld(); + + world.destroyJoint(this.joint); + + _joint = null; + }, + }; +} diff --git a/src/lib/components/PolygonShape.ts b/src/lib/components/PolygonShape.ts index b183a48..af39ab0 100644 --- a/src/lib/components/PolygonShape.ts +++ b/src/lib/components/PolygonShape.ts @@ -11,6 +11,7 @@ export interface KPPolygonShapeComp extends KPShapeComp { export interface KPPolygonShapeOpt extends KPShapeOpt { vertices?: Vec2Value[]; + fill?: boolean; } type PolygonShapeCompThis = GameObj; @@ -44,6 +45,7 @@ export default function polygonShape( k.drawPolygon({ ...renderingProps, pts, + fill: opt?.fill, }); }, destroy() { diff --git a/src/lib/components/PrismaticJoint.ts b/src/lib/components/PrismaticJoint.ts new file mode 100644 index 0000000..d18724f --- /dev/null +++ b/src/lib/components/PrismaticJoint.ts @@ -0,0 +1,146 @@ +import type { GameObj, KAPLAYCtx } from "kaplay"; +import { PrismaticJoint, type PrismaticJointDef, type Vec2 } from "planck"; +import { getWorldFromGameObj } from "../utils"; +import type { KPBodyComp } from "./Body"; +import joint, { type KPJointComp } from "./Joint"; +import type { KPPosComp } from "./Position"; +import type { KPRotateComp } from "./Rotate"; +import type { KPWorldComp } from "./World"; + +export interface KPPrismaticJointDef + extends Omit { + draw?: boolean; +} + +export interface KPPrismaticJointComp extends KPJointComp { + joint: PrismaticJoint; + + enableLimit(flag: boolean): void; + enableMotor(flag: boolean): void; + getJointSpeed(): number; + getJointTranslation(): number; + getLocalAnchorA(): Vec2; + getLocalAnchorB(): Vec2; + getLocalAxisA(): Vec2; + getLowerLimit(): number; + getMaxMotorForce(): number; + getMotorSpeed(): number; + getReferenceAngle(): number; + getUpperLimit(): number; + isLimitEnabled(): boolean; + isMotorEnabled(): boolean; + setLimits(lower: number, upper: number): void; + setMaxMotorForce(force: number): void; + setMotorSpeed(speed: number): void; +} + +type PrismaticJointCompThis = GameObj; + +export default function prismaticJoint( + k: KAPLAYCtx, + def: KPPrismaticJointDef, + gameObjA: GameObj, + gameObjB: GameObj, + worldContainer?: GameObj, +): KPPrismaticJointComp { + let _joint: PrismaticJoint | null = null; + + return { + ...joint(k, gameObjA, gameObjB, def.draw), + + id: "kpPrismaticJoint", + + get joint() { + if (!_joint) { + throw new Error("kpPrismaticJoint not initialized"); + } + + return _joint; + }, + + enableLimit(flag: boolean) { + this.joint.enableLimit(flag); + }, + enableMotor(flag: boolean) { + this.joint.enableMotor(flag); + }, + getJointSpeed() { + return this.joint.getJointSpeed(); + }, + getJointTranslation() { + return this.joint.getJointTranslation(); + }, + getLocalAnchorA() { + return this.joint.getLocalAnchorA(); + }, + getLocalAnchorB() { + return this.joint.getLocalAnchorB(); + }, + getLocalAxisA() { + return this.joint.getLocalAxisA(); + }, + getLowerLimit() { + return this.joint.getLowerLimit(); + }, + getMaxMotorForce() { + return this.joint.getMaxMotorForce(); + }, + getMotorSpeed() { + return this.joint.getMotorSpeed(); + }, + getReferenceAngle() { + return this.joint.getReferenceAngle(); + }, + getUpperLimit() { + return this.joint.getUpperLimit(); + }, + isLimitEnabled() { + return this.joint.isLimitEnabled(); + }, + isMotorEnabled() { + return this.joint.isMotorEnabled(); + }, + setLimits(lower: number, upper: number) { + this.joint.setLimits(lower, upper); + }, + setMaxMotorForce(force: number) { + this.joint.setMaxMotorForce(force); + }, + setMotorSpeed(speed: number) { + this.joint.setMotorSpeed(speed); + }, + + add(this: PrismaticJointCompThis) { + const world = worldContainer + ? worldContainer.world + : getWorldFromGameObj(this); + + if (!world) { + throw new Error( + "kpPrismaticJoint requires to be a descendant of kpWorld", + ); + } + + const userData = def?.userData ?? {}; + + _joint = world.createJoint( + new PrismaticJoint({ + ...def, + bodyA: gameObjA.body, + bodyB: gameObjB.body, + userData: { + ...userData, + gameObj: this, + }, + }), + ); + }, + destroy() { + const world = this.joint.getBodyA().getWorld(); + + world.destroyJoint(this.joint); + + _joint = null; + }, + }; +} diff --git a/src/lib/components/PulleyJoint.ts b/src/lib/components/PulleyJoint.ts new file mode 100644 index 0000000..1d73f1c --- /dev/null +++ b/src/lib/components/PulleyJoint.ts @@ -0,0 +1,104 @@ +import type { GameObj, KAPLAYCtx } from "kaplay"; +import { PulleyJoint, type PulleyJointDef, type Vec2 } from "planck"; +import { getWorldFromGameObj } from "../utils"; +import type { KPBodyComp } from "./Body"; +import joint, { type KPJointComp } from "./Joint"; +import type { KPPosComp } from "./Position"; +import type { KPRotateComp } from "./Rotate"; +import type { KPWorldComp } from "./World"; + +export interface KPPulleyJointDef + extends Omit { + draw?: boolean; +} + +export interface KPPulleyJointComp extends KPJointComp { + joint: PulleyJoint; + + getCurrentLengthA(): number; + getCurrentLengthB(): number; + getGroundAnchorA(): Vec2; + getGroundAnchorB(): Vec2; + getLengthA(): number; + getLengthB(): number; + getRatio(): number; +} + +type PulleyJointCompThis = GameObj; + +export default function pulleyJoint( + k: KAPLAYCtx, + def: KPPulleyJointDef, + gameObjA: GameObj, + gameObjB: GameObj, + worldContainer?: GameObj, +): KPPulleyJointComp { + let _joint: PulleyJoint | null = null; + + return { + ...joint(k, gameObjA, gameObjB, def.draw), + + id: "kpPulleyJoint", + + get joint() { + if (!_joint) { + throw new Error("kpPulleyJoint not initialized"); + } + + return _joint; + }, + + getCurrentLengthA() { + return this.joint.getCurrentLengthA(); + }, + getCurrentLengthB() { + return this.joint.getCurrentLengthB(); + }, + getGroundAnchorA() { + return this.joint.getGroundAnchorA(); + }, + getGroundAnchorB() { + return this.joint.getGroundAnchorB(); + }, + getLengthA() { + return this.joint.getLengthA(); + }, + getLengthB() { + return this.joint.getLengthB(); + }, + getRatio() { + return this.joint.getRatio(); + }, + + add(this: PulleyJointCompThis) { + const world = worldContainer + ? worldContainer.world + : getWorldFromGameObj(this); + + if (!world) { + throw new Error("kpPulleyJoint requires to be a descendant of kpWorld"); + } + + const userData = def?.userData ?? {}; + + _joint = world.createJoint( + new PulleyJoint({ + ...def, + bodyA: gameObjA.body, + bodyB: gameObjB.body, + userData: { + ...userData, + gameObj: this, + }, + }), + ); + }, + destroy() { + const world = this.joint.getBodyA().getWorld(); + + world.destroyJoint(this.joint); + + _joint = null; + }, + }; +} diff --git a/src/lib/components/RevoluteJoint.ts b/src/lib/components/RevoluteJoint.ts new file mode 100644 index 0000000..dfbe98a --- /dev/null +++ b/src/lib/components/RevoluteJoint.ts @@ -0,0 +1,142 @@ +import type { GameObj, KAPLAYCtx } from "kaplay"; +import { RevoluteJoint, type RevoluteJointDef, type Vec2 } from "planck"; +import { getWorldFromGameObj } from "../utils"; +import type { KPBodyComp } from "./Body"; +import joint, { type KPJointComp } from "./Joint"; +import type { KPPosComp } from "./Position"; +import type { KPRotateComp } from "./Rotate"; +import type { KPWorldComp } from "./World"; + +export interface KPRevoluteJointDef + extends Omit { + draw?: boolean; +} + +export interface KPRevoluteJointComp extends KPJointComp { + joint: RevoluteJoint; + + enableLimit(flag: boolean): void; + enableMotor(flag: boolean): void; + getJointAngle(): number; + getJointSpeed(): number; + getLocalAnchorA(): Vec2; + getLocalAnchorB(): Vec2; + getLowerLimit(): number; + getMaxMotorTorque(): number; + getMotorSpeed(): number; + getReferenceAngle(): number; + getUpperLimit(): number; + isLimitEnabled(): boolean; + isMotorEnabled(): boolean; + setLimits(lower: number, upper: number): void; + setMaxMotorTorque(torque: number): void; + setMotorSpeed(speed: number): void; +} + +type RevoluteJointCompThis = GameObj; + +export default function revoluteJoint( + k: KAPLAYCtx, + def: KPRevoluteJointDef, + gameObjA: GameObj, + gameObjB: GameObj, + worldContainer?: GameObj, +): KPRevoluteJointComp { + let _joint: RevoluteJoint | null = null; + + return { + ...joint(k, gameObjA, gameObjB, def.draw), + + id: "kpRevoluteJoint", + + get joint() { + if (!_joint) { + throw new Error("kpRevoluteJoint not initialized"); + } + + return _joint; + }, + + enableLimit(flag: boolean) { + this.joint.enableLimit(flag); + }, + enableMotor(flag: boolean) { + this.joint.enableMotor(flag); + }, + getJointAngle() { + return this.joint.getJointAngle(); + }, + getJointSpeed() { + return this.joint.getJointSpeed(); + }, + getLocalAnchorA() { + return this.joint.getLocalAnchorA(); + }, + getLocalAnchorB() { + return this.joint.getLocalAnchorB(); + }, + getLowerLimit() { + return this.joint.getLowerLimit(); + }, + getMaxMotorTorque() { + return this.joint.getMaxMotorTorque(); + }, + getMotorSpeed() { + return this.joint.getMotorSpeed(); + }, + getReferenceAngle() { + return this.joint.getReferenceAngle(); + }, + getUpperLimit() { + return this.joint.getUpperLimit(); + }, + isLimitEnabled() { + return this.joint.isLimitEnabled(); + }, + isMotorEnabled() { + return this.joint.isMotorEnabled(); + }, + setLimits(lower: number, upper: number) { + this.joint.setLimits(lower, upper); + }, + setMaxMotorTorque(torque: number) { + this.joint.setMaxMotorTorque(torque); + }, + setMotorSpeed(speed: number) { + this.joint.setMotorSpeed(speed); + }, + + add(this: RevoluteJointCompThis) { + const world = worldContainer + ? worldContainer.world + : getWorldFromGameObj(this); + + if (!world) { + throw new Error( + "kpRevoluteJoint requires to be a descendant of kpWorld", + ); + } + + const userData = def?.userData ?? {}; + + _joint = world.createJoint( + new RevoluteJoint({ + ...def, + bodyA: gameObjA.body, + bodyB: gameObjB.body, + userData: { + ...userData, + gameObj: this, + }, + }), + ); + }, + destroy() { + const world = this.joint.getBodyA().getWorld(); + + world.destroyJoint(this.joint); + + _joint = null; + }, + }; +} diff --git a/src/lib/components/RopeJoint.ts b/src/lib/components/RopeJoint.ts new file mode 100644 index 0000000..3c79a38 --- /dev/null +++ b/src/lib/components/RopeJoint.ts @@ -0,0 +1,95 @@ +import type { GameObj, KAPLAYCtx } from "kaplay"; +import { RopeJoint, type RopeJointDef, type Vec2 } from "planck"; +import { getWorldFromGameObj } from "../utils"; +import type { KPBodyComp } from "./Body"; +import joint, { type KPJointComp } from "./Joint"; +import type { KPPosComp } from "./Position"; +import type { KPRotateComp } from "./Rotate"; +import type { KPWorldComp } from "./World"; + +export interface KPRopeJointDef extends Omit { + draw?: boolean; +} + +export interface KPRopeJointComp extends KPJointComp { + joint: RopeJoint; + + getLimitState(): number; + getLocalAnchorA(): Vec2; + getLocalAnchorB(): Vec2; + getMaxLength(): number; + setMaxLength(maxLen: number): void; +} + +type RopeJointCompThis = GameObj; + +export default function ropeJoint( + k: KAPLAYCtx, + def: KPRopeJointDef, + gameObjA: GameObj, + gameObjB: GameObj, + worldContainer?: GameObj, +): KPRopeJointComp { + let _joint: RopeJoint | null = null; + + return { + ...joint(k, gameObjA, gameObjB, def.draw), + + id: "kpRopeJoint", + + get joint() { + if (!_joint) { + throw new Error("kpRopeJoint not initialized"); + } + + return _joint; + }, + + getLimitState() { + return this.joint.getLimitState(); + }, + getLocalAnchorA() { + return this.joint.getLocalAnchorA(); + }, + getLocalAnchorB() { + return this.joint.getLocalAnchorB(); + }, + getMaxLength() { + return this.joint.getMaxLength(); + }, + setMaxLength(maxLen: number) { + this.joint.setMaxLength(maxLen); + }, + + add(this: RopeJointCompThis) { + const world = worldContainer + ? worldContainer.world + : getWorldFromGameObj(this); + + if (!world) { + throw new Error("kpRopeJoint requires to be a descendant of kpWorld"); + } + + const userData = def?.userData ?? {}; + + _joint = world.createJoint( + new RopeJoint({ + ...def, + bodyA: gameObjA.body, + bodyB: gameObjB.body, + userData: { + ...userData, + gameObj: this, + }, + }), + ); + }, + destroy() { + const world = this.joint.getBodyA().getWorld(); + + world.destroyJoint(this.joint); + + _joint = null; + }, + }; +} diff --git a/src/lib/components/WeldJoint.ts b/src/lib/components/WeldJoint.ts new file mode 100644 index 0000000..048b363 --- /dev/null +++ b/src/lib/components/WeldJoint.ts @@ -0,0 +1,99 @@ +import type { GameObj, KAPLAYCtx } from "kaplay"; +import { type Vec2, WeldJoint, type WeldJointDef } from "planck"; +import { getWorldFromGameObj } from "../utils"; +import type { KPBodyComp } from "./Body"; +import joint, { type KPJointComp } from "./Joint"; +import type { KPPosComp } from "./Position"; +import type { KPRotateComp } from "./Rotate"; +import type { KPWorldComp } from "./World"; + +export interface KPWeldJointDef extends Omit { + draw?: boolean; +} + +export interface KPWeldJointComp extends KPJointComp { + joint: WeldJoint; + + getDampingRatio(): number; + getFrequency(): number; + getLocalAnchorA(): Vec2; + getLocalAnchorB(): Vec2; + setDampingRatio(dampingRatio: number): void; + setFrequency(hz: number): void; +} + +type WeldJointCompThis = GameObj; + +export default function weldJoint( + k: KAPLAYCtx, + def: KPWeldJointDef, + gameObjA: GameObj, + gameObjB: GameObj, + worldContainer?: GameObj, +): KPWeldJointComp { + let _joint: WeldJoint | null = null; + + return { + ...joint(k, gameObjA, gameObjB, def.draw), + + id: "kpWeldJoint", + + get joint() { + if (!_joint) { + throw new Error("kpWeldJoint not initialized"); + } + + return _joint; + }, + + getDampingRatio() { + return this.joint.getDampingRatio(); + }, + getFrequency() { + return this.joint.getFrequency(); + }, + getLocalAnchorA() { + return this.joint.getLocalAnchorA(); + }, + getLocalAnchorB() { + return this.joint.getLocalAnchorB(); + }, + setDampingRatio(dampingRatio: number) { + this.joint.setDampingRatio(dampingRatio); + }, + setFrequency(hz: number) { + this.joint.setFrequency(hz); + }, + + add(this: WeldJointCompThis) { + const world = worldContainer + ? worldContainer.world + : getWorldFromGameObj(this); + + if (!world) { + throw new Error("kpWeldJoint requires to be a descendant of kpWorld"); + } + + const userData = def?.userData ?? {}; + + _joint = world.createJoint( + new WeldJoint({ + ...def, + bodyA: gameObjA.body, + bodyB: gameObjB.body, + userData: { + ...userData, + gameObj: this, + }, + }), + ); + }, + destroy() { + const world = this.joint.getBodyA().getWorld(); + + world.destroyJoint(this.joint); + + _joint = null; + }, + }; +} diff --git a/src/lib/components/WheelJoint.ts b/src/lib/components/WheelJoint.ts new file mode 100644 index 0000000..6b8ab7d --- /dev/null +++ b/src/lib/components/WheelJoint.ts @@ -0,0 +1,136 @@ +import type { GameObj, KAPLAYCtx } from "kaplay"; +import { type Vec2, WheelJoint, type WheelJointDef } from "planck"; +import { getWorldFromGameObj } from "../utils"; +import type { KPBodyComp } from "./Body"; +import joint, { type KPJointComp } from "./Joint"; +import type { KPPosComp } from "./Position"; +import type { KPRotateComp } from "./Rotate"; +import type { KPWorldComp } from "./World"; + +export interface KPWheelJointDef + extends Omit { + draw?: boolean; +} + +export interface KPWheelJointComp extends KPJointComp { + joint: WheelJoint; + + enableMotor(flag: boolean): void; + getJointSpeed(): number; + getJointTranslation(): number; + getLocalAnchorA(): Vec2; + getLocalAnchorB(): Vec2; + getLocalAxisA(): Vec2; + getMaxMotorTorque(): number; + getMotorSpeed(): number; + getSpringDampingRatio(): number; + getSpringFrequencyHz(): number; + isMotorEnabled(): boolean; + setMaxMotorTorque(torque: number): void; + setMotorSpeed(speed: number): void; + setSpringDampingRatio(ratio: number): void; + setSpringFrequencyHz(hz: number): void; +} + +type WheelJointCompThis = GameObj; + +export default function wheelJoint( + k: KAPLAYCtx, + def: KPWheelJointDef, + gameObjA: GameObj, + gameObjB: GameObj, + worldContainer?: GameObj, +): KPWheelJointComp { + let _joint: WheelJoint | null = null; + + return { + ...joint(k, gameObjA, gameObjB, def.draw), + + id: "kpWheelJoint", + + get joint() { + if (!_joint) { + throw new Error("kpWheelJoint not initialized"); + } + + return _joint; + }, + + enableMotor(flag: boolean) { + this.joint.enableMotor(flag); + }, + getJointSpeed() { + return this.joint.getJointSpeed(); + }, + getJointTranslation() { + return this.joint.getJointTranslation(); + }, + getLocalAnchorA() { + return this.joint.getLocalAnchorA(); + }, + getLocalAnchorB() { + return this.joint.getLocalAnchorB(); + }, + getLocalAxisA() { + return this.joint.getLocalAxisA(); + }, + getMaxMotorTorque() { + return this.joint.getMaxMotorTorque(); + }, + getMotorSpeed() { + return this.joint.getMotorSpeed(); + }, + getSpringDampingRatio() { + return this.joint.getSpringDampingRatio(); + }, + getSpringFrequencyHz() { + return this.joint.getSpringFrequencyHz(); + }, + isMotorEnabled() { + return this.joint.isMotorEnabled(); + }, + setMaxMotorTorque(torque: number) { + this.joint.setMaxMotorTorque(torque); + }, + setMotorSpeed(speed: number) { + this.joint.setMotorSpeed(speed); + }, + setSpringDampingRatio(ratio: number) { + this.joint.setSpringDampingRatio(ratio); + }, + setSpringFrequencyHz(hz: number) { + this.joint.setSpringFrequencyHz(hz); + }, + + add(this: WheelJointCompThis) { + const world = worldContainer + ? worldContainer.world + : getWorldFromGameObj(this); + + if (!world) { + throw new Error("kpWheelJoint requires to be a descendant of kpWorld"); + } + + const userData = def?.userData ?? {}; + + _joint = world.createJoint( + new WheelJoint({ + ...def, + bodyA: gameObjA.body, + bodyB: gameObjB.body, + userData: { + ...userData, + gameObj: this, + }, + }), + ); + }, + destroy() { + const world = this.joint.getBodyA().getWorld(); + + world.destroyJoint(this.joint); + + _joint = null; + }, + }; +} diff --git a/src/lib/index.ts b/src/lib/index.ts index 51f0924..1aa3489 100644 --- a/src/lib/index.ts +++ b/src/lib/index.ts @@ -6,7 +6,7 @@ export default KaPlanckPlugin; export type { KaPlanckPluginCtx, KaPlanckPluginOpts } from "./plugin"; // export all components' types -export type { KPBodyComp, KPBodyDef, KPBodyUserData } from "./components/Body"; +export type { KPBodyComp, KPBodyDef } from "./components/Body"; export type { KPBoxShapeComp, KPBoxShapeOpt } from "./components/BoxShape"; export type { KPChainShapeComp, @@ -16,13 +16,50 @@ export type { KPCircleShapeComp, KPCircleShapeOpt, } from "./components/CircleShape"; +export type { + KPDistanceJointComp, + KPDistanceJointDef, +} from "./components/DistanceJoint"; export type { KPEdgeShapeComp, KPEdgeShapeOpt } from "./components/EdgeShape"; export type { KPFixtureComp, KPFixtureDef } from "./components/Fixture"; +export type { + KPFrictionJointComp, + KPFrictionJointDef, +} from "./components/FrictionJoint"; +export type { KPGearJointComp, KPGearJointDef } from "./components/GearJoint"; +export type { KPJointComp } from "./components/Joint"; +export type { + KPMotorJointComp, + KPMotorJointDef, +} from "./components/MotorJoint"; +export type { + KPMouseJointComp, + KPMouseJointDef, +} from "./components/MouseJoint"; export type { KPPolygonShapeComp, KPPolygonShapeOpt, } from "./components/PolygonShape"; export type { KPPosComp } from "./components/Position"; +export type { + KPPrismaticJointComp, + KPPrismaticJointDef, +} from "./components/PrismaticJoint"; +export type { + KPPulleyJointComp, + KPPulleyJointDef, +} from "./components/PulleyJoint"; +export type { + KPRevoluteJointComp, + KPRevoluteJointDef, +} from "./components/RevoluteJoint"; +export type { KPRopeJointComp, KPRopeJointDef } from "./components/RopeJoint"; export type { KPRotateComp } from "./components/Rotate"; export type { KPShapeComp, KPShapeOpt } from "./components/Shape"; +export type { KPWeldJointComp, KPWeldJointDef } from "./components/WeldJoint"; +export type { + KPWheelJointComp, + KPWheelJointDef, +} from "./components/WheelJoint"; export type { KPWorldComp } from "./components/World"; +export type { KPUserData } from "./types"; diff --git a/src/lib/plugin.ts b/src/lib/plugin.ts index 30833fa..7b12bb4 100644 --- a/src/lib/plugin.ts +++ b/src/lib/plugin.ts @@ -34,19 +34,62 @@ import polygonShape, { import pos, { type KPPosComp, type KPVec2Args } from "./components/Position"; import world, { type KPWorldComp } from "./components/World"; +import distanceJoint, { + type KPDistanceJointComp, + type KPDistanceJointDef, +} from "./components/DistanceJoint"; +import frictionJoint, { + type KPFrictionJointComp, + type KPFrictionJointDef, +} from "./components/FrictionJoint"; +import gearJoint, { + type KPGearJointComp, + type KPGearJointDef, +} from "./components/GearJoint"; +import motorJoint, { + type KPMotorJointComp, + type KPMotorJointDef, +} from "./components/MotorJoint"; +import mouseJoint, { + type KPMouseJointComp, + type KPMouseJointDef, +} from "./components/MouseJoint"; +import prismaticJoint, { + type KPPrismaticJointComp, + type KPPrismaticJointDef, +} from "./components/PrismaticJoint"; +import pulleyJoint, { + type KPPulleyJointComp, + type KPPulleyJointDef, +} from "./components/PulleyJoint"; +import revoluteJoint, { + type KPRevoluteJointComp, + type KPRevoluteJointDef, +} from "./components/RevoluteJoint"; +import ropeJoint, { + type KPRopeJointComp, + type KPRopeJointDef, +} from "./components/RopeJoint"; import rotate, { type KPRotateComp } from "./components/Rotate"; +import weldJoint, { + type KPWeldJointComp, + type KPWeldJointDef, +} from "./components/WeldJoint"; +import wheelJoint, { + type KPWheelJointComp, + type KPWheelJointDef, +} from "./components/WheelJoint"; import { center, findWorldContainer, k2pVec2, + m2p, p2kVec2, - p2u, - u2p, + p2m, } from "./utils"; export interface KaPlanckPluginCtx { // transform components - /** * Sets the position of a body. * @@ -160,6 +203,175 @@ export interface KaPlanckPluginCtx { * @return {KPPolygonShapeComp} */ kpPolygonShape(opt?: KPPolygonShapeOpt): KPPolygonShapeComp; + /** + * Defines a distance joint. + * + * @param {KPDistanceJointDef} def The definition for the distance joint. + * @param {GameObj} gameObjA The first body to connect with the joint. + * @param {GameObj} gameObjB The second body to connect with the joint. + * @param {GameObj} [worldContainer] The game object with `kpWorld` component. + * @return {KPDistanceJointComp} + */ + kpDistanceJoint( + def: KPDistanceJointDef, + gameObjA: GameObj, + gameObjB: GameObj, + worldContainer?: GameObj, + ): KPDistanceJointComp; + /** + * Defines a friction joint. + * + * @param {KPFrictionJointDef} def The definition for the friction joint. + * @param {(GameObj)} gameObjA The first body to connect with the joint. + * @param {(GameObj)} gameObjB The second body to connect with the joint. + * @param {GameObj} [worldContainer] The game object with `kpWorld` component. + * @return {KPFrictionJointComp} + */ + kpFrictionJoint( + def: KPFrictionJointDef, + gameObjA: GameObj, + gameObjB: GameObj, + worldContainer?: GameObj, + ): KPFrictionJointComp; + /** + * Defines a gear joint. + * + * @param {KPGearJointDef} def The definition for the gear joint. + * @param {(GameObj)} gameObjA The first body to connect with the joint. + * @param {(GameObj)} gameObjB The second body to connect with the joint. + * @param {(GameObj | GameObj)} jointGameObj1 The first joint to connect with the gear. + * @param {(GameObj | GameObj)} jointGameObj2 The second joint to connect with the gear. + * @param {GameObj} [worldContainer] The game object with `kpWorld` component. + * @return {KPGearJointComp} + */ + kpGearJoint( + def: KPGearJointDef, + gameObjA: GameObj, + gameObjB: GameObj, + jointGameObj1: GameObj | GameObj, + jointGameObj2: GameObj | GameObj, + worldContainer?: GameObj, + ): KPGearJointComp; + /** + * Defines a motor joint. + * + * @param {KPMotorJointDef} def The definition for the motor joint. + * @param {(GameObj)} gameObjA The first body to connect with the joint. + * @param {(GameObj)} gameObjB The second body to connect with the joint. + * @param {GameObj} [worldContainer] The game object with `kpWorld` component. + * @return {KPMotorJointComp} + */ + kpMotorJoint( + def: KPMotorJointDef, + gameObjA: GameObj, + gameObjB: GameObj, + worldContainer?: GameObj, + ): KPMotorJointComp; + /** + * Defines a mouse joint. + * + * @param {KPMouseJointDef} def The definition for the mouse joint. + * @param {(GameObj)} gameObjA The first body to connect with the joint. + * @param {(GameObj)} gameObjB The second body to connect with the joint. + * @param {GameObj} [worldContainer] The game object with `kpWorld` component. + * @return {KPMouseJointComp} + */ + kpMouseJoint( + def: KPMouseJointDef, + gameObjA: GameObj, + gameObjB: GameObj, + worldContainer?: GameObj, + ): KPMouseJointComp; + /** + * Defines a prismatic joint. + * + * @param {KPPrismaticJointDef} def The definition for the prismatic joint. + * @param {(GameObj)} gameObjA The first body to connect with the joint. + * @param {(GameObj)} gameObjB The second body to connect with the joint. + * @param {GameObj} [worldContainer] The game object with `kpWorld` component. + * @return {KPPrismaticJointComp} + */ + kpPrismaticJoint( + def: KPPrismaticJointDef, + gameObjA: GameObj, + gameObjB: GameObj, + worldContainer?: GameObj, + ): KPPrismaticJointComp; + /** + * Defines a pulley joint. + * + * @param {KPPulleyJointDef} def The definition for the pulley joint. + * @param {(GameObj)} gameObjA The first body to connect with the joint. + * @param {(GameObj)} gameObjB The second body to connect with the joint. + * @param {GameObj} [worldContainer] The game object with `kpWorld` component. + * @return {KPPulleyJointComp} + */ + kpPulleyJoint( + def: KPPulleyJointDef, + gameObjA: GameObj, + gameObjB: GameObj, + worldContainer?: GameObj, + ): KPPulleyJointComp; + /** + * Defines a revolute joint. + * + * @param {KPRevoluteJointDef} def The definition for the revolute joint. + * @param {(GameObj)} gameObjA The first body to connect with the joint. + * @param {(GameObj)} gameObjB The second body to connect with the joint. + * @param {GameObj} [worldContainer] The game object with `kpWorld` component. + * @return {KPRevoluteJointComp} + */ + kpRevoluteJoint( + def: KPRevoluteJointDef, + gameObjA: GameObj, + gameObjB: GameObj, + worldContainer?: GameObj, + ): KPRevoluteJointComp; + /** + * Defines a distance joint. + * + * @param {KPRopeJointDef} def The definition for the distance joint. + * @param {(GameObj)} gameObjA The first body to connect with the joint. + * @param {(GameObj)} gameObjB The second body to connect with the joint. + * @param {GameObj} [worldContainer] The game object with `kpWorld` component. + * @return {KPRopeJointComp} + */ + kpRopeJoint( + def: KPRopeJointDef, + gameObjA: GameObj, + gameObjB: GameObj, + worldContainer?: GameObj, + ): KPRopeJointComp; + /** + * Defines a weld joint. + * + * @param {KPWeldJointDef} def The definition for the weld joint. + * @param {(GameObj)} gameObjA The first body to connect with the joint. + * @param {(GameObj)} gameObjB The second body to connect with the joint. + * @param {GameObj} [worldContainer] The world container to add the joint. + * @return {KPWeldJointComp} + */ + kpWeldJoint( + def: KPWeldJointDef, + gameObjA: GameObj, + gameObjB: GameObj, + worldContainer?: GameObj, + ): KPWeldJointComp; + /** + * Defines a distance joint. + * + * @param {KPWheelJointDef} def The definition for the distance joint. + * @param {(GameObj)} gameObjA The first body to connect with the joint. + * @param {(GameObj)} gameObjB The second body to connect with the joint. + * @param {GameObj} [worldContainer] The world container to add the joint. + * @return {KPWheelJointComp} + */ + kpWheelJoint( + def: KPWheelJointDef, + gameObjA: GameObj, + gameObjB: GameObj, + worldContainer?: GameObj, + ): KPWheelJointComp; // events /** @@ -233,19 +445,19 @@ export interface KaPlanckPluginCtx { */ kpMousePos(): Vec2; /** - * Converts unit to pixel. + * Converts meter to pixel. * - * @param {number} m The unit value. + * @param {number} m The meter value. * @return {number} */ - u2p(m: number): number; + m2p(m: number): number; /** * Converts pixel to unit. * * @param {number} p The pixel value. * @return {number} */ - p2u(p: number): number; + p2m(p: number): number; /** * Converts a vector from KAPLAY Vector to Planck Vector. * @@ -264,7 +476,7 @@ export interface KaPlanckPluginCtx { export interface KaPlanckPluginOpts { /** - * The ratio of pixels per unit. + * The ratio of pixels per meter. * * Defaults to `10`. * @@ -322,6 +534,108 @@ const KaPlanckPlugin = kpPolygonShape(opt?: KPPolygonShapeOpt) { return polygonShape(k, opt); }, + kpDistanceJoint( + def: KPDistanceJointDef, + gameObjA: GameObj, + gameObjB: GameObj, + worldContainer?: GameObj, + ) { + return distanceJoint(k, def, gameObjA, gameObjB, worldContainer); + }, + kpFrictionJoint( + def: KPFrictionJointDef, + gameObjA: GameObj, + gameObjB: GameObj, + worldContainer?: GameObj, + ) { + return frictionJoint(k, def, gameObjA, gameObjB, worldContainer); + }, + kpGearJoint( + def: KPGearJointDef, + gameObjA: GameObj, + gameObjB: GameObj, + jointGameObj1: + | GameObj + | GameObj, + jointGameObj2: + | GameObj + | GameObj, + worldContainer?: GameObj, + ) { + return gearJoint( + k, + def, + gameObjA, + gameObjB, + jointGameObj1, + jointGameObj2, + worldContainer, + ); + }, + kpMotorJoint( + def: KPMotorJointDef, + gameObjA: GameObj, + gameObjB: GameObj, + worldContainer?: GameObj, + ) { + return motorJoint(k, def, gameObjA, gameObjB, worldContainer); + }, + kpMouseJoint( + def: KPMouseJointDef, + gameObjA: GameObj, + gameObjB: GameObj, + worldContainer?: GameObj, + ) { + return mouseJoint(k, def, gameObjA, gameObjB, worldContainer); + }, + kpPrismaticJoint( + def: KPPrismaticJointDef, + gameObjA: GameObj, + gameObjB: GameObj, + worldContainer?: GameObj, + ) { + return prismaticJoint(k, def, gameObjA, gameObjB, worldContainer); + }, + kpPulleyJoint( + def: KPPulleyJointDef, + gameObjA: GameObj, + gameObjB: GameObj, + worldContainer?: GameObj, + ) { + return pulleyJoint(k, def, gameObjA, gameObjB, worldContainer); + }, + kpRevoluteJoint( + def: KPRevoluteJointDef, + gameObjA: GameObj, + gameObjB: GameObj, + worldContainer?: GameObj, + ) { + return revoluteJoint(k, def, gameObjA, gameObjB, worldContainer); + }, + kpRopeJoint( + def: KPRopeJointDef, + gameObjA: GameObj, + gameObjB: GameObj, + worldContainer?: GameObj, + ) { + return ropeJoint(k, def, gameObjA, gameObjB, worldContainer); + }, + kpWeldJoint( + def: KPWeldJointDef, + gameObjA: GameObj, + gameObjB: GameObj, + worldContainer?: GameObj, + ) { + return weldJoint(k, def, gameObjA, gameObjB, worldContainer); + }, + kpWheelJoint( + def: KPWheelJointDef, + gameObjA: GameObj, + gameObjB: GameObj, + worldContainer?: GameObj, + ) { + return wheelJoint(k, def, gameObjA, gameObjB, worldContainer); + }, onKPCollide( tagA: Tag, @@ -449,8 +763,8 @@ const KaPlanckPlugin = kpMousePos() { return k2pVec2(k.mousePos()); }, - u2p, - p2u, + m2p, + p2m, k2pVec2, p2kVec2(vec: Vec2) { return p2kVec2(k, vec); diff --git a/src/lib/types.ts b/src/lib/types.ts new file mode 100644 index 0000000..9846169 --- /dev/null +++ b/src/lib/types.ts @@ -0,0 +1,5 @@ +import type { GameObj } from "kaplay"; + +export interface KPUserData { + gameObj: GameObj; +} diff --git a/src/lib/utils.ts b/src/lib/utils.ts index c0c327a..6c15416 100644 --- a/src/lib/utils.ts +++ b/src/lib/utils.ts @@ -3,27 +3,27 @@ import { Settings, Vec2, type Vec2Value, type World } from "planck"; import type { KPWorldComp } from "./components/World"; -export function u2p(u: number) { - return u * Settings.lengthUnitsPerMeter; +export function m2p(m: number) { + return m * Settings.lengthUnitsPerMeter; } -export function p2u(p: number) { +export function p2m(p: number) { return p / Settings.lengthUnitsPerMeter; } export function center(k: KAPLAYCtx) { const center = k.center(); - const vec = new Vec2(p2u(center.x), p2u(center.y)); + const vec = new Vec2(p2m(center.x), p2m(center.y)); return vec; } export function k2pVec2(vec: KaVec2): Vec2 { - return new Vec2(p2u(vec.x), p2u(vec.y)); + return new Vec2(p2m(vec.x), p2m(vec.y)); } export function p2kVec2(k: KAPLAYCtx, vec: Vec2Value): KaVec2 { - return k.vec2(u2p(vec.x), u2p(vec.y)); + return k.vec2(m2p(vec.x), m2p(vec.y)); } export function getWorldFromGameObj(obj: GameObj): World | null { diff --git a/src/shared.ts b/src/shared.ts index f2bf807..90dc2d7 100644 --- a/src/shared.ts +++ b/src/shared.ts @@ -4,8 +4,10 @@ import bodyScene from "./checks/body"; import eightBallScene from "./examples/8-Ball"; import addPairScene from "./examples/AddPair"; import applyForceScene from "./examples/ApplyForce"; +import motorJointScene from "./examples/MotorJoint"; import sampleScene from "./examples/Sample"; import tumblerScene from "./examples/Tumbler"; +import webScene from "./examples/Web"; import type { KaPlanckPluginCtx } from "./lib"; export type KAPLANCKCtx = KAPLAYCtx & KaPlanckPluginCtx; @@ -19,7 +21,9 @@ export const examples: [SceneName, ExampleCheckScene][] = [ ["eightBall", eightBallScene], ["addPair", addPairScene], ["applyForce", applyForceScene], + ["motorJoint", motorJointScene], ["tumbler", tumblerScene], + ["web", webScene], ]; export function addScenesButtons(k: KAPLAYCtx, scene: GameObj) {