汇总检测两个圆形边界的矩

问题描述 投票:0回答:1

const canvas = document.querySelector("canvas"); canvas.width = window.innerWidth / 1.2; canvas.height = window.innerHeight / 1.2; window.addEventListener('resize', () => { canvas.width = window.innerWidth / 1.2; canvas.height = window.innerHeight / 1.2; }) const ctx = canvas.getContext("2d"); type Rect = { x: number; y: number; w: number; h: number; }; type RoundedRect = Rect & { borderRadius: number; }; const isFirstRectRighterThanSecond = (rect1: Rect, rect2: Rect): boolean => rect1.x + rect1.w < rect2.x; const isFirstRectBelowThanSecond = (rect1: Rect, rect2: Rect): boolean => rect1.y + rect1.h < rect2.y; const hasNotAABBCollision = (rect1: Rect, rect2: Rect): boolean => isFirstRectRighterThanSecond(rect1, rect2) || isFirstRectRighterThanSecond(rect2, rect1) || isFirstRectBelowThanSecond(rect1, rect2) || isFirstRectBelowThanSecond(rect2, rect1); class Vector { constructor( x: number = 0, y: number = 0, ) { this.x = x; this.y = y } } const getRoundedRectTopLeftRoundedCornerCenter = (roundedRect: RoundedRect): Vector => new Vector(roundedRect.x + roundedRect.borderRadius, roundedRect.y + roundedRect.borderRadius); const getRoundedRectTopRightRoundedCornerCenter = (roundedRect: RoundedRect): Vector => new Vector(roundedRect.x + roundedRect.w - roundedRect.borderRadius, roundedRect.y + roundedRect.borderRadius); const getRoundedRectBottomRightRoundedCornerCenter = (roundedRect: RoundedRect): Vector => new Vector( roundedRect.x + roundedRect.w - roundedRect.borderRadius, roundedRect.y + roundedRect.h - roundedRect.borderRadius, ); const getRoundedRectBottomLeftRoundedCornerCenter = (roundedRect: RoundedRect): Vector => new Vector(roundedRect.x + roundedRect.borderRadius, roundedRect.y + roundedRect.h - roundedRect.borderRadius); const getRoundedRectRoundedCornersCenters = (roundedRect: RoundedRect): Vector[] => [ getRoundedRectTopLeftRoundedCornerCenter(roundedRect), getRoundedRectTopRightRoundedCornerCenter(roundedRect), getRoundedRectBottomRightRoundedCornerCenter(roundedRect), getRoundedRectBottomLeftRoundedCornerCenter(roundedRect), ]; const sqr = (x: number) => x * x; const squareDistance = (vector1: Vector, vector2: Vector): number => sqr(vector1.x - vector2.x) + sqr(vector1.y - vector2.y); const doTwoCirclesCollide = ( circle1Center: Vector, circle1Radius: number, circle2Center: Vector, circle2Radius: number, ): boolean => squareDistance(circle1Center, circle2Center) <= sqr(circle1Radius + circle2Radius); const doCirclesCollide = (circles1Centers: Vector[], circles1Radius: number, circles2Centers: Vector[], circles2Radius: number): boolean => circles1Centers.some( circle1Center => circles2Centers.some( circle2Center => doTwoCirclesCollide(circle1Center, circles1Radius, circle2Center, circles2Radius) ) ); class Segment { constructor( start: Vector = new Vector(0, 0), end: Vector = new Vector(0, 0), ) { this.start = start; this.end = end; } } const getRoundedRectTopSegment = (roundedRect: RoundedRect): Segment => new Segment( new Vector(roundedRect.x + roundedRect.borderRadius, roundedRect.y), new Vector(roundedRect.x + roundedRect.w - roundedRect.borderRadius, roundedRect.y), ); const getRoundedRectRightSegment = (roundedRect: RoundedRect): Segment => new Segment( new Vector(roundedRect.x + roundedRect.w, roundedRect.y + roundedRect.borderRadius), new Vector(roundedRect.x + roundedRect.w, roundedRect.y + roundedRect.h - roundedRect.borderRadius), ); const getRoundedRectBottomSegment = (roundedRect: RoundedRect): Segment => new Segment( new Vector(roundedRect.x + roundedRect.borderRadius, roundedRect.y + roundedRect.h), new Vector(roundedRect.x + roundedRect.w - roundedRect.borderRadius, roundedRect.y + roundedRect.h), ); const getRoundedRectLeftSegment = (roundedRect: RoundedRect): Segment => new Segment( new Vector(roundedRect.x, roundedRect.y + roundedRect.borderRadius), new Vector(roundedRect.x, roundedRect.y + roundedRect.h - roundedRect.borderRadius), ); const getRoundedRectSegments = (roundedRect: RoundedRect): Segment[] => [ getRoundedRectTopSegment(roundedRect), getRoundedRectRightSegment(roundedRect), getRoundedRectBottomSegment(roundedRect), getRoundedRectLeftSegment(roundedRect), ]; const crossProduct = (vector1: Vector, vector2: Vector): number => vector1.x * vector2.y - vector1.y * vector2.x; const Orientations = { Collinear: 0, Clockwise: 1, Counterclockwise: 2, } const getOrientation = (vector1: Vector, vector2: Vector, vector3: Vector): Orientations => { const result = crossProduct( new Vector(vector3.x - vector2.x, vector3.y - vector2.y), new Vector(vector2.x - vector1.x, vector2.y - vector1.y), ); if (result === 0) return Orientations.Collinear; if (result > 0) return Orientations.Clockwise; return Orientations.Counterclockwise; }; const Axis = { X: "x", Y: "y", } const isDotOnSegmentProjection = (segment: Segment, dot: Vector, axis: Axis): boolean => dot[axis] <= Math.max(segment.start[axis], segment.end[axis]) && dot[axis] >= Math.min(segment.start[axis], segment.end[axis]); const isDotOnSegmentProjections = (segment: Segment, dot: Vector): boolean => isDotOnSegmentProjection(segment, dot, Axis.X) && isDotOnSegmentProjection(segment, dot, Axis.Y); const doTwoSegmentsIntersect = (segment1: Segment, segment2: Segment): boolean => { const orientation1 = getOrientation(segment1.start, segment1.end, segment2.start); const orientation2 = getOrientation(segment1.start, segment1.end, segment2.end); const orientation3 = getOrientation(segment2.start, segment2.end, segment1.start); const orientation4 = getOrientation(segment2.start, segment2.end, segment1.end); if (orientation1 !== orientation2 && orientation3 !== orientation4) return true; return ( (orientation1 === Orientations.Collinear && isDotOnSegmentProjections(segment1, segment2.start)) || (orientation2 === Orientations.Collinear && isDotOnSegmentProjections(segment1, segment2.end)) || (orientation3 === Orientations.Collinear && isDotOnSegmentProjections(segment2, segment1.start)) || (orientation4 === Orientations.Collinear && isDotOnSegmentProjections(segment2, segment1.end)) ); }; const doSegmentsIntersect = (segments1: Segment[], segments2: Segment[]): boolean => segments1.some( segment1 => segments2.some( segment2 => doTwoSegmentsIntersect(segment1, segment2) ) ); const distToSegmentSquared = (dot: Vector, segment: Segment) => { const squaredSegmentLength = squareDistance(segment.start, segment.end); if (squaredSegmentLength === 0) return squareDistance(dot, segment.start); const t = ((dot.x - segment.start.x) * (segment.end.x - segment.start.x) + (dot.y - segment.start.y) * (segment.end.y - segment.start.y)) / squaredSegmentLength; const clampedT = Math.max(0, Math.min(1, t)); return squareDistance( dot, new Vector( segment.start.x + clampedT * (segment.end.x - segment.start.x), segment.start.y + clampedT * (segment.end.y - segment.start.y), ), ); }; const doCircleIntersectWithSegment = (circleCenter: Vector, circleRadius: number, segment: Segment): boolean => distToSegmentSquared(circleCenter, segment) <= sqr(circleRadius); const doCirclesIntersectWithSegments = (circlesCenters: Vector[], circlesRadius: number, segments: Segment[]): boolean => circlesCenters.some( circleCenter => segments.some( segment => doCircleIntersectWithSegment(circleCenter, circlesRadius, segment) ) ); const doSegmentsIntersectOnProjection = (segment1: Segment, segment2: Segment, axis: Axis): boolean => isDotOnSegmentProjection(segment1, segment2.start, axis) || isDotOnSegmentProjection(segment1, segment2.end, axis) || isDotOnSegmentProjection(segment2, segment1.start, axis) || isDotOnSegmentProjection(segment2, segment1.end, axis) const doRoundedRectsCollide = (roundedRect1: RoundedRect, roundedRect2: RoundedRect): boolean => { if (hasNotAABBCollision(roundedRect1, roundedRect2)) { return false; } const roundedRect1CornersCenters = getRoundedRectRoundedCornersCenters(roundedRect1); const roundedRect2CornersCenters = getRoundedRectRoundedCornersCenters(roundedRect2); if ( doCirclesCollide( roundedRect1CornersCenters, roundedRect1.borderRadius, roundedRect2CornersCenters, roundedRect2.borderRadius ) ) return true; const roundedRect1Segments = getRoundedRectSegments(roundedRect1); const roundedRect2Segments = getRoundedRectSegments(roundedRect2); if (doSegmentsIntersect(roundedRect1Segments, roundedRect2Segments)) return true; if (doCirclesIntersectWithSegments(roundedRect1CornersCenters, roundedRect1.borderRadius, roundedRect2Segments)) return true; if (doCirclesIntersectWithSegments(roundedRect2CornersCenters, roundedRect2.borderRadius, roundedRect1Segments)) return true; /* Check if one of the rects is inside another one The below algorithm works only because we already tested a lot of other cases THIS ALGORITHM MUST NOT BE USED IN GENERAL CASE */ /* The arguments passed in that way for optimization purposes If you don't want to depend on the order of elements of an array which is returned by `getRoundedRectSegments` then you should use `getRoundedRectTopSegment` and `getRoundedRectRightSegment` functions respectively */ return ( doSegmentsIntersectOnProjection(roundedRect1Segments[0], roundedRect2Segments[0], Axis.X) || doSegmentsIntersectOnProjection(roundedRect1Segments[1], roundedRect2Segments[1], Axis.Y) ); }; class RoundedRectElement { constructor( ctx: CanvasRenderingContext2D, x: number, y: number, w: number, h: number, borderRadius: number, boundRectColor: string = 'white', roundedRectColor: string = 'green', circlesColor: string = 'orange', circlesCentersColor: string = 'red', segmentsColor: string = 'blue', ) { this.ctx = ctx; this.x = x; this.y = y; this.w = w; this.h = h; this.borderRadius = borderRadius; this.boundRectColor = boundRectColor; this.roundedRectColor = roundedRectColor; this.circlesColor = circlesColor; this.circlesCentersColor = circlesCentersColor; this.segmentsColor = segmentsColor; } draw() { const path = new Path2D(); path.rect(this.x, this.y, this.w, this.h); this.ctx.strokeStyle = this.boundRectColor; this.ctx.stroke(path); const path2 = new Path2D(); path2.roundRect(this.x, this.y, this.w, this.h, this.borderRadius); this.ctx.strokeStyle = this.roundedRectColor; this.ctx.stroke(path2); const circlesCenters = getRoundedRectRoundedCornersCenters(this); for (const circleCenter of circlesCenters) { const center = new Path2D(); center.arc(circleCenter.x, circleCenter.y, 4, 0, 2 * Math.PI); this.ctx.fillStyle = this.circlesCentersColor; this.ctx.fill(center); const circle = new Path2D(); circle.arc(circleCenter.x, circleCenter.y, this.borderRadius, 0, 2 * Math.PI); this.ctx.strokeStyle = this.circlesColor; this.ctx.stroke(circle); } const segments = getRoundedRectSegments(this); for (const segment of segments) { const line = new Path2D(); line.moveTo(segment.start.x, segment.start.y); line.lineTo(segment.end.x, segment.end.y); this.ctx.strokeStyle = this.segmentsColor; this.ctx.stroke(line); } } }; const roundedRect1 = new RoundedRectElement( ctx, 150, 150, 400, 400, 50 ); const roundedRect2 = new RoundedRectElement( ctx, 0, 0, 125, 100, 25 ); const objects = [ roundedRect1, roundedRect2 ]; window.addEventListener('mousemove', (e) => { roundedRect2.x = e.offsetX; roundedRect2.y = e.offsetY; console.log(doRoundedRectsCollide(roundedRect1, roundedRect2)); }); const draw = () => { requestAnimationFrame(draw); ctx.clearRect(0, 0, canvas.width, canvas.height); objects.forEach(object => object.draw()); }; requestAnimationFrame(draw);
*,
*:before,
*:after {
  box-sizing: border-box;
  margin: 0;
  padding: 0;
}

