UNPKG

5.89 kBPlain TextView Raw
1import type { NodeGeometry, Indexable } from './types';
2
3/**
4 * Rectangle Geometry
5 * @beta
6 *
7 * @remarks
8 * This interface simply represents a rectangle geometry.
9 */
10export interface RectangleGeometry {
11
12 /**
13 * X start of the rectangle (top left).
14 */
15 x: number
16
17 /**
18 * Y start of the rectangle (top left).
19 */
20 y: number
21
22 /**
23 * Width of the rectangle.
24 */
25 width: number
26
27 /**
28 * Height of the rectangle.
29 */
30 height: number
31}
32
33/**
34 * Rectangle Constructor Properties
35 * @beta
36 * @typeParam CustomDataType - Type of the custom data property (optional, inferred automatically).
37 */
38export interface RectangleProps<CustomDataType = void> extends RectangleGeometry {
39
40 /**
41 * Custom data
42 */
43 data?: CustomDataType
44}
45
46/**
47 * Class representing a Rectangle
48 * @typeParam CustomDataType - Type of the custom data property (optional, inferred automatically).
49 *
50 * @example Without custom data (JS/TS):
51 * ```typescript
52 * const rectangle = new Rectangle({
53 * x: 10,
54 * y: 20,
55 * width: 30,
56 * height: 40,
57 * });
58 * ```
59 *
60 * @example With custom data (JS/TS):
61 * ```javascript
62 * const rectangle = new Rectangle({
63 * x: 10,
64 * y: 20,
65 * width: 30,
66 * height: 40,
67 * data: {
68 * name: 'Jane',
69 * health: 100,
70 * },
71 * });
72 * ```
73 *
74 * @example With custom data (TS):
75 * ```typescript
76 * interface ObjectData {
77 * name: string
78 * health: number
79 * }
80 * const entity: ObjectData = {
81 * name: 'Jane',
82 * health: 100,
83 * };
84 *
85 * // Typescript will infer the type of the data property
86 * const rectangle1 = new Rectangle({
87 * x: 10,
88 * y: 20,
89 * width: 30,
90 * height: 40,
91 * data: entity,
92 * });
93 *
94 * // You can also pass in a generic type for the data property
95 * const rectangle2 = new Rectangle<ObjectData>({
96 * x: 10,
97 * y: 20,
98 * width: 30,
99 * height: 40,
100 * });
101 * rectangle2.data = entity;
102 * ```
103 *
104 * @example With custom class extending Rectangle (implements {@link RectangleGeometry} (x, y, width, height)):
105 * ```javascript
106 * // extending inherits the qtIndex method
107 * class Box extends Rectangle {
108 *
109 * constructor(props) {
110 * // call super to set x, y, width, height (and data, if given)
111 * super(props);
112 * this.content = props.content;
113 * }
114 * }
115 *
116 * const box = new Box({
117 * content: 'Gravity Boots',
118 * x: 10,
119 * y: 20,
120 * width: 30,
121 * height: 40,
122 * });
123 * ```
124 *
125 * @example With custom class and mapping {@link RectangleGeometry}:
126 * ```javascript
127 * // no need to extend if you don't implement RectangleGeometry
128 * class Box {
129 *
130 * constructor(content) {
131 * this.content = content;
132 * this.position = [10, 20];
133 * this.size = [30, 40];
134 * }
135 *
136 * // add a qtIndex method to your class
137 * qtIndex(node) {
138 * // map your properties to RectangleGeometry
139 * return Rectangle.prototype.qtIndex.call({
140 * x: this.position[0],
141 * y: this.position[1],
142 * width: this.size[0],
143 * height: this.size[1],
144 * }, node);
145 * }
146 * }
147 *
148 * const box = new Box('Gravity Boots');
149 * ```
150 *
151 * @example With custom object that implements {@link RectangleGeometry}:
152 * ```javascript
153 * const player = {
154 * name: 'Jane',
155 * health: 100,
156 * x: 10,
157 * y: 20,
158 * width: 30,
159 * height: 30,
160 * qtIndex: Rectangle.prototype.qtIndex,
161 * });
162 * ```
163 *
164 * @example With custom object and mapping {@link RectangleGeometry}:
165 * ```javascript
166 * // Note: this is not recommended but possible.
167 * // Using this technique, each object would have it's own qtIndex method.
168 * // Rather add qtIndex to your prototype, e.g. by using classes like shown above.
169 * const player = {
170 * name: 'Jane',
171 * health: 100,
172 * position: [10, 20],
173 * size: [30, 40],
174 * qtIndex: function(node) {
175 * return Rectangle.prototype.qtIndex.call({
176 * x: this.position[0],
177 * y: this.position[1],
178 * width: this.size[0],
179 * height: this.size[1],
180 * }, node);
181 * },
182 * });
183 * ```
184 */
185export class Rectangle<CustomDataType = void> implements RectangleGeometry, Indexable {
186
187 /**
188 * X start of the rectangle (top left).
189 */
190 x: number;
191
192 /**
193 * Y start of the rectangle (top left).
194 */
195 y: number;
196
197 /**
198 * Width of the rectangle.
199 */
200 width: number;
201
202 /**
203 * Height of the rectangle.
204 */
205 height: number;
206
207 /**
208 * Custom data.
209 */
210 data?: CustomDataType;
211
212 constructor(props:RectangleProps<CustomDataType>) {
213
214 this.x = props.x;
215 this.y = props.y;
216 this.width = props.width;
217 this.height = props.height;
218 this.data = props.data;
219 }
220
221 /**
222 * Determine which quadrant this rectangle belongs to.
223 * @param node - Quadtree node to be checked
224 * @returns Array containing indexes of intersecting subnodes (0-3 = top-right, top-left, bottom-left, bottom-right)
225 */
226 qtIndex(node:NodeGeometry): number[] {
227
228 const indexes:number[] = [],
229 boundsCenterX = node.x + (node.width/2),
230 boundsCenterY = node.y + (node.height/2);
231
232 const startIsNorth = this.y < boundsCenterY,
233 startIsWest = this.x < boundsCenterX,
234 endIsEast = this.x + this.width > boundsCenterX,
235 endIsSouth = this.y + this.height > boundsCenterY;
236
237 //top-right quad
238 if(startIsNorth && endIsEast) {
239 indexes.push(0);
240 }
241
242 //top-left quad
243 if(startIsWest && startIsNorth) {
244 indexes.push(1);
245 }
246
247 //bottom-left quad
248 if(startIsWest && endIsSouth) {
249 indexes.push(2);
250 }
251
252 //bottom-right quad
253 if(endIsEast && endIsSouth) {
254 indexes.push(3);
255 }
256
257 return indexes;
258 }
259}
\No newline at end of file