1- import { drawBox } from "../../helpers" ;
21import { Vector } from "../../Math/Vector" ;
32import { Game } from "../Game" ;
43import { Part } from "../Part" ;
@@ -9,151 +8,128 @@ import { PolygonCollider } from "./PolygonCollider";
98export class BoxCollider extends Collider {
109 start : Vector ;
1110 end : Vector ;
12- realWorldStart : Vector ;
13- realWorldEnd : Vector ;
14- rotatedCorners : Vector [ ] = [ ] ; // Store the four corners of the rotated box
15- constructor ( { width, height } : { width : number , height : number } ) {
16- super ( ) ;
11+ rotatedCorners : Vector [ ] = [ ] ;
12+ width : number ;
13+ height : number ;
14+
15+ private cachedAxes : Vector [ ] = [ ] ;
16+ private lastRotation : number = NaN ;
17+ private lastScale : Vector = new Vector ( NaN , NaN ) ;
18+
19+ constructor ( { width, height, tag } : { width : number ; height : number ; tag ?: string } ) {
20+ super ( { tag } ) ;
1721 this . name = "BoxCollider" ;
22+ this . width = width ;
23+ this . height = height ;
1824 this . start = new Vector ( - width / 2 , - height / 2 ) ;
1925 this . end = new Vector ( width / 2 , height / 2 ) ;
20- this . realWorldStart = this . start ; // Will be updated in act(), onMount()
21- this . realWorldEnd = this . end ; // Will be updated in act(), onMount()
26+ this . radius = Math . sqrt ( ( width / 2 ) ** 2 + ( height / 2 ) ** 2 ) ;
2227 this . type = "BoxCollider" ;
23- this . base = "Collider" ;
24- }
25- onMount ( parent : Part ) {
26- super . onMount ( parent ) ;
27- const transform = this . sibling < Transform > ( "Transform" )
28- if ( ! transform ) {
29- this . top ?. warn (
30- `BoxCollider <${ this . name } > (${ this . id } ) does not have Transform sibling. Skipping`
31- ) ;
32- return ;
28+
29+ for ( let i = 0 ; i < 4 ; i ++ ) {
30+ this . rotatedCorners . push ( new Vector ( 0 , 0 ) ) ;
3331 }
32+ }
3433
35- // Initialize realWorldStart and realWorldEnd based on parent's Transform
36- this . updateRotatedBounds ( transform ) ;
34+ get worldVertices ( ) : Vector [ ] {
35+ return this . rotatedCorners ;
36+ }
3737
38+ updateCollider ( transform : Transform ) {
39+ const cos = Math . cos ( transform . rotation ) ;
40+ const sin = Math . sin ( transform . rotation ) ;
3841
39- }
42+ const halfW = ( this . width * transform . scale . x ) / 2 ;
43+ const halfH = ( this . height * transform . scale . y ) / 2 ;
4044
41- // Helper method to calculate rotated bounding box
42- updateRotatedBounds ( transform : Transform ) {
43- const scaledStart = this . start . multiply ( transform . scale ) ;
44- const scaledEnd = this . end . multiply ( transform . scale ) ;
45-
46- // Calculate the center of the box (in local space)
47- const center = new Vector (
48- ( scaledStart . x + scaledEnd . x ) / 2 ,
49- ( scaledStart . y + scaledEnd . y ) / 2
50- ) ;
51-
52- // Corners in local space (top-left, top-right, bottom-right, bottom-left)
53- const corners = [
54- scaledStart , // top-left
55- new Vector ( scaledEnd . x , scaledStart . y ) , // top-right
56- scaledEnd , // bottom-right
57- new Vector ( scaledStart . x , scaledEnd . y ) , // bottom-left
45+ const localCorners = [
46+ new Vector ( - halfW , - halfH ) ,
47+ new Vector ( halfW , - halfH ) ,
48+ new Vector ( halfW , halfH ) ,
49+ new Vector ( - halfW , halfH ) ,
5850 ] ;
5951
60- // Rotate each corner around the center, then translate to world position
61- this . rotatedCorners = corners . map ( corner => {
62- const rel = corner . subtract ( center ) ;
63- if ( transform . rotation === 0 ) {
64- return transform . worldPosition . add ( corner ) ;
65- }
66- const cos = Math . cos ( transform . rotation ) ;
67- const sin = Math . sin ( transform . rotation ) ;
68- const rotated = new Vector (
69- rel . x * cos - rel . y * sin ,
70- rel . x * sin + rel . y * cos
71- ) ;
72-
73- return transform . worldPosition . add ( rotated . add ( center ) ) ;
74- } ) ;
52+ let minX = Infinity , minY = Infinity , maxX = - Infinity , maxY = - Infinity ;
7553
76- // Calculate axis-aligned bounding box from rotated corners
77- const xs = this . rotatedCorners . map ( corner => corner . x ) ;
78- const ys = this . rotatedCorners . map ( corner => corner . y ) ;
54+ for ( let i = 0 ; i < 4 ; i ++ ) {
55+ const c = localCorners [ i ] ;
56+ const x = c . x * cos - c . y * sin + transform . worldPosition . x ;
57+ const y = c . x * sin + c . y * cos + transform . worldPosition . y ;
7958
80- this . realWorldStart = new Vector ( Math . min ( ...xs ) , Math . min ( ...ys ) ) ;
81- this . realWorldEnd = new Vector ( Math . max ( ...xs ) , Math . max ( ...ys ) ) ;
82- }
59+ this . rotatedCorners [ i ] . set ( x , y ) ;
8360
61+ if ( x < minX ) minX = x ;
62+ if ( x > maxX ) maxX = x ;
63+ if ( y < minY ) minY = y ;
64+ if ( y > maxY ) maxY = y ;
65+ }
8466
85- checkCollision ( other : Collider ) : boolean {
67+ this . realWorldStart . set ( minX , minY ) ;
68+ this . realWorldEnd . set ( maxX , maxY ) ;
8669
70+ if ( transform . rotation !== this . lastRotation || ! transform . scale . equals ( this . lastScale ) ) {
71+ this . cachedAxes = this . getAxes ( this . rotatedCorners ) ;
72+ this . lastRotation = transform . rotation ;
73+ this . lastScale = transform . scale . clone ( ) ;
74+ }
75+ }
76+
77+ narrowPhaseCheck ( other : Collider ) : boolean {
8778 if ( other instanceof BoxCollider ) {
88- return ! (
89- this . realWorldEnd . x < other . realWorldStart . x || this . realWorldStart . x > other . realWorldEnd . x ||
90- this . realWorldEnd . y < other . realWorldStart . y || this . realWorldStart . y > other . realWorldEnd . y
91- ) ;
79+ return this . checkBoxVsBox ( this , other ) ;
9280 } else if ( other instanceof PolygonCollider ) {
93- // Delegate to PolygonCollider's checkCollision method
94- return other . checkCollision ( this ) ;
81+ return other . narrowPhaseCheck ( this ) ;
9582 }
96- this . top ?. warn ( "Collision checks are only supported between BoxColliders and PolygonColliders." ) ;
83+
84+ this . top ?. warn ( `Collision with unsupported collider type: ${ other . type } ` ) ;
9785 return false ;
9886 }
99- get vertices ( ) : Vector [ ] {
100- const width = this . end . x - this . start . x ;
101- const height = this . end . y - this . start . y ;
102- const halfWidth = width / 2 ;
103- const halfHeight = height / 2 ;
104-
105- const vertices = [
106- new Vector ( - halfWidth , - halfHeight ) ,
107- new Vector ( halfWidth , - halfHeight ) ,
108- new Vector ( halfWidth , halfHeight ) ,
109- new Vector ( - halfWidth , halfHeight ) ,
110- ] ;
111- return vertices ;
87+
88+ private checkBoxVsBox ( box1 : BoxCollider , box2 : BoxCollider ) : boolean {
89+ const axes = box1 . cachedAxes . concat ( box2 . cachedAxes ) ;
90+ for ( const axis of axes ) {
91+ const proj1 = this . project ( box1 . rotatedCorners , axis ) ;
92+ const proj2 = this . project ( box2 . rotatedCorners , axis ) ;
93+ if ( ! this . overlap ( proj1 , proj2 ) ) return false ;
94+ }
95+ return true ;
11296 }
97+
11398 act ( delta : number ) {
114- super . act ( delta ) ; // Pass to children
115- const transform = this . sibling < Transform > ( "Transform" ) ;
116- if ( ! transform ) {
117- throw new Error ( `BoxCollider <${ this . name } > (${ this . id } ) does not have a Transform sibling. Ensure it is mounted to a GameObject with a Transform component.` ) ;
99+ super . act ( delta ) ;
100+ }
101+
102+ drawDebug ( ctx : CanvasRenderingContext2D ) : void {
103+ ctx . save ( ) ;
104+ ctx . strokeStyle = this . colliding ? "rgba(255,0,0,0.5)" : "rgba(0,255,0,0.5)" ;
105+ ctx . lineWidth = 1 ;
106+ ctx . beginPath ( ) ;
107+ ctx . moveTo ( this . rotatedCorners [ 0 ] . x , this . rotatedCorners [ 0 ] . y ) ;
108+ for ( let i = 1 ; i < 4 ; i ++ ) {
109+ ctx . lineTo ( this . rotatedCorners [ i ] . x , this . rotatedCorners [ i ] . y ) ;
118110 }
111+ ctx . closePath ( ) ;
112+ ctx . stroke ( ) ;
113+ ctx . restore ( ) ;
114+ }
119115
120- if ( transform . rotation !== 0 ) {
121- // If rotated, replace with a PolygonCollider
122- const vertices = this . vertices ;
116+ override clone ( memo = new Map ( ) ) : this {
117+ if ( memo . has ( this ) ) return memo . get ( this ) ;
123118
124- const polygonCollider = new PolygonCollider ( { vertices } ) ;
125- this . parent ?. addChild ( polygonCollider ) ;
126- this . parent ?. removeChild ( this ) ;
127- return ; // Stop further execution for this BoxCollider instance
128- }
129- this . updateRotatedBounds ( transform ) ;
130-
131- this . colliding = false ;
132-
133- const layer = this . registrations ?. layer as Part | undefined ;
134- const colliders = layer ?. flats . colliders as Collider [ ] || [ ] ;
135- this . collidingWith = new Set < Collider > ( ) ; ; // Reset collidingWith list for this frame
136- for ( const other of colliders ) {
137- if ( other === this ) continue ;
138- if ( this . checkCollision ( other ) ) {
139- this . colliding = true ;
140- this . collidingWith . add ( other ) ;
141- }
142- }
143- if ( this . top instanceof Game && this . top . devmode ) {
144- if ( transform . rotation === 0 ) {
145- // Draw regular box if not rotated
146- drawBox (
147- this . top . context ,
148- this . realWorldStart . x ,
149- this . realWorldStart . y ,
150- this . realWorldEnd . x - this . realWorldStart . x ,
151- this . realWorldEnd . y - this . realWorldStart . y ,
152- this . colliding
153- ? "rgba(255, 0, 0, 0.5)"
154- : "rgba(0, 255, 0, 0.5)"
155- ) ;
156- }
157- }
119+ const cloned = new BoxCollider ( {
120+ width : this . width ,
121+ height : this . height ,
122+ tag : this . tag ,
123+ } ) ;
124+ memo . set ( this , cloned ) ;
125+
126+ this . _cloneProperties ( cloned , memo ) ;
127+
128+ cloned . colliding = false ;
129+ cloned . base = this . base ;
130+ cloned . type = this . type ;
131+ cloned . collidingWith = new Set < Collider > ( ) ;
132+
133+ return cloned as this;
158134 }
159135}
0 commit comments