html {
  height: 100%;
}

body {
  display: flex;
  justify-content: center;
  align-items: center;

  background-color: black;

  height: 100%;
}

canvas {
  background-color: black;
  outline: 2px solid white;
}
<canvas></canvas>

算法通过了我的测试,所以它可以很好地工作,但我认为我的算法是如此复杂,可以简化

这里是我算法的主要步骤:

检查是否有AABB碰撞。如果没有,请返回
    false
  1. coundles coundles whcih创建了这两个矩形的圆形边界。然后检查第一个矩形的任何圆是否与其他圆圈发生碰撞。如果是,请返回
    true
  2. thin以下所有段的所有段。然后检查第一个矩形的任何片段是否与其他部分相撞。如果是,请返回
    true
  3. 检查,如果任何第一个矩形的部分都与另一个圆形碰撞。如果是,请返回
    true
  4. 检查第二个矩形的任何段与另一个部分相撞。如果是,请返回
    true
  5. 检查一个rect之一在另一个内部。如果是,返回
    true
  6. 返回
  7. false
    
    
  8. 可以跳过此步骤吗?
    可能会简化一些数学。 BeaCuse我试图找到所有步骤的简单解决方案,据我了解,它们涵盖了很多我可能不需要的情况(例如,我的矩形无法转动,所以我可以使用它来简化或优化)
我感谢任何帮助!

它似乎逆逻辑更简单:

如果AABB碰撞结果为
javascript typescript algorithm collision-detection collision
1个回答
0
投票
-对于带有圆角的矩形可能会给什么情况

false

?我只看到外角角壳。


我们现在可以检查4个碰撞,以了解“内部” AABB的-EFHG和KJIL与第二个矩形的类似物的碰撞

如果所有结果都是错误的,我们肯定有近距离的案例。所以 我们确定应该处理哪些角落。使用矩形中心之间的矢量的组件符号,我们可以选择唯一的相应角对,并检查半径和是否大于圆圈中心距离 enter image description here

最新问题
© www.soinside.com 2019 - 2025. All rights reserved.