diff --git a/cocos/physics/bullet/bullet-cache.ts b/cocos/physics/bullet/bullet-cache.ts index fe8c0567173..3e13c29795d 100644 --- a/cocos/physics/bullet/bullet-cache.ts +++ b/cocos/physics/bullet/bullet-cache.ts @@ -23,7 +23,7 @@ */ import { Collider, TriggerEventType, CollisionEventType, IContactEquation, CharacterController } from '../../../exports/physics-framework'; -import { Vec3, Quat, Mat4 } from '../../core'; +import { Vec3, Quat, Mat4, Color } from '../../core'; import { CharacterTriggerEventType } from '../framework'; import { bt } from './instantiated'; @@ -88,5 +88,6 @@ export const CC_QUAT_0 = new Quat(); export const CC_QUAT_1 = new Quat(); export const CC_MAT4_0 = new Mat4(); export const CC_MAT4_1 = new Mat4(); +export const CC_COLOR_0 = new Color(); bt.CACHE = BulletCache; diff --git a/cocos/physics/bullet/bullet-env.ts b/cocos/physics/bullet/bullet-env.ts index 096a0fe310f..c8341da93e3 100644 --- a/cocos/physics/bullet/bullet-env.ts +++ b/cocos/physics/bullet/bullet-env.ts @@ -44,4 +44,21 @@ export const importFunc = { const cct = bt.CACHE.getWrapper(controller, bt.CCT_CACHE_NAME); cct.onShapeHitExt(hit); }, + onDebugDrawLine (from: number, to: number, color: number): void { + const bt = globalThis.Bullet; + const world = bt.CACHE.world; + if (world) { + world.onDebugDrawLine(from, to, color); + } + }, + onClearLines (): void { + const bt = globalThis.Bullet; + const world = bt.CACHE.world; + if (world) { + world.onClearLines(); + } + }, + onFlushLines (): void { + //empty + }, }; diff --git a/cocos/physics/bullet/bullet-world.ts b/cocos/physics/bullet/bullet-world.ts index 431158135f4..4db8edf017f 100644 --- a/cocos/physics/bullet/bullet-world.ts +++ b/cocos/physics/bullet/bullet-world.ts @@ -28,21 +28,24 @@ import { BulletRigidBody } from './bullet-rigid-body'; import { BulletShape } from './shapes/bullet-shape'; import { ArrayCollisionMatrix } from '../utils/array-collision-matrix'; import { TupleDictionary } from '../utils/tuple-dictionary'; -import { TriggerEventObject, CollisionEventObject, CC_V3_0, CC_V3_1, CC_V3_2, BulletCache, CharacterTriggerEventObject } from './bullet-cache'; +import { TriggerEventObject, CollisionEventObject, CC_V3_0, CC_V3_1, CC_V3_2, CC_COLOR_0, BulletCache, CharacterTriggerEventObject } from './bullet-cache'; import { bullet2CocosVec3, cocos2BulletQuat, cocos2BulletVec3 } from './bullet-utils'; import { IRaycastOptions, IPhysicsWorld } from '../spec/i-physics-world'; -import { PhysicsRayResult, PhysicsMaterial, CharacterControllerContact } from '../framework'; -import { error, RecyclePool, Vec3, js, IVec3Like, geometry, IQuatLike, Quat } from '../../core'; +import { PhysicsRayResult, PhysicsMaterial, CharacterControllerContact, EPhysicsDrawFlags } from '../framework'; +import { error, RecyclePool, Vec3, js, IVec3Like, geometry, IQuatLike, Quat, Color } from '../../core'; import { BulletContactData } from './bullet-contact-data'; import { BulletConstraint } from './constraints/bullet-constraint'; import { BulletCharacterController } from './character-controllers/bullet-character-controller'; -import { bt, EBulletType, EBulletTriangleRaycastFlag } from './instantiated'; +import { bt, EBulletType, EBulletTriangleRaycastFlag, EBulletDebugDrawModes } from './instantiated'; import { Node } from '../../scene-graph'; +import { director } from '../../game'; +import { GeometryRenderer } from '../../rendering/geometry-renderer'; const contactsPool: BulletContactData[] = []; const v3_0 = CC_V3_0; const v3_1 = CC_V3_1; const v3_2 = CC_V3_2; +const c_0 = CC_COLOR_0; const emitHit = new CharacterControllerContact(); export class BulletWorld implements IPhysicsWorld { setDefaultMaterial (v: PhysicsMaterial): void { @@ -115,6 +118,12 @@ export class BulletWorld implements IPhysicsWorld { private readonly _broadphase: Bullet.ptr; private readonly _solver: Bullet.ptr; private readonly _dispatcher: Bullet.ptr; + private readonly _debugDraw: Bullet.ptr; + + private _debugLineCount = 0; + private _MAX_DEBUG_LINE_COUNT = 16384; + private _debugDrawFlags = EPhysicsDrawFlags.NONE; + private _debugConstraintSize = 0.3; //B3_DEFAULT_DEBUGDRAW_SIZE private _needEmitEvents = false; private _needSyncAfterEvents = false; @@ -136,10 +145,24 @@ export class BulletWorld implements IPhysicsWorld { private static _sweepCapsuleGeometry: number; constructor () { + bt.CACHE.world = this; this._broadphase = bt.DbvtBroadphase_new(); this._dispatcher = bt.CollisionDispatcher_new(); this._solver = bt.SequentialImpulseConstraintSolver_new(); + this._debugDraw = bt.DebugDraw_new(); this._world = bt.ccDiscreteDynamicsWorld_new(this._dispatcher, this._broadphase, this._solver); + bt.CollisionWorld_setDebugDrawer(this._world, this._debugDraw); + + bt.DebugDraw_setDebugMode(this._debugDraw, EBulletDebugDrawModes.DBG_NoDebug); + bt.DebugDraw_setAABBColor(this._debugDraw, 0, 1, 1); + // set color for all shapes + bt.DebugDraw_setActiveObjectColor(this._debugDraw, 1, 0, 1); + bt.DebugDraw_setDeactiveObjectColor(this._debugDraw, 1, 0, 1); + bt.DebugDraw_setWantsDeactivationObjectColor(this._debugDraw, 1, 0, 1); + bt.DebugDraw_setDisabledDeactivationObjectColor(this._debugDraw, 1, 0, 1); + bt.DebugDraw_setDisabledSimulationObjectColor(this._debugDraw, 1, 0, 1); + // set color for all shapes END + bt.DebugDraw_setConstraintLimitColor(this._debugDraw, 0.5, 0.5, 0.5); } destroy (): void { @@ -148,6 +171,7 @@ export class BulletWorld implements IPhysicsWorld { bt._safe_delete(this._broadphase, EBulletType.EBulletTypeDbvtBroadPhase); bt._safe_delete(this._dispatcher, EBulletType.EBulletTypeCollisionDispatcher); bt._safe_delete(this._solver, EBulletType.EBulletTypeSequentialImpulseConstraintSolver); + bt._safe_delete(this._debugDraw, EBulletType.EBulletTypeDebugDraw); (this as any).bodies = null; (this as any).ghosts = null; (this as any).ccts = null; @@ -165,6 +189,7 @@ export class BulletWorld implements IPhysicsWorld { if (!this.bodies.length && !this.ghosts.length) return; if (timeSinceLastCalled === undefined) timeSinceLastCalled = deltaTime; bt.DynamicsWorld_stepSimulation(this._world, timeSinceLastCalled, maxSubStep, deltaTime); + bt.CollisionWorld_debugDrawWorld(this._world); } syncSceneToPhysics (): void { @@ -808,4 +833,71 @@ export class BulletWorld implements IPhysicsWorld { } } } + + get debugDrawFlags (): EPhysicsDrawFlags { + return this._debugDrawFlags; + } + + set debugDrawFlags (v: EPhysicsDrawFlags) { + this._debugDrawFlags = v; + if (this._debugDraw) { + this._setDebugDrawMode(); + } + } + + get debugDrawConstraintSize (): number { + return this._debugConstraintSize; + } + + set debugDrawConstraintSize (v) { + this._debugConstraintSize = v; + for (let i = 0; i < this.constraints.length; i++) { + this.constraints[i].updateDebugDrawSize(); + } + } + + private _setDebugDrawMode (): void { + let btDrawMode = 0; + if (this._debugDrawFlags & EPhysicsDrawFlags.WIRE_FRAME) { + btDrawMode |= EBulletDebugDrawModes.DBG_DrawWireframe; + } + + if (this._debugDrawFlags & EPhysicsDrawFlags.CONSTRAINT) { + btDrawMode |= EBulletDebugDrawModes.DBG_DrawConstraints; + btDrawMode |= EBulletDebugDrawModes.DBG_DrawConstraintLimits; + } + + if (this._debugDrawFlags & EPhysicsDrawFlags.AABB) { + btDrawMode |= EBulletDebugDrawModes.DBG_DrawAabb; + } + + bt.DebugDraw_setDebugMode(this._debugDraw, btDrawMode); + } + + private _getDebugRenderer (): GeometryRenderer|null { + const cameras = director.root!.mainWindow?.cameras; + if (!cameras) return null; + if (cameras.length === 0) return null; + if (!cameras[0]) return null; + cameras[0].initGeometryRenderer(); + + return cameras[0].geometryRenderer; + } + + // callback function called by bullet wasm + public onDebugDrawLine (from: number, to: number, color: number): void { + const debugRenderer = this._getDebugRenderer(); + if (debugRenderer && this._debugLineCount < this._MAX_DEBUG_LINE_COUNT) { + this._debugLineCount++; + bullet2CocosVec3(v3_0, from); + bullet2CocosVec3(v3_1, to); + bullet2CocosVec3(v3_2, color); + c_0.set(v3_2.x * 255, v3_2.y * 255, v3_2.z * 255, 255); + debugRenderer.addLine(v3_0, v3_1, c_0); + } + } + + public onClearLines (): void { + this._debugLineCount = 0; + } } diff --git a/cocos/physics/bullet/constraints/bullet-configurable-constraint.ts b/cocos/physics/bullet/constraints/bullet-configurable-constraint.ts index 491943f761c..1715e442dc8 100644 --- a/cocos/physics/bullet/constraints/bullet-configurable-constraint.ts +++ b/cocos/physics/bullet/constraints/bullet-configurable-constraint.ts @@ -360,6 +360,7 @@ export class BulletConfigurableConstraint extends BulletConstraint implements IC this.setBreakTorque(this.constraint.breakTorque); this.updateFrames(); + this.updateDebugDrawSize(); } updateFrames (): void { diff --git a/cocos/physics/bullet/constraints/bullet-constraint.ts b/cocos/physics/bullet/constraints/bullet-constraint.ts index 4676a79fdfd..21f7f32b83f 100644 --- a/cocos/physics/bullet/constraints/bullet-constraint.ts +++ b/cocos/physics/bullet/constraints/bullet-constraint.ts @@ -24,9 +24,10 @@ /* eslint-disable new-cap */ import { IBaseConstraint } from '../../spec/i-physics-constraint'; -import { Constraint, RigidBody } from '../../framework'; +import { Constraint, PhysicsSystem, RigidBody } from '../../framework'; import { BulletRigidBody } from '../bullet-rigid-body'; import { bt, EBulletType } from '../instantiated'; +import { BulletWorld } from '../bullet-world'; export abstract class BulletConstraint implements IBaseConstraint { setConnectedBody (v: RigidBody | null): void { @@ -100,6 +101,13 @@ export abstract class BulletConstraint implements IBaseConstraint { this.setEnableCollision(this._collided); } + updateDebugDrawSize (): void { + if (this.impl) { + const size = (PhysicsSystem.instance.physicsWorld as BulletWorld).debugDrawConstraintSize; + bt.TypedConstraint_setDbgDrawSize(this.impl, size); + } + } + // virtual protected abstract onComponentSet(): void; diff --git a/cocos/physics/bullet/constraints/bullet-fixed-constraint.ts b/cocos/physics/bullet/constraints/bullet-fixed-constraint.ts index afdc2567432..1168953a6f5 100644 --- a/cocos/physics/bullet/constraints/bullet-fixed-constraint.ts +++ b/cocos/physics/bullet/constraints/bullet-fixed-constraint.ts @@ -55,6 +55,7 @@ export class BulletFixedConstraint extends BulletConstraint implements IFixedCon this.setBreakForce(this.constraint.breakForce); this.setBreakTorque(this.constraint.breakTorque); this.updateFrames(); + this.updateDebugDrawSize(); } updateFrames (): void { diff --git a/cocos/physics/bullet/constraints/bullet-hinge-constraint.ts b/cocos/physics/bullet/constraints/bullet-hinge-constraint.ts index b11fde4a362..d736cb5e3c3 100644 --- a/cocos/physics/bullet/constraints/bullet-hinge-constraint.ts +++ b/cocos/physics/bullet/constraints/bullet-hinge-constraint.ts @@ -102,6 +102,7 @@ export class BulletHingeConstraint extends BulletConstraint implements IHingeCon this.setMotorVelocity(this.constraint.motorVelocity); this.setMotorForceLimit(this.constraint.motorForceLimit); this.updateFrames(); + this.updateDebugDrawSize(); } updateFrames (): void { diff --git a/cocos/physics/bullet/constraints/bullet-p2p-constraint.ts b/cocos/physics/bullet/constraints/bullet-p2p-constraint.ts index 248e4125c09..2b402adc136 100644 --- a/cocos/physics/bullet/constraints/bullet-p2p-constraint.ts +++ b/cocos/physics/bullet/constraints/bullet-p2p-constraint.ts @@ -72,6 +72,7 @@ export class BulletP2PConstraint extends BulletConstraint implements IPointToPoi this._impl = bt.P2PConstraint_new(bodyA, bodyB, pivotA, pivotB); this.setPivotA(this.constraint.pivotA); this.setPivotB(this.constraint.pivotB); + this.updateDebugDrawSize(); } updateScale0 (): void { diff --git a/cocos/physics/bullet/instantiated.ts b/cocos/physics/bullet/instantiated.ts index ed709063cfa..17d479cc3f3 100644 --- a/cocos/physics/bullet/instantiated.ts +++ b/cocos/physics/bullet/instantiated.ts @@ -44,7 +44,8 @@ export enum EBulletType{ EBulletTypeDbvtBroadPhase, EBulletTypeSequentialImpulseConstraintSolver, EBulletTypeCollisionWorld, - EBulletTypeTypedConstraint + EBulletTypeTypedConstraint, + EBulletTypeDebugDraw } //corresponds to btTriangleRaycastCallback::EFlags @@ -56,6 +57,29 @@ export enum EBulletTriangleRaycastFlag { UseGjkConvexCastRaytest = 1 << 3 } +//btIDebugDraw::EBulletDebugDrawModes +export enum EBulletDebugDrawModes +{ + DBG_NoDebug=0, + DBG_DrawWireframe = 1, + DBG_DrawAabb=2, + DBG_DrawFeaturesText=4, + DBG_DrawContactPoints=8, + DBG_NoDeactivation=16, + DBG_NoHelpText = 32, + DBG_DrawText=64, + DBG_ProfileTimings = 128, + DBG_EnableSatComparison = 256, + DBG_DisableBulletLCP = 512, + DBG_EnableCCD = 1024, + DBG_DrawConstraints = (1 << 11), + DBG_DrawConstraintLimits = (1 << 12), + DBG_FastWireframe = (1 << 13), + DBG_DrawNormals = (1 << 14), + DBG_DrawFrames = (1 << 15), + DBG_MAX_DEBUG_DRAW_MODE +} + interface instanceExt extends Bullet.instance { CACHE: any, BODY_CACHE_NAME: string, diff --git a/cocos/physics/cannon/cannon-world.ts b/cocos/physics/cannon/cannon-world.ts index 03b271d7aed..26c87052436 100644 --- a/cocos/physics/cannon/cannon-world.ts +++ b/cocos/physics/cannon/cannon-world.ts @@ -23,15 +23,20 @@ */ import CANNON from '@cocos/cannon'; -import { Vec3, RecyclePool, error, js, geometry, IVec3Like, IQuatLike, warnID } from '../../core'; +import { Vec3, RecyclePool, error, js, geometry, IVec3Like, IQuatLike, warnID, Color } from '../../core'; import { fillRaycastResult, toCannonRaycastOptions } from './cannon-util'; import { CannonConstraint } from './constraints/cannon-constraint'; import { CannonShape } from './shapes/cannon-shape'; import { CannonSharedBody } from './cannon-shared-body'; import { IPhysicsWorld, IRaycastOptions } from '../spec/i-physics-world'; -import { PhysicsMaterial, PhysicsRayResult } from '../framework'; +import { EPhysicsDrawFlags, PhysicsMaterial, PhysicsRayResult } from '../framework'; import { CannonRigidBody } from './cannon-rigid-body'; import { Node } from '../../scene-graph'; +import { GeometryRenderer } from '../../rendering/geometry-renderer'; +import { director } from '../../game'; + +const aabbTemp = new geometry.AABB(); +const AABB_LINE_COUNT = 12; export class CannonWorld implements IPhysicsWorld { get impl (): CANNON.World { @@ -64,6 +69,13 @@ export class CannonWorld implements IPhysicsWorld { private _world: CANNON.World; static readonly rayResult = new CANNON.RaycastResult(); + private _debugLineCount = 0; + private _MAX_DEBUG_LINE_COUNT = 16384; + private _debugDrawFlags = EPhysicsDrawFlags.NONE; + private _debugConstraintSize = 0.3; + private _aabbColor = new Color(0, 255, 255, 255); + private _wireframeColor = new Color(255, 0, 255, 255); + constructor () { this._world = new CANNON.World(); this._world.broadphase = new CANNON.NaiveBroadphase(); @@ -76,38 +88,71 @@ export class CannonWorld implements IPhysicsWorld { this._world.defaultContactMaterial.frictionEquationRelaxation = 3; } - sweepBox (worldRay: geometry.Ray, halfExtent: IVec3Like, orientation: IQuatLike, - options: IRaycastOptions, pool: RecyclePool, results: PhysicsRayResult[]): boolean { + sweepBox ( + worldRay: geometry.Ray, + halfExtent: IVec3Like, + orientation: IQuatLike, + options: IRaycastOptions, + pool: RecyclePool, + results: PhysicsRayResult[], + ): boolean { warnID(9641); return false; } - sweepBoxClosest (worldRay: geometry.Ray, halfExtent: IVec3Like, orientation: IQuatLike, - options: IRaycastOptions, result: PhysicsRayResult): boolean { + sweepBoxClosest ( + worldRay: geometry.Ray, + halfExtent: IVec3Like, + orientation: IQuatLike, + options: IRaycastOptions, + result: PhysicsRayResult, + ): boolean { warnID(9641); return false; } - sweepSphere (worldRay: geometry.Ray, radius: number, options: IRaycastOptions, - pool: RecyclePool, results: PhysicsRayResult[]): boolean { + sweepSphere ( + worldRay: geometry.Ray, + radius: number, + options: IRaycastOptions, + pool: RecyclePool, + results: PhysicsRayResult[], + ): boolean { warnID(9641); return false; } - sweepSphereClosest (worldRay: geometry.Ray, radius: number, options: IRaycastOptions, - result: PhysicsRayResult): boolean { + sweepSphereClosest ( + worldRay: geometry.Ray, + radius: number, + options: IRaycastOptions, + result: PhysicsRayResult, + ): boolean { warnID(9641); return false; } - sweepCapsule (worldRay: geometry.Ray, radius: number, height: number, orientation: IQuatLike, - options: IRaycastOptions, pool: RecyclePool, results: PhysicsRayResult[]): boolean { + sweepCapsule ( + worldRay: geometry.Ray, + radius: number, + height: number, + orientation: IQuatLike, + options: IRaycastOptions, + pool: RecyclePool, + results: PhysicsRayResult[], + ): boolean { warnID(9641); return false; } - sweepCapsuleClosest (worldRay: geometry.Ray, radius: number, height: number, - orientation: IQuatLike, options: IRaycastOptions, result: PhysicsRayResult): boolean { + sweepCapsuleClosest ( + worldRay: geometry.Ray, + radius: number, + height: number, + orientation: IQuatLike, + options: IRaycastOptions, + result: PhysicsRayResult, + ): boolean { warnID(9641); return false; } @@ -141,6 +186,8 @@ export class CannonWorld implements IPhysicsWorld { for (let i = 0; i < this.bodies.length; i++) { this.bodies[i].syncPhysicsToScene(); } + + this._debugDraw(); } raycastClosest (worldRay: geometry.Ray, options: IRaycastOptions, result: PhysicsRayResult): boolean { @@ -203,6 +250,52 @@ export class CannonWorld implements IPhysicsWorld { this._world.removeConstraint(constraint.impl); } } + + get debugDrawFlags (): EPhysicsDrawFlags { + return this._debugDrawFlags; + } + + set debugDrawFlags (v: EPhysicsDrawFlags) { + this._debugDrawFlags = v; + } + + get debugDrawConstraintSize (): number { + return this._debugConstraintSize; + } + + set debugDrawConstraintSize (v) { + this._debugConstraintSize = v; + } + + private _getDebugRenderer (): GeometryRenderer|null { + const cameras = director.root!.mainWindow?.cameras; + if (!cameras) return null; + if (cameras.length === 0) return null; + if (!cameras[0]) return null; + cameras[0].initGeometryRenderer(); + + return cameras[0].geometryRenderer; + } + + private _debugDraw (): void { + const debugRenderer = this._getDebugRenderer(); + if (!debugRenderer) return; + + this._debugLineCount = 0; + if (this._debugDrawFlags & EPhysicsDrawFlags.AABB) { + for (let i = 0; i < this.bodies.length; i++) { + const body = this.bodies[i]; + for (let j = 0; j < body.wrappedShapes.length; j++) { + const shape = body.wrappedShapes[j]; + if (this._debugLineCount + AABB_LINE_COUNT < this._MAX_DEBUG_LINE_COUNT) { + this._debugLineCount += AABB_LINE_COUNT; + shape.getAABB(aabbTemp); + debugRenderer.addBoundingBox(aabbTemp, this._aabbColor); + } + } + } + } + } } const from = new CANNON.Vec3(); diff --git a/cocos/physics/cocos/builtin-world.ts b/cocos/physics/cocos/builtin-world.ts index 304cea733a2..6ef27c336fb 100644 --- a/cocos/physics/cocos/builtin-world.ts +++ b/cocos/physics/cocos/builtin-world.ts @@ -22,7 +22,7 @@ THE SOFTWARE. */ -import { Vec3, RecyclePool, error, js, IVec3Like, geometry, IQuatLike, warnID } from '../../core'; +import { Vec3, RecyclePool, error, js, IVec3Like, geometry, IQuatLike, warnID, Color } from '../../core'; import { PhysicsRayResult } from '../framework/physics-ray-result'; import { BuiltinSharedBody } from './builtin-shared-body'; import { BuiltinShape } from './shapes/builtin-shape'; @@ -30,9 +30,12 @@ import { ArrayCollisionMatrix } from '../utils/array-collision-matrix'; import { IPhysicsWorld, IRaycastOptions } from '../spec/i-physics-world'; import { PhysicsMaterial } from '../framework/assets/physics-material'; import { TriggerEventType } from '../framework/physics-interface'; -import { Collider } from '../../../exports/physics-framework'; +import { Collider, EPhysicsDrawFlags } from '../../../exports/physics-framework'; import { BuiltinRigidBody } from './builtin-rigid-body'; import { Node } from '../../scene-graph'; +import { GeometryRenderer } from '../../rendering/geometry-renderer'; +import { director } from '../../game'; +import { VEC3_0 } from '../utils/util'; const hitPoint = new Vec3(); const TriggerEventObject = { @@ -42,51 +45,93 @@ const TriggerEventObject = { impl: {} as any, }; +const aabbTemp = new geometry.AABB(); +const AABB_LINE_COUNT = 12; + /** * Built-in collision system, intended for use as a * efficient discrete collision detector, * not a full physical simulator */ export class BuiltInWorld implements IPhysicsWorld { - sweepBox (worldRay: geometry.Ray, halfExtent: IVec3Like, orientation: IQuatLike, - options: IRaycastOptions, pool: RecyclePool, results: PhysicsRayResult[]): boolean { + sweepBox ( + worldRay: geometry.Ray, + halfExtent: IVec3Like, + orientation: IQuatLike, + options: IRaycastOptions, + pool: RecyclePool, + results: PhysicsRayResult[], + ): boolean { warnID(9640); return false; } - sweepBoxClosest (worldRay: geometry.Ray, halfExtent: IVec3Like, orientation: IQuatLike, - options: IRaycastOptions, result: PhysicsRayResult): boolean { + sweepBoxClosest ( + worldRay: geometry.Ray, + halfExtent: IVec3Like, + orientation: IQuatLike, + options: IRaycastOptions, + result: PhysicsRayResult, + ): boolean { warnID(9640); return false; } - sweepSphere (worldRay: geometry.Ray, radius: number, options: IRaycastOptions, - pool: RecyclePool, results: PhysicsRayResult[]): boolean { + sweepSphere ( + worldRay: geometry.Ray, + radius: number, + options: IRaycastOptions, + pool: RecyclePool, + results: PhysicsRayResult[], + ): boolean { warnID(9640); return false; } - sweepSphereClosest (worldRay: geometry.Ray, radius: number, - options: IRaycastOptions, result: PhysicsRayResult): boolean { + sweepSphereClosest ( + worldRay: geometry.Ray, + radius: number, + options: IRaycastOptions, + result: PhysicsRayResult, + ): boolean { warnID(9640); return false; } - sweepCapsule (worldRay: geometry.Ray, radius: number, height: number, orientation: IQuatLike, - options: IRaycastOptions, pool: RecyclePool, results: PhysicsRayResult[]): boolean { + sweepCapsule ( + worldRay: geometry.Ray, + radius: number, + height: number, + orientation: IQuatLike, + options: IRaycastOptions, + pool: RecyclePool, + results: PhysicsRayResult[], + ): boolean { warnID(9640); return false; } - sweepCapsuleClosest (worldRay: geometry.Ray, radius: number, height: number, - orientation: IQuatLike, options: IRaycastOptions, result: PhysicsRayResult): boolean { + sweepCapsuleClosest ( + worldRay: geometry.Ray, + radius: number, + height: number, + orientation: IQuatLike, + options: IRaycastOptions, + result: PhysicsRayResult, + ): boolean { warnID(9640); return false; } - setGravity (v: IVec3Like): void { } - setAllowSleep (v: boolean): void { } - setDefaultMaterial (v: PhysicsMaterial): void { } + setGravity (v: IVec3Like): void { + //empty + } + setAllowSleep (v: boolean): void { + //empty + } + setDefaultMaterial (v: PhysicsMaterial): void { + //empty + } get impl (): BuiltInWorld { return this; } shapeArr: BuiltinShape[] = []; readonly bodies: BuiltinSharedBody[] = []; @@ -95,6 +140,29 @@ export class BuiltInWorld implements IPhysicsWorld { private _collisionMatrix: ArrayCollisionMatrix = new ArrayCollisionMatrix(); private _collisionMatrixPrev: ArrayCollisionMatrix = new ArrayCollisionMatrix(); + private _debugLineCount = 0; + private _MAX_DEBUG_LINE_COUNT = 16384; + private _debugDrawFlags = EPhysicsDrawFlags.NONE; + private _debugConstraintSize = 0.3; + private _aabbColor = new Color(0, 255, 255, 255); + private _wireframeColor = new Color(255, 0, 255, 255); + + get debugDrawFlags (): EPhysicsDrawFlags { + return this._debugDrawFlags; + } + + set debugDrawFlags (v: EPhysicsDrawFlags) { + this._debugDrawFlags = v; + } + + get debugDrawConstraintSize (): number { + return this._debugConstraintSize; + } + + set debugDrawConstraintSize (v) { + this._debugConstraintSize = v; + } + destroy (): void { if (this.bodies.length) error('You should destroy all physics component first.'); } @@ -120,6 +188,8 @@ export class BuiltInWorld implements IPhysicsWorld { bodyA.intersects(bodyB); } } + + this._debugDraw(); } syncSceneToPhysics (): void { @@ -265,4 +335,34 @@ export class BuiltInWorld implements IPhysicsWorld { this._collisionMatrix.matrix = temp; this._collisionMatrix.reset(); } + + private _getDebugRenderer (): GeometryRenderer|null { + const cameras = director.root!.mainWindow?.cameras; + if (!cameras) return null; + if (cameras.length === 0) return null; + if (!cameras[0]) return null; + cameras[0].initGeometryRenderer(); + + return cameras[0].geometryRenderer; + } + + private _debugDraw (): void { + const debugRenderer = this._getDebugRenderer(); + if (!debugRenderer) return; + + this._debugLineCount = 0; + if (this._debugDrawFlags & EPhysicsDrawFlags.AABB) { + for (let i = 0; i < this.bodies.length; i++) { + const body = this.bodies[i]; + for (let j = 0; j < body.shapes.length; j++) { + const shape = body.shapes[j]; + if (this._debugLineCount + AABB_LINE_COUNT < this._MAX_DEBUG_LINE_COUNT) { + this._debugLineCount += AABB_LINE_COUNT; + shape.getAABB(aabbTemp); + debugRenderer.addBoundingBox(aabbTemp, this._aabbColor); + } + } + } + } + } } diff --git a/cocos/physics/cocos/shapes/builtin-box-shape.ts b/cocos/physics/cocos/shapes/builtin-box-shape.ts index 5c868652725..dba0245cfc1 100644 --- a/cocos/physics/cocos/shapes/builtin-box-shape.ts +++ b/cocos/physics/cocos/shapes/builtin-box-shape.ts @@ -27,6 +27,9 @@ import { BuiltinShape } from './builtin-shape'; import { IBoxShape } from '../../spec/i-physics-shape'; import { BoxCollider } from '../../../../exports/physics-framework'; +const tempMin = new Vec3(); +const tempMax = new Vec3(); + export class BuiltinBoxShape extends BuiltinShape implements IBoxShape { get localObb (): geometry.OBB { return this._localShape as geometry.OBB; @@ -55,4 +58,9 @@ export class BuiltinBoxShape extends BuiltinShape implements IBoxShape { super.onLoad(); this.updateSize(); } + + getAABB (v: geometry.AABB): void { + this.worldObb.getBoundary(tempMin, tempMax); + geometry.AABB.fromPoints(v, tempMin, tempMax); + } } diff --git a/cocos/physics/cocos/shapes/builtin-capsule-shape.ts b/cocos/physics/cocos/shapes/builtin-capsule-shape.ts index c687c5eeea5..650727a0071 100644 --- a/cocos/physics/cocos/shapes/builtin-capsule-shape.ts +++ b/cocos/physics/cocos/shapes/builtin-capsule-shape.ts @@ -24,9 +24,11 @@ import { BuiltinShape } from './builtin-shape'; import { ICapsuleShape } from '../../spec/i-physics-shape'; -import { geometry } from '../../../core'; +import { Vec3, geometry } from '../../../core'; import { EAxisDirection, CapsuleCollider } from '../../framework'; +const temp0 = new Vec3(); +const temp1 = new Vec3(); export class BuiltinCapsuleShape extends BuiltinShape implements ICapsuleShape { get localCapsule (): geometry.Capsule { return this._localShape as geometry.Capsule; @@ -90,4 +92,20 @@ export class BuiltinCapsuleShape extends BuiltinShape implements ICapsuleShape { this.setRadius(this.collider.radius); this.setDirection(this.collider.direction); } + + getAABB (v: geometry.AABB): void { + //capsule has not implemented getBoundary + v.center.set(this.worldCapsule.center); + v.halfExtents.set(0, 0, 0); + temp0.set(this.worldCapsule.radius, this.worldCapsule.radius, this.worldCapsule.radius); + + Vec3.add(temp1, this.worldCapsule.ellipseCenter0, temp0); + v.mergePoint(temp1); + Vec3.subtract(temp1, this.worldCapsule.ellipseCenter0, temp0); + v.mergePoint(temp1); + Vec3.add(temp1, this.worldCapsule.ellipseCenter1, temp0); + v.mergePoint(temp1); + Vec3.subtract(temp1, this.worldCapsule.ellipseCenter1, temp0); + v.mergePoint(temp1); + } } diff --git a/cocos/physics/cocos/shapes/builtin-sphere-shape.ts b/cocos/physics/cocos/shapes/builtin-sphere-shape.ts index 45a82ff5e1f..5d1b63cbacb 100644 --- a/cocos/physics/cocos/shapes/builtin-sphere-shape.ts +++ b/cocos/physics/cocos/shapes/builtin-sphere-shape.ts @@ -22,12 +22,14 @@ THE SOFTWARE. */ -import { geometry } from '../../../core'; +import { Vec3, geometry } from '../../../core'; import { BuiltinShape } from './builtin-shape'; import { ISphereShape } from '../../spec/i-physics-shape'; import { maxComponent } from '../../utils/util'; import { SphereCollider } from '../../../../exports/physics-framework'; +const tempMin = new Vec3(); +const tempMax = new Vec3(); export class BuiltinSphereShape extends BuiltinShape implements ISphereShape { updateRadius (): void { this.localSphere.radius = this.collider.radius; @@ -57,4 +59,9 @@ export class BuiltinSphereShape extends BuiltinShape implements ISphereShape { super.onLoad(); this.updateRadius(); } + + getAABB (v: geometry.AABB): void { + this.worldSphere.getBoundary(tempMin, tempMax); + geometry.AABB.fromPoints(v, tempMin, tempMax); + } } diff --git a/cocos/physics/framework/physics-enum.ts b/cocos/physics/framework/physics-enum.ts index a84d210aa48..90766a75d69 100644 --- a/cocos/physics/framework/physics-enum.ts +++ b/cocos/physics/framework/physics-enum.ts @@ -402,3 +402,38 @@ export enum PhysicsGroup { DEFAULT = 1, } Enum(PhysicsGroup); + +export enum EPhysicsDrawFlags { + /** + * @en + * Draw nothing. + * @zh + * 不绘制。 + */ + NONE = 0, + + /** + * @en + * Draw wireframe + * @zh + * 绘制线框。 + */ + WIRE_FRAME = 0x0001, + + /** + * @en + * Draw Constraint. + * @zh + * 绘制约束 + */ + CONSTRAINT = 0x0002, + + /** + * @en + * Draw AABB. + * @zh + * 绘制包围盒。 + */ + AABB = 0x0004, +} +Enum(EPhysicsDrawFlags); diff --git a/cocos/physics/framework/physics-selector.ts b/cocos/physics/framework/physics-selector.ts index 9be81501389..f65b5cc6bc0 100644 --- a/cocos/physics/framework/physics-selector.ts +++ b/cocos/physics/framework/physics-selector.ts @@ -202,6 +202,8 @@ export function constructDefaultWorld (data: IWorldInitData): void { const FUNC = (...v: any): any => 0 as any; const ENTIRE_WORLD: IPhysicsWorld = { impl: null, + debugDrawFlags: 0, + debugDrawConstraintSize: 0, setGravity: FUNC, setAllowSleep: FUNC, setDefaultMaterial: FUNC, diff --git a/cocos/physics/framework/physics-system.ts b/cocos/physics/framework/physics-system.ts index ae6668b9642..e83c672d0aa 100644 --- a/cocos/physics/framework/physics-system.ts +++ b/cocos/physics/framework/physics-system.ts @@ -339,7 +339,7 @@ export class PhysicsSystem extends System implements IWorldInitData { mask: -1, queryTrigger: true, maxDistance: 10000000, - } + }; private readonly raycastResultPool = new RecyclePool((): PhysicsRayResult => new PhysicsRayResult(), 1); private readonly sweepResultPool = new RecyclePool((): PhysicsRayResult => new PhysicsRayResult(), 1); @@ -462,6 +462,39 @@ export class PhysicsSystem extends System implements IWorldInitData { if (this.physicsWorld) this.physicsWorld.emitEvents(); } + /** + * @en + * Get or set debug draw flags. Default is EPhysicsDrawFlags.NONE. + * Refer to EPhysicsDrawFlags. + * Note: Since physics debug draw uses Geometry-Renderer to do drawing, + * make sure Geometry-Renderer is not cropped in Project Setting. + * @zh + * 获取或设置调试绘制标志。默认为 EPhysicsDrawFlags.NONE。 + * 参考 EPhysicsDrawFlags。 + * 注意:因为物理调试绘制使用几何渲染器来绘制,请确保项目设置中几何渲染器没有被裁剪掉。 + */ + get debugDrawFlags (): number { + return this.physicsWorld.debugDrawFlags; + } + + set debugDrawFlags (v) { + this.physicsWorld.debugDrawFlags = v; + } + + /** + * @en + * Get or set constraint debug draw size. Default is 0.3. + * @zh + * 获取或设置约束的调试绘制尺寸。默认为 0.3。 + */ + get debugDrawConstraintSize (): number { + return this.physicsWorld.debugDrawConstraintSize; + } + + set debugDrawConstraintSize (v) { + this.physicsWorld.debugDrawConstraintSize = v; + } + /** * @en * Collision detect all collider, and record all the detected results, through PhysicsSystem.Instance.RaycastResults access to the results. @@ -607,16 +640,28 @@ export class PhysicsSystem extends System implements IWorldInitData { * @param queryTrigger @zh 是否检测触发器 @en Whether to detect triggers * @return {boolean} @zh 表示是否有检测到碰撞 @en Indicates whether a collision has been detected */ - sweepBox (worldRay: geometry.Ray, halfExtent: IVec3Like, orientation: IQuatLike, - mask = 0xffffffff, maxDistance = 10000000, queryTrigger = true): boolean { + sweepBox ( + worldRay: geometry.Ray, + halfExtent: IVec3Like, + orientation: IQuatLike, + mask = 0xffffffff, + maxDistance = 10000000, + queryTrigger = true, + ): boolean { if (!this.physicsWorld) return false; this.sweepResultPool.reset(); this.sweepCastResults.length = 0; this.raycastOptions.mask = mask >>> 0; this.raycastOptions.maxDistance = maxDistance; this.raycastOptions.queryTrigger = queryTrigger; - return this.physicsWorld.sweepBox(worldRay, halfExtent, orientation, - this.raycastOptions, this.sweepResultPool, this.sweepCastResults); + return this.physicsWorld.sweepBox( + worldRay, + halfExtent, + orientation, + this.raycastOptions, + this.sweepResultPool, + this.sweepCastResults, + ); } /** @@ -634,14 +679,25 @@ export class PhysicsSystem extends System implements IWorldInitData { * @param queryTrigger @zh 是否检测触发器 @en Whether to detect triggers * @return {boolean} @zh 表示是否有检测到碰撞 @en Indicates whether a collision has been detected */ - sweepBoxClosest (worldRay: geometry.Ray, halfExtent: IVec3Like, orientation: IQuatLike, - mask = 0xffffffff, maxDistance = 10000000, queryTrigger = true): boolean { + sweepBoxClosest ( + worldRay: geometry.Ray, + halfExtent: IVec3Like, + orientation: IQuatLike, + mask = 0xffffffff, + maxDistance = 10000000, + queryTrigger = true, + ): boolean { if (!this.physicsWorld) return false; this.raycastOptions.mask = mask >>> 0; this.raycastOptions.maxDistance = maxDistance; this.raycastOptions.queryTrigger = queryTrigger; - return this.physicsWorld.sweepBoxClosest(worldRay, halfExtent, orientation, - this.raycastOptions, this.sweepCastClosestResult); + return this.physicsWorld.sweepBoxClosest( + worldRay, + halfExtent, + orientation, + this.raycastOptions, + this.sweepCastClosestResult, + ); } /** @@ -658,16 +714,26 @@ export class PhysicsSystem extends System implements IWorldInitData { * @param queryTrigger @zh 是否检测触发器 @en Whether to detect triggers * @return {boolean} @zh 表示是否有检测到碰撞 @en Indicates whether a collision has been detected */ - sweepSphere (worldRay: geometry.Ray, radius: number, - mask = 0xffffffff, maxDistance = 10000000, queryTrigger = true): boolean { + sweepSphere ( + worldRay: geometry.Ray, + radius: number, + mask = 0xffffffff, + maxDistance = 10000000, + queryTrigger = true, + ): boolean { if (!this.physicsWorld) return false; this.sweepResultPool.reset(); this.sweepCastResults.length = 0; this.raycastOptions.mask = mask >>> 0; this.raycastOptions.maxDistance = maxDistance; this.raycastOptions.queryTrigger = queryTrigger; - return this.physicsWorld.sweepSphere(worldRay, radius, - this.raycastOptions, this.sweepResultPool, this.sweepCastResults); + return this.physicsWorld.sweepSphere( + worldRay, + radius, + this.raycastOptions, + this.sweepResultPool, + this.sweepCastResults, + ); } /** @@ -684,14 +750,23 @@ export class PhysicsSystem extends System implements IWorldInitData { * @param queryTrigger @zh 是否检测触发器 @en Whether to detect triggers * @return {boolean} @zh 表示是否有检测到碰撞 @en Indicates whether a collision has been detected */ - sweepSphereClosest (worldRay: geometry.Ray, radius: number, - mask = 0xffffffff, maxDistance = 10000000, queryTrigger = true): boolean { + sweepSphereClosest ( + worldRay: geometry.Ray, + radius: number, + mask = 0xffffffff, + maxDistance = 10000000, + queryTrigger = true, + ): boolean { if (!this.physicsWorld) return false; this.raycastOptions.mask = mask >>> 0; this.raycastOptions.maxDistance = maxDistance; this.raycastOptions.queryTrigger = queryTrigger; - return this.physicsWorld.sweepSphereClosest(worldRay, radius, - this.raycastOptions, this.sweepCastClosestResult); + return this.physicsWorld.sweepSphereClosest( + worldRay, + radius, + this.raycastOptions, + this.sweepCastClosestResult, + ); } /** @@ -712,16 +787,30 @@ export class PhysicsSystem extends System implements IWorldInitData { * @param queryTrigger @zh 是否检测触发器 @en Whether to detect triggers * @return {boolean} @zh 表示是否有检测到碰撞 @en Indicates whether a collision has been detected */ - sweepCapsule (worldRay: geometry.Ray, radius: number, height: number, orientation: IQuatLike, - mask = 0xffffffff, maxDistance = 10000000, queryTrigger = true): boolean { + sweepCapsule ( + worldRay: geometry.Ray, + radius: number, + height: number, + orientation: IQuatLike, + mask = 0xffffffff, + maxDistance = 10000000, + queryTrigger = true, + ): boolean { if (!this.physicsWorld) return false; this.sweepResultPool.reset(); this.sweepCastResults.length = 0; this.raycastOptions.mask = mask >>> 0; this.raycastOptions.maxDistance = maxDistance; this.raycastOptions.queryTrigger = queryTrigger; - return this.physicsWorld.sweepCapsule(worldRay, radius, height, orientation, - this.raycastOptions, this.sweepResultPool, this.sweepCastResults); + return this.physicsWorld.sweepCapsule( + worldRay, + radius, + height, + orientation, + this.raycastOptions, + this.sweepResultPool, + this.sweepCastResults, + ); } /** @@ -742,14 +831,27 @@ export class PhysicsSystem extends System implements IWorldInitData { * @param queryTrigger @zh 是否检测触发器 @en Whether to detect triggers * @return {boolean} @zh 表示是否有检测到碰撞 @en Indicates whether a collision has been detected */ - sweepCapsuleClosest (worldRay: geometry.Ray, radius: number, height: number, orientation: IQuatLike, - mask = 0xffffffff, maxDistance = 10000000, queryTrigger = true): boolean { + sweepCapsuleClosest ( + worldRay: geometry.Ray, + radius: number, + height: number, + orientation: IQuatLike, + mask = 0xffffffff, + maxDistance = 10000000, + queryTrigger = true, + ): boolean { if (!this.physicsWorld) return false; this.raycastOptions.mask = mask >>> 0; this.raycastOptions.maxDistance = maxDistance; this.raycastOptions.queryTrigger = queryTrigger; - return this.physicsWorld.sweepCapsuleClosest(worldRay, radius, height, orientation, - this.raycastOptions, this.sweepCastClosestResult); + return this.physicsWorld.sweepCapsuleClosest( + worldRay, + radius, + height, + orientation, + this.raycastOptions, + this.sweepCastClosestResult, + ); } private _updateMaterial (): void { diff --git a/cocos/physics/physx/joints/physx-configurable-joint.ts b/cocos/physics/physx/joints/physx-configurable-joint.ts index fcedd4401e1..76c6648c991 100644 --- a/cocos/physics/physx/joints/physx-configurable-joint.ts +++ b/cocos/physics/physx/joints/physx-configurable-joint.ts @@ -359,6 +359,7 @@ export class PhysXConfigurableJoint extends PhysXJoint implements IConfigurableC this._updateDriveSettings(); this.updateFrames(); + this.enableDebugVisualization(true); } updateFrames (): void { @@ -422,9 +423,9 @@ export class PhysXConfigurableJoint extends PhysXJoint implements IConfigurableC private static _drive_x: any = null; private static _drive_y: any = null; private static _drive_z: any = null; - private static _drive_twist: any= null; - private static _drive_swing1: any= null; - private static _drive_swing2: any= null; + private static _drive_twist: any = null; + private static _drive_swing1: any = null; + private static _drive_swing2: any = null; private static _drive: any[] = []; private static _initCache (): void { if (!PhysXConfigurableJoint._jointToleranceScale) { diff --git a/cocos/physics/physx/joints/physx-fixed-joint.ts b/cocos/physics/physx/joints/physx-fixed-joint.ts index 1609e81cf11..25c276c1478 100644 --- a/cocos/physics/physx/joints/physx-fixed-joint.ts +++ b/cocos/physics/physx/joints/physx-fixed-joint.ts @@ -59,6 +59,7 @@ export class PhysXFixedJoint extends PhysXJoint implements IFixedConstraint { this.setBreakForce(this.constraint.breakForce); this.setBreakTorque(this.constraint.breakTorque); this.updateFrame(); + this.enableDebugVisualization(true); } updateFrame (): void { diff --git a/cocos/physics/physx/joints/physx-joint.ts b/cocos/physics/physx/joints/physx-joint.ts index 86a5dd5a691..6be6778e8e0 100644 --- a/cocos/physics/physx/joints/physx-joint.ts +++ b/cocos/physics/physx/joints/physx-joint.ts @@ -92,6 +92,12 @@ export class PhysXJoint implements IBaseConstraint { } } + enableDebugVisualization (v: boolean): void { + if (this.impl) { + this.impl.setConstraintFlag(1 << 4, v);// PxConstraintFlag::eVISUALIZATION + } + } + // virtual protected onComponentSet (): void { } diff --git a/cocos/physics/physx/joints/physx-revolute-joint.ts b/cocos/physics/physx/joints/physx-revolute-joint.ts index 4099c72b52e..83d2c12aeb4 100644 --- a/cocos/physics/physx/joints/physx-revolute-joint.ts +++ b/cocos/physics/physx/joints/physx-revolute-joint.ts @@ -113,6 +113,7 @@ export class PhysXRevoluteJoint extends PhysXJoint implements IHingeConstraint { this.setMotorVelocity(this.constraint.motorVelocity); this.setMotorForceLimit(this.constraint.motorForceLimit); this.updateFrames(); + this.enableDebugVisualization(true); } updateFrames (): void { diff --git a/cocos/physics/physx/joints/physx-spherical-joint.ts b/cocos/physics/physx/joints/physx-spherical-joint.ts index cba277046cd..f463614754a 100644 --- a/cocos/physics/physx/joints/physx-spherical-joint.ts +++ b/cocos/physics/physx/joints/physx-spherical-joint.ts @@ -66,6 +66,7 @@ export class PhysXSphericalJoint extends PhysXJoint implements IPointToPointCons this._impl = PX.createSphericalJoint(PhysXJoint.tempActor, _pxtrans, null, _pxtrans); this.setPivotA(this.constraint.pivotA); this.setPivotB(this.constraint.pivotB); + this.enableDebugVisualization(true); } updateScale0 (): void { diff --git a/cocos/physics/physx/physx-adapter.ts b/cocos/physics/physx/physx-adapter.ts index a295fe96879..d6caf0be551 100644 --- a/cocos/physics/physx/physx-adapter.ts +++ b/cocos/physics/physx/physx-adapter.ts @@ -33,7 +33,7 @@ import { WebAssemblySupportMode } from '../../misc/webassembly-support'; import { ensureWasmModuleReady, instantiateWasm } from 'pal/wasm'; import { BYTEDANCE, DEBUG, EDITOR, TEST, WASM_SUPPORT_MODE } from 'internal:constants'; -import { IQuatLike, IVec3Like, Quat, RecyclePool, Vec3, cclegacy, geometry, Settings, settings, sys, error } from '../../core'; +import { IQuatLike, IVec3Like, Quat, RecyclePool, Vec3, cclegacy, geometry, Settings, settings, sys, Color } from '../../core'; import { shrinkPositions } from '../utils/util'; import { IRaycastOptions } from '../spec/i-physics-world'; import { IPhysicsConfig, PhysicsRayResult, PhysicsSystem, CharacterControllerContact } from '../framework'; @@ -46,7 +46,7 @@ import { Director, director, game } from '../../game'; import { degreesToRadians } from '../../core/utils/misc'; import { PhysXCharacterController } from './character-controllers/physx-character-controller'; -export const PX = {} as any; +export let PX = {} as any; const globalThis = cclegacy._global; // Use bytedance native or js physics if nativePhysX is not null. const USE_BYTEDANCE = BYTEDANCE && globalThis.nativePhysX; @@ -120,7 +120,7 @@ function initWASM (physxWasmFactory, physxWasmUrl): any { if (!EDITOR && !TEST) console.debug('[PHYSICS]:', `${USE_EXTERNAL_PHYSX ? 'External' : 'Internal'} PhysX wasm libs loaded.`); initAdaptWrapper(Instance); initConfigAndCacheObject(Instance); - Object.assign(PX, Instance); + PX = Instance; }, (reason: any): void => { console.error('[PHYSICS]:', `PhysX wasm load failed: ${reason}`); }); } else { if (!EDITOR && !TEST) console.error('[PHYSICS]:', 'Failed to load PhysX wasm libs, package may be not found.'); @@ -198,6 +198,13 @@ type IPxTransformExt = { [x in keyof typeof _trans]: typeof _trans[x]; } & setQuaternion(quat: IQuatLike): void; }; +export function getColorPXColor (color: Color, rgba: number): void { + color.b = ((rgba >> 16) & 0xff); + color.g = ((rgba >> 8) & 0xff); + color.r = ((rgba) & 0xff); + color.a = 255; +} + export const _pxtrans = _trans as unknown as IPxTransformExt; export function addReference (shape: PhysXShape, impl: any): void { @@ -336,11 +343,11 @@ export function applyTorqueForce (impl: any, vec: IVec3Like): void { export function getShapeFlags (isTrigger: boolean): any { if (USE_BYTEDANCE) { const flag = (isTrigger ? PX.ShapeFlag.eTRIGGER_SHAPE : PX.ShapeFlag.eSIMULATION_SHAPE) - | PX.ShapeFlag.eSCENE_QUERY_SHAPE; + | PX.ShapeFlag.eSCENE_QUERY_SHAPE | PX.ShapeFlag.eVISUALIZATION; return flag; } const flag = (isTrigger ? PX.PxShapeFlag.eTRIGGER_SHAPE.value : PX.PxShapeFlag.eSIMULATION_SHAPE.value) - | PX.PxShapeFlag.eSCENE_QUERY_SHAPE.value; + | PX.PxShapeFlag.eSCENE_QUERY_SHAPE.value | PX.PxShapeFlag.eVISUALIZATION.value; return new PX.PxShapeFlags(flag); } @@ -826,6 +833,7 @@ export function initializeWorld (world: any): void { const sceneDesc = PX.getDefaultSceneDesc(PhysXInstance.physics.getTolerancesScale(), 0, PhysXInstance.simulationCB); world.scene = PhysXInstance.physics.createScene(sceneDesc); + world.scene.setVisualizationParameter(PX.PxVisualizationParameter.eSCALE, 1); world.controllerManager = PX.PxCreateControllerManager(world.scene, false); } } diff --git a/cocos/physics/physx/physx-shared-body.ts b/cocos/physics/physx/physx-shared-body.ts index cae1a5e9c44..8695c571df0 100644 --- a/cocos/physics/physx/physx-shared-body.ts +++ b/cocos/physics/physx/physx-shared-body.ts @@ -149,6 +149,7 @@ export class PhysXSharedBody { if (this._staticActor) return; const t = getTempTransform(this.node.worldPosition, this.node.worldRotation); this._staticActor = PhysXInstance.physics.createRigidStatic(t); + this._staticActor.setActorFlag(PX.ActorFlag.eVISUALIZATION, true); if (this._staticActor.$$) PX.IMPL_PTR[this._staticActor.$$.ptr] = this; } @@ -161,6 +162,7 @@ export class PhysXSharedBody { if (wb) { const rb = wb.rigidBody; this._dynamicActor.setMass(rb.mass); + this._dynamicActor.setActorFlag(PX.ActorFlag.eVISUALIZATION, true); this._dynamicActor.setActorFlag(PX.ActorFlag.eDISABLE_GRAVITY, !rb.useGravity); this.setLinearDamping(rb.linearDamping); this.setAngularDamping(rb.angularDamping); diff --git a/cocos/physics/physx/physx-world.ts b/cocos/physics/physx/physx-world.ts index 6ffe301e263..6d1a9a63966 100644 --- a/cocos/physics/physx/physx-world.ts +++ b/cocos/physics/physx/physx-world.ts @@ -25,13 +25,14 @@ /* eslint-disable @typescript-eslint/no-unsafe-return */ import { IPhysicsWorld, IRaycastOptions } from '../spec/i-physics-world'; import { PhysicsMaterial, PhysicsRayResult, CollisionEventType, TriggerEventType, CharacterTriggerEventType, - CharacterControllerContact } from '../framework'; -import { error, RecyclePool, js, IVec3Like, geometry, IQuatLike, Vec3, Quat } from '../../core'; + CharacterControllerContact, + EPhysicsDrawFlags } from '../framework'; +import { error, RecyclePool, js, IVec3Like, geometry, IQuatLike, Vec3, Quat, Color } from '../../core'; import { IBaseConstraint } from '../spec/i-physics-constraint'; import { PhysXRigidBody } from './physx-rigid-body'; import { addActorToScene, raycastAll, simulateScene, initializeWorld, raycastClosest, sweepClosest, - gatherEvents, getWrapShape, PX, getContactDataOrByteOffset, sweepAll, + gatherEvents, getWrapShape, PX, getContactDataOrByteOffset, sweepAll, getColorPXColor, } from './physx-adapter'; import { PhysXSharedBody } from './physx-shared-body'; import { TupleDictionary } from '../utils/tuple-dictionary'; @@ -42,9 +43,14 @@ import { EFilterDataWord3 } from './physx-enum'; import { PhysXInstance } from './physx-instance'; import { Node } from '../../scene-graph'; import { PhysXCharacterController } from './character-controllers/physx-character-controller'; +import { GeometryRenderer } from '../../rendering/geometry-renderer'; +import { director } from '../../game'; const CC_QUAT_0 = new Quat(); - +const CC_V3_0 = new Vec3(); +const CC_V3_1 = new Vec3(); +const CC_V3_2 = new Vec3(); +const CC_COLOR_0 = new Color(0, 0, 0, 0); export class PhysXWorld extends PhysXInstance implements IPhysicsWorld { setAllowSleep (_v: boolean): void { } setDefaultMaterial (_v: PhysicsMaterial): void { } @@ -66,6 +72,11 @@ export class PhysXWorld extends PhysXInstance implements IPhysicsWorld { private static _sweepSphereGeometry: any; private static _sweepCapsuleGeometry: any; + private _debugLineCount = 0; + private _MAX_DEBUG_LINE_COUNT = 16384; + private _debugDrawFlags = EPhysicsDrawFlags.NONE; + private _debugConstraintSize = 0.3; + constructor () { super(); initializeWorld(this); @@ -86,6 +97,8 @@ export class PhysXWorld extends PhysXInstance implements IPhysicsWorld { body.syncPhysicsToScene(); } } + + this._debugDraw(); } private _simulate (dt: number): void { @@ -131,6 +144,116 @@ export class PhysXWorld extends PhysXInstance implements IPhysicsWorld { } } + get debugDrawFlags (): EPhysicsDrawFlags { + return this._debugDrawFlags; + } + + set debugDrawFlags (v: EPhysicsDrawFlags) { + this._debugDrawFlags = v; + this._setDebugDrawMode(); + } + + get debugDrawConstraintSize (): number { + return this._debugConstraintSize; + } + + set debugDrawConstraintSize (v) { + this._debugConstraintSize = v; + this._setDebugDrawMode(); + } + + private _setDebugDrawMode (): void { + if (this._debugDrawFlags & EPhysicsDrawFlags.WIRE_FRAME) { + this.scene.setVisualizationParameter(PX.PxVisualizationParameter.eCOLLISION_SHAPES, 1); + } else { + this.scene.setVisualizationParameter(PX.PxVisualizationParameter.eCOLLISION_SHAPES, 0); + } + + const drawConstraint = Boolean(this._debugDrawFlags & EPhysicsDrawFlags.CONSTRAINT); + const internalConstraintSize = drawConstraint ? this._debugConstraintSize : 0; + this.scene.setVisualizationParameter(PX.PxVisualizationParameter.eJOINT_LOCAL_FRAMES, internalConstraintSize); + this.scene.setVisualizationParameter(PX.PxVisualizationParameter.eJOINT_LIMITS, internalConstraintSize); + + if (this._debugDrawFlags & EPhysicsDrawFlags.AABB) { + this.scene.setVisualizationParameter(PX.PxVisualizationParameter.eCOLLISION_AABBS, 1); + } else { + this.scene.setVisualizationParameter(PX.PxVisualizationParameter.eCOLLISION_AABBS, 0); + } + } + + private _getDebugRenderer (): GeometryRenderer|null { + const cameras = director.root!.mainWindow?.cameras; + if (!cameras) return null; + if (cameras.length === 0) return null; + if (!cameras[0]) return null; + cameras[0].initGeometryRenderer(); + + return cameras[0].geometryRenderer; + } + + private _debugDraw (): void { + const debugRenderer = this._getDebugRenderer(); + if (!debugRenderer) return; + + this._debugLineCount = 0; + const rbPtr = this.scene.getRenderBufferPtr();//PxRenderBuffer + const nbLine = PX.PxRenderBuffer_GetNbLines(rbPtr); + for (let i = 0; i < nbLine; i++) { + const linePtr = PX.PxRenderBuffer_GetLineAt(rbPtr, i) as number;//PxDebugLine + this._onDebugDrawLine(linePtr); + } + const nbTriangle = PX.PxRenderBuffer_GetNbTriangles(rbPtr); + for (let i = 0; i < nbTriangle; i++) { + const trianglePtr = PX.PxRenderBuffer_GetTriangleAt(rbPtr, i) as number;//PxDebugTriangle + this._onDebugDrawTriangle(trianglePtr); + } + } + + private _onDebugDrawLine (linePtr: number): void { + const debugRenderer = this._getDebugRenderer(); + if (debugRenderer && this._debugLineCount < this._MAX_DEBUG_LINE_COUNT) { + this._debugLineCount++; + const f32RawPtr = PX.HEAPF32.subarray(linePtr / 4, linePtr / 4 + 3 * 8); + const u32RawPtr = PX.HEAPU32.subarray(linePtr / 4, linePtr / 4 + 3 * 8); + CC_V3_0.x = f32RawPtr[0]; + CC_V3_0.y = f32RawPtr[1]; + CC_V3_0.z = f32RawPtr[2]; + const color0 = u32RawPtr[3] as number; + CC_V3_1.x = f32RawPtr[4]; + CC_V3_1.y = f32RawPtr[5]; + CC_V3_1.z = f32RawPtr[6]; + getColorPXColor(CC_COLOR_0, color0); + debugRenderer.addLine(CC_V3_0, CC_V3_1, CC_COLOR_0); + } + } + + private _onDebugDrawTriangle (trianglePtr: number): void { + const debugRenderer = this._getDebugRenderer(); + if (debugRenderer && (this._MAX_DEBUG_LINE_COUNT - this._debugLineCount) >= 3) { + this._debugLineCount += 3; + const f32RawPtr = PX.HEAPF32.subarray(trianglePtr / 4, trianglePtr / 4 + 3 * 12); + const u32RawPtr = PX.HEAPU32.subarray(trianglePtr / 4, trianglePtr / 4 + 3 * 12); + CC_V3_0.x = f32RawPtr[0]; + CC_V3_0.y = f32RawPtr[1]; + CC_V3_0.z = f32RawPtr[2]; + const color0 = u32RawPtr[3] as number; + CC_V3_1.x = f32RawPtr[4]; + CC_V3_1.y = f32RawPtr[5]; + CC_V3_1.z = f32RawPtr[6]; + // const color1 = u32RawPtr[7] as number; + CC_V3_2.x = f32RawPtr[8]; + CC_V3_2.y = f32RawPtr[9]; + CC_V3_2.z = f32RawPtr[10]; + // const color2 = u32RawPtr[11] as number; + getColorPXColor(CC_COLOR_0, color0); + debugRenderer.addLine(CC_V3_0, CC_V3_1, CC_COLOR_0); + // getColorPXColor(CC_COLOR_0, color1); + debugRenderer.addLine(CC_V3_1, CC_V3_2, CC_COLOR_0); + // getColorPXColor(CC_COLOR_0, color2); + debugRenderer.addLine(CC_V3_2, CC_V3_0, CC_COLOR_0); + } + } + getSharedBody (node: Node, wrappedBody?: PhysXRigidBody): PhysXSharedBody { return PhysXSharedBody.getSharedBody(node, this, wrappedBody); } @@ -177,8 +300,14 @@ export class PhysXWorld extends PhysXInstance implements IPhysicsWorld { return raycastClosest(this, worldRay, options, result); } - sweepBox (worldRay: geometry.Ray, halfExtent: IVec3Like, orientation: IQuatLike, - options: IRaycastOptions, pool: RecyclePool, results: PhysicsRayResult[]): boolean { + sweepBox ( + worldRay: geometry.Ray, + halfExtent: IVec3Like, + orientation: IQuatLike, + options: IRaycastOptions, + pool: RecyclePool, + results: PhysicsRayResult[], + ): boolean { if (!PhysXWorld._sweepBoxGeometry) { PhysXWorld._sweepBoxGeometry = new PX.BoxGeometry(halfExtent); } @@ -186,8 +315,13 @@ export class PhysXWorld extends PhysXInstance implements IPhysicsWorld { return sweepAll(this, worldRay, PhysXWorld._sweepBoxGeometry, orientation, options, pool, results); } - sweepBoxClosest (worldRay: geometry.Ray, halfExtent: IVec3Like, orientation: IQuatLike, - options: IRaycastOptions, result: PhysicsRayResult): boolean { + sweepBoxClosest ( + worldRay: geometry.Ray, + halfExtent: IVec3Like, + orientation: IQuatLike, + options: IRaycastOptions, + result: PhysicsRayResult, + ): boolean { if (!PhysXWorld._sweepBoxGeometry) { PhysXWorld._sweepBoxGeometry = new PX.BoxGeometry(halfExtent); } @@ -195,8 +329,13 @@ export class PhysXWorld extends PhysXInstance implements IPhysicsWorld { return sweepClosest(this, worldRay, PhysXWorld._sweepBoxGeometry, orientation, options, result); } - sweepSphere (worldRay: geometry.Ray, radius: number, - options: IRaycastOptions, pool: RecyclePool, results: PhysicsRayResult[]): boolean { + sweepSphere ( + worldRay: geometry.Ray, + radius: number, + options: IRaycastOptions, + pool: RecyclePool, + results: PhysicsRayResult[], + ): boolean { if (!PhysXWorld._sweepSphereGeometry) { PhysXWorld._sweepSphereGeometry = new PX.SphereGeometry(radius); } @@ -204,8 +343,12 @@ export class PhysXWorld extends PhysXInstance implements IPhysicsWorld { return sweepAll(this, worldRay, PhysXWorld._sweepSphereGeometry, Quat.IDENTITY, options, pool, results); } - sweepSphereClosest (worldRay: geometry.Ray, radius: number, - options: IRaycastOptions, result: PhysicsRayResult): boolean { + sweepSphereClosest ( + worldRay: geometry.Ray, + radius: number, + options: IRaycastOptions, + result: PhysicsRayResult, + ): boolean { if (!PhysXWorld._sweepSphereGeometry) { PhysXWorld._sweepSphereGeometry = new PX.SphereGeometry(radius); } @@ -213,8 +356,15 @@ export class PhysXWorld extends PhysXInstance implements IPhysicsWorld { return sweepClosest(this, worldRay, PhysXWorld._sweepSphereGeometry, Quat.IDENTITY, options, result); } - sweepCapsule (worldRay: geometry.Ray, radius: number, height: number, orientation: IQuatLike, - options: IRaycastOptions, pool: RecyclePool, results: PhysicsRayResult[]): boolean { + sweepCapsule ( + worldRay: geometry.Ray, + radius: number, + height: number, + orientation: IQuatLike, + options: IRaycastOptions, + pool: RecyclePool, + results: PhysicsRayResult[], + ): boolean { if (!PhysXWorld._sweepCapsuleGeometry) { PhysXWorld._sweepCapsuleGeometry = new PX.CapsuleGeometry(radius, height / 2); } @@ -227,8 +377,14 @@ export class PhysXWorld extends PhysXInstance implements IPhysicsWorld { return sweepAll(this, worldRay, PhysXWorld._sweepCapsuleGeometry, finalOrientation, options, pool, results); } - sweepCapsuleClosest (worldRay: geometry.Ray, radius: number, height: number, orientation: IQuatLike, - options: IRaycastOptions, result: PhysicsRayResult): boolean { + sweepCapsuleClosest ( + worldRay: geometry.Ray, + radius: number, + height: number, + orientation: IQuatLike, + options: IRaycastOptions, + result: PhysicsRayResult, + ): boolean { if (!PhysXWorld._sweepCapsuleGeometry) { PhysXWorld._sweepCapsuleGeometry = new PX.CapsuleGeometry(radius, height / 2); } @@ -518,8 +674,11 @@ const PhysXCallback = { const motionDir = new Vec3(); motionDir.set(hit.dir.x, hit.dir.y, hit.dir.z); const motionLength = hit.length; - item = cctShapeEventDic.set(hit.getCurrentController(), hit.getTouchedShape(), - { PhysXCharacterController: cct, PhysXShape: s, worldPos, worldNormal, motionDir, motionLength }); + item = cctShapeEventDic.set( + hit.getCurrentController(), + hit.getTouchedShape(), + { PhysXCharacterController: cct, PhysXShape: s, worldPos, worldNormal, motionDir, motionLength }, + ); } }, onControllerHit (hit: any): void { //PX.ControllersHit diff --git a/cocos/physics/spec/i-physics-world.ts b/cocos/physics/spec/i-physics-world.ts index 170b838ecf3..fdaaaa41e48 100644 --- a/cocos/physics/spec/i-physics-world.ts +++ b/cocos/physics/spec/i-physics-world.ts @@ -24,7 +24,7 @@ import { IVec3Like, RecyclePool, geometry, IQuatLike } from '../../core'; import { PhysicsRayResult } from '../framework/physics-ray-result'; -import { PhysicsMaterial } from '../framework'; +import { EPhysicsDrawFlags, PhysicsMaterial } from '../framework'; export interface IRaycastOptions { mask: number; @@ -35,6 +35,8 @@ export interface IRaycastOptions { export interface IPhysicsWorld { readonly impl: any; + debugDrawFlags: EPhysicsDrawFlags; + debugDrawConstraintSize: number; setGravity: (v: IVec3Like) => void; setAllowSleep: (v: boolean) => void; setDefaultMaterial: (v: PhysicsMaterial) => void; diff --git a/exports/physics-framework.ts b/exports/physics-framework.ts index cbadf86dfa5..1cb60ec9de5 100644 --- a/exports/physics-framework.ts +++ b/exports/physics-framework.ts @@ -65,6 +65,7 @@ export { EAxisDirection, ERigidBodyType, EColliderType, + EPhysicsDrawFlags, } from '../cocos/physics/framework'; export type { diff --git a/native/cocos/physics/physx/PhysXUtils.h b/native/cocos/physics/physx/PhysXUtils.h index 46fc6444571..59a79a771cd 100644 --- a/native/cocos/physics/physx/PhysXUtils.h +++ b/native/cocos/physics/physx/PhysXUtils.h @@ -27,6 +27,7 @@ #include "base/Macros.h" #include "base/std/container/unordered_map.h" #include "base/std/container/vector.h" +#include "renderer/pipeline/Define.h" #include "math/Vec3.h" #include "math/Vec4.h" #include "physics/physx/PhysXFilterShader.h" @@ -116,6 +117,13 @@ inline void pxSetQuatExt(T1 &p, const T2 &cp) { p = T1(cp.x, cp.y, cp.z, cp.w); } +inline void pxSetColor(gfx::Color& color, physx::PxU32 rgba) { + color.z = ((rgba >> 16) & 0xff); + color.y = ((rgba >> 8) & 0xff); + color.x = ((rgba) & 0xff); + color.w = 255; +} + template inline T pxAbsMax(const T &a, const T &b) { return physx::PxAbs(a) > physx::PxAbs(b) ? a : b; diff --git a/native/cocos/physics/physx/PhysXWorld.cpp b/native/cocos/physics/physx/PhysXWorld.cpp index 96f0d4084f7..bc19ec27eb2 100644 --- a/native/cocos/physics/physx/PhysXWorld.cpp +++ b/native/cocos/physics/physx/PhysXWorld.cpp @@ -29,6 +29,11 @@ #include "physics/physx/PhysXUtils.h" #include "physics/physx/joints/PhysXJoint.h" #include "physics/spec/IWorld.h" +#include "core/Root.h" +#include "scene/Camera.h" +#include "scene/RenderWindow.h" +#include "renderer/pipeline/Define.h" +#include "renderer/pipeline/RenderPipeline.h" namespace cc { namespace physics { @@ -86,6 +91,7 @@ PhysXWorld::PhysXWorld() { sceneDesc.filterShader = simpleFilterShader; sceneDesc.simulationEventCallback = &_mEventMgr->getEventCallback(); _mScene = _mPhysics->createScene(sceneDesc); + _mScene->setVisualizationParameter(physx::PxVisualizationParameter::eSCALE, 1.0F); _mControllerManager = PxCreateControllerManager(*_mScene); @@ -119,6 +125,102 @@ void PhysXWorld::step(float fixedTimeStep) { _mScene->simulate(fixedTimeStep); _mScene->fetchResults(true); syncPhysicsToScene(); + debugDraw(); +} + +pipeline::GeometryRenderer* PhysXWorld::getDebugRenderer () { + auto cameras = Root::getInstance()->getMainWindow()->getCameras(); + scene::Camera* camera = nullptr; + for (int c = 0; c < cameras.size(); c++) { + if (!cameras[c]) + continue; + const bool defaultCamera = cameras[c]->getVisibility() & static_cast(pipeline::LayerList::DEFAULT); + if (defaultCamera) { + camera = cameras[c]; + break; + } + } + + if (camera) { + camera->initGeometryRenderer(); + return camera->getGeometryRenderer(); + } + + return nullptr; +} + +void PhysXWorld::debugDraw () { + pipeline::GeometryRenderer* debugRenderer = getDebugRenderer(); + if (!debugRenderer) return; + _debugLineCount = 0; + static Vec3 v0, v1; + static gfx::Color c; + auto& rb = _mScene->getRenderBuffer(); + // lines + for (int i = 0; i < rb.getNbLines(); i++) { + if (_debugLineCount < _MAX_DEBUG_LINE_COUNT){ + _debugLineCount++; + const physx::PxDebugLine& line = rb.getLines()[i]; + pxSetColor(c, line.color0); + pxSetVec3Ext(v0, line.pos0); + pxSetVec3Ext(v1, line.pos1); + debugRenderer->addLine(v0, v1, c); + } + } + // triangles + for (int i = 0; i < rb.getNbTriangles(); i++) { + if (_debugLineCount < _MAX_DEBUG_LINE_COUNT - 3) { + _debugLineCount = _debugLineCount + 3; + const physx::PxDebugTriangle& triangle = rb.getTriangles()[i]; + pxSetColor(c, triangle.color0); + pxSetVec3Ext(v0, triangle.pos0); + pxSetVec3Ext(v1, triangle.pos1); + debugRenderer->addLine(v0, v1, c); + pxSetVec3Ext(v0, triangle.pos1); + pxSetVec3Ext(v1, triangle.pos2); + debugRenderer->addLine(v0, v1, c); + pxSetVec3Ext(v0, triangle.pos2); + pxSetVec3Ext(v1, triangle.pos0); + debugRenderer->addLine(v0, v1, c); + } + } +} + +void PhysXWorld::setDebugDrawMode() { + if (uint32_t(_debugDrawFlags) & uint32_t(EPhysicsDrawFlags::WIRE_FRAME)) { + _mScene->setVisualizationParameter(physx::PxVisualizationParameter::eCOLLISION_SHAPES, 1); + } else { + _mScene->setVisualizationParameter(physx::PxVisualizationParameter::eCOLLISION_SHAPES, 0); + } + + bool drawConstraint = bool(uint32_t(_debugDrawFlags) & uint32_t(EPhysicsDrawFlags::CONSTRAINT)); + float internalConstraintSize = drawConstraint ? _debugConstraintSize : 0; + _mScene->setVisualizationParameter(physx::PxVisualizationParameter::eJOINT_LOCAL_FRAMES, internalConstraintSize); + _mScene->setVisualizationParameter(physx::PxVisualizationParameter::eJOINT_LIMITS, internalConstraintSize); + + if (uint32_t(_debugDrawFlags) & uint32_t(EPhysicsDrawFlags::AABB)) { + _mScene->setVisualizationParameter(physx::PxVisualizationParameter::eCOLLISION_AABBS, 1); + } else { + _mScene->setVisualizationParameter(physx::PxVisualizationParameter::eCOLLISION_AABBS, 0); + } +} + +void PhysXWorld::setDebugDrawFlags(EPhysicsDrawFlags flags) { + _debugDrawFlags = flags; + setDebugDrawMode(); +} + +EPhysicsDrawFlags PhysXWorld::getDebugDrawFlags() { + return _debugDrawFlags; +} + +void PhysXWorld::setDebugDrawConstraintSize(float size) { + _debugConstraintSize = size; + setDebugDrawMode(); +} + +float PhysXWorld::getDebugDrawConstraintSize() { + return _debugConstraintSize; } void PhysXWorld::setGravity(float x, float y, float z) { diff --git a/native/cocos/physics/physx/PhysXWorld.h b/native/cocos/physics/physx/PhysXWorld.h index 08cfa6a9d43..561fde2efaf 100644 --- a/native/cocos/physics/physx/PhysXWorld.h +++ b/native/cocos/physics/physx/PhysXWorld.h @@ -28,6 +28,7 @@ #include "base/Macros.h" #include "base/std/container/vector.h" #include "core/scene-graph/Node.h" +#include "renderer/pipeline/GeometryRenderer.h" #include "physics/physx/PhysXEventManager.h" #include "physics/physx/PhysXFilterShader.h" #include "physics/physx/PhysXInc.h" @@ -124,6 +125,17 @@ class PhysXWorld final : virtual public IPhysicsWorld { float getFixedTimeStep() const override { return _fixedTimeStep; } void setFixedTimeStep(float fixedTimeStep) override { _fixedTimeStep = fixedTimeStep; } + virtual void setDebugDrawFlags(EPhysicsDrawFlags flags) override; + virtual EPhysicsDrawFlags getDebugDrawFlags() override; + + virtual void setDebugDrawConstraintSize(float size) override; + virtual float getDebugDrawConstraintSize() override; + +private: + pipeline::GeometryRenderer* getDebugRenderer(); + void debugDraw(); + void setDebugDrawMode(); + private: static PhysXWorld *instance; physx::PxFoundation *_mFoundation; @@ -147,6 +159,11 @@ class PhysXWorld final : virtual public IPhysicsWorld { ccstd::unordered_map _mWrapperObjects; float _fixedTimeStep{1 / 60.0F}; + + uint32_t _debugLineCount = 0; + uint32_t _MAX_DEBUG_LINE_COUNT = 16384; + EPhysicsDrawFlags _debugDrawFlags = EPhysicsDrawFlags::NONE; + float _debugConstraintSize = 0.3; }; } // namespace physics diff --git a/native/cocos/physics/physx/joints/PhysXFixedJoint.cpp b/native/cocos/physics/physx/joints/PhysXFixedJoint.cpp index c44b5f2e39d..fd172d9d032 100644 --- a/native/cocos/physics/physx/joints/PhysXFixedJoint.cpp +++ b/native/cocos/physics/physx/joints/PhysXFixedJoint.cpp @@ -44,6 +44,7 @@ void PhysXFixedJoint::onComponentSet() { _mJoint = PxFixedJointCreate(PxGetPhysics(), actor0, _transA, actor1, _transB); updatePose(); + setEnableDebugVisualization(true); } void PhysXFixedJoint::setBreakForce(float force) { diff --git a/native/cocos/physics/physx/joints/PhysXGenericJoint.cpp b/native/cocos/physics/physx/joints/PhysXGenericJoint.cpp index 2519189da09..cace104cb28 100644 --- a/native/cocos/physics/physx/joints/PhysXGenericJoint.cpp +++ b/native/cocos/physics/physx/joints/PhysXGenericJoint.cpp @@ -36,6 +36,7 @@ namespace physics { void PhysXGenericJoint::onComponentSet() { _mJoint = PxD6JointCreate(PxGetPhysics(), &getTempRigidActor(), physx::PxTransform{physx::PxIdentity}, nullptr, physx::PxTransform{physx::PxIdentity}); + setEnableDebugVisualization(true); } inline auto mapAxis(uint32_t index) -> physx::PxD6Axis::Enum { diff --git a/native/cocos/physics/physx/joints/PhysXJoint.cpp b/native/cocos/physics/physx/joints/PhysXJoint.cpp index aff2e264f05..1b1fa4a4c9f 100644 --- a/native/cocos/physics/physx/joints/PhysXJoint.cpp +++ b/native/cocos/physics/physx/joints/PhysXJoint.cpp @@ -104,6 +104,13 @@ void PhysXJoint::setEnableCollision(const bool v) { } } +void PhysXJoint::setEnableDebugVisualization(const bool v) { + _mEnableDebugVisualization = v; + if (_mJoint) { + _mJoint->setConstraintFlag(physx::PxConstraintFlag::eVISUALIZATION, _mEnableDebugVisualization); + } +} + physx::PxRigidActor &PhysXJoint::getTempRigidActor() { if (!PhysXJoint::tempRigidActor) { PhysXJoint::tempRigidActor = PxGetPhysics().createRigidDynamic(physx::PxTransform{physx::PxIdentity}); diff --git a/native/cocos/physics/physx/joints/PhysXJoint.h b/native/cocos/physics/physx/joints/PhysXJoint.h index f7f02836b85..d17f48c3654 100644 --- a/native/cocos/physics/physx/joints/PhysXJoint.h +++ b/native/cocos/physics/physx/joints/PhysXJoint.h @@ -45,6 +45,7 @@ class PhysXJoint : virtual public IBaseJoint { void onDestroy() override; void setConnectedBody(uint32_t rigidBodyID) override; void setEnableCollision(bool v) override; + void setEnableDebugVisualization(bool v); virtual void updateScale0() = 0; virtual void updateScale1() = 0; static physx::PxRigidActor &getTempRigidActor(); @@ -56,6 +57,7 @@ class PhysXJoint : virtual public IBaseJoint { PhysXSharedBody *_mSharedBody{nullptr}; PhysXSharedBody *_mConnectedBody{nullptr}; bool _mEnableCollision{false}; + bool _mEnableDebugVisualization{ false }; virtual void onComponentSet() = 0; uint32_t _mObjectID{0}; diff --git a/native/cocos/physics/physx/joints/PhysXRevolute.cpp b/native/cocos/physics/physx/joints/PhysXRevolute.cpp index 6ceb5ef3d92..b8e69174d64 100644 --- a/native/cocos/physics/physx/joints/PhysXRevolute.cpp +++ b/native/cocos/physics/physx/joints/PhysXRevolute.cpp @@ -45,6 +45,8 @@ void PhysXRevolute::onComponentSet() { joint->setConstraintFlag(physx::PxConstraintFlag::eDRIVE_LIMITS_ARE_FORCES, true); joint->setProjectionAngularTolerance(0.2); joint->setProjectionLinearTolerance(0.2); + + setEnableDebugVisualization(true); } void PhysXRevolute::setPivotA(float x, float y, float z) { diff --git a/native/cocos/physics/physx/joints/PhysXSpherical.cpp b/native/cocos/physics/physx/joints/PhysXSpherical.cpp index 6458f73cb3b..df51ecbdfde 100644 --- a/native/cocos/physics/physx/joints/PhysXSpherical.cpp +++ b/native/cocos/physics/physx/joints/PhysXSpherical.cpp @@ -32,6 +32,7 @@ namespace physics { void PhysXSpherical::onComponentSet() { _mJoint = PxSphericalJointCreate(PxGetPhysics(), &getTempRigidActor(), physx::PxTransform{physx::PxIdentity}, nullptr, physx::PxTransform{physx::PxIdentity}); + setEnableDebugVisualization(true); } void PhysXSpherical::setPivotA(float x, float y, float z) { diff --git a/native/cocos/physics/sdk/World.cpp b/native/cocos/physics/sdk/World.cpp index 6849def1576..5bdbeeb9653 100644 --- a/native/cocos/physics/sdk/World.cpp +++ b/native/cocos/physics/sdk/World.cpp @@ -86,6 +86,22 @@ ccstd::vector> &World::getCCTTriggerEventPa return _impl->getCCTTriggerEventPairs(); } +void World::setDebugDrawFlags(EPhysicsDrawFlags flags) { + _impl->setDebugDrawFlags(flags); +} + +EPhysicsDrawFlags World::getDebugDrawFlags() { + return _impl->getDebugDrawFlags(); +} + +void World::setDebugDrawConstraintSize(float size) { + _impl->setDebugDrawConstraintSize(size); +} + +float World::getDebugDrawConstraintSize() { + return _impl->getDebugDrawConstraintSize(); +} + void World::setCollisionMatrix(uint32_t i, uint32_t m) { _impl->setCollisionMatrix(i, m); } diff --git a/native/cocos/physics/sdk/World.h b/native/cocos/physics/sdk/World.h index 33be08ac30c..dec44009df6 100644 --- a/native/cocos/physics/sdk/World.h +++ b/native/cocos/physics/sdk/World.h @@ -40,6 +40,10 @@ class CC_DLL World final : public IPhysicsWorld { void emitEvents() override; void syncSceneToPhysics() override; void syncSceneWithCheck() override; + void setDebugDrawFlags(EPhysicsDrawFlags flags) override; + EPhysicsDrawFlags getDebugDrawFlags() override; + void setDebugDrawConstraintSize(float size) override; + float getDebugDrawConstraintSize() override; void setCollisionMatrix(uint32_t i, uint32_t m) override; ccstd::vector> &getTriggerEventPairs() override; ccstd::vector>& getContactEventPairs() override; diff --git a/native/cocos/physics/spec/IWorld.h b/native/cocos/physics/spec/IWorld.h index 062b8d37cdc..e0c9d8c09fb 100644 --- a/native/cocos/physics/spec/IWorld.h +++ b/native/cocos/physics/spec/IWorld.h @@ -39,6 +39,13 @@ enum class ETouchState : uint8_t { EXIT = 2, }; +enum class EPhysicsDrawFlags : uint32_t { + NONE = 0, + WIRE_FRAME = 0x0001, + CONSTRAINT = 0x0002, + AABB = 0x0004 +}; + struct TriggerEventPair { uint32_t shapeA; //wrapper object ID uint32_t shapeB; //wrapper object ID @@ -145,6 +152,10 @@ class IPhysicsWorld { virtual void syncSceneToPhysics() = 0; virtual void syncSceneWithCheck() = 0; virtual void destroy() = 0; + virtual void setDebugDrawFlags(EPhysicsDrawFlags f) = 0; + virtual EPhysicsDrawFlags getDebugDrawFlags() = 0; + virtual void setDebugDrawConstraintSize(float s) = 0; + virtual float getDebugDrawConstraintSize() = 0; virtual void setCollisionMatrix(uint32_t i, uint32_t m) = 0; virtual ccstd::vector> &getTriggerEventPairs() = 0; virtual ccstd::vector>& getContactEventPairs() = 0; diff --git a/native/external-config.json b/native/external-config.json index e1d7e3fb97e..a768805a4d8 100644 --- a/native/external-config.json +++ b/native/external-config.json @@ -3,6 +3,6 @@ "type": "github", "owner": "cocos-creator", "name": "engine-native-external", - "checkout": "v3.8.2-7" + "checkout": "v3.8.2-8" } } \ No newline at end of file diff --git a/platforms/native/engine/jsb-physics.js b/platforms/native/engine/jsb-physics.js index 89a488659c1..87b4d8b93cc 100644 --- a/platforms/native/engine/jsb-physics.js +++ b/platforms/native/engine/jsb-physics.js @@ -192,6 +192,22 @@ class PhysicsWorld { this._impl.step(f); } + set debugDrawFlags (v) { + this._impl.setDebugDrawFlags(v); + } + + get debugDrawFlags () { + return this._impl.getDebugDrawFlags(); + } + + set debugDrawConstraintSize (v) { + this._impl.setDebugDrawConstraintSize(v); + } + + get debugDrawConstraintSize () { + return this._impl.getDebugDrawConstraintSize(); + } + raycast (r, o, p, rs) { raycastOptions.origin = r.o; raycastOptions.unitDir = r.d;