The one-way tolerance angle is a field: a one-way collider and a one-way tile gain One-way angle (0 to 90 degrees, default 45), a contact blocks when its normal is within that angle of the blocking side, without a value the limit is bit-identical to before, and the rule holds for Move, Raycast and dynamic bodies in Rapier; tiles with different angles never merge into one rectangle, the scene view draws the angle as a cone in the middle of the thick side, and the inspector shows Blocks from and One-way angle only while One-way is on, through the new schema option shownWith (#159)
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
1 parent
d4ea04d4d6
commit
5385d9e152
14 files changed
+454
-62
No files matched your search
@@ -16,6 +16,7 @@ import {
|
||||
contactBetween,
|
||||
isTrigger,
|
||||
landsOn,
|
||||
type OneWaySide,
|
||||
movedShape,
|
||||
piecesOf,
|
||||
shapeArea,
|
||||
@@ -100,7 +101,7 @@ interface Body {
|
||||
interface Part {
|
||||
entity: Entity;
|
||||
shape: ColliderShape;
|
||||
facing: Vec2 | null;
|
||||
facing: OneWaySide | null;
|
||||
owner: Body;
|
||||
}
|
||||
|
||||
@@ -497,7 +498,7 @@ export class PhysicsSystem implements System, CollisionSource {
|
||||
local.map(({ entity, pieces, facing }) => [
|
||||
entity.id,
|
||||
pieces.map(pieceKey),
|
||||
facing && [round(facing.x), round(facing.y)],
|
||||
facing && [round(facing.x), round(facing.y), facing.cone],
|
||||
entity.type === 'collider' ? [isTrigger(entity.props), colliderMaterial(entity.props)] : 0,
|
||||
]),
|
||||
) + JSON.stringify(want.settings?.mass ?? 0);
|
||||
@@ -599,10 +600,11 @@ export class PhysicsSystem implements System, CollisionSource {
|
||||
/**
|
||||
* Whether each dynamic body near a one-way collider passes through it in the coming step (#155).
|
||||
* It lands when it is on the blocking side (or sunk in no deeper than a landing sinks), the contact
|
||||
* faces that side within 45° and it is not moving away from it; a body that landed is held as long
|
||||
* as the two overlap, however deep a fast fall took it in one step. Otherwise it passes – from
|
||||
* below, from the side, from inside – and so does a body told to ignore one-way colliders. One that
|
||||
* is to pass is woken: a resting body sleeps, and a new answer alone would not move it.
|
||||
* faces that side within the collider's angle (#159) and it is not moving away from it; a body
|
||||
* that landed is held as long as the two overlap, however deep a fast fall took it in one step.
|
||||
* Otherwise it passes – from below, from the side, from inside – and so does a body told to ignore
|
||||
* one-way colliders. One that is to pass is woken: a resting body sleeps, and a new answer alone
|
||||
* would not move it.
|
||||
*/
|
||||
private decideOneWay(world: World): void {
|
||||
const platforms: [number, Part][] = [];
|
||||
@@ -657,13 +659,13 @@ export class PhysicsSystem implements System, CollisionSource {
|
||||
return movedShape(part.shape, move, turn);
|
||||
}
|
||||
|
||||
private facingNow(part: Part): Vec2 {
|
||||
private facingNow(part: Part): OneWaySide {
|
||||
const facing = part.facing!;
|
||||
if (!part.owner.body.isDynamic()) return facing;
|
||||
const turn = part.owner.body.rotation() - part.owner.placed.angle;
|
||||
const cos = Math.cos(turn);
|
||||
const sin = Math.sin(turn);
|
||||
return { x: facing.x * cos - facing.y * sin, y: facing.x * sin + facing.y * cos };
|
||||
return { x: facing.x * cos - facing.y * sin, y: facing.x * sin + facing.y * cos, cone: facing.cone };
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -7,6 +7,7 @@ import {
|
||||
cellPolygonShape,
|
||||
type CellRect,
|
||||
cellRectShape,
|
||||
oneWayAngles,
|
||||
solidPolygons,
|
||||
solidRects,
|
||||
} from '../../core/model/tilemapCollision';
|
||||
@@ -19,7 +20,9 @@ import {
|
||||
isTrigger,
|
||||
landsOn,
|
||||
movedShape,
|
||||
oneWayCone,
|
||||
oneWayFacing,
|
||||
type OneWaySide,
|
||||
overlapDepth,
|
||||
shapeExtent,
|
||||
shapesOverlap,
|
||||
@@ -83,11 +86,11 @@ export const NOT_BLOCKED: MoveResult = Object.freeze({
|
||||
|
||||
/**
|
||||
* A shape of a collider or tilemap, and for a one-way one the world direction it blocks from (#155):
|
||||
* only a contact whose normal lies within 45° of it stops anything.
|
||||
* only a contact whose normal lies within its angle of it stops anything (#159).
|
||||
*/
|
||||
export interface SolidShape {
|
||||
shape: ColliderShape;
|
||||
facing: Vec2 | null;
|
||||
facing: OneWaySide | null;
|
||||
}
|
||||
|
||||
/** A shape in the mover's way and the collider or tilemap it belongs to. */
|
||||
@@ -167,10 +170,13 @@ export class Solids {
|
||||
? {
|
||||
rects: solidRects(project, props, asset),
|
||||
polygons: solidPolygons(project, props, asset),
|
||||
oneWayRects: solidRects(project, props, asset, true),
|
||||
oneWayPolygons: solidPolygons(project, props, asset, true),
|
||||
oneWay: oneWayAngles(project, asset).map((angle) => ({
|
||||
angle,
|
||||
rects: solidRects(project, props, asset, angle),
|
||||
polygons: solidPolygons(project, props, asset, angle),
|
||||
})),
|
||||
}
|
||||
: { rects: [], polygons: [], oneWayRects: [], oneWayPolygons: [] };
|
||||
: { rects: [], polygons: [], oneWay: [] };
|
||||
this.blocks.set(tilemap, blocks);
|
||||
}
|
||||
return blocks;
|
||||
@@ -179,19 +185,25 @@ export class Solids {
|
||||
/**
|
||||
* The shapes a body occupies in the world: one for a collider; for a tilemap one box per merged
|
||||
* rectangle and one polygon per outlined cell (#141), the one-way cells blocking from the map's
|
||||
* top (#155). A tilemap placed before the first tick is worked out on the spot.
|
||||
* top (#155) within their tile's angle (#159). A tilemap placed before the first tick is worked out
|
||||
* on the spot.
|
||||
*/
|
||||
solidShapesOf(world: World, body: Entity): SolidShape[] {
|
||||
const m = worldMatrixOf(body);
|
||||
if (body.type !== 'tilemap')
|
||||
return [{ shape: colliderShape(body.props, m), facing: oneWayFacing(body.props, m) }];
|
||||
const { rects, polygons, oneWayRects, oneWayPolygons } = this.blocksOf(world, body);
|
||||
const { rects, polygons, oneWay } = this.blocksOf(world, body);
|
||||
const up = oneWayFacing({ oneWay: true }, m);
|
||||
const shapes = (cells: CellRect[], outlines: CellPolygon[], facing: Vec2 | null) => [
|
||||
const shapes = (cells: CellRect[], outlines: CellPolygon[], facing: OneWaySide | null) => [
|
||||
...cells.map((rect) => ({ shape: cellRectShape(body.props, rect, m), facing })),
|
||||
...outlines.map((polygon) => ({ shape: cellPolygonShape(body.props, polygon, m), facing })),
|
||||
];
|
||||
return [...shapes(rects, polygons, null), ...shapes(oneWayRects, oneWayPolygons, up)];
|
||||
return [
|
||||
...shapes(rects, polygons, null),
|
||||
...oneWay.flatMap((group) =>
|
||||
shapes(group.rects, group.polygons, up && { ...up, cone: oneWayCone(group.angle) }),
|
||||
),
|
||||
];
|
||||
}
|
||||
|
||||
shapesOf(world: World, body: Entity): ColliderShape[] {
|
||||
@@ -401,8 +413,8 @@ function unitOf(v: Vec2): Vec2 {
|
||||
interface TilemapBlocks {
|
||||
rects: CellRect[];
|
||||
polygons: CellPolygon[];
|
||||
oneWayRects: CellRect[];
|
||||
oneWayPolygons: CellPolygon[];
|
||||
/** The one-way cells, one group per angle their tiles land things within. */
|
||||
oneWay: { angle: number; rects: CellRect[]; polygons: CellPolygon[] }[];
|
||||
}
|
||||
|
||||
export function shapeOf(collider: Entity): ColliderShape {
|
||||
|
||||
Reference in new issue
Block a